mirror of
https://github.com/Routstr/routstrd.git
synced 2026-09-14 02:55:07 +00:00
feat(onboard): allow non-interactive integration via --pi-agent/--opencode flags
This commit is contained in:
+63
-15
@@ -28,7 +28,7 @@ import {
|
||||
type RoutstrdConfig,
|
||||
} from "./utils/config";
|
||||
import { COCO_LOGS_DIR, logger } from "./utils/logger";
|
||||
import { setupIntegration, runIntegrationsForClients } from "./integrations";
|
||||
import { setupIntegration, runIntegrationsForClients, type IntegrationKey } from "./integrations";
|
||||
import {
|
||||
assertLegacyCocodNotRunning,
|
||||
claimLegacyCocodPidFile,
|
||||
@@ -215,7 +215,7 @@ async function requireLocalDaemon(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function initDaemon(): Promise<void> {
|
||||
async function initDaemon(integrationKey?: IntegrationKey): Promise<void> {
|
||||
console.log("Initializing routstrd...");
|
||||
|
||||
// Create config directory (0700, correcting existing installs too)
|
||||
@@ -273,7 +273,7 @@ async function initDaemon(): Promise<void> {
|
||||
|
||||
await startDaemon({ port: String(config.port || 8008), host: config.host || undefined });
|
||||
|
||||
await setupIntegration(config);
|
||||
await setupIntegration(config, integrationKey);
|
||||
|
||||
console.log("\nInitialization complete!");
|
||||
console.log(
|
||||
@@ -574,20 +574,68 @@ program
|
||||
.description(
|
||||
"Initialize routstrd (creates config directory and initializes wallet)",
|
||||
)
|
||||
.action(async () => {
|
||||
await requireLocalDaemon();
|
||||
try {
|
||||
await initDaemon();
|
||||
} catch (error) {
|
||||
// An expected, user-actionable refusal — print the structured message
|
||||
// without Bun's unhandled-rejection source snippet and stack trace.
|
||||
if (error instanceof WalletMigrationConflictError) {
|
||||
console.error(error.message);
|
||||
.option("--opencode", "Set up OpenCode integration (non-interactive)")
|
||||
.option("--openclaw", "Set up OpenClaw integration (non-interactive)")
|
||||
.option("--pi-agent", "Set up Pi Agent integration (non-interactive)")
|
||||
.option("--claude-code", "Set up Claude Code integration (non-interactive)")
|
||||
.option("--hermes", "Set up Hermes integration (non-interactive)")
|
||||
.option("--skip-integration", "Skip integration setup")
|
||||
.action(
|
||||
async (options: {
|
||||
opencode?: boolean;
|
||||
openclaw?: boolean;
|
||||
piAgent?: boolean;
|
||||
claudeCode?: boolean;
|
||||
hermes?: boolean;
|
||||
skipIntegration?: boolean;
|
||||
}) => {
|
||||
await requireLocalDaemon();
|
||||
|
||||
const integrationFlags: Record<string, boolean | undefined> = {
|
||||
opencode: options.opencode,
|
||||
openclaw: options.openclaw,
|
||||
"pi-agent": options.piAgent,
|
||||
"claude-code": options.claudeCode,
|
||||
hermes: options.hermes,
|
||||
};
|
||||
const selectedIntegrations = Object.keys(integrationFlags).filter(
|
||||
(key) => integrationFlags[key],
|
||||
);
|
||||
|
||||
if (selectedIntegrations.length > 1) {
|
||||
console.error(
|
||||
"Error: use only one integration flag, or use 'routstrd clients add' for multiple clients.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
if (options.skipIntegration && selectedIntegrations.length > 0) {
|
||||
console.error(
|
||||
"Error: --skip-integration cannot be combined with an integration flag.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let integrationKey: IntegrationKey | undefined;
|
||||
if (options.skipIntegration) {
|
||||
integrationKey = "skip";
|
||||
} else if (selectedIntegrations.length === 1) {
|
||||
integrationKey = selectedIntegrations[0];
|
||||
}
|
||||
|
||||
try {
|
||||
await initDaemon(integrationKey);
|
||||
} catch (error) {
|
||||
// An expected, user-actionable refusal — print the structured message
|
||||
// without Bun's unhandled-rejection source snippet and stack trace.
|
||||
if (error instanceof WalletMigrationConflictError) {
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Start - start the background daemon
|
||||
program
|
||||
|
||||
+64
-35
@@ -64,9 +64,72 @@ function parseChoice(input: string): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Either a client integration key (as used in CLIENT_CONFIGS) or "skip".
|
||||
*/
|
||||
export type IntegrationKey = keyof typeof CLIENT_CONFIGS | "skip";
|
||||
|
||||
/**
|
||||
* Create/find the API key for a client and run its install integration.
|
||||
* Shared by the interactive menu in setupIntegration and direct onboarding
|
||||
* via `routstrd onboard --<client>` flags.
|
||||
*/
|
||||
async function installIntegrationByKey(
|
||||
config: RoutstrdConfig,
|
||||
key: keyof typeof CLIENT_CONFIGS,
|
||||
): Promise<void> {
|
||||
const integrationConfig = CLIENT_CONFIGS[key];
|
||||
if (!integrationConfig) {
|
||||
console.log(`Unknown integration: ${key}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { client, created } = await addDaemonClient(
|
||||
integrationConfig.name,
|
||||
);
|
||||
|
||||
if (created) {
|
||||
console.log(`Created new API key for ${integrationConfig.name}`);
|
||||
} else {
|
||||
console.log(`Using existing API key for ${integrationConfig.name}`);
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case "opencode":
|
||||
await installOpencodeIntegration(config, client.apiKey, integrationConfig);
|
||||
return;
|
||||
case "openclaw":
|
||||
await installOpenClawIntegration(config, client.apiKey, integrationConfig);
|
||||
return;
|
||||
case "pi-agent":
|
||||
await installPiIntegration(config, client.apiKey, integrationConfig);
|
||||
return;
|
||||
case "claude-code":
|
||||
await installClaudeCodeIntegration(config, client.apiKey, integrationConfig);
|
||||
return;
|
||||
case "hermes":
|
||||
await installHermesIntegration(config, client.apiKey, integrationConfig);
|
||||
return;
|
||||
default:
|
||||
console.log(`Unknown integration: ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function setupIntegration(
|
||||
config: RoutstrdConfig,
|
||||
integrationKey?: IntegrationKey,
|
||||
): Promise<void> {
|
||||
// Non-interactive selection (e.g. `routstrd onboard --pi-agent`).
|
||||
if (integrationKey === "skip") {
|
||||
console.log("Skipping integration setup.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (integrationKey !== undefined) {
|
||||
await installIntegrationByKey(config, integrationKey);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("\nChoose an integration to set up:");
|
||||
console.log("1. OpenCode (default)");
|
||||
console.log("2. OpenClaw");
|
||||
@@ -92,39 +155,5 @@ export async function setupIntegration(
|
||||
return;
|
||||
}
|
||||
|
||||
const integrationConfig = CLIENT_CONFIGS[key]!;
|
||||
const { client, created } = await addDaemonClient(
|
||||
integrationConfig.name,
|
||||
);
|
||||
|
||||
if (created) {
|
||||
console.log(`Created new API key for ${integrationConfig.name}`);
|
||||
} else {
|
||||
console.log(`Using existing API key for ${integrationConfig.name}`);
|
||||
}
|
||||
|
||||
if (key === "opencode") {
|
||||
await installOpencodeIntegration(config, client.apiKey, integrationConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "openclaw") {
|
||||
await installOpenClawIntegration(config, client.apiKey, integrationConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "pi-agent") {
|
||||
await installPiIntegration(config, client.apiKey, integrationConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "claude-code") {
|
||||
await installClaudeCodeIntegration(config, client.apiKey, integrationConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "hermes") {
|
||||
await installHermesIntegration(config, client.apiKey, integrationConfig);
|
||||
return;
|
||||
}
|
||||
await installIntegrationByKey(config, key);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user