feat: refactor CLI registration and remove unused dependencies

This commit is contained in:
Lauren ten Hoor
2026-02-09 14:04:02 +08:00
parent 53dac61911
commit b5bcd313e8
4 changed files with 10387 additions and 209 deletions

View File

@@ -6,7 +6,7 @@ import { createSessionHealthTool } from "./lib/tools/session-health.js";
import { createProjectRegisterTool } from "./lib/tools/project-register.js"; import { createProjectRegisterTool } from "./lib/tools/project-register.js";
import { createTaskCreateTool } from "./lib/tools/task-create.js"; import { createTaskCreateTool } from "./lib/tools/task-create.js";
import { createSetupTool } from "./lib/tools/devclaw-setup.js"; import { createSetupTool } from "./lib/tools/devclaw-setup.js";
import { runCli } from "./lib/cli.js"; import { registerCli } from "./lib/cli.js";
const plugin = { const plugin = {
id: "devclaw", id: "devclaw",
@@ -61,12 +61,9 @@ const plugin = {
names: ["devclaw_setup"], names: ["devclaw_setup"],
}); });
// CLI commands // CLI: `openclaw devclaw setup`
api.registerCli("setup", { api.registerCli(({ program }: { program: any }) => registerCli(program), {
description: "Set up DevClaw: create agent, configure models, write workspace files", commands: ["devclaw"],
run: async (argv: string[]) => {
await runCli(argv);
},
}); });
api.logger.info("DevClaw plugin registered (7 tools, 1 CLI command)"); api.logger.info("DevClaw plugin registered (7 tools, 1 CLI command)");

View File

