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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { getConfigPath, loadConfigRaw, SUPPORTED_PROVIDERS } from "@babyclaw/gateway";
|
|
3
|
+
import { c } from "../../ui/theme.js";
|
|
4
|
+
function maskApiKey(key) {
|
|
5
|
+
if (key.length <= 8)
|
|
6
|
+
return "****";
|
|
7
|
+
return key.slice(0, 4) + "..." + key.slice(-4);
|
|
8
|
+
}
|
|
9
|
+
function getProviderDisplayName(id) {
|
|
10
|
+
const meta = SUPPORTED_PROVIDERS.find((p) => p.id === id);
|
|
11
|
+
return meta?.displayName ?? id;
|
|
12
|
+
}
|
|
13
|
+
export default command({
|
|
14
|
+
description: "Show current model configuration",
|
|
15
|
+
handler: async ({ client }) => {
|
|
16
|
+
const config = await loadConfigRaw();
|
|
17
|
+
if (!config) {
|
|
18
|
+
client.log(c.error(`No valid config found at ${getConfigPath()}`));
|
|
19
|
+
client.log(c.muted(" Run ") + c.info("babyclaw config init") + c.muted(" first."));
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const providerEntries = Object.entries(config.ai.providers);
|
|
24
|
+
const aliasEntries = Object.entries(config.ai.aliases);
|
|
25
|
+
client.log(c.bold(" Model Configuration"));
|
|
26
|
+
client.log("");
|
|
27
|
+
client.log(` ${c.brand(c.bold("Providers"))}`);
|
|
28
|
+
if (providerEntries.length === 0) {
|
|
29
|
+
client.log(c.muted(" (none configured)"));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
for (const [key, provider] of providerEntries) {
|
|
33
|
+
const name = c.bold(getProviderDisplayName(key));
|
|
34
|
+
const detail = c.muted(` (${key}) — ${maskApiKey(provider.apiKey)}`);
|
|
35
|
+
const url = provider.baseUrl ? c.muted(` [${provider.baseUrl}]`) : "";
|
|
36
|
+
client.log(` ${c.success("●")} ${name}${detail}${url}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
client.log("");
|
|
40
|
+
client.log(` ${c.brand(c.bold("Active Models"))}`);
|
|
41
|
+
client.log(` ${c.info("chat ")}${config.ai.models.chat}`);
|
|
42
|
+
client.log("");
|
|
43
|
+
client.log(` ${c.brand(c.bold("Aliases"))}`);
|
|
44
|
+
if (aliasEntries.length === 0) {
|
|
45
|
+
client.log(c.muted(" (none defined — use ") +
|
|
46
|
+
c.info("babyclaw model alias set") +
|
|
47
|
+
c.muted(" to create)"));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
for (const [name, ref] of aliasEntries) {
|
|
51
|
+
client.log(` ${c.warning(name)}${c.muted(" → ")}${ref}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { c } from "../ui/theme.js";
|
|
3
|
+
const CLAW_ART = `
|
|
4
|
+
╭━━╮ ╭━━╮
|
|
5
|
+
╭╯ ╰╮ ╭╯ ╰╮
|
|
6
|
+
│ ╭╮ │ │ ╭╮ │
|
|
7
|
+
│ ││ ╰─╯ ││ │
|
|
8
|
+
│ ╰╯ ╰╯ │
|
|
9
|
+
╰╮ ╭─╮ ╭╯
|
|
10
|
+
│ │ │ │
|
|
11
|
+
╰───╯ ╰───╯
|
|
12
|
+
╱ ╲
|
|
13
|
+
╱ ╲
|
|
14
|
+
╱ snip ╲
|
|
15
|
+
╱ snip ╲
|
|
16
|
+
`;
|
|
17
|
+
const FACTS = [
|
|
18
|
+
"Crabs have been around for over 200 million years. Your gateway has been around for slightly less.",
|
|
19
|
+
"A group of crabs is called a 'cast'. A group of gateways is called 'overengineering'.",
|
|
20
|
+
"The Japanese spider crab has a leg span of up to 3.7 meters. Your config file is not that big.",
|
|
21
|
+
"Crabs can walk in all directions, but prefer to walk sideways. Your agent prefers to walk forward.",
|
|
22
|
+
"Hermit crabs swap shells when they find bigger ones. Your gateway swaps configs on restart.",
|
|
23
|
+
"The coconut crab can crack coconuts with its claws. Your gateway can crack... tasks.",
|
|
24
|
+
"Some crabs communicate by drumming their claws. Your gateway communicates via Telegram.",
|
|
25
|
+
"Fiddler crabs wave their large claw to attract mates. Your CLI waves banners to attract developers.",
|
|
26
|
+
];
|
|
27
|
+
export default command({
|
|
28
|
+
description: "A claw-some easter egg",
|
|
29
|
+
handler: async ({ client }) => {
|
|
30
|
+
const fact = FACTS[Math.floor(Math.random() * FACTS.length)];
|
|
31
|
+
client.log(c.brand(CLAW_ART));
|
|
32
|
+
client.log(` ${c.muted(`🦀 ${fact}`)}`);
|
|
33
|
+
},
|
|
34
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { c } from "../../ui/theme.js";
|
|
3
|
+
import { detectPlatform, install, getStatus } from "../../service/adapter.js";
|
|
4
|
+
export default command({
|
|
5
|
+
description: "Install the gateway as a system service",
|
|
6
|
+
handler: async ({ client }) => {
|
|
7
|
+
try {
|
|
8
|
+
const plat = detectPlatform();
|
|
9
|
+
if (plat === "unsupported") {
|
|
10
|
+
client.log(c.error("✗ Unsupported platform. Only macOS (launchd) and Linux (systemd) are supported."));
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const info = getStatus();
|
|
15
|
+
if (info.installed) {
|
|
16
|
+
client.log(c.warning("⚠ Service is already installed."));
|
|
17
|
+
client.log(c.muted(" Use ") +
|
|
18
|
+
c.info("babyclaw service uninstall") +
|
|
19
|
+
c.muted(" to remove it first."));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const result = install();
|
|
23
|
+
client.log(c.success("✓ Service installed!"));
|
|
24
|
+
client.log(c.muted(` ${result.path}`));
|
|
25
|
+
client.log(c.muted(" Run ") + c.info("babyclaw service start") + c.muted(" to fire it up."));
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
client.log(c.error("✗ Failed to install service"));
|
|
29
|
+
client.log(c.muted(` ${err instanceof Error ? err.message : String(err)}`));
|
|
30
|
+
process.exitCode = 1;
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { c } from "../../ui/theme.js";
|
|
3
|
+
import { detectPlatform, getStatus, restart } from "../../service/adapter.js";
|
|
4
|
+
export default command({
|
|
5
|
+
description: "Restart the gateway service",
|
|
6
|
+
handler: async ({ client }) => {
|
|
7
|
+
try {
|
|
8
|
+
const plat = detectPlatform();
|
|
9
|
+
if (plat === "unsupported") {
|
|
10
|
+
client.log(c.error("✗ Unsupported platform."));
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const info = getStatus();
|
|
15
|
+
if (!info.installed) {
|
|
16
|
+
client.log(c.error("✗ Service is not installed."));
|
|
17
|
+
client.log(c.muted(" Run ") + c.info("babyclaw service install") + c.muted(" first."));
|
|
18
|
+
process.exitCode = 1;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
restart();
|
|
22
|
+
client.log(c.success("✓ Gateway restarted! Fresh pincers, same claw. 🦀"));
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
client.log(c.error("✗ Failed to restart gateway"));
|
|
26
|
+
client.log(c.muted(` ${err instanceof Error ? err.message : String(err)}`));
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { c } from "../../ui/theme.js";
|
|
3
|
+
import { detectPlatform, getStatus, start } from "../../service/adapter.js";
|
|
4
|
+
export default command({
|
|
5
|
+
description: "Start the gateway service",
|
|
6
|
+
handler: async ({ client }) => {
|
|
7
|
+
try {
|
|
8
|
+
const plat = detectPlatform();
|
|
9
|
+
if (plat === "unsupported") {
|
|
10
|
+
client.log(c.error("✗ Unsupported platform."));
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const info = getStatus();
|
|
15
|
+
if (!info.installed) {
|
|
16
|
+
client.log(c.error("✗ Service is not installed."));
|
|
17
|
+
client.log(c.muted(" Run ") + c.info("babyclaw service install") + c.muted(" first."));
|
|
18
|
+
process.exitCode = 1;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (info.running) {
|
|
22
|
+
client.log(c.warning("⚠ Gateway is already running. Use ") +
|
|
23
|
+
c.info("babyclaw service restart") +
|
|
24
|
+
c.warning(" to bounce it."));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
start();
|
|
28
|
+
client.log(c.success("✓ Gateway started! Pincers are hot. 🦀"));
|
|
29
|
+
client.log(c.muted(" Run ") + c.info("babyclaw gateway status") + c.muted(" to verify."));
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
client.log(c.error("✗ Failed to start gateway"));
|
|
33
|
+
client.log(c.muted(` ${err instanceof Error ? err.message : String(err)}`));
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { c } from "../../ui/theme.js";
|
|
3
|
+
import { detectPlatform, getStatus } from "../../service/adapter.js";
|
|
4
|
+
export default command({
|
|
5
|
+
description: "Check if the gateway service is installed/running",
|
|
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 info = getStatus();
|
|
13
|
+
if (json) {
|
|
14
|
+
client.log(JSON.stringify(info, null, 2));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const plat = detectPlatform();
|
|
18
|
+
if (plat === "unsupported") {
|
|
19
|
+
client.log(c.error("✗ Unsupported platform. Only macOS and Linux are supported."));
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!info.installed) {
|
|
24
|
+
client.log(`${c.muted("●")} Service is ${c.bold("not installed")}`);
|
|
25
|
+
client.log(c.muted(" Run ") + c.info("babyclaw service install") + c.muted(" to set it up."));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const statusColor = info.running ? c.success : c.error;
|
|
29
|
+
const statusText = info.running ? "running" : "stopped";
|
|
30
|
+
client.log(`${statusColor("●")} Service is ${statusColor(statusText)}`);
|
|
31
|
+
client.log(` ${c.muted("Platform ")}${info.platform}`);
|
|
32
|
+
if (info.pid) {
|
|
33
|
+
client.log(` ${c.muted("PID ")}${info.pid}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
client.log(c.error("✗ Failed to check service status"));
|
|
38
|
+
client.log(c.muted(` ${err instanceof Error ? err.message : String(err)}`));
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { c } from "../../ui/theme.js";
|
|
3
|
+
import { detectPlatform, getStatus, stop } from "../../service/adapter.js";
|
|
4
|
+
export default command({
|
|
5
|
+
description: "Stop the gateway service",
|
|
6
|
+
handler: async ({ client }) => {
|
|
7
|
+
try {
|
|
8
|
+
const plat = detectPlatform();
|
|
9
|
+
if (plat === "unsupported") {
|
|
10
|
+
client.log(c.error("✗ Unsupported platform."));
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const info = getStatus();
|
|
15
|
+
if (!info.installed) {
|
|
16
|
+
client.log(c.warning("⚠ Service is not installed."));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!info.running) {
|
|
20
|
+
client.log(c.warning("⚠ Gateway is not running. Nothing to stop."));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
stop();
|
|
24
|
+
client.log(c.success("✓ Gateway stopped. The claw rests. 🦞"));
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
client.log(c.error("✗ Failed to stop gateway"));
|
|
28
|
+
client.log(c.muted(` ${err instanceof Error ? err.message : String(err)}`));
|
|
29
|
+
process.exitCode = 1;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { c } from "../../ui/theme.js";
|
|
3
|
+
import { detectPlatform, getStatus, uninstall } from "../../service/adapter.js";
|
|
4
|
+
export default command({
|
|
5
|
+
description: "Uninstall the gateway system service",
|
|
6
|
+
handler: async ({ client }) => {
|
|
7
|
+
try {
|
|
8
|
+
const plat = detectPlatform();
|
|
9
|
+
if (plat === "unsupported") {
|
|
10
|
+
client.log(c.error("✗ Unsupported platform."));
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const info = getStatus();
|
|
15
|
+
if (!info.installed) {
|
|
16
|
+
client.log(c.warning("⚠ Service is not installed. Nothing to uninstall."));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
uninstall();
|
|
20
|
+
client.log(c.success("✓ Service uninstalled. Goodbye, old friend."));
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
client.log(c.error("✗ Failed to uninstall service"));
|
|
24
|
+
client.log(c.muted(` ${err instanceof Error ? err.message : String(err)}`));
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { command } from "@gud/cli";
|
|
2
|
+
import { getConfigPath, getDefaultConfigTemplate, loadConfigRaw, writeConfig, babyclawConfigSchema, SUPPORTED_PROVIDERS, } from "@babyclaw/gateway";
|
|
3
|
+
const SHELL_MODES = ["allowlist", "full-access"];
|
|
4
|
+
import { c, getRandomBanner } from "../ui/theme.js";
|
|
5
|
+
import { detectPlatform, install as installService, start as startService, getStatus as getServiceStatus, } from "../service/adapter.js";
|
|
6
|
+
function isValidTimezone(value) {
|
|
7
|
+
try {
|
|
8
|
+
new Intl.DateTimeFormat("en-US", { timeZone: value });
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export default command({
|
|
16
|
+
description: "Interactive first-time setup wizard",
|
|
17
|
+
handler: async ({ client }) => {
|
|
18
|
+
// ── 1. Welcome ──────────────────────────────────────────────────────
|
|
19
|
+
client.log(c.brand(getRandomBanner()));
|
|
20
|
+
client.log("");
|
|
21
|
+
client.log(c.bold(" Welcome to BabyClaw setup!"));
|
|
22
|
+
client.log(c.muted(" This wizard will walk you through the full configuration.\n"));
|
|
23
|
+
let baseConfig;
|
|
24
|
+
const existing = await loadConfigRaw();
|
|
25
|
+
let telegramToken = "";
|
|
26
|
+
if (existing) {
|
|
27
|
+
client.log(c.warning(" A config file already exists at:"));
|
|
28
|
+
client.log(c.muted(` ${getConfigPath()}\n`));
|
|
29
|
+
const action = await client.prompt({
|
|
30
|
+
type: "select",
|
|
31
|
+
message: "What would you like to do?",
|
|
32
|
+
choices: [
|
|
33
|
+
{ title: "Update existing configuration", value: "update" },
|
|
34
|
+
{ title: "Start fresh (overwrite)", value: "fresh" },
|
|
35
|
+
{ title: "Cancel", value: "cancel" },
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
if (action === "cancel") {
|
|
39
|
+
client.log(c.muted(" Setup cancelled."));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (action === "update") {
|
|
43
|
+
baseConfig = existing;
|
|
44
|
+
telegramToken = existing.channels?.telegram?.botToken ?? "";
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const template = JSON.parse(getDefaultConfigTemplate());
|
|
48
|
+
baseConfig = babyclawConfigSchema.parse(template);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const template = JSON.parse(getDefaultConfigTemplate());
|
|
53
|
+
baseConfig = babyclawConfigSchema.parse(template);
|
|
54
|
+
}
|
|
55
|
+
// ── 2. Telegram Bot Token ───────────────────────────────────────────
|
|
56
|
+
client.log("");
|
|
57
|
+
client.log(c.bold(" Step 1 · Telegram Bot Token"));
|
|
58
|
+
client.log(c.muted(" Get one from @BotFather on Telegram. Leave empty to skip.\n"));
|
|
59
|
+
const tokenInput = await client.prompt({
|
|
60
|
+
type: "password",
|
|
61
|
+
message: "Telegram bot token",
|
|
62
|
+
initial: telegramToken,
|
|
63
|
+
});
|
|
64
|
+
telegramToken = tokenInput ?? "";
|
|
65
|
+
if (!telegramToken) {
|
|
66
|
+
client.log(c.warning(" Skipped — Telegram channel will be disabled."));
|
|
67
|
+
}
|
|
68
|
+
// ── 3. AI Providers ─────────────────────────────────────────────────
|
|
69
|
+
const providers = existing
|
|
70
|
+
? Object.entries(existing.ai.providers).map(([id, cfg]) => ({
|
|
71
|
+
id,
|
|
72
|
+
apiKey: cfg.apiKey,
|
|
73
|
+
baseUrl: cfg.baseUrl ?? "",
|
|
74
|
+
}))
|
|
75
|
+
: [];
|
|
76
|
+
let addingProviders = true;
|
|
77
|
+
while (addingProviders) {
|
|
78
|
+
const providerChoices = SUPPORTED_PROVIDERS.map((p) => {
|
|
79
|
+
const configured = providers.find((e) => e.id === p.id);
|
|
80
|
+
return {
|
|
81
|
+
title: configured ? `${p.displayName} (configured)` : p.displayName,
|
|
82
|
+
value: p.id,
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
providerChoices.push({
|
|
86
|
+
title: providers.length > 0 ? "Done adding providers →" : "(add at least one provider first)",
|
|
87
|
+
value: "__done__",
|
|
88
|
+
});
|
|
89
|
+
client.log("");
|
|
90
|
+
client.log(c.bold(" Step 2 · AI Providers"));
|
|
91
|
+
if (providers.length > 0) {
|
|
92
|
+
client.log(c.muted(` Configured: ${providers.map((p) => p.id).join(", ")}`));
|
|
93
|
+
}
|
|
94
|
+
const selected = await client.prompt({
|
|
95
|
+
type: "select",
|
|
96
|
+
message: "Select a provider to configure (or Done when finished)",
|
|
97
|
+
choices: providerChoices,
|
|
98
|
+
});
|
|
99
|
+
if (selected === "__done__") {
|
|
100
|
+
if (providers.length === 0) {
|
|
101
|
+
client.log(c.warning(" Add at least one provider before continuing."));
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
addingProviders = false;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const providerId = selected;
|
|
108
|
+
const meta = SUPPORTED_PROVIDERS.find((p) => p.id === providerId);
|
|
109
|
+
const existingEntry = providers.find((p) => p.id === providerId);
|
|
110
|
+
client.log(c.bold(` Configure ${meta?.displayName ?? providerId}`));
|
|
111
|
+
const apiKey = await client.prompt({
|
|
112
|
+
type: "password",
|
|
113
|
+
message: "API Key",
|
|
114
|
+
initial: existingEntry?.apiKey,
|
|
115
|
+
});
|
|
116
|
+
const baseUrl = await client.prompt({
|
|
117
|
+
type: "text",
|
|
118
|
+
message: "Base URL (leave empty for default)",
|
|
119
|
+
initial: existingEntry?.baseUrl,
|
|
120
|
+
});
|
|
121
|
+
const entry = {
|
|
122
|
+
id: providerId,
|
|
123
|
+
apiKey: apiKey,
|
|
124
|
+
baseUrl: baseUrl ?? "",
|
|
125
|
+
};
|
|
126
|
+
const idx = providers.findIndex((p) => p.id === entry.id);
|
|
127
|
+
if (idx >= 0) {
|
|
128
|
+
providers[idx] = entry;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
providers.push(entry);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// ── 4. Model Selection ──────────────────────────────────────────────
|
|
135
|
+
const knownModels = providers.flatMap((p) => {
|
|
136
|
+
const meta = SUPPORTED_PROVIDERS.find((m) => m.id === p.id);
|
|
137
|
+
return (meta?.exampleModels ?? []).map((m) => `${p.id}:${m}`);
|
|
138
|
+
});
|
|
139
|
+
let chatModel = baseConfig.ai.models.chat;
|
|
140
|
+
client.log("");
|
|
141
|
+
client.log(c.bold(" Step 3 · Model Selection"));
|
|
142
|
+
client.log(c.muted(" Format: provider:model-id\n"));
|
|
143
|
+
if (knownModels.length > 0) {
|
|
144
|
+
const modelChoices = knownModels.map((m) => ({ title: m, value: m }));
|
|
145
|
+
const suggestModel = (input, choices) => Promise.resolve(input
|
|
146
|
+
? choices.filter((ch) => ch.title.toLowerCase().includes(input.toLowerCase()))
|
|
147
|
+
: choices);
|
|
148
|
+
chatModel = (await client.prompt({
|
|
149
|
+
type: "autocomplete",
|
|
150
|
+
message: "Chat model",
|
|
151
|
+
choices: modelChoices,
|
|
152
|
+
initial: chatModel,
|
|
153
|
+
suggest: suggestModel,
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
chatModel = (await client.prompt({
|
|
158
|
+
type: "text",
|
|
159
|
+
message: "Chat model (provider:model-id)",
|
|
160
|
+
initial: chatModel,
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
// ── 5. Timezone ─────────────────────────────────────────────────────
|
|
164
|
+
client.log("");
|
|
165
|
+
client.log(c.bold(" Step 4 · Scheduler Timezone"));
|
|
166
|
+
let timezone = baseConfig.scheduler.timezone;
|
|
167
|
+
let validTz = false;
|
|
168
|
+
while (!validTz) {
|
|
169
|
+
timezone = (await client.prompt({
|
|
170
|
+
type: "text",
|
|
171
|
+
message: "IANA timezone (e.g. America/New_York, Europe/London)",
|
|
172
|
+
initial: timezone,
|
|
173
|
+
}));
|
|
174
|
+
if (isValidTimezone(timezone)) {
|
|
175
|
+
validTz = true;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
client.log(c.error(` "${timezone}" is not a valid IANA timezone.`));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// ── 6. Tool Settings ────────────────────────────────────────────────
|
|
182
|
+
client.log("");
|
|
183
|
+
client.log(c.bold(" Step 5 · Tool Settings"));
|
|
184
|
+
const shellMode = (await client.prompt({
|
|
185
|
+
type: "select",
|
|
186
|
+
message: "Shell access mode",
|
|
187
|
+
choices: SHELL_MODES.map((mode) => ({
|
|
188
|
+
title: mode === "allowlist"
|
|
189
|
+
? "allowlist (safer — only pre-approved commands)"
|
|
190
|
+
: "full-access (unrestricted shell)",
|
|
191
|
+
value: mode,
|
|
192
|
+
})),
|
|
193
|
+
}));
|
|
194
|
+
client.log(c.muted(" Brave Search API enables web search. Leave empty to skip."));
|
|
195
|
+
const braveKey = (await client.prompt({
|
|
196
|
+
type: "password",
|
|
197
|
+
message: "Brave Search API key (optional)",
|
|
198
|
+
initial: baseConfig.tools.webSearch.braveApiKey ?? "",
|
|
199
|
+
}));
|
|
200
|
+
// ── 7. Review + Save ────────────────────────────────────────────────
|
|
201
|
+
client.log("");
|
|
202
|
+
client.log(c.bold(" Review Configuration"));
|
|
203
|
+
client.log("");
|
|
204
|
+
client.log(` ${c.brand(c.bold("Telegram:"))} ${telegramToken ? c.success("configured") : c.muted("skipped")}`);
|
|
205
|
+
client.log(` ${c.brand(c.bold("Providers:"))}`);
|
|
206
|
+
for (const p of providers) {
|
|
207
|
+
const url = p.baseUrl ? c.muted(` [${p.baseUrl}]`) : "";
|
|
208
|
+
client.log(` ${c.success("●")} ${p.id}${url}`);
|
|
209
|
+
}
|
|
210
|
+
client.log(` ${c.brand(c.bold("Models:"))}`);
|
|
211
|
+
client.log(` chat: ${c.info(chatModel)}`);
|
|
212
|
+
client.log(` ${c.brand(c.bold("Timezone:"))} ${c.info(timezone)}`);
|
|
213
|
+
client.log(` ${c.brand(c.bold("Tools:"))}`);
|
|
214
|
+
client.log(` shell: ${c.info(shellMode)}`);
|
|
215
|
+
client.log(` search: ${braveKey ? c.success("configured") : c.muted("disabled")}`);
|
|
216
|
+
client.log("");
|
|
217
|
+
const confirmed = await client.confirm("Save configuration?");
|
|
218
|
+
if (!confirmed) {
|
|
219
|
+
client.log(c.muted(" Setup cancelled. Nothing was saved."));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const providersObj = {};
|
|
223
|
+
for (const p of providers) {
|
|
224
|
+
providersObj[p.id] = {
|
|
225
|
+
apiKey: p.apiKey,
|
|
226
|
+
...(p.baseUrl ? { baseUrl: p.baseUrl } : {}),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const config = {
|
|
230
|
+
...baseConfig,
|
|
231
|
+
channels: telegramToken ? { telegram: { botToken: telegramToken } } : {},
|
|
232
|
+
ai: {
|
|
233
|
+
providers: providersObj,
|
|
234
|
+
models: { chat: chatModel },
|
|
235
|
+
aliases: baseConfig.ai.aliases,
|
|
236
|
+
},
|
|
237
|
+
scheduler: { timezone },
|
|
238
|
+
tools: {
|
|
239
|
+
...baseConfig.tools,
|
|
240
|
+
shell: {
|
|
241
|
+
...baseConfig.tools.shell,
|
|
242
|
+
mode: shellMode,
|
|
243
|
+
},
|
|
244
|
+
webSearch: {
|
|
245
|
+
braveApiKey: braveKey || null,
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
try {
|
|
250
|
+
await writeConfig({ config });
|
|
251
|
+
client.log(c.success(`\n ✓ Config saved to ${getConfigPath()}`));
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
client.log(c.error("\n ✗ Failed to save configuration"));
|
|
255
|
+
client.log(c.muted(` ${err instanceof Error ? err.message : String(err)}`));
|
|
256
|
+
process.exitCode = 1;
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// ── 8. Service Install (optional) ───────────────────────────────────
|
|
260
|
+
const plat = detectPlatform();
|
|
261
|
+
if (plat !== "unsupported") {
|
|
262
|
+
const serviceInfo = getServiceStatus();
|
|
263
|
+
if (!serviceInfo.installed) {
|
|
264
|
+
client.log("");
|
|
265
|
+
const installIt = await client.confirm("Install BabyClaw as a system service? (auto-starts on boot)");
|
|
266
|
+
if (installIt) {
|
|
267
|
+
try {
|
|
268
|
+
const result = installService();
|
|
269
|
+
client.log(c.success(" ✓ Service installed!"));
|
|
270
|
+
client.log(c.muted(` ${result.path}`));
|
|
271
|
+
const startIt = await client.confirm("Start the service now?");
|
|
272
|
+
if (startIt) {
|
|
273
|
+
try {
|
|
274
|
+
startService();
|
|
275
|
+
client.log(c.success(" ✓ Service started!"));
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
client.log(c.error(" ✗ Failed to start service"));
|
|
279
|
+
client.log(c.muted(` ${err instanceof Error ? err.message : String(err)}`));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
client.log(c.error(" ✗ Failed to install service"));
|
|
285
|
+
client.log(c.muted(` ${err instanceof Error ? err.message : String(err)}`));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
client.log(c.muted("\n Service is already installed."));
|
|
291
|
+
if (!serviceInfo.running) {
|
|
292
|
+
const startIt = await client.confirm("Start the service now?");
|
|
293
|
+
if (startIt) {
|
|
294
|
+
try {
|
|
295
|
+
startService();
|
|
296
|
+
client.log(c.success(" ✓ Service started!"));
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
client.log(c.error(" ✗ Failed to start service"));
|
|
300
|
+
client.log(c.muted(` ${err instanceof Error ? err.message : String(err)}`));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// ── 9. Finish ───────────────────────────────────────────────────────
|
|
307
|
+
client.log("");
|
|
308
|
+
client.log(c.success(c.bold(" Setup complete! 🦀")));
|
|
309
|
+
client.log(c.muted(" Run ") + c.info("babyclaw doctor") + c.muted(" to verify everything is working."));
|
|
310
|
+
},
|
|
311
|
+
});
|