babyclaw 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +5 -0
- package/build/commands/config/edit.js +34 -0
- package/build/commands/config/init.js +44 -0
- package/build/commands/config/validate.js +51 -0
- package/build/commands/doctor.js +121 -0
- package/build/commands/gateway/reload.js +23 -0
- package/build/commands/gateway/status.js +38 -0
- package/build/commands/index.js +37 -0
- package/build/commands/model/alias/index.js +27 -0
- package/build/commands/model/alias/remove.js +52 -0
- package/build/commands/model/alias/set.js +63 -0
- package/build/commands/model/configure.js +154 -0
- package/build/commands/model/index.js +55 -0
- package/build/commands/pinch.js +34 -0
- package/build/commands/service/install.js +33 -0
- package/build/commands/service/restart.js +30 -0
- package/build/commands/service/start.js +37 -0
- package/build/commands/service/status.js +42 -0
- package/build/commands/service/stop.js +32 -0
- package/build/commands/service/uninstall.js +28 -0
- package/build/commands/setup.js +311 -0
- package/build/commands/skills/install.js +85 -0
- package/build/commands/skills/search.js +114 -0
- package/build/service/adapter.js +265 -0
- package/build/service/adapter.test.js +33 -0
- package/build/ui/theme.js +69 -0
- package/build/ui/theme.test.js +33 -0
- package/package.json +44 -0
package/build/cli.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import { getConfigPath } from "@babyclaw/gateway";
|
|
6
|
+
import { c } from "../../ui/theme.js";
|
|
7
|
+
export default command({
|
|
8
|
+
description: "Open config in your editor",
|
|
9
|
+
handler: async ({ client }) => {
|
|
10
|
+
const configPath = getConfigPath();
|
|
11
|
+
if (!existsSync(configPath)) {
|
|
12
|
+
client.log(c.error("✗ No config file found at:"));
|
|
13
|
+
client.log(c.muted(` ${configPath}`));
|
|
14
|
+
client.log(c.muted(" Run ") + c.info("babyclaw config init") + c.muted(" first."));
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const editor = process.env["EDITOR"] || process.env["VISUAL"] || "vi";
|
|
19
|
+
try {
|
|
20
|
+
execSync(`${editor} ${JSON.stringify(configPath)}`, {
|
|
21
|
+
stdio: "inherit",
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
const content = await readFile(configPath, "utf8");
|
|
26
|
+
process.stdout.write(`\nCurrent config at ${configPath}:\n\n`);
|
|
27
|
+
process.stdout.write(content);
|
|
28
|
+
process.stdout.write("\nEdit this file manually, then run 'babyclaw config validate'.\n");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
client.log(c.success("✓ Editor closed."));
|
|
32
|
+
client.log(c.muted(" Run ") + c.info("babyclaw config validate") + c.muted(" to check for issues."));
|
|
33
|
+
},
|
|
34
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { getConfigPath, getDefaultConfigTemplate } from "@babyclaw/gateway";
|
|
6
|
+
import { c } from "../../ui/theme.js";
|
|
7
|
+
export default command({
|
|
8
|
+
description: "Create a fresh configuration file",
|
|
9
|
+
options: {
|
|
10
|
+
force: {
|
|
11
|
+
type: "boolean",
|
|
12
|
+
description: "Overwrite existing config file",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
handler: async ({ options, client }) => {
|
|
16
|
+
const force = await options.force();
|
|
17
|
+
const configPath = getConfigPath();
|
|
18
|
+
try {
|
|
19
|
+
if (existsSync(configPath) && !force) {
|
|
20
|
+
client.log(c.warning("⚠ Config file already exists at:"));
|
|
21
|
+
client.log(c.muted(` ${configPath}`));
|
|
22
|
+
const overwrite = await client.confirm("Overwrite existing config?", false);
|
|
23
|
+
if (!overwrite)
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
await mkdir(dirname(configPath), { recursive: true });
|
|
27
|
+
await writeFile(configPath, getDefaultConfigTemplate(), "utf8");
|
|
28
|
+
client.log(c.success("✓ Config created!"));
|
|
29
|
+
client.log(c.muted(` ${configPath}`));
|
|
30
|
+
client.log(c.muted(" Next: run ") +
|
|
31
|
+
c.info("babyclaw model configure") +
|
|
32
|
+
c.muted(" to set up AI providers, or edit ") +
|
|
33
|
+
c.info("channels.telegram.botToken") +
|
|
34
|
+
c.muted(" and ") +
|
|
35
|
+
c.info("ai.providers") +
|
|
36
|
+
c.muted(" manually."));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
client.log(c.error("✗ Failed to create config"));
|
|
40
|
+
client.log(c.muted(error instanceof Error ? error.message : String(error)));
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { getConfigPath, babyclawConfigSchema } from "@babyclaw/gateway";
|
|
5
|
+
import { c } from "../../ui/theme.js";
|
|
6
|
+
export default command({
|
|
7
|
+
description: "Validate your current config",
|
|
8
|
+
handler: async ({ client }) => {
|
|
9
|
+
const configPath = getConfigPath();
|
|
10
|
+
try {
|
|
11
|
+
if (!existsSync(configPath)) {
|
|
12
|
+
client.log(c.error("✗ No config file found at:"));
|
|
13
|
+
client.log(c.muted(` ${configPath}`));
|
|
14
|
+
client.log(c.muted(" Run ") + c.info("babyclaw config init") + c.muted(" to create one."));
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const raw = await readFile(configPath, "utf8");
|
|
19
|
+
let json;
|
|
20
|
+
try {
|
|
21
|
+
json = JSON.parse(raw);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
client.log(c.error("✗ Config is invalid"));
|
|
25
|
+
client.log(c.muted(` ${configPath}`));
|
|
26
|
+
client.log(c.warning(" • Config file is not valid JSON."));
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const result = babyclawConfigSchema.safeParse(json);
|
|
31
|
+
if (result.success) {
|
|
32
|
+
client.log(c.success("✓ Config is valid!"));
|
|
33
|
+
client.log(c.muted(` ${configPath}`));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
client.log(c.error("✗ Config is invalid"));
|
|
37
|
+
client.log(c.muted(` ${configPath}`));
|
|
38
|
+
for (const issue of result.error.issues) {
|
|
39
|
+
const path = issue.path.length > 0 ? issue.path.map(String).join(".") : "$";
|
|
40
|
+
client.log(c.warning(` • ${path}: ${issue.message}`));
|
|
41
|
+
}
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
client.log(c.error("✗ Error reading config"));
|
|
47
|
+
client.log(c.muted(error instanceof Error ? error.message : String(error)));
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { AdminClient, getAdminSocketPath, getConfigPath, babyclawConfigSchema, } from "@babyclaw/gateway";
|
|
5
|
+
import { c } from "../ui/theme.js";
|
|
6
|
+
import { getStatus as getServiceStatus } from "../service/adapter.js";
|
|
7
|
+
function icon(status) {
|
|
8
|
+
if (status === "pass")
|
|
9
|
+
return c.success("✓");
|
|
10
|
+
if (status === "warn")
|
|
11
|
+
return c.warning("⚠");
|
|
12
|
+
return c.error("✗");
|
|
13
|
+
}
|
|
14
|
+
export default command({
|
|
15
|
+
description: "Run diagnostics on your setup",
|
|
16
|
+
handler: async ({ client }) => {
|
|
17
|
+
const checks = [];
|
|
18
|
+
const configPath = getConfigPath();
|
|
19
|
+
if (existsSync(configPath)) {
|
|
20
|
+
checks.push({
|
|
21
|
+
label: "Config file exists",
|
|
22
|
+
status: "pass",
|
|
23
|
+
detail: configPath,
|
|
24
|
+
});
|
|
25
|
+
try {
|
|
26
|
+
const raw = await readFile(configPath, "utf8");
|
|
27
|
+
const json = JSON.parse(raw);
|
|
28
|
+
const parsed = babyclawConfigSchema.safeParse(json);
|
|
29
|
+
if (parsed.success) {
|
|
30
|
+
checks.push({ label: "Config passes validation", status: "pass" });
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const count = parsed.error.issues.length;
|
|
34
|
+
checks.push({
|
|
35
|
+
label: "Config passes validation",
|
|
36
|
+
status: "fail",
|
|
37
|
+
detail: `${count} issue${count === 1 ? "" : "s"} found`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
checks.push({
|
|
43
|
+
label: "Config passes validation",
|
|
44
|
+
status: "fail",
|
|
45
|
+
detail: "Invalid JSON",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
checks.push({
|
|
51
|
+
label: "Config file exists",
|
|
52
|
+
status: "fail",
|
|
53
|
+
detail: `Not found at ${configPath}`,
|
|
54
|
+
});
|
|
55
|
+
checks.push({
|
|
56
|
+
label: "Config passes validation",
|
|
57
|
+
status: "fail",
|
|
58
|
+
detail: "No config to validate",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const info = getServiceStatus();
|
|
63
|
+
checks.push({
|
|
64
|
+
label: "Service installed",
|
|
65
|
+
status: info.installed ? "pass" : "warn",
|
|
66
|
+
detail: info.installed ? `${info.platform}` : "Not installed yet",
|
|
67
|
+
});
|
|
68
|
+
checks.push({
|
|
69
|
+
label: "Service running",
|
|
70
|
+
status: info.running ? "pass" : "warn",
|
|
71
|
+
detail: info.running ? `PID ${info.pid}` : "Not running",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
checks.push({
|
|
76
|
+
label: "Service installed",
|
|
77
|
+
status: "warn",
|
|
78
|
+
detail: "Could not check (unsupported platform?)",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const adminClient = new AdminClient({
|
|
83
|
+
socketPath: getAdminSocketPath(),
|
|
84
|
+
});
|
|
85
|
+
const status = await adminClient.status();
|
|
86
|
+
checks.push({
|
|
87
|
+
label: "Gateway admin socket reachable",
|
|
88
|
+
status: "pass",
|
|
89
|
+
detail: `State: ${status.state}`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
checks.push({
|
|
94
|
+
label: "Gateway admin socket reachable",
|
|
95
|
+
status: "warn",
|
|
96
|
+
detail: "Not reachable (gateway may not be running)",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const passCount = checks.filter((ck) => ck.status === "pass").length;
|
|
100
|
+
const failCount = checks.filter((ck) => ck.status === "fail").length;
|
|
101
|
+
const warnCount = checks.filter((ck) => ck.status === "warn").length;
|
|
102
|
+
client.log(c.bold(" 🩺 babyclaw doctor"));
|
|
103
|
+
client.log("");
|
|
104
|
+
for (const check of checks) {
|
|
105
|
+
const detail = check.detail ? c.muted(` — ${check.detail}`) : "";
|
|
106
|
+
client.log(` ${icon(check.status)} ${check.label}${detail}`);
|
|
107
|
+
}
|
|
108
|
+
client.log("");
|
|
109
|
+
if (failCount === 0 && warnCount === 0) {
|
|
110
|
+
client.log(c.success(" All clear! Your claw is in perfect shape. 🦀"));
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
let summary = c.success(`${passCount} passed`);
|
|
114
|
+
if (warnCount > 0)
|
|
115
|
+
summary += c.warning(` · ${warnCount} warning${warnCount > 1 ? "s" : ""}`);
|
|
116
|
+
if (failCount > 0)
|
|
117
|
+
summary += c.error(` · ${failCount} failed`);
|
|
118
|
+
client.log(` ${summary}`);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { AdminClient, getAdminSocketPath } from "@babyclaw/gateway";
|
|
3
|
+
import { c } from "../../ui/theme.js";
|
|
4
|
+
export default command({
|
|
5
|
+
description: "Check gateway health / signal a reload",
|
|
6
|
+
handler: async ({ client }) => {
|
|
7
|
+
try {
|
|
8
|
+
const adminClient = new AdminClient({
|
|
9
|
+
socketPath: getAdminSocketPath(),
|
|
10
|
+
});
|
|
11
|
+
await adminClient.health();
|
|
12
|
+
client.log(c.success("✓ Gateway is alive. Config reload requires a service restart for now."));
|
|
13
|
+
client.log(c.muted(" Run ") +
|
|
14
|
+
c.info("babyclaw service restart") +
|
|
15
|
+
c.muted(" to apply config changes."));
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
client.log(c.error("✗ Could not reach the gateway"));
|
|
19
|
+
client.log(c.muted(` ${err instanceof Error ? err.message : String(err)}`));
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { AdminClient, getAdminSocketPath } from "@babyclaw/gateway";
|
|
3
|
+
import { c, formatUptime } from "../../ui/theme.js";
|
|
4
|
+
export default command({
|
|
5
|
+
description: "Query the running gateway for live status",
|
|
6
|
+
options: {
|
|
7
|
+
json: { type: "boolean", description: "Output raw JSON" },
|
|
8
|
+
},
|
|
9
|
+
handler: async ({ options, client }) => {
|
|
10
|
+
const json = await options.json();
|
|
11
|
+
try {
|
|
12
|
+
const adminClient = new AdminClient({
|
|
13
|
+
socketPath: getAdminSocketPath(),
|
|
14
|
+
});
|
|
15
|
+
const data = await adminClient.status();
|
|
16
|
+
if (json) {
|
|
17
|
+
client.log(JSON.stringify(data, null, 2));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const uptime = data.uptimeMs != null ? formatUptime(data.uptimeMs) : "—";
|
|
21
|
+
client.log(`${c.success("●")} Gateway is ${c.success(data.state)}`);
|
|
22
|
+
client.log(` ${c.muted("PID ")}${data.pid}`);
|
|
23
|
+
client.log(` ${c.muted("Uptime ")}${uptime}`);
|
|
24
|
+
client.log(` ${c.muted("Version ")}${data.version}`);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
if (json) {
|
|
28
|
+
client.log(JSON.stringify({
|
|
29
|
+
state: "stopped",
|
|
30
|
+
error: err instanceof Error ? err.message : String(err),
|
|
31
|
+
}, null, 2));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
client.log(`${c.error("●")} Gateway is ${c.error(c.bold("not running"))}`);
|
|
35
|
+
client.log(c.muted(` ${err instanceof Error ? err.message : String(err)}`));
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { c, getRandomBanner, getRandomTip } from "../ui/theme.js";
|
|
3
|
+
export default command({
|
|
4
|
+
description: "Your friendly neighborhood agent gateway",
|
|
5
|
+
handler: async ({ client }) => {
|
|
6
|
+
client.log(c.brand(getRandomBanner()));
|
|
7
|
+
const pad = (cmd, width) => cmd + " ".repeat(Math.max(1, width - cmd.length));
|
|
8
|
+
const cmds = [
|
|
9
|
+
["config init", "Create a fresh configuration file"],
|
|
10
|
+
["config validate", "Validate your current config"],
|
|
11
|
+
["config edit", "Open config in your editor"],
|
|
12
|
+
["service install", "Install the gateway as a system service"],
|
|
13
|
+
["service uninstall", "Uninstall the gateway system service"],
|
|
14
|
+
["service status", "Check if the gateway service is installed/running"],
|
|
15
|
+
["service start", "Start the gateway service"],
|
|
16
|
+
["service stop", "Stop the gateway service"],
|
|
17
|
+
["service restart", "Restart the gateway service"],
|
|
18
|
+
["model", "Show current model configuration"],
|
|
19
|
+
["model configure", "Interactive model provider setup wizard"],
|
|
20
|
+
["model alias", "List model aliases"],
|
|
21
|
+
["model alias set", "Create or update a model alias"],
|
|
22
|
+
["model alias remove", "Remove a model alias"],
|
|
23
|
+
["gateway status", "Query the running gateway for live status"],
|
|
24
|
+
["gateway reload", "Check gateway health / signal a reload"],
|
|
25
|
+
["skill install", "Install a skill from ClawHub"],
|
|
26
|
+
["skill search", "Search for skills on ClawHub"],
|
|
27
|
+
["doctor", "Run diagnostics on your setup"],
|
|
28
|
+
];
|
|
29
|
+
client.log("");
|
|
30
|
+
client.log(" " + c.bold("Commands:"));
|
|
31
|
+
for (const [name, desc] of cmds) {
|
|
32
|
+
client.log(` ${c.info(pad(name, 20))}${c.muted("· ")}${desc}`);
|
|
33
|
+
}
|
|
34
|
+
client.log("");
|
|
35
|
+
client.log(` ${c.muted(`💡 tip: ${getRandomTip()}`)}`);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { getConfigPath, loadConfigRaw } from "@babyclaw/gateway";
|
|
3
|
+
import { c } from "../../../ui/theme.js";
|
|
4
|
+
export default command({
|
|
5
|
+
description: "List model aliases",
|
|
6
|
+
handler: async ({ client }) => {
|
|
7
|
+
const config = await loadConfigRaw();
|
|
8
|
+
if (!config) {
|
|
9
|
+
client.log(c.error(`No valid config found at ${getConfigPath()}`));
|
|
10
|
+
client.log(c.muted(" Run ") + c.info("babyclaw config init") + c.muted(" first."));
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const entries = Object.entries(config.ai.aliases);
|
|
15
|
+
client.log(c.bold(" Model Aliases"));
|
|
16
|
+
if (entries.length === 0) {
|
|
17
|
+
client.log(c.muted(" No aliases configured. Use ") +
|
|
18
|
+
c.info("babyclaw model alias set") +
|
|
19
|
+
c.muted(" to create one."));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
for (const [name, ref] of entries) {
|
|
23
|
+
client.log(` ${c.warning(c.bold(name))}${c.muted(" → ")}${ref}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { getConfigPath, loadConfigRaw, writeConfig } from "@babyclaw/gateway";
|
|
3
|
+
import { c } from "../../../ui/theme.js";
|
|
4
|
+
export default command({
|
|
5
|
+
description: "Remove a model alias",
|
|
6
|
+
options: {
|
|
7
|
+
name: {
|
|
8
|
+
type: "string",
|
|
9
|
+
description: "Alias name to remove",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
handler: async ({ options, client }) => {
|
|
13
|
+
try {
|
|
14
|
+
const config = await loadConfigRaw();
|
|
15
|
+
if (!config) {
|
|
16
|
+
client.log(c.error(`No valid config found at ${getConfigPath()}`));
|
|
17
|
+
client.log(c.muted(" Run ") + c.info("babyclaw config init") + c.muted(" first."));
|
|
18
|
+
process.exitCode = 1;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const entries = Object.entries(config.ai.aliases);
|
|
22
|
+
let name = await options.name();
|
|
23
|
+
if (!name) {
|
|
24
|
+
if (entries.length === 0) {
|
|
25
|
+
client.log(c.muted("No aliases configured. Nothing to remove."));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
name = (await client.prompt({
|
|
29
|
+
type: "select",
|
|
30
|
+
message: "Select alias to remove",
|
|
31
|
+
choices: entries.map(([n, ref]) => ({
|
|
32
|
+
title: `${n} ${c.muted(`→ ${ref}`)}`,
|
|
33
|
+
value: n,
|
|
34
|
+
})),
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
if (!(name in config.ai.aliases)) {
|
|
38
|
+
client.log(c.warning(`Alias ${c.bold(name)} does not exist.`));
|
|
39
|
+
client.log(c.muted(" Run ") + c.info("babyclaw model alias") + c.muted(" to see current aliases."));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
delete config.ai.aliases[name];
|
|
43
|
+
await writeConfig({ config });
|
|
44
|
+
client.log(c.success(`Removed alias ${c.bold(name)}`));
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
client.log(c.error("Failed to remove alias"));
|
|
48
|
+
client.log(c.muted(err instanceof Error ? err.message : String(err)));
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { getConfigPath, loadConfigRaw, writeConfig } from "@babyclaw/gateway";
|
|
3
|
+
import { c } from "../../../ui/theme.js";
|
|
4
|
+
export default command({
|
|
5
|
+
description: "Create or update a model alias",
|
|
6
|
+
options: {
|
|
7
|
+
name: {
|
|
8
|
+
type: "string",
|
|
9
|
+
description: "Alias name (e.g. fast, smart)",
|
|
10
|
+
required: true,
|
|
11
|
+
},
|
|
12
|
+
model: {
|
|
13
|
+
type: "string",
|
|
14
|
+
description: "Model reference in provider:modelId format",
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
handler: async ({ options, client }) => {
|
|
19
|
+
const name = await options.name({
|
|
20
|
+
prompt: "Alias name",
|
|
21
|
+
validate: (val) => {
|
|
22
|
+
if (!val)
|
|
23
|
+
return "Alias name is required";
|
|
24
|
+
const cleaned = val.toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
25
|
+
if (!cleaned)
|
|
26
|
+
return "Use only lowercase letters, numbers, hyphens, and underscores";
|
|
27
|
+
return true;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const model = await options.model({
|
|
31
|
+
prompt: "Model reference (provider:modelId)",
|
|
32
|
+
validate: (val) => {
|
|
33
|
+
if (!val)
|
|
34
|
+
return "Model reference is required";
|
|
35
|
+
if (!val.includes(":"))
|
|
36
|
+
return "Expected format: provider:modelId";
|
|
37
|
+
return true;
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
const config = await loadConfigRaw();
|
|
42
|
+
if (!config) {
|
|
43
|
+
client.log(c.error(`No valid config found at ${getConfigPath()}`));
|
|
44
|
+
client.log(c.muted(" Run ") + c.info("babyclaw config init") + c.muted(" first."));
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const aliasName = name.toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
49
|
+
const wasUpdate = aliasName in config.ai.aliases;
|
|
50
|
+
config.ai.aliases[aliasName] = model;
|
|
51
|
+
await writeConfig({ config });
|
|
52
|
+
client.log(c.success(`${wasUpdate ? "Updated" : "Created"} alias `) +
|
|
53
|
+
c.warning(c.bold(aliasName)) +
|
|
54
|
+
c.muted(" → ") +
|
|
55
|
+
model);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
client.log(c.error("Failed to set alias"));
|
|
59
|
+
client.log(c.muted(err instanceof Error ? err.message : String(err)));
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { getConfigPath, loadConfigRaw, writeConfig, getDefaultConfigTemplate, babyclawConfigSchema, SUPPORTED_PROVIDERS, } from "@babyclaw/gateway";
|
|
3
|
+
import { c } from "../../ui/theme.js";
|
|
4
|
+
export default command({
|
|
5
|
+
description: "Interactive model provider setup wizard",
|
|
6
|
+
handler: async ({ client }) => {
|
|
7
|
+
let baseConfig;
|
|
8
|
+
const existing = await loadConfigRaw();
|
|
9
|
+
if (existing) {
|
|
10
|
+
baseConfig = existing;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
const template = JSON.parse(getDefaultConfigTemplate());
|
|
14
|
+
baseConfig = babyclawConfigSchema.parse(template);
|
|
15
|
+
}
|
|
16
|
+
const providers = existing
|
|
17
|
+
? Object.entries(existing.ai.providers).map(([id, cfg]) => ({
|
|
18
|
+
id,
|
|
19
|
+
apiKey: cfg.apiKey,
|
|
20
|
+
baseUrl: cfg.baseUrl ?? "",
|
|
21
|
+
}))
|
|
22
|
+
: [];
|
|
23
|
+
let chatModel = baseConfig.ai.models.chat;
|
|
24
|
+
let addingProviders = true;
|
|
25
|
+
while (addingProviders) {
|
|
26
|
+
const providerChoices = SUPPORTED_PROVIDERS.map((p) => {
|
|
27
|
+
const configured = providers.find((e) => e.id === p.id);
|
|
28
|
+
return {
|
|
29
|
+
title: configured ? `${p.displayName} (configured)` : p.displayName,
|
|
30
|
+
value: p.id,
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
providerChoices.push({
|
|
34
|
+
title: providers.length > 0 ? "Done adding providers →" : "(add at least one provider first)",
|
|
35
|
+
value: "__done__",
|
|
36
|
+
});
|
|
37
|
+
client.log("");
|
|
38
|
+
client.log(c.bold(" Model Provider Setup"));
|
|
39
|
+
if (providers.length > 0) {
|
|
40
|
+
client.log(c.muted(` Configured: ${providers.map((p) => p.id).join(", ")}`));
|
|
41
|
+
}
|
|
42
|
+
const selected = await client.prompt({
|
|
43
|
+
type: "select",
|
|
44
|
+
message: "Select a provider to configure (or Done when finished)",
|
|
45
|
+
choices: providerChoices,
|
|
46
|
+
});
|
|
47
|
+
if (selected === "__done__") {
|
|
48
|
+
if (providers.length === 0) {
|
|
49
|
+
client.log(c.warning(" Add at least one provider before continuing."));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
addingProviders = false;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const providerId = selected;
|
|
56
|
+
const meta = SUPPORTED_PROVIDERS.find((p) => p.id === providerId);
|
|
57
|
+
const existingEntry = providers.find((p) => p.id === providerId);
|
|
58
|
+
client.log(c.bold(` Configure ${meta?.displayName ?? providerId}`));
|
|
59
|
+
const apiKey = await client.prompt({
|
|
60
|
+
type: "password",
|
|
61
|
+
message: "API Key",
|
|
62
|
+
initial: existingEntry?.apiKey,
|
|
63
|
+
});
|
|
64
|
+
const baseUrl = await client.prompt({
|
|
65
|
+
type: "text",
|
|
66
|
+
message: "Base URL (leave empty for default)",
|
|
67
|
+
initial: existingEntry?.baseUrl,
|
|
68
|
+
});
|
|
69
|
+
const entry = {
|
|
70
|
+
id: providerId,
|
|
71
|
+
apiKey: apiKey,
|
|
72
|
+
baseUrl: baseUrl ?? "",
|
|
73
|
+
};
|
|
74
|
+
const idx = providers.findIndex((p) => p.id === entry.id);
|
|
75
|
+
if (idx >= 0) {
|
|
76
|
+
providers[idx] = entry;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
providers.push(entry);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const knownModels = providers.flatMap((p) => {
|
|
83
|
+
const meta = SUPPORTED_PROVIDERS.find((m) => m.id === p.id);
|
|
84
|
+
return (meta?.exampleModels ?? []).map((m) => `${p.id}:${m}`);
|
|
85
|
+
});
|
|
86
|
+
client.log("");
|
|
87
|
+
client.log(c.bold(" Model Selection"));
|
|
88
|
+
client.log(c.muted(" Type to search or enter a custom provider:model-id"));
|
|
89
|
+
if (knownModels.length > 0) {
|
|
90
|
+
const modelChoices = knownModels.map((m) => ({ title: m, value: m }));
|
|
91
|
+
const suggestModel = (input, choices) => Promise.resolve(input
|
|
92
|
+
? choices.filter((ch) => ch.title.toLowerCase().includes(input.toLowerCase()))
|
|
93
|
+
: choices);
|
|
94
|
+
chatModel = (await client.prompt({
|
|
95
|
+
type: "autocomplete",
|
|
96
|
+
message: "Chat model",
|
|
97
|
+
choices: modelChoices,
|
|
98
|
+
initial: chatModel,
|
|
99
|
+
suggest: suggestModel,
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
chatModel = (await client.prompt({
|
|
104
|
+
type: "text",
|
|
105
|
+
message: "Chat model (provider:model-id)",
|
|
106
|
+
initial: chatModel,
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
client.log("");
|
|
110
|
+
client.log(c.bold(" Review Configuration"));
|
|
111
|
+
client.log(` ${c.brand(c.bold("Providers:"))}`);
|
|
112
|
+
for (const p of providers) {
|
|
113
|
+
const url = p.baseUrl ? c.muted(` [${p.baseUrl}]`) : "";
|
|
114
|
+
client.log(` ${c.success("●")} ${p.id}${url}`);
|
|
115
|
+
}
|
|
116
|
+
client.log(` ${c.brand(c.bold("Models:"))}`);
|
|
117
|
+
client.log(` chat: ${c.info(chatModel)}`);
|
|
118
|
+
client.log("");
|
|
119
|
+
const confirmed = await client.confirm("Save configuration?");
|
|
120
|
+
if (!confirmed) {
|
|
121
|
+
client.log(c.muted(" Configuration not saved."));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const providersObj = {};
|
|
126
|
+
for (const p of providers) {
|
|
127
|
+
providersObj[p.id] = {
|
|
128
|
+
apiKey: p.apiKey,
|
|
129
|
+
...(p.baseUrl ? { baseUrl: p.baseUrl } : {}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const config = {
|
|
133
|
+
...baseConfig,
|
|
134
|
+
ai: {
|
|
135
|
+
providers: providersObj,
|
|
136
|
+
models: { chat: chatModel },
|
|
137
|
+
aliases: baseConfig.ai.aliases,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
await writeConfig({ config });
|
|
141
|
+
client.log(c.success(`Configuration saved to ${getConfigPath()}`));
|
|
142
|
+
client.log(c.muted(" Run ") +
|
|
143
|
+
c.info("babyclaw config validate") +
|
|
144
|
+
c.muted(" to verify, or ") +
|
|
145
|
+
c.info("babyclaw model") +
|
|
146
|
+
c.muted(" to review."));
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
client.log(c.error("Failed to save configuration"));
|
|
150
|
+
client.log(c.muted(err instanceof Error ? err.message : String(err)));
|
|
151
|
+
process.exitCode = 1;
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
});
|