@michael-joseph-miller/ant-bot 0.3.1 → 0.4.1
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/CHANGELOG.md +59 -0
- package/README.md +9 -8
- package/dist/{chunk-I3GHIX3G.js → chunk-G4IIENWF.js} +33 -5
- package/dist/chunk-G4IIENWF.js.map +7 -0
- package/dist/index.js +336 -183
- package/dist/index.js.map +4 -4
- package/dist/server.js +940 -391
- package/dist/server.js.map +4 -4
- package/package.json +1 -1
- package/web/dist/assets/index-Bd_zE95N.css +2 -0
- package/web/dist/assets/index-Cpi4IKb8.js +130 -0
- package/web/dist/index.html +2 -2
- package/dist/chunk-I3GHIX3G.js.map +0 -7
- package/web/dist/assets/index-B74AZxue.js +0 -130
- package/web/dist/assets/index-B90EjIDl.css +0 -2
package/dist/index.js
CHANGED
|
@@ -3,11 +3,11 @@ import { createRequire as __antbotCreateRequire } from 'node:module';
|
|
|
3
3
|
const require = __antbotCreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
5
|
SettingsSchema
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-G4IIENWF.js";
|
|
7
7
|
|
|
8
8
|
// cli/src/index.ts
|
|
9
9
|
import fs10 from "node:fs";
|
|
10
|
-
import
|
|
10
|
+
import readline3 from "node:readline/promises";
|
|
11
11
|
import path11 from "node:path";
|
|
12
12
|
|
|
13
13
|
// cli/src/args.ts
|
|
@@ -21,6 +21,7 @@ var KNOWN_COMMANDS = [
|
|
|
21
21
|
"skill",
|
|
22
22
|
"mcp",
|
|
23
23
|
"connector",
|
|
24
|
+
"secret",
|
|
24
25
|
"backup",
|
|
25
26
|
"restore",
|
|
26
27
|
"update"
|
|
@@ -43,6 +44,7 @@ var COMMAND_FLAGS = {
|
|
|
43
44
|
skill: {},
|
|
44
45
|
mcp: {},
|
|
45
46
|
connector: {},
|
|
47
|
+
secret: {},
|
|
46
48
|
backup: {
|
|
47
49
|
out: { type: "string" }
|
|
48
50
|
},
|
|
@@ -54,7 +56,7 @@ var COMMAND_FLAGS = {
|
|
|
54
56
|
yes: { type: "boolean", default: false }
|
|
55
57
|
}
|
|
56
58
|
};
|
|
57
|
-
var PASSTHROUGH_COMMANDS = /* @__PURE__ */ new Set(["mcp", "connector"]);
|
|
59
|
+
var PASSTHROUGH_COMMANDS = /* @__PURE__ */ new Set(["mcp", "connector", "secret"]);
|
|
58
60
|
function isKnownCommand(value) {
|
|
59
61
|
return KNOWN_COMMANDS.includes(value);
|
|
60
62
|
}
|
|
@@ -152,6 +154,7 @@ Daemon:
|
|
|
152
154
|
Data:
|
|
153
155
|
skill <subcommand> Manage the skills your bots can use
|
|
154
156
|
mcp <subcommand> Manage the MCP servers your bots can use
|
|
157
|
+
secret <subcommand> Credentials in your keychain (mcp add stores them for you)
|
|
155
158
|
backup [--out PATH] Archive the database, config, skills, and bot memory
|
|
156
159
|
restore <path> Restore from a backup archive
|
|
157
160
|
|
|
@@ -166,42 +169,40 @@ Environment:
|
|
|
166
169
|
ANTBOT_HOME Data directory (default: ~/.ant-bot)
|
|
167
170
|
|
|
168
171
|
The UI lives at http://127.0.0.1:4780 and is served by the daemon itself.`;
|
|
169
|
-
var MCP_HELP = `antbot mcp \u2014
|
|
172
|
+
var MCP_HELP = `antbot mcp \u2014 the MCP servers your bots can use
|
|
170
173
|
|
|
171
174
|
Usage: antbot mcp <subcommand>
|
|
172
175
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
antbot mcp add
|
|
190
|
-
antbot mcp add
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
176
|
+
add <name> [<command> | <url>] Add one. No command or URL means a built-in (e.g. gmail).
|
|
177
|
+
--env VAR[=value] stdio: an env var. Without =value you are prompted, and the
|
|
178
|
+
value goes to the keychain \u2014 it never appears in config.
|
|
179
|
+
--header K[=V] http: a header, same rule.
|
|
180
|
+
--bots a,b | all | none Which bots get it. Asked interactively if omitted.
|
|
181
|
+
check <name> One honest verdict: ready / needs sign-in / needs credential /
|
|
182
|
+
unreachable, plus the tools it offers.
|
|
183
|
+
login <name> Sign in (again). --client-id / --client-secret for providers
|
|
184
|
+
that will not register apps themselves (Google).
|
|
185
|
+
logout <name> Forget a stored sign-in.
|
|
186
|
+
enable|disable <name> Turn one off for every bot at once, or back on.
|
|
187
|
+
remove <name> Delete it, and every bot's assignment to it.
|
|
188
|
+
list Everything, with state and which bots have each.
|
|
189
|
+
|
|
190
|
+
Examples:
|
|
191
|
+
antbot mcp add gmail built-in; guided setup
|
|
192
|
+
antbot mcp add github "npx -y @modelcontextprotocol/server-github" --env GITHUB_TOKEN
|
|
193
|
+
antbot mcp add vercel https://mcp.vercel.com signs in automatically
|
|
194
|
+
|
|
195
|
+
A connector is registered once and assigned to bots in Bot settings \u2014 a bot with no assignment
|
|
196
|
+
cannot see its tools at all. Tools reach bots as \`mcp__<name>__<tool>\` and pass the permission
|
|
197
|
+
gateway like any other tool, so the first call asks you for approval.
|
|
195
198
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
Providers that support dynamic client registration need nothing else. Some \u2014 Google
|
|
199
|
-
among them \u2014 do not, and want a client ID you create in their console, with
|
|
200
|
-
http://127.0.0.1:4780/api/connectors/oauth/callback as an authorised redirect URI:
|
|
199
|
+
\`connector\` is the older name for this command and still works.`;
|
|
200
|
+
var SECRET_HELP = `antbot secret \u2014 credentials in your keychain
|
|
201
201
|
|
|
202
|
-
|
|
202
|
+
Usage: antbot secret list | set <NAME> | remove <NAME>
|
|
203
203
|
|
|
204
|
-
|
|
204
|
+
Values are prompted for, never passed on the command line, and never shown again. Most people
|
|
205
|
+
never need this directly: \`antbot mcp add \u2026 --env VAR\` stores what a connector needs for you.`;
|
|
205
206
|
var COMMAND_HELP = {
|
|
206
207
|
start: `antbot start \u2014 start the daemon in the background
|
|
207
208
|
|
|
@@ -304,6 +305,7 @@ In a git checkout there is nothing for a package manager to update \u2014 use
|
|
|
304
305
|
\`git pull && pnpm install\` instead.`,
|
|
305
306
|
connector: MCP_HELP,
|
|
306
307
|
mcp: MCP_HELP,
|
|
308
|
+
secret: SECRET_HELP,
|
|
307
309
|
restore: `antbot restore \u2014 restore from a backup archive
|
|
308
310
|
|
|
309
311
|
Usage: antbot restore <path> [--yes]
|
|
@@ -1218,22 +1220,9 @@ async function lintSkills(args) {
|
|
|
1218
1220
|
return 0;
|
|
1219
1221
|
}
|
|
1220
1222
|
|
|
1221
|
-
// cli/src/
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1225
|
-
if (argv[i] !== flag) continue;
|
|
1226
|
-
const raw = argv[i + 1] ?? "";
|
|
1227
|
-
const eq = raw.indexOf("=");
|
|
1228
|
-
if (eq > 0) out[raw.slice(0, eq)] = raw.slice(eq + 1);
|
|
1229
|
-
i++;
|
|
1230
|
-
}
|
|
1231
|
-
return out;
|
|
1232
|
-
}
|
|
1233
|
-
function flagValue(argv, flag) {
|
|
1234
|
-
const i = argv.indexOf(flag);
|
|
1235
|
-
return i >= 0 ? argv[i + 1] : void 0;
|
|
1236
|
-
}
|
|
1223
|
+
// cli/src/mcp.ts
|
|
1224
|
+
import readline from "node:readline/promises";
|
|
1225
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
1237
1226
|
function splitCommand(input) {
|
|
1238
1227
|
const parts = input.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
|
|
1239
1228
|
const clean = parts.map(
|
|
@@ -1241,131 +1230,259 @@ function splitCommand(input) {
|
|
|
1241
1230
|
);
|
|
1242
1231
|
return { command: clean[0] ?? "", args: clean.slice(1) };
|
|
1243
1232
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1233
|
+
var looksLikeUrl = (s) => /^https?:\/\//i.test(s.trim());
|
|
1234
|
+
function collectFlag(argv, flag) {
|
|
1235
|
+
const out = [];
|
|
1236
|
+
for (let i = 0; i < argv.length; i++) if (argv[i] === flag && argv[i + 1] !== void 0) out.push(argv[++i]);
|
|
1237
|
+
return out;
|
|
1238
|
+
}
|
|
1239
|
+
function flagValue(argv, flag) {
|
|
1240
|
+
const i = argv.indexOf(flag);
|
|
1241
|
+
return i >= 0 ? argv[i + 1] : void 0;
|
|
1242
|
+
}
|
|
1243
|
+
var secretNameFor = (connector, key) => `mcp/${connector}/${key}`;
|
|
1244
|
+
function planPairs(raw) {
|
|
1245
|
+
return raw.map((r) => {
|
|
1246
|
+
const eq = r.indexOf("=");
|
|
1247
|
+
if (eq > 0) return { key: r.slice(0, eq), value: r.slice(eq + 1), secret: false };
|
|
1248
|
+
return { key: r, value: "", secret: true };
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
function resolveBots(spec, roster) {
|
|
1252
|
+
if (spec === void 0) return "ask";
|
|
1253
|
+
if (spec === "none" || spec === "") return null;
|
|
1254
|
+
if (spec === "all") return roster.map((r) => r.bot.id);
|
|
1255
|
+
const wanted = spec.split(",").map((x) => x.trim().toLowerCase()).filter(Boolean);
|
|
1256
|
+
return roster.filter((r) => wanted.includes(r.bot.slug) || wanted.includes(r.bot.name.toLowerCase())).map((r) => r.bot.id);
|
|
1257
|
+
}
|
|
1258
|
+
function describeVerdict(name, check) {
|
|
1259
|
+
const n = check.tools.length;
|
|
1260
|
+
switch (check.status) {
|
|
1261
|
+
case "ready":
|
|
1262
|
+
return green(`\u2713 ${name}: ready, ${n} tool${n === 1 ? "" : "s"}`);
|
|
1263
|
+
case "needs-sign-in":
|
|
1264
|
+
if (check.alternative) return red(`\u2717 ${name}: ${check.detail ?? `use the built-in instead: antbot mcp add ${check.alternative}`}`);
|
|
1265
|
+
return yellow(`! ${name}: needs sign-in${check.provider ? ` (${check.provider})` : ""}${n ? `, ${n} tools` : ""}`);
|
|
1266
|
+
case "needs-credential":
|
|
1267
|
+
return yellow(`! ${name}: ${check.detail ?? "needs a credential"}`);
|
|
1268
|
+
case "unreachable":
|
|
1269
|
+
return red(`\u2717 ${name}: unreachable${check.detail ? ` \u2014 ${check.detail}` : ""}`);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
async function ask(question, hidden = false) {
|
|
1273
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
1274
|
+
if (hidden) {
|
|
1275
|
+
const out = process.stdout;
|
|
1276
|
+
const orig = out.write.bind(out);
|
|
1277
|
+
process.stdout.write(question);
|
|
1278
|
+
let muted = true;
|
|
1279
|
+
out.write = ((s) => muted ? true : orig(s));
|
|
1280
|
+
try {
|
|
1281
|
+
const v = await rl.question("");
|
|
1282
|
+
return v.trim();
|
|
1283
|
+
} finally {
|
|
1284
|
+
muted = false;
|
|
1285
|
+
out.write = orig;
|
|
1286
|
+
process.stdout.write("\n");
|
|
1287
|
+
rl.close();
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
try {
|
|
1291
|
+
return (await rl.question(question)).trim();
|
|
1292
|
+
} finally {
|
|
1293
|
+
rl.close();
|
|
1265
1294
|
}
|
|
1266
|
-
return { error: 'Give either --stdio "<command>" or --url <url>.' };
|
|
1267
1295
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1296
|
+
function openBrowser(url) {
|
|
1297
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1298
|
+
try {
|
|
1299
|
+
const child = spawn3(cmd, [url], { stdio: "ignore", detached: true });
|
|
1300
|
+
child.on("error", () => {
|
|
1301
|
+
});
|
|
1302
|
+
child.unref();
|
|
1303
|
+
} catch {
|
|
1304
|
+
}
|
|
1305
|
+
console.log(dim(" If a browser did not open, use this link:"));
|
|
1306
|
+
console.log(` ${url}`);
|
|
1307
|
+
}
|
|
1308
|
+
async function load(port) {
|
|
1309
|
+
try {
|
|
1310
|
+
return await getJson(port, "/api/connectors");
|
|
1311
|
+
} catch (err) {
|
|
1312
|
+
console.error(red(`Could not reach the daemon on port ${port}: ${err.message}`));
|
|
1313
|
+
console.error(dim("Start it with `antbot start`."));
|
|
1314
|
+
return null;
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
async function storePairs(port, connector, pairs) {
|
|
1318
|
+
const out = {};
|
|
1319
|
+
for (const p of pairs) {
|
|
1320
|
+
if (!p.secret) {
|
|
1321
|
+
out[p.key] = p.value;
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
const value = await ask(` ${p.key}: `, true);
|
|
1325
|
+
if (!value) throw new Error(`${p.key} needs a value.`);
|
|
1326
|
+
const name = secretNameFor(connector, p.key);
|
|
1327
|
+
await postJson(port, "/api/secrets", { name, value });
|
|
1328
|
+
out[p.key] = `{{secret:${name}}}`;
|
|
1329
|
+
}
|
|
1330
|
+
return out;
|
|
1271
1331
|
}
|
|
1272
|
-
async function
|
|
1332
|
+
async function signIn(port, row, opts) {
|
|
1333
|
+
let res;
|
|
1334
|
+
try {
|
|
1335
|
+
res = await postJson(port, `/api/connectors/${row.id}/login`, opts);
|
|
1336
|
+
} catch (err) {
|
|
1337
|
+
console.error(yellow(` ${err.message}`));
|
|
1338
|
+
return false;
|
|
1339
|
+
}
|
|
1340
|
+
console.log(dim(" Opening your browser to sign in\u2026"));
|
|
1341
|
+
openBrowser(res.authorizeUrl);
|
|
1342
|
+
const deadline = Date.now() + 5 * 6e4;
|
|
1343
|
+
while (Date.now() < deadline) {
|
|
1344
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
1345
|
+
const rows = await getJson(port, "/api/connectors").catch(() => []);
|
|
1346
|
+
const cur = rows.find((r) => r.id === row.id);
|
|
1347
|
+
if (cur?.signedIn) return true;
|
|
1348
|
+
}
|
|
1349
|
+
console.log(yellow(" Still waiting for the sign-in. Finish it in the browser, then run: antbot mcp check " + row.name));
|
|
1350
|
+
return false;
|
|
1351
|
+
}
|
|
1352
|
+
async function collectClientCredentials(entry, provider) {
|
|
1353
|
+
console.log(yellow(` ${provider ?? "This provider"} needs an OAuth client you create yourself \u2014 a one-time setup:`));
|
|
1354
|
+
for (const [i, step] of (entry?.setupSteps ?? []).entries()) console.log(` ${i + 1}. ${step}`);
|
|
1355
|
+
const clientId = await ask(" Client ID (blank to do this later): ");
|
|
1356
|
+
if (!clientId) return null;
|
|
1357
|
+
const clientSecret = await ask(" Client secret (blank if none): ", true);
|
|
1358
|
+
return { clientId, ...clientSecret ? { clientSecret } : {} };
|
|
1359
|
+
}
|
|
1360
|
+
async function runMcpCommand(argv, port) {
|
|
1273
1361
|
const sub = argv[0];
|
|
1274
1362
|
const hint = () => {
|
|
1275
|
-
console.error(dim("Subcommands: list, add, login, logout, enable, disable, remove
|
|
1363
|
+
console.error(dim("Subcommands: list, add, check, login, logout, enable, disable, remove."));
|
|
1276
1364
|
console.error(dim("Run `antbot mcp --help` for the full usage."));
|
|
1277
1365
|
};
|
|
1278
1366
|
if (!sub) {
|
|
1279
|
-
console.error(red("antbot
|
|
1367
|
+
console.error(red("antbot mcp needs a subcommand."));
|
|
1280
1368
|
hint();
|
|
1281
1369
|
return 2;
|
|
1282
1370
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
console.error(red(`Could not reach the daemon on port ${port}: ${err.message}`));
|
|
1290
|
-
console.error(dim("Start it with `antbot start`."));
|
|
1291
|
-
return false;
|
|
1292
|
-
}
|
|
1371
|
+
const byName = async (name) => {
|
|
1372
|
+
const rows = await load(port);
|
|
1373
|
+
if (!rows) return null;
|
|
1374
|
+
const m = rows.find((c) => c.name === name);
|
|
1375
|
+
if (!m) console.error(red(`No connector named "${name}".`));
|
|
1376
|
+
return m ?? null;
|
|
1293
1377
|
};
|
|
1294
1378
|
switch (sub) {
|
|
1295
1379
|
case "list": {
|
|
1296
|
-
|
|
1380
|
+
const rows = await load(port);
|
|
1381
|
+
if (!rows) return 1;
|
|
1297
1382
|
if (!rows.length) {
|
|
1298
|
-
console.log(dim("No connectors yet. Add one with `antbot
|
|
1383
|
+
console.log(dim("No connectors yet. Add one with `antbot mcp add`."));
|
|
1299
1384
|
return 0;
|
|
1300
1385
|
}
|
|
1386
|
+
const roster = await getJson(port, "/api/bots").catch(() => []);
|
|
1387
|
+
const assigned = /* @__PURE__ */ new Map();
|
|
1388
|
+
for (const r of roster) {
|
|
1389
|
+
const cs = await getJson(port, `/api/bots/${r.bot.id}/connectors`).catch(() => []);
|
|
1390
|
+
for (const c of cs) assigned.set(c.name, [...assigned.get(c.name) ?? [], r.bot.name]);
|
|
1391
|
+
}
|
|
1301
1392
|
for (const c of rows) {
|
|
1302
|
-
const
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
if (c.
|
|
1307
|
-
|
|
1308
|
-
}
|
|
1393
|
+
const kind = c.kind === "builtin" ? "built-in" : String(c.config.transport ?? "?");
|
|
1394
|
+
const state = !c.enabled ? dim("disabled") : c.signedIn ? green("signed in") : c.missingSecrets.length ? yellow(`missing secret: ${c.missingSecrets.join(", ")}`) : c.lastStatus === "ready" || c.lastStatus === "connected" ? green("ready") : c.lastStatus ? yellow(c.lastStatus) : dim("unchecked");
|
|
1395
|
+
const bots = assigned.get(c.name)?.length ? dim(`\u2192 ${assigned.get(c.name).join(", ")}`) : dim("\u2192 no bots");
|
|
1396
|
+
console.log(`${bold(c.name.padEnd(14))} ${kind.padEnd(9)} ${state} ${bots}`);
|
|
1397
|
+
if (c.description) console.log(` ${dim(c.description)}`);
|
|
1398
|
+
if (c.lastError && c.lastStatus !== "ready") console.log(` ${dim(c.lastError)}`);
|
|
1309
1399
|
}
|
|
1310
1400
|
return 0;
|
|
1311
1401
|
}
|
|
1312
1402
|
case "add": {
|
|
1313
1403
|
const name = argv[1];
|
|
1404
|
+
const target = argv[2] && !argv[2].startsWith("--") ? argv[2] : void 0;
|
|
1314
1405
|
if (!name || name.startsWith("--")) {
|
|
1315
|
-
console.error(red(
|
|
1406
|
+
console.error(red("Usage: antbot mcp add <name> [<command> | <url>] [--env VAR[=value]]... [--header K=V]... [--bots a,b|all]"));
|
|
1316
1407
|
return 2;
|
|
1317
1408
|
}
|
|
1318
|
-
const
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1409
|
+
const catalog = await getJson(port, "/api/connectors/catalog").catch(() => []);
|
|
1410
|
+
const entry = catalog.find((e) => e.name === name) ?? null;
|
|
1411
|
+
let body;
|
|
1412
|
+
if (!target) {
|
|
1413
|
+
if (!entry) {
|
|
1414
|
+
console.error(red(`"${name}" is not a built-in connector. Give a command or a URL:`));
|
|
1415
|
+
console.error(dim(` antbot mcp add ${name} "npx -y some-server" or antbot mcp add ${name} https://\u2026/mcp`));
|
|
1416
|
+
if (catalog.length) console.error(dim(` Built-in: ${catalog.map((e) => e.name).join(", ")}`));
|
|
1417
|
+
return 2;
|
|
1418
|
+
}
|
|
1419
|
+
console.log(`${bold(entry.displayName)} is built into ant-bot. ${entry.description}`);
|
|
1420
|
+
body = { name, builtin: name };
|
|
1421
|
+
} else if (looksLikeUrl(target)) {
|
|
1422
|
+
const headers = await storePairs(port, name, planPairs(collectFlag(argv, "--header")));
|
|
1423
|
+
const transport = flagValue(argv, "--transport") === "sse" ? "sse" : "http";
|
|
1424
|
+
body = { name, config: { transport, url: target.trim(), headers } };
|
|
1425
|
+
} else {
|
|
1426
|
+
const { command, args } = splitCommand(target);
|
|
1427
|
+
if (!command) {
|
|
1428
|
+
console.error(red("The command is empty."));
|
|
1429
|
+
return 2;
|
|
1430
|
+
}
|
|
1431
|
+
const env = await storePairs(port, name, planPairs(collectFlag(argv, "--env")));
|
|
1432
|
+
body = { name, config: { transport: "stdio", command, args, env } };
|
|
1433
|
+
}
|
|
1434
|
+
if (flagValue(argv, "--desc")) body.description = flagValue(argv, "--desc");
|
|
1435
|
+
const roster = await getJson(port, "/api/bots").catch(() => []);
|
|
1436
|
+
let botIds = resolveBots(flagValue(argv, "--bots"), roster);
|
|
1437
|
+
if (botIds === "ask") {
|
|
1438
|
+
if (roster.length) {
|
|
1439
|
+
const names = roster.map((r) => r.bot.name).join(", ");
|
|
1440
|
+
const ans = await ask(` Give it to which bots? [${names}, all, none] `);
|
|
1441
|
+
botIds = resolveBots(ans || "none", roster);
|
|
1442
|
+
} else botIds = null;
|
|
1322
1443
|
}
|
|
1444
|
+
if (botIds && botIds.length) body.botIds = botIds;
|
|
1445
|
+
let created;
|
|
1323
1446
|
try {
|
|
1324
|
-
|
|
1325
|
-
name,
|
|
1326
|
-
description: flagValue(argv, "--desc") ?? "",
|
|
1327
|
-
config: built.config
|
|
1328
|
-
});
|
|
1329
|
-
console.log(green(`Added connector "${created.name}".`));
|
|
1330
|
-
console.log(dim("Assign it to a bot in Bot settings, then `antbot connector test` to check it."));
|
|
1331
|
-
return 0;
|
|
1447
|
+
created = await postJson(port, "/api/connectors", body);
|
|
1332
1448
|
} catch (err) {
|
|
1333
1449
|
console.error(red(err.message));
|
|
1334
1450
|
return 1;
|
|
1335
1451
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1452
|
+
console.log(describeVerdict(name, created.check));
|
|
1453
|
+
if (created.check.status === "needs-sign-in" && !created.check.alternative) {
|
|
1454
|
+
let creds = {};
|
|
1455
|
+
if (!created.check.selfRegistration) {
|
|
1456
|
+
const c = await collectClientCredentials(entry, created.check.provider);
|
|
1457
|
+
if (!c) {
|
|
1458
|
+
console.log(dim(` Later: antbot mcp login ${name} --client-id ID --client-secret SECRET`));
|
|
1459
|
+
return 0;
|
|
1460
|
+
}
|
|
1461
|
+
creds = c;
|
|
1462
|
+
}
|
|
1463
|
+
const ok = await signIn(port, created, creds);
|
|
1464
|
+
if (ok) {
|
|
1465
|
+
const check = await postJson(port, `/api/connectors/${created.id}/check`, {});
|
|
1466
|
+
console.log(describeVerdict(name, check));
|
|
1467
|
+
}
|
|
1349
1468
|
}
|
|
1350
|
-
|
|
1351
|
-
console.log(
|
|
1469
|
+
if (botIds && botIds.length) console.log(dim(` Assigned to ${botIds.length} bot${botIds.length === 1 ? "" : "s"}.`));
|
|
1470
|
+
else console.log(dim(" Not assigned to any bot yet \u2014 tick it in Bot settings when you want a bot to have it."));
|
|
1352
1471
|
return 0;
|
|
1353
1472
|
}
|
|
1354
|
-
case "
|
|
1473
|
+
case "check": {
|
|
1355
1474
|
const name = argv[1];
|
|
1356
1475
|
if (!name) {
|
|
1357
|
-
console.error(red("Usage: antbot
|
|
1476
|
+
console.error(red("Usage: antbot mcp check <name>"));
|
|
1358
1477
|
return 2;
|
|
1359
1478
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
console.log(green(`Removed connector "${name}". Bot assignments for it are gone too.`));
|
|
1368
|
-
return 0;
|
|
1479
|
+
const row = await byName(name);
|
|
1480
|
+
if (!row) return 1;
|
|
1481
|
+
const check = await postJson(port, `/api/connectors/${row.id}/check`, {});
|
|
1482
|
+
console.log(describeVerdict(name, check));
|
|
1483
|
+
for (const t of check.tools) console.log(` ${bold(`mcp__${name}__${t.name}`)}${t.description ? dim(` ${t.description.slice(0, 90)}`) : ""}`);
|
|
1484
|
+
if (check.status === "needs-sign-in" && !check.alternative) console.log(dim(` Sign in with: antbot mcp login ${name}`));
|
|
1485
|
+
return check.status === "ready" ? 0 : 1;
|
|
1369
1486
|
}
|
|
1370
1487
|
case "login": {
|
|
1371
1488
|
const name = argv[1];
|
|
@@ -1373,29 +1490,20 @@ async function runConnectorCommand(argv, port) {
|
|
|
1373
1490
|
console.error(red("Usage: antbot mcp login <name> [--client-id ID] [--client-secret SECRET]"));
|
|
1374
1491
|
return 2;
|
|
1375
1492
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
console.error(red(`No connector named "${name}".`));
|
|
1380
|
-
return 1;
|
|
1381
|
-
}
|
|
1382
|
-
const body = {};
|
|
1493
|
+
const row = await byName(name);
|
|
1494
|
+
if (!row) return 1;
|
|
1495
|
+
const creds = {};
|
|
1383
1496
|
const cid = flagValue(argv, "--client-id");
|
|
1384
1497
|
const csec = flagValue(argv, "--client-secret");
|
|
1385
|
-
|
|
1386
|
-
if (
|
|
1387
|
-
|
|
1388
|
-
if (
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
console.log(bold("Open this URL to sign in:"));
|
|
1392
|
-
console.log(` ${res.authorizeUrl}`);
|
|
1393
|
-
console.log(dim("The browser returns to ant-bot when you are done. Then: antbot mcp test " + name));
|
|
1498
|
+
if (cid) creds.clientId = cid;
|
|
1499
|
+
if (csec) creds.clientSecret = csec;
|
|
1500
|
+
const ok = await signIn(port, row, creds);
|
|
1501
|
+
if (ok) {
|
|
1502
|
+
const check = await postJson(port, `/api/connectors/${row.id}/check`, {});
|
|
1503
|
+
console.log(describeVerdict(name, check));
|
|
1394
1504
|
return 0;
|
|
1395
|
-
} catch (err) {
|
|
1396
|
-
console.error(red(err.message));
|
|
1397
|
-
return 1;
|
|
1398
1505
|
}
|
|
1506
|
+
return 1;
|
|
1399
1507
|
}
|
|
1400
1508
|
case "logout": {
|
|
1401
1509
|
const name = argv[1];
|
|
@@ -1403,49 +1511,89 @@ async function runConnectorCommand(argv, port) {
|
|
|
1403
1511
|
console.error(red("Usage: antbot mcp logout <name>"));
|
|
1404
1512
|
return 2;
|
|
1405
1513
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
console.error(red(`No connector named "${name}".`));
|
|
1410
|
-
return 1;
|
|
1411
|
-
}
|
|
1412
|
-
await deleteJson(port, `/api/connectors/${match.id}/login`);
|
|
1514
|
+
const row = await byName(name);
|
|
1515
|
+
if (!row) return 1;
|
|
1516
|
+
await deleteJson(port, `/api/connectors/${row.id}/login`);
|
|
1413
1517
|
console.log(green(`Signed out of "${name}".`));
|
|
1414
1518
|
return 0;
|
|
1415
1519
|
}
|
|
1416
|
-
case "
|
|
1520
|
+
case "enable":
|
|
1521
|
+
case "disable": {
|
|
1522
|
+
const name = argv[1];
|
|
1523
|
+
if (!name) {
|
|
1524
|
+
console.error(red(`Usage: antbot mcp ${sub} <name>`));
|
|
1525
|
+
return 2;
|
|
1526
|
+
}
|
|
1527
|
+
const row = await byName(name);
|
|
1528
|
+
if (!row) return 1;
|
|
1529
|
+
await patchJson(port, `/api/connectors/${row.id}`, { enabled: sub === "enable" });
|
|
1530
|
+
console.log(green(`Connector "${name}" ${sub}d.`));
|
|
1531
|
+
return 0;
|
|
1532
|
+
}
|
|
1533
|
+
case "remove": {
|
|
1417
1534
|
const name = argv[1];
|
|
1418
1535
|
if (!name) {
|
|
1419
|
-
console.error(red("Usage: antbot
|
|
1536
|
+
console.error(red("Usage: antbot mcp remove <name>"));
|
|
1420
1537
|
return 2;
|
|
1421
1538
|
}
|
|
1422
|
-
|
|
1539
|
+
const row = await byName(name);
|
|
1540
|
+
if (!row) return 1;
|
|
1541
|
+
await deleteJson(port, `/api/connectors/${row.id}`);
|
|
1542
|
+
console.log(green(`Removed "${name}". Bot assignments for it are gone too.`));
|
|
1543
|
+
return 0;
|
|
1544
|
+
}
|
|
1545
|
+
default:
|
|
1546
|
+
console.error(red(`Unknown subcommand "${sub}".`));
|
|
1547
|
+
hint();
|
|
1548
|
+
return 2;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
async function runSecretCommand(argv, port) {
|
|
1552
|
+
const sub = argv[0];
|
|
1553
|
+
switch (sub) {
|
|
1554
|
+
case "list": {
|
|
1423
1555
|
try {
|
|
1424
|
-
|
|
1556
|
+
const r = await getJson(port, "/api/secrets");
|
|
1557
|
+
if (!r.names.length) console.log(dim(`No secrets stored (backend: ${r.backend}).`));
|
|
1558
|
+
for (const n of r.names) console.log(n);
|
|
1559
|
+
return 0;
|
|
1425
1560
|
} catch (err) {
|
|
1426
|
-
console.error(red(
|
|
1561
|
+
console.error(red(err.message));
|
|
1427
1562
|
return 1;
|
|
1428
1563
|
}
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1564
|
+
}
|
|
1565
|
+
case "set": {
|
|
1566
|
+
const name = argv[1];
|
|
1567
|
+
if (!name) {
|
|
1568
|
+
console.error(red("Usage: antbot secret set <NAME>"));
|
|
1569
|
+
return 2;
|
|
1570
|
+
}
|
|
1571
|
+
const value = await ask(` Value for ${name}: `, true);
|
|
1572
|
+
if (!value) {
|
|
1573
|
+
console.error(red("A value is required."));
|
|
1574
|
+
return 2;
|
|
1432
1575
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
console.
|
|
1576
|
+
try {
|
|
1577
|
+
await postJson(port, "/api/secrets", { name, value });
|
|
1578
|
+
console.log(green(`Stored ${name} in the keychain.`));
|
|
1579
|
+
return 0;
|
|
1580
|
+
} catch (err) {
|
|
1581
|
+
console.error(red(err.message));
|
|
1436
1582
|
return 1;
|
|
1437
1583
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1584
|
+
}
|
|
1585
|
+
case "remove": {
|
|
1586
|
+
const name = argv[1];
|
|
1587
|
+
if (!name) {
|
|
1588
|
+
console.error(red("Usage: antbot secret remove <NAME>"));
|
|
1589
|
+
return 2;
|
|
1443
1590
|
}
|
|
1591
|
+
await deleteJson(port, `/api/secrets/${encodeURIComponent(name)}`);
|
|
1592
|
+
console.log(green(`Removed ${name}.`));
|
|
1444
1593
|
return 0;
|
|
1445
1594
|
}
|
|
1446
1595
|
default:
|
|
1447
|
-
console.error(red(
|
|
1448
|
-
hint();
|
|
1596
|
+
console.error(red("Usage: antbot secret list | set <NAME> | remove <NAME>"));
|
|
1449
1597
|
return 2;
|
|
1450
1598
|
}
|
|
1451
1599
|
}
|
|
@@ -1453,7 +1601,7 @@ async function runConnectorCommand(argv, port) {
|
|
|
1453
1601
|
// cli/src/updateCommand.ts
|
|
1454
1602
|
import fs9 from "node:fs";
|
|
1455
1603
|
import path10 from "node:path";
|
|
1456
|
-
import
|
|
1604
|
+
import readline2 from "node:readline/promises";
|
|
1457
1605
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1458
1606
|
|
|
1459
1607
|
// cli/src/update.ts
|
|
@@ -1628,7 +1776,7 @@ async function runUpdateCommand(opts) {
|
|
|
1628
1776
|
const pm = detectPackageManager(here);
|
|
1629
1777
|
const { cmd, args } = updateCommand(pm);
|
|
1630
1778
|
if (!opts.yes) {
|
|
1631
|
-
const rl =
|
|
1779
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
1632
1780
|
const answer = await rl.question(`Run \`${cmd} ${args.join(" ")}\`? [y/N] `);
|
|
1633
1781
|
rl.close();
|
|
1634
1782
|
if (!/^y(es)?$/i.test(answer.trim())) {
|
|
@@ -1744,7 +1892,7 @@ async function runRestoreCommand(archivePath, skipConfirm) {
|
|
|
1744
1892
|
}
|
|
1745
1893
|
const paths = loadPaths();
|
|
1746
1894
|
if (!skipConfirm) {
|
|
1747
|
-
const rl =
|
|
1895
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
1748
1896
|
const answer = await rl.question(
|
|
1749
1897
|
`This will overwrite files under ${paths.root} with the contents of ${archivePath}. Continue? [y/N] `
|
|
1750
1898
|
);
|
|
@@ -1829,7 +1977,12 @@ async function main() {
|
|
|
1829
1977
|
case "mcp":
|
|
1830
1978
|
case "connector": {
|
|
1831
1979
|
const cfg = loadConfig();
|
|
1832
|
-
process.exit(await
|
|
1980
|
+
process.exit(await runMcpCommand(parsed.positionals, cfg.port));
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
case "secret": {
|
|
1984
|
+
const cfg = loadConfig();
|
|
1985
|
+
process.exit(await runSecretCommand(parsed.positionals, cfg.port));
|
|
1833
1986
|
return;
|
|
1834
1987
|
}
|
|
1835
1988
|
case "backup":
|