Simplify instance lifecycle commands
This commit is contained in:
+29
-105
@@ -1,122 +1,46 @@
|
||||
#!/usr/bin/env node
|
||||
import process from "node:process";
|
||||
import { discoverBackends } from "./acp/discovery.js";
|
||||
import { assertOperationalConfig, loadConfigFile } from "./cli/config-file.js";
|
||||
import { runDoctor } from "./cli/doctor.js";
|
||||
import { runInstanceCommand } from "./cli/instance.js";
|
||||
import { localBaseUrl } from "./cli/net.js";
|
||||
import { printFeishu } from "./cli/print.js";
|
||||
import { runSetup } from "./cli/setup.js";
|
||||
import { defaultStateFile } from "./config.js";
|
||||
import { startServer } from "./server.js";
|
||||
|
||||
interface ParsedArgs { command?: string; rest: string[]; configPath?: string; json: boolean; help: boolean }
|
||||
const COMMANDS = ["init", "setup", "start", "stop", "restart", "status", "logs", "doctor", "list"] as const;
|
||||
type Command = typeof COMMANDS[number];
|
||||
|
||||
async function main(argv: string[]): Promise<number> {
|
||||
const parsed = parseArgs(argv);
|
||||
if (parsed.help || !parsed.command) { printHelp(); return 0; }
|
||||
validateCommandArgs(parsed);
|
||||
if (parsed.command === "instance") return runInstanceCommand(parsed.rest[0], parsed.rest.slice(1));
|
||||
if (parsed.command === "setup") { await runSetup(parsed.configPath); return 0; }
|
||||
if (parsed.command === "discover-backends" || parsed.command === "discover-agents") {
|
||||
const backends = await discoverBackends();
|
||||
if (parsed.json) console.log(JSON.stringify(backends, null, 2));
|
||||
else for (const backend of backends) {
|
||||
const version = backend.version ? ` (${backend.version})` : "";
|
||||
const reason = backend.reason ? ` - ${backend.reason}` : "";
|
||||
console.log(`${backend.id}\t${backend.status}\t${backend.command} ${backend.args.join(" ")}${version}${reason}`.trim());
|
||||
}
|
||||
if (argv.length === 0 || (argv.length === 1 && ["--help", "-h"].includes(argv[0]))) {
|
||||
printHelp();
|
||||
return 0;
|
||||
}
|
||||
if (parsed.command === "start") {
|
||||
const loaded = loadConfigFile(parsed.configPath);
|
||||
assertOperationalConfig(loaded.config, loaded.path);
|
||||
const running = await startServer(loaded.config);
|
||||
return await new Promise<number>((resolve) => {
|
||||
let stopping = false;
|
||||
const stop = (signal: string): void => {
|
||||
if (stopping) return;
|
||||
stopping = true;
|
||||
console.log(`Received ${signal}; shutting down...`);
|
||||
void running.shutdown().then(() => resolve(0), (error) => { console.error(error); resolve(1); });
|
||||
};
|
||||
process.once("SIGINT", () => stop("SIGINT"));
|
||||
process.once("SIGTERM", () => stop("SIGTERM"));
|
||||
});
|
||||
}
|
||||
if (parsed.command === "status") { await printStatus(parsed.configPath); return 0; }
|
||||
if (parsed.command === "doctor") { const loaded = loadConfigFile(parsed.configPath); return runDoctor(loaded.config, loaded.path); }
|
||||
if (parsed.command === "print") {
|
||||
const loaded = loadConfigFile(parsed.configPath);
|
||||
await printFeishu(loaded.config);
|
||||
return 0;
|
||||
}
|
||||
throw new Error(`Unknown command: ${parsed.command}`);
|
||||
}
|
||||
|
||||
function parseArgs(argv: string[]): ParsedArgs {
|
||||
const rest: string[] = []; let command: string | undefined; let configPath: string | undefined; let json = false; let help = false;
|
||||
for (let index = 0; index < argv.length; index++) {
|
||||
const arg = argv[index];
|
||||
if (arg === "--help" || arg === "-h") help = true;
|
||||
else if (arg === "--json") json = true;
|
||||
else if (arg === "--config") {
|
||||
if (configPath !== undefined) throw new Error("--config may only be specified once");
|
||||
const value = argv[++index];
|
||||
if (!value || value.startsWith("-")) throw new Error("--config requires a path");
|
||||
configPath = value;
|
||||
} else if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`);
|
||||
else if (!command) command = arg;
|
||||
else rest.push(arg);
|
||||
const [command, ...args] = argv;
|
||||
const option = argv.find((arg) => arg.startsWith("-"));
|
||||
if (option) throw new Error(`Unknown option: ${option}`);
|
||||
if (!COMMANDS.includes(command as Command)) throw new Error(`Unknown command: ${command}`);
|
||||
if (command === "list") {
|
||||
if (args.length !== 0) throw new Error("list does not accept <bot-id>");
|
||||
} else if (args.length !== 1) {
|
||||
throw new Error(`${command} requires exactly one <bot-id>`);
|
||||
}
|
||||
return { command, rest, configPath, json, help };
|
||||
}
|
||||
|
||||
function validateCommandArgs(parsed: ParsedArgs): void {
|
||||
const { command, rest, configPath, json } = parsed;
|
||||
if (command === "instance") {
|
||||
if (configPath || json) throw new Error("instance commands do not accept --config or --json");
|
||||
return;
|
||||
}
|
||||
if (command === "setup") {
|
||||
if (json || rest.length > 0) throw new Error("setup accepts only --config <path>");
|
||||
return;
|
||||
}
|
||||
if (command === "discover-backends" || command === "discover-agents") {
|
||||
if (configPath || rest.length > 0) throw new Error(`${command} accepts only --json`);
|
||||
return;
|
||||
}
|
||||
if (["start", "status", "doctor"].includes(command || "")) {
|
||||
if (json || rest.length > 0) throw new Error(`${command} accepts only --config <path>`);
|
||||
return;
|
||||
}
|
||||
if (command === "print") {
|
||||
if (json || rest.length !== 1 || rest[0] !== "feishu") throw new Error("print requires exactly the topic 'feishu'");
|
||||
return;
|
||||
}
|
||||
throw new Error(`Unknown command: ${command}`);
|
||||
}
|
||||
|
||||
async function printStatus(configPath?: string): Promise<void> {
|
||||
const loaded = loadConfigFile(configPath); const baseUrl = localBaseUrl(loaded.config);
|
||||
console.log(`Config: ${loaded.path}`);
|
||||
console.log(`Config version: ${loaded.config.configVersion}`);
|
||||
console.log(`Bot: ${loaded.config.bot.id}`);
|
||||
console.log(`Agent: ${loaded.config.bot.agent.id}`);
|
||||
console.log(`Platform: ${loaded.config.gateway.platform.type}`);
|
||||
console.log(`Server: ${loaded.config.gateway.server.host}:${loaded.config.gateway.server.port}`);
|
||||
console.log(`Workspace: ${loaded.config.bot.workspace}`);
|
||||
console.log(`State file: ${defaultStateFile(loaded.config)}`);
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(1_000) });
|
||||
console.log(`Health probe: HTTP ${response.status} ${await response.text()}`);
|
||||
} catch { console.log("Health probe: not reachable on local URL"); }
|
||||
return runInstanceCommand(command, args);
|
||||
}
|
||||
|
||||
function printHelp(): void {
|
||||
console.log(`gori-agent - single-Bot ACP gateway\n\nUsage:\n gori-agent instance init <bot-id>\n gori-agent instance start <bot-id>\n gori-agent instance stop <bot-id>\n gori-agent instance restart <bot-id>\n gori-agent instance status <bot-id>\n gori-agent instance logs <bot-id>\n gori-agent instance doctor <bot-id>\n gori-agent instance list\n\nDebug commands:\n gori-agent setup [--config path]\n gori-agent discover-backends [--json]\n gori-agent start --config path\n gori-agent status --config path\n gori-agent doctor --config path\n gori-agent print feishu --config path\n\nInstances root: \${GORI_AGENT_ROOT:-$HOME/.gori-agent}/instances`);
|
||||
console.log(`gori-agent - single-Bot ACP gateway
|
||||
|
||||
Usage:
|
||||
gori-agent init <bot-id>
|
||||
gori-agent setup <bot-id>
|
||||
gori-agent start <bot-id>
|
||||
gori-agent stop <bot-id>
|
||||
gori-agent restart <bot-id>
|
||||
gori-agent status <bot-id>
|
||||
gori-agent logs <bot-id>
|
||||
gori-agent doctor <bot-id>
|
||||
gori-agent list
|
||||
|
||||
Instances root: \${GORI_AGENT_ROOT:-$HOME/.gori-agent}/instances`);
|
||||
}
|
||||
|
||||
main(process.argv.slice(2)).then((code) => { if (Number.isInteger(code)) process.exitCode = code; }).catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1;
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user