Skip to content

Commit 7aad19e

Browse files
feat(cli): add chat, config, and doctor commands
- chat: interactive terminal loop with /help, /new, /exit slash commands - config show: prints current config with secrets masked (last 4 chars) - config set model|api-key|bot-token: interactive update via prompts - doctor: 11 health checks (config, workspace, API key formats, Telegram and OpenRouter connectivity); exits 1 on critical failures - Update splash screen with the three new commands - Fix Docs URL (was same as GitHub; now points to #readme anchor) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a1a444e commit 7aad19e

4 files changed

Lines changed: 591 additions & 0 deletions

File tree

src/cli/chat.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* CLI command for interactive terminal chat with the agent
3+
*/
4+
5+
import { existsSync } from "node:fs";
6+
import { join } from "node:path";
7+
import * as p from "@clack/prompts";
8+
import pc from "picocolors";
9+
10+
const CONFIG_PATH = join(process.cwd(), ".adk-claw", "config.json");
11+
12+
export async function chat() {
13+
if (!existsSync(CONFIG_PATH)) {
14+
p.log.error("ADK Claw is not initialized. Run 'adk-claw init' first.");
15+
process.exit(1);
16+
}
17+
18+
const s = p.spinner();
19+
s.start("Initializing agent...");
20+
21+
const { createClawdAgent } = await import("../agents/agent.js");
22+
const { getConfig } = await import("../config/index.js");
23+
24+
let agentResult: Awaited<ReturnType<typeof createClawdAgent>>;
25+
let agentName: string;
26+
27+
try {
28+
agentResult = await createClawdAgent({ channel: "cli" });
29+
agentName = getConfig().agentName;
30+
s.stop(`${pc.cyan(agentName)} is ready`);
31+
} catch (err) {
32+
s.stop("Failed to initialize agent");
33+
p.log.error(err instanceof Error ? err.message : "Unknown error");
34+
process.exit(1);
35+
}
36+
37+
p.intro(
38+
`${pc.bold(pc.magenta(agentName))} ${pc.dim("type /help for commands, /exit to quit")}`,
39+
);
40+
41+
while (true) {
42+
const input = await p.text({
43+
message: pc.cyan("You:"),
44+
placeholder: "Say something...",
45+
});
46+
47+
if (p.isCancel(input)) {
48+
break;
49+
}
50+
51+
const trimmed = (input as string).trim();
52+
53+
if (!trimmed) {
54+
continue;
55+
}
56+
57+
if (trimmed === "/exit" || trimmed === "/quit") {
58+
break;
59+
}
60+
61+
if (trimmed === "/help") {
62+
console.log(`
63+
${pc.bold("Commands:")}
64+
/help Show this help
65+
/new Start a fresh session
66+
/exit Quit the chat
67+
`);
68+
continue;
69+
}
70+
71+
if (trimmed === "/new") {
72+
const newS = p.spinner();
73+
newS.start("Starting new session...");
74+
try {
75+
agentResult = await createClawdAgent({ channel: "cli" });
76+
newS.stop("New session started");
77+
} catch (err) {
78+
newS.stop("Failed to start new session");
79+
p.log.error(err instanceof Error ? err.message : "Unknown error");
80+
}
81+
continue;
82+
}
83+
84+
const thinkS = p.spinner();
85+
thinkS.start("Thinking...");
86+
87+
try {
88+
const response = await agentResult.runner.ask(trimmed);
89+
thinkS.stop("");
90+
console.log(`\n ${pc.bold(pc.magenta(agentName))}: ${response}\n`);
91+
} catch (err) {
92+
thinkS.stop("Error");
93+
p.log.error(err instanceof Error ? err.message : "Unknown error");
94+
}
95+
}
96+
97+
p.outro("Goodbye!");
98+
}

src/cli/config.ts

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/**
2+
* CLI command for viewing and updating configuration
3+
*/
4+
5+
import { existsSync } from "node:fs";
6+
import { join } from "node:path";
7+
import * as p from "@clack/prompts";
8+
import pc from "picocolors";
9+
import { updateConfig } from "../lib/configWriter.js";
10+
import { OPENROUTER_MODELS } from "../lib/models.js";
11+
12+
const CONFIG_PATH = join(process.cwd(), ".adk-claw", "config.json");
13+
14+
function maskSecret(value: string): string {
15+
if (!value || value.length <= 4) return "****";
16+
return `${"*".repeat(value.length - 4)}${value.slice(-4)}`;
17+
}
18+
19+
async function showConfig() {
20+
if (!existsSync(CONFIG_PATH)) {
21+
p.log.error("ADK Claw is not initialized. Run 'adk-claw init' first.");
22+
process.exit(1);
23+
}
24+
25+
const { getRawConfig } = await import("../config/index.js");
26+
const config = getRawConfig();
27+
28+
p.intro(pc.bgCyan(pc.black(" ADK Claw Configuration ")));
29+
30+
console.log(` ${pc.bold("Agent")}`);
31+
console.log(` Name: ${pc.cyan(config.agent.name)}`);
32+
console.log(` Workspace: ${pc.dim(config.agent.workspace)}`);
33+
console.log();
34+
console.log(` ${pc.bold("Auth")}`);
35+
console.log(` Provider: ${pc.cyan(config.auth.provider)}`);
36+
console.log(` Model: ${pc.cyan(config.auth.model)}`);
37+
console.log(` API Key: ${pc.dim(maskSecret(config.auth.apiKey))}`);
38+
console.log();
39+
console.log(` ${pc.bold("Telegram")}`);
40+
console.log(
41+
` Enabled: ${config.channels.telegram.enabled ? pc.green("yes") : pc.red("no")}`,
42+
);
43+
console.log(
44+
` Bot Token: ${pc.dim(maskSecret(config.channels.telegram.botToken))}`,
45+
);
46+
console.log(` DM Policy: ${pc.dim(config.channels.telegram.dmPolicy)}`);
47+
console.log();
48+
console.log(` ${pc.bold("Skills")}`);
49+
console.log(
50+
` Installed: ${config.skills.installed.length > 0 ? pc.cyan(config.skills.installed.join(", ")) : pc.dim("none")}`,
51+
);
52+
console.log();
53+
console.log(pc.dim(` Updated: ${config.meta.lastUpdatedAt}`));
54+
55+
p.outro(pc.dim("Use 'adk-claw config set <field>' to update values."));
56+
}
57+
58+
async function setModel() {
59+
if (!existsSync(CONFIG_PATH)) {
60+
p.log.error("ADK Claw is not initialized. Run 'adk-claw init' first.");
61+
process.exit(1);
62+
}
63+
64+
const model = await p.select({
65+
message: "Select a model:",
66+
options: OPENROUTER_MODELS,
67+
});
68+
69+
if (p.isCancel(model)) {
70+
p.cancel("Cancelled.");
71+
return;
72+
}
73+
74+
const s = p.spinner();
75+
s.start("Updating model...");
76+
updateConfig((config) => {
77+
config.auth.model = model as string;
78+
});
79+
s.stop(`Model updated to ${pc.cyan(model as string)}`);
80+
}
81+
82+
async function setApiKey() {
83+
if (!existsSync(CONFIG_PATH)) {
84+
p.log.error("ADK Claw is not initialized. Run 'adk-claw init' first.");
85+
process.exit(1);
86+
}
87+
88+
const apiKey = await p.password({
89+
message: "Enter new OpenRouter API key:",
90+
validate: (value) => {
91+
if (!value || !value.trim()) return "API key is required";
92+
if (!value.startsWith("sk-"))
93+
return "Invalid API key format (should start with sk-)";
94+
return undefined;
95+
},
96+
});
97+
98+
if (p.isCancel(apiKey)) {
99+
p.cancel("Cancelled.");
100+
return;
101+
}
102+
103+
const s = p.spinner();
104+
s.start("Updating API key...");
105+
updateConfig((config) => {
106+
config.auth.apiKey = apiKey;
107+
});
108+
s.stop("API key updated");
109+
}
110+
111+
async function setBotToken() {
112+
if (!existsSync(CONFIG_PATH)) {
113+
p.log.error("ADK Claw is not initialized. Run 'adk-claw init' first.");
114+
process.exit(1);
115+
}
116+
117+
const botToken = await p.password({
118+
message: "Enter new Telegram bot token:",
119+
validate: (value) => {
120+
if (!value || !value.trim()) return "Bot token is required";
121+
if (!value.includes(":")) return "Invalid bot token format";
122+
return undefined;
123+
},
124+
});
125+
126+
if (p.isCancel(botToken)) {
127+
p.cancel("Cancelled.");
128+
return;
129+
}
130+
131+
const s = p.spinner();
132+
s.start("Updating bot token...");
133+
updateConfig((config) => {
134+
config.channels.telegram.botToken = botToken;
135+
});
136+
s.stop("Bot token updated");
137+
}
138+
139+
function printConfigHelp() {
140+
console.log(`
141+
${pc.bold("adk-claw config")} - View and update configuration
142+
143+
${pc.bold("Usage:")}
144+
adk-claw config [subcommand]
145+
146+
${pc.bold("Subcommands:")}
147+
show Print current config (secrets masked)
148+
set model Change the AI model
149+
set api-key Change the OpenRouter API key
150+
set bot-token Change the Telegram bot token
151+
`);
152+
}
153+
154+
export async function configCommand(args: string[]) {
155+
const subcommand = args[0];
156+
157+
switch (subcommand) {
158+
case "show":
159+
case undefined:
160+
await showConfig();
161+
break;
162+
case "set": {
163+
const field = args[1];
164+
switch (field) {
165+
case "model":
166+
await setModel();
167+
break;
168+
case "api-key":
169+
await setApiKey();
170+
break;
171+
case "bot-token":
172+
await setBotToken();
173+
break;
174+
default:
175+
p.log.error(
176+
`Unknown field: ${field ?? "(none)"}. Valid fields: model, api-key, bot-token`,
177+
);
178+
printConfigHelp();
179+
process.exit(1);
180+
}
181+
break;
182+
}
183+
case "help":
184+
case "--help":
185+
case "-h":
186+
printConfigHelp();
187+
break;
188+
default:
189+
p.log.error(`Unknown config command: ${subcommand}`);
190+
printConfigHelp();
191+
process.exit(1);
192+
}
193+
}

0 commit comments

Comments
 (0)