mirror of
https://github.com/Routstr/routstrd.git
synced 2026-10-05 12:28:23 +00:00
feat(onboard): allow non-interactive integration via --pi-agent/--opencode flags (#91)
Co-authored-by: redshift <213178690+1ftredsh@users.noreply.github.com>
This commit is contained in:
+63
-15
@@ -28,7 +28,7 @@ import {
|
|||||||
type RoutstrdConfig,
|
type RoutstrdConfig,
|
||||||
} from "./utils/config";
|
} from "./utils/config";
|
||||||
import { COCO_LOGS_DIR, logger } from "./utils/logger";
|
import { COCO_LOGS_DIR, logger } from "./utils/logger";
|
||||||
import { setupIntegration, runIntegrationsForClients } from "./integrations";
|
import { setupIntegration, runIntegrationsForClients, type IntegrationKey } from "./integrations";
|
||||||
import {
|
import {
|
||||||
assertLegacyCocodNotRunning,
|
assertLegacyCocodNotRunning,
|
||||||
claimLegacyCocodPidFile,
|
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...");
|
console.log("Initializing routstrd...");
|
||||||
|
|
||||||
// Create config directory (0700, correcting existing installs too)
|
// 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 startDaemon({ port: String(config.port || 8008), host: config.host || undefined });
|
||||||
|
|
||||||
await setupIntegration(config);
|
await setupIntegration(config, integrationKey);
|
||||||
|
|
||||||
console.log("\nInitialization complete!");
|
console.log("\nInitialization complete!");
|
||||||
console.log(
|
console.log(
|
||||||
@@ -574,20 +574,68 @@ program
|
|||||||
.description(
|
.description(
|
||||||
"Initialize routstrd (creates config directory and initializes wallet)",
|
"Initialize routstrd (creates config directory and initializes wallet)",
|
||||||
)
|
)
|
||||||
.action(async () => {
|
.option("--opencode", "Set up OpenCode integration (non-interactive)")
|
||||||
await requireLocalDaemon();
|
.option("--openclaw", "Set up OpenClaw integration (non-interactive)")
|
||||||
try {
|
.option("--pi-agent", "Set up Pi Agent integration (non-interactive)")
|
||||||
await initDaemon();
|
.option("--claude-code", "Set up Claude Code integration (non-interactive)")
|
||||||
} catch (error) {
|
.option("--hermes", "Set up Hermes integration (non-interactive)")
|
||||||
// An expected, user-actionable refusal — print the structured message
|
.option("--skip-integration", "Skip integration setup")
|
||||||
// without Bun's unhandled-rejection source snippet and stack trace.
|
.action(
|
||||||
if (error instanceof WalletMigrationConflictError) {
|
async (options: {
|
||||||
console.error(error.message);
|
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);
|
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
|
// Start - start the background daemon
|
||||||
program
|
program
|
||||||
|
|||||||
+64
-35
@@ -64,9 +64,72 @@ function parseChoice(input: string): number {
|
|||||||
return 1;
|
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(
|
export async function setupIntegration(
|
||||||
config: RoutstrdConfig,
|
config: RoutstrdConfig,
|
||||||
|
integrationKey?: IntegrationKey,
|
||||||
): Promise<void> {
|
): 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("\nChoose an integration to set up:");
|
||||||
console.log("1. OpenCode (default)");
|
console.log("1. OpenCode (default)");
|
||||||
console.log("2. OpenClaw");
|
console.log("2. OpenClaw");
|
||||||
@@ -92,39 +155,5 @@ export async function setupIntegration(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const integrationConfig = CLIENT_CONFIGS[key]!;
|
await installIntegrationByKey(config, 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user