Merge pull request #37 from jeroenubbink/fix/onboard-silent-output-and-blocked-postinstall

fix(onboard): show integration menu and progress in terminal
This commit is contained in:
redshift
2026-05-30 06:21:30 +00:00
committed by GitHub
7 changed files with 57 additions and 62 deletions
+13 -13
View File
@@ -81,7 +81,7 @@ async function printLightningInvoice(invoice: string): Promise<void> {
}
async function installCocodOrExit(): Promise<void> {
logger.log("cocod not found. Installing globally with bun...");
console.log("cocod not found. Installing globally with bun...");
const installProc = Bun.spawn(
["bun", "install", "--global", "@routstr/cocod"],
@@ -93,13 +93,13 @@ async function installCocodOrExit(): Promise<void> {
const installCode = await installProc.exited;
if (installCode !== 0 || !(await isCocodInstalled())) {
logger.error(
console.error(
"Failed to install cocod. Please run 'bun install --global @routstr/cocod' manually.",
);
throw new Error("cocod installation failed");
}
logger.log("cocod installed successfully.");
console.log("cocod installed successfully.");
}
async function requireLocalDaemon(): Promise<void> {
@@ -113,12 +113,12 @@ async function requireLocalDaemon(): Promise<void> {
}
async function initDaemon(): Promise<void> {
logger.log("Initializing routstrd...");
console.log("Initializing routstrd...");
// Create config directory
if (!existsSync(CONFIG_DIR)) {
mkdirSync(CONFIG_DIR, { recursive: true });
logger.log(`Created config directory: ${CONFIG_DIR}`);
console.log(`Created config directory: ${CONFIG_DIR}`);
}
// Create initial config
@@ -128,7 +128,7 @@ async function initDaemon(): Promise<void> {
cocodPath: null,
};
await Bun.write(CONFIG_FILE, JSON.stringify(config, null, 2));
logger.log(`Created config file: ${CONFIG_FILE}`);
console.log(`Created config file: ${CONFIG_FILE}`);
}
const config = await loadConfig();
@@ -190,30 +190,30 @@ async function initDaemon(): Promise<void> {
const alreadyInitialized = combinedOutput.includes("already initialized");
if (initCode !== 0 && !alreadyInitialized) {
logger.error(
console.error(
"Failed to initialize cocod. Please run 'cocod init' manually.",
);
return;
}
if (alreadyInitialized) {
logger.log("cocod is already initialized.");
console.log("cocod is already initialized.");
} else {
logger.log("cocod initialized successfully.");
console.log("cocod initialized successfully.");
}
await startDaemon({ port: String(config.port || 8008) });
await setupIntegration(config);
logger.log("\nInitialization complete!");
logger.log(
console.log("\nInitialization complete!");
console.log(
"\n use 'routstrd receive <cashu-token>' or 'routstrd receive 2100' to top up your local wallet using Lightning!",
);
logger.log(
console.log(
"\n full wallet commands still work too, e.g. 'routstrd wallet receive cashu <token>' and 'routstrd wallet receive bolt11 2100'.",
);
logger.log(
console.log(
"\nTo ensure routstrd persists across system restarts, run: 'routstrd service install'",
);
}
+9 -10
View File
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync } from "fs";
import { readFile, writeFile } from "fs/promises";
import { dirname } from "path";
import type { RoutstrdConfig } from "../utils/config";
import { logger } from "../utils/logger";
import type { IntegrationConfig, RoutstrModel } from "./registry";
import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
@@ -13,8 +12,8 @@ export async function installClaudeCodeIntegration(
): Promise<void> {
const { name, configPath } = integrationConfig;
logger.log(`\nInstalling routstr configuration in ${configPath}...`);
logger.log(`Using API key for ${name}`);
console.log(`\nInstalling routstr configuration in ${configPath}...`);
console.log(`Using API key for ${name}`);
const baseUrl = getDaemonBaseUrl(config);
@@ -28,7 +27,7 @@ export async function installClaudeCodeIntegration(
settings = JSON.parse(content);
}
} catch (error) {
logger.error(`Error reading ${configPath}, creating new one.`);
console.error(`Error reading ${configPath}, creating new one.`);
}
if (!settings.env) {
@@ -49,25 +48,25 @@ export async function installClaudeCodeIntegration(
settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = opus.id;
settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = sonnet.id;
settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = haiku.id;
logger.log(`Set Claude models: Opus=${opus.id}, Sonnet=${sonnet.id}, Haiku=${haiku.id}`);
console.log(`Set Claude models: Opus=${opus.id}, Sonnet=${sonnet.id}, Haiku=${haiku.id}`);
} else if (models.length > 0) {
const model = models[0]!;
logger.log(`Only ${models.length} models available, falling back to defaults.`);
console.log(`Only ${models.length} models available, falling back to defaults.`);
settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = model.id;
settings.env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = model.id;
settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = model.id;
} else {
logger.log("No models available from routstr daemon.");
console.log("No models available from routstr daemon.");
}
} catch (error) {
logger.error("Failed to fetch models for Claude Code integration:", error);
console.error("Failed to fetch models for Claude Code integration:", error);
}
try {
mkdirSync(dirname(configPath), { recursive: true });
await writeFile(configPath, JSON.stringify(settings, null, 2));
logger.log(`Successfully updated ${configPath} with routstr settings.`);
console.log(`Successfully updated ${configPath} with routstr settings.`);
} catch (error) {
logger.error(`Failed to write to ${configPath}:`, error);
console.error(`Failed to write to ${configPath}:`, error);
}
}
+10 -11
View File
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync } from "fs";
import { readFile, writeFile } from "fs/promises";
import { dirname } from "path";
import type { RoutstrdConfig } from "../utils/config";
import { logger } from "../utils/logger";
import type { IntegrationConfig, RoutstrModel } from "./registry";
import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
@@ -13,8 +12,8 @@ export async function installHermesIntegration(
): Promise<void> {
const { name, configPath } = integrationConfig;
logger.log(`\nInstalling routstr configuration in ${configPath}...`);
logger.log(`Using API key for ${name}`);
console.log(`\nInstalling routstr configuration in ${configPath}...`);
console.log(`Using API key for ${name}`);
const baseUrl = getDaemonBaseUrl(config);
const baseUrlV1 = `${baseUrl}/v1`;
@@ -27,16 +26,16 @@ export async function installHermesIntegration(
if (models.length >= 3) {
defaultModel = models[2]!.id;
logger.log(`Set default model to 3rd available model: ${defaultModel}`);
console.log(`Set default model to 3rd available model: ${defaultModel}`);
} else if (models.length > 0) {
defaultModel = models[0]!.id;
logger.log(`Only ${models.length} models available, using ${defaultModel} as default.`);
console.log(`Only ${models.length} models available, using ${defaultModel} as default.`);
} else {
logger.log("No models available from routstr daemon, using fallback default.");
console.log("No models available from routstr daemon, using fallback default.");
}
} catch (error) {
logger.error("Failed to fetch models for Hermes integration:", error);
logger.log("Using fallback default model.");
console.error("Failed to fetch models for Hermes integration:", error);
console.log("Using fallback default model.");
}
let content = "";
@@ -45,7 +44,7 @@ export async function installHermesIntegration(
content = await readFile(configPath, "utf-8");
}
} catch (error) {
logger.error(`Error reading ${configPath}, creating new one.`);
console.error(`Error reading ${configPath}, creating new one.`);
}
// Remove existing model block
@@ -80,8 +79,8 @@ export async function installHermesIntegration(
try {
mkdirSync(dirname(configPath), { recursive: true });
await writeFile(configPath, newContent);
logger.log(`Successfully updated ${configPath} with routstr settings.`);
console.log(`Successfully updated ${configPath} with routstr settings.`);
} catch (error) {
logger.error(`Failed to write to ${configPath}:`, error);
console.error(`Failed to write to ${configPath}:`, error);
}
}
+10 -10
View File
@@ -67,13 +67,13 @@ function parseChoice(input: string): number {
export async function setupIntegration(
config: RoutstrdConfig,
): Promise<void> {
logger.log("\nChoose an integration to set up:");
logger.log("1. OpenCode (default)");
logger.log("2. OpenClaw");
logger.log("3. Pi");
logger.log("4. Claude Code");
logger.log("5. Hermes");
logger.log("6. Skip for now");
console.log("\nChoose an integration to set up:");
console.log("1. OpenCode (default)");
console.log("2. OpenClaw");
console.log("3. Pi");
console.log("4. Claude Code");
console.log("5. Hermes");
console.log("6. Skip for now");
const answer = await ask("Select integration [1]: ");
const choice = parseChoice(answer);
@@ -88,7 +88,7 @@ export async function setupIntegration(
const key = integrationByChoice[choice];
if (!key) {
logger.log("Skipping integration setup.");
console.log("Skipping integration setup.");
return;
}
@@ -98,9 +98,9 @@ export async function setupIntegration(
);
if (created) {
logger.log(`Created new API key for ${integrationConfig.name}`);
console.log(`Created new API key for ${integrationConfig.name}`);
} else {
logger.log(`Using existing API key for ${integrationConfig.name}`);
console.log(`Using existing API key for ${integrationConfig.name}`);
}
if (key === "opencode") {
+5 -6
View File
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync } from "fs";
import { readFile, writeFile } from "fs/promises";
import { dirname } from "path";
import type { RoutstrdConfig } from "../utils/config";
import { logger } from "../utils/logger";
import type { IntegrationConfig, RoutstrModel } from "./registry";
import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
@@ -56,8 +55,8 @@ export async function installOpenClawIntegration(
): Promise<void> {
const { name, configPath } = integrationConfig;
logger.log("\nInstalling routstr models in openclaw.json...");
logger.log(`Using API key for ${name}`);
console.log("\nInstalling routstr models in openclaw.json...");
console.log(`Using API key for ${name}`);
const baseUrl = getDaemonBaseUrl(config);
@@ -92,7 +91,7 @@ export async function installOpenClawIntegration(
const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || [];
if (models.length === 0) {
logger.log("No models found from routstr daemon.");
console.log("No models found from routstr daemon.");
return;
}
@@ -134,8 +133,8 @@ export async function installOpenClawIntegration(
// openclawConfig.agents.defaults.models = aliasMap;
await writeFile(configPath, JSON.stringify(openclawConfig, null, 2));
logger.log(`Added "${OPENCLAW_PROVIDER_ID}" provider with ${models.length} models to openclaw.json`);
console.log(`Added "${OPENCLAW_PROVIDER_ID}" provider with ${models.length} models to openclaw.json`);
} catch (error) {
logger.error("Failed to install models in openclaw.json:", error);
console.error("Failed to install models in openclaw.json:", error);
}
}
+5 -6
View File
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync } from "fs";
import { readFile, writeFile } from "fs/promises";
import { dirname } from "path";
import type { RoutstrdConfig } from "../utils/config";
import { logger } from "../utils/logger";
import type { IntegrationConfig, RoutstrModel } from "./registry";
import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
@@ -15,8 +14,8 @@ export async function installOpencodeIntegration(
): Promise<void> {
const { name, configPath } = integrationConfig;
logger.log("\nInstalling routstr models in opencode.json...");
logger.log(`Using API key for ${name}`);
console.log("\nInstalling routstr models in opencode.json...");
console.log(`Using API key for ${name}`);
const baseUrl = getDaemonBaseUrl(config);
@@ -56,7 +55,7 @@ export async function installOpencodeIntegration(
const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || [];
if (models.length === 0) {
logger.log("No models found from routstr daemon.");
console.log("No models found from routstr daemon.");
return;
}
@@ -78,8 +77,8 @@ export async function installOpencodeIntegration(
opencodeConfig.small_model = OPENCODE_SMALL_MODEL;
await writeFile(configPath, JSON.stringify(opencodeConfig, null, 2));
logger.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
console.log(`Added "routstr" provider with ${models.length} models to opencode.json`);
} catch (error) {
logger.error("Failed to install models in opencode.json:", error);
console.error("Failed to install models in opencode.json:", error);
}
}
+5 -6
View File
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync } from "fs";
import { readFile, writeFile } from "fs/promises";
import { dirname } from "path";
import type { RoutstrdConfig } from "../utils/config";
import { logger } from "../utils/logger";
import type { IntegrationConfig, RoutstrModel } from "./registry";
import { callDaemon, getDaemonBaseUrl } from "../utils/daemon-client";
@@ -28,8 +27,8 @@ export async function installPiIntegration(
): Promise<void> {
const { name, configPath } = integrationConfig;
logger.log("\nInstalling routstr models in pi models.json...");
logger.log(`Using API key for ${name}`);
console.log("\nInstalling routstr models in pi models.json...");
console.log(`Using API key for ${name}`);
const baseUrl = `${getDaemonBaseUrl(config)}/v1`;
@@ -56,7 +55,7 @@ export async function installPiIntegration(
const models = (data.output as { models: RoutstrModel[] } | undefined)?.models || [];
if (models.length === 0) {
logger.log("No models found from routstr daemon.");
console.log("No models found from routstr daemon.");
return;
}
@@ -72,8 +71,8 @@ export async function installPiIntegration(
};
await writeFile(configPath, JSON.stringify(piConfig, null, 2));
logger.log(`Added "routstr" provider with ${models.length} models to pi models.json`);
console.log(`Added "routstr" provider with ${models.length} models to pi models.json`);
} catch (error) {
logger.error("Failed to install models in pi models.json:", error);
console.error("Failed to install models in pi models.json:", error);
}
}