@@ -1,208 +1,68 @@
/** /**
* cli.ts — CLI command for `openclaw devclaw setup`. * cli.ts — CLI registration for `openclaw devclaw setup`.
* *
* Interactive and non-interactive modes for onboarding. * Uses Commander.js (provided by OpenClaw plugin SDK context).
*/ */
import { createInterface } from "node:readline"; import type { Command } from "commander";
import { runSetup, type SetupOpts } from "./setup.js"; import { runSetup } from "./setup.js";
import { ALL_TIERS, DEFAULT_MODELS, type Tier } from "./tiers.js"; import { ALL_TIERS, DEFAULT_MODELS, type Tier } from "./tiers.js";
type CliArgs = {
/** Create a new agent */
newAgent?: string;
/** Use existing agent */
agent?: string;
/** Direct workspace path */
workspace?: string;
/** Model overrides */
junior?: string;
medior?: string;
senior?: string;
qa?: string;
/** Skip prompts */
nonInteractive?: boolean;
};
/** /**
* Parse CLI arguments from argv-style array. * Register the `devclaw` CLI command group on a Commander program.
* Expects: ["setup", "--new-agent", "name", "--junior", "model", ...]
*/ */
export function parseArgs(argv: string[]): CliArgs { export function registerCli(program: Command): void {
const args: CliArgs = {}; const devclaw = program
for (let i = 0; i < argv.length; i++) { .command("devclaw")
const arg = argv[i]; .description("DevClaw development pipeline tools");
const next = argv[i + 1];
switch (arg) {
case "--new-agent":
args.newAgent = next;
i++;
break;
case "--agent":
args.agent = next;
i++;
break;
case "--workspace":
args.workspace = next;
i++;
break;
case "--junior":
args.junior = next;
i++;
break;
case "--medior":
args.medior = next;
i++;
break;
case "--senior":
args.senior = next;
i++;
break;
case "--qa":
args.qa = next;
i++;
break;
case "--non-interactive":
args.nonInteractive = true;
break;
}
}
return args;
}
/** devclaw
* Run the interactive setup wizard. .command("setup")
*/ .description("Set up DevClaw: create agent, configure models, write workspace files")
async function interactiveSetup(): Promise<SetupOpts> { .option("--new-agent <name>", "Create a new agent with this name")
const rl = createInterface({ .option("--agent <id>", "Use an existing agent by ID")
input: process.stdin, .option("--workspace <path>", "Direct workspace path")
output: process.stdout, .option("--junior <model>", `Junior dev model (default: ${DEFAULT_MODELS.junior})`)
.option("--medior <model>", `Medior dev model (default: ${DEFAULT_MODELS.medior})`)
.option("--senior <model>", `Senior dev model (default: ${DEFAULT_MODELS.senior})`)
.option("--qa <model>", `QA engineer model (default: ${DEFAULT_MODELS.qa})`)
.action(async (opts) => {
const models: Partial<Record<Tier, string>> = {};
if (opts.junior) models.junior = opts.junior;
if (opts.medior) models.medior = opts.medior;
if (opts.senior) models.senior = opts.senior;
if (opts.qa) models.qa = opts.qa;
const result = await runSetup({
newAgentName: opts.newAgent,
agentId: opts.agent,
workspacePath: opts.workspace,
models: Object.keys(models).length > 0 ? models : undefined,
}); });
const ask = (question: string): Promise<string> =>
new Promise((resolve) => rl.question(question, resolve));
console.log("");
console.log("DevClaw Setup");
console.log("=============");
console.log("");
// Step 1: Agent
console.log("Step 1: Agent");
console.log("─────────────");
const agentChoice = await ask(
"Create a new agent or use an existing one? [new/existing]: ",
);
let newAgentName: string | undefined;
let agentId: string | undefined;
if (agentChoice.toLowerCase().startsWith("n")) {
newAgentName = await ask("Agent name: ");
if (!newAgentName.trim()) {
rl.close();
throw new Error("Agent name cannot be empty");
}
newAgentName = newAgentName.trim();
} else {
agentId = await ask("Agent ID: ");
if (!agentId.trim()) {
rl.close();
throw new Error("Agent ID cannot be empty");
}
agentId = agentId.trim();
}
// Step 2: Models
console.log("");
console.log("Step 2: Developer Team (models)");
console.log("───────────────────────────────");
console.log("Press Enter to accept defaults.");
console.log("");
const models: Partial<Record<Tier, string>> = {};
for (const tier of ALL_TIERS) {
const label =
tier === "junior"
? "Junior dev (fast, cheap tasks)"
: tier === "medior"
? "Medior dev (standard tasks)"
: tier === "senior"
? "Senior dev (complex tasks)"
: "QA engineer (code review)";
const answer = await ask(` ${label} [${DEFAULT_MODELS[tier]}]: `);
if (answer.trim()) {
models[tier] = answer.trim();
}
}
rl.close();
console.log("");
console.log("Step 3: Workspace");
console.log("─────────────────");
return { newAgentName, agentId, models };
}
/**
* Main CLI entry point.
*/
export async function runCli(argv: string[]): Promise<void> {
const args = parseArgs(argv);
let opts: SetupOpts;
if (args.nonInteractive || args.newAgent || args.agent || args.workspace) {
// Non-interactive mode
const models: Partial<Record<Tier, string>> = {};
if (args.junior) models.junior = args.junior;
if (args.medior) models.medior = args.medior;
if (args.senior) models.senior = args.senior;
if (args.qa) models.qa = args.qa;
opts = {
newAgentName: args.newAgent,
agentId: args.agent,
workspacePath: args.workspace,
models: Object.keys(models).length > 0 ? models : undefined,
};
} else {
// Interactive mode
opts = await interactiveSetup();
}
console.log("");
const result = await runSetup(opts);
// Print results
if (result.agentCreated) { if (result.agentCreated) {
console.log(`Agent "${result.agentId}" created`); console.log(`Agent "${result.agentId}" created`);
} }
console.log(` Models configured:`); console.log("Models configured:");
for (const tier of ALL_TIERS) { for (const tier of ALL_TIERS) {
console.log(` ${tier}: ${result.models[tier]}`); console.log(` ${tier}: ${result.models[tier]}`);
} }
console.log(` Files written:`); console.log("Files written:");
for (const file of result.filesWritten) { for (const file of result.filesWritten) {
console.log(` ${file}`); console.log(` ${file}`);
} }
if (result.warnings.length > 0) { if (result.warnings.length > 0) {
console.log(""); console.log("\nWarnings:");
console.log(" Warnings:");
for (const w of result.warnings) { for (const w of result.warnings) {
console.log(` ${w}`); console.log(` ${w}`);
} }
} }
console.log(""); console.log("\nDone! Next steps:");
console.log("Done! Next steps:");
console.log(" 1. Add bot to a Telegram group"); console.log(" 1. Add bot to a Telegram group");
console.log( console.log(' 2. Register a project: "Register project <name> at <repo> for group <id>"');
' 2. Register a project: "Register project <name> at <repo> for group <id>"',
);
console.log(" 3. Create your first issue and pick it up"); console.log(" 3. Create your first issue and pick it up");
console.log(""); });
} }

10320
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@
"dev-pipeline", "dev-pipeline",
"orchestration", "orchestration",
"gitlab", "gitlab",
"github",
"multi-project" "multi-project"
], ],
"engines": { "engines": {
@@ -25,7 +26,7 @@
"./index.ts" "./index.ts"
] ]
}, },
"devDependencies": { "peerDependencies": {
"openclaw": "workspace:*" "openclaw": ">=2026.0.0"
} }
} }