@michael-joseph-miller/ant-bot 0.3.0 → 0.4.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/CHANGELOG.md +61 -0
- package/README.md +9 -8
- package/dist/{chunk-I3GHIX3G.js → chunk-BKF7CUVN.js} +27 -5
- package/dist/chunk-BKF7CUVN.js.map +7 -0
- package/dist/index.js +335 -183
- package/dist/index.js.map +4 -4
- package/dist/server.js +962 -395
- package/dist/server.js.map +4 -4
- package/package.json +1 -1
- package/web/dist/assets/index-9uemvauV.js +130 -0
- package/web/dist/assets/index-Bd_zE95N.css +2 -0
- package/web/dist/index.html +2 -2
- package/dist/chunk-I3GHIX3G.js.map +0 -7
- package/web/dist/assets/index-B90EjIDl.css +0 -2
- package/web/dist/assets/index-uIFP-bJK.js +0 -130
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-BKF7CUVN.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,258 @@ 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
|
+
return yellow(`! ${name}: needs sign-in${check.provider ? ` (${check.provider})` : ""}${n ? `, ${n} tools` : ""}`);
|
|
1265
|
+
case "needs-credential":
|
|
1266
|
+
return yellow(`! ${name}: ${check.detail ?? "needs a credential"}`);
|
|
1267
|
+
case "unreachable":
|
|
1268
|
+
return red(`\u2717 ${name}: unreachable${check.detail ? ` \u2014 ${check.detail}` : ""}`);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
async function ask(question, hidden = false) {
|
|
1272
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
1273
|
+
if (hidden) {
|
|
1274
|
+
const out = process.stdout;
|
|
1275
|
+
const orig = out.write.bind(out);
|
|
1276
|
+
process.stdout.write(question);
|
|
1277
|
+
let muted = true;
|
|
1278
|
+
out.write = ((s) => muted ? true : orig(s));
|
|
1279
|
+
try {
|
|
1280
|
+
const v = await rl.question("");
|
|
1281
|
+
return v.trim();
|
|
1282
|
+
} finally {
|
|
1283
|
+
muted = false;
|
|
1284
|
+
out.write = orig;
|
|
1285
|
+
process.stdout.write("\n");
|
|
1286
|
+
rl.close();
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
try {
|
|
1290
|
+
return (await rl.question(question)).trim();
|
|
1291
|
+
} finally {
|
|
1292
|
+
rl.close();
|
|
1265
1293
|
}
|
|
1266
|
-
return { error: 'Give either --stdio "<command>" or --url <url>.' };
|
|
1267
1294
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1295
|
+
function openBrowser(url) {
|
|
1296
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1297
|
+
try {
|
|
1298
|
+
const child = spawn3(cmd, [url], { stdio: "ignore", detached: true });
|
|
1299
|
+
child.on("error", () => {
|
|
1300
|
+
});
|
|
1301
|
+
child.unref();
|
|
1302
|
+
} catch {
|
|
1303
|
+
}
|
|
1304
|
+
console.log(dim(" If a browser did not open, use this link:"));
|
|
1305
|
+
console.log(` ${url}`);
|
|
1306
|
+
}
|
|
1307
|
+
async function load(port) {
|
|
1308
|
+
try {
|
|
1309
|
+
return await getJson(port, "/api/connectors");
|
|
1310
|
+
} catch (err) {
|
|
1311
|
+
console.error(red(`Could not reach the daemon on port ${port}: ${err.message}`));
|
|
1312
|
+
console.error(dim("Start it with `antbot start`."));
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
async function storePairs(port, connector, pairs) {
|
|
1317
|
+
const out = {};
|
|
1318
|
+
for (const p of pairs) {
|
|
1319
|
+
if (!p.secret) {
|
|
1320
|
+
out[p.key] = p.value;
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
const value = await ask(` ${p.key}: `, true);
|
|
1324
|
+
if (!value) throw new Error(`${p.key} needs a value.`);
|
|
1325
|
+
const name = secretNameFor(connector, p.key);
|
|
1326
|
+
await postJson(port, "/api/secrets", { name, value });
|
|
1327
|
+
out[p.key] = `{{secret:${name}}}`;
|
|
1328
|
+
}
|
|
1329
|
+
return out;
|
|
1271
1330
|
}
|
|
1272
|
-
async function
|
|
1331
|
+
async function signIn(port, row, opts) {
|
|
1332
|
+
let res;
|
|
1333
|
+
try {
|
|
1334
|
+
res = await postJson(port, `/api/connectors/${row.id}/login`, opts);
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
console.error(yellow(` ${err.message}`));
|
|
1337
|
+
return false;
|
|
1338
|
+
}
|
|
1339
|
+
console.log(dim(" Opening your browser to sign in\u2026"));
|
|
1340
|
+
openBrowser(res.authorizeUrl);
|
|
1341
|
+
const deadline = Date.now() + 5 * 6e4;
|
|
1342
|
+
while (Date.now() < deadline) {
|
|
1343
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
1344
|
+
const rows = await getJson(port, "/api/connectors").catch(() => []);
|
|
1345
|
+
const cur = rows.find((r) => r.id === row.id);
|
|
1346
|
+
if (cur?.signedIn) return true;
|
|
1347
|
+
}
|
|
1348
|
+
console.log(yellow(" Still waiting for the sign-in. Finish it in the browser, then run: antbot mcp check " + row.name));
|
|
1349
|
+
return false;
|
|
1350
|
+
}
|
|
1351
|
+
async function collectClientCredentials(entry, provider) {
|
|
1352
|
+
console.log(yellow(` ${provider ?? "This provider"} needs an OAuth client you create yourself \u2014 a one-time setup:`));
|
|
1353
|
+
for (const [i, step] of (entry?.setupSteps ?? []).entries()) console.log(` ${i + 1}. ${step}`);
|
|
1354
|
+
const clientId = await ask(" Client ID (blank to do this later): ");
|
|
1355
|
+
if (!clientId) return null;
|
|
1356
|
+
const clientSecret = await ask(" Client secret (blank if none): ", true);
|
|
1357
|
+
return { clientId, ...clientSecret ? { clientSecret } : {} };
|
|
1358
|
+
}
|
|
1359
|
+
async function runMcpCommand(argv, port) {
|
|
1273
1360
|
const sub = argv[0];
|
|
1274
1361
|
const hint = () => {
|
|
1275
|
-
console.error(dim("Subcommands: list, add, login, logout, enable, disable, remove
|
|
1362
|
+
console.error(dim("Subcommands: list, add, check, login, logout, enable, disable, remove."));
|
|
1276
1363
|
console.error(dim("Run `antbot mcp --help` for the full usage."));
|
|
1277
1364
|
};
|
|
1278
1365
|
if (!sub) {
|
|
1279
|
-
console.error(red("antbot
|
|
1366
|
+
console.error(red("antbot mcp needs a subcommand."));
|
|
1280
1367
|
hint();
|
|
1281
1368
|
return 2;
|
|
1282
1369
|
}
|
|
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
|
-
}
|
|
1370
|
+
const byName = async (name) => {
|
|
1371
|
+
const rows = await load(port);
|
|
1372
|
+
if (!rows) return null;
|
|
1373
|
+
const m = rows.find((c) => c.name === name);
|
|
1374
|
+
if (!m) console.error(red(`No connector named "${name}".`));
|
|
1375
|
+
return m ?? null;
|
|
1293
1376
|
};
|
|
1294
1377
|
switch (sub) {
|
|
1295
1378
|
case "list": {
|
|
1296
|
-
|
|
1379
|
+
const rows = await load(port);
|
|
1380
|
+
if (!rows) return 1;
|
|
1297
1381
|
if (!rows.length) {
|
|
1298
|
-
console.log(dim("No connectors yet. Add one with `antbot
|
|
1382
|
+
console.log(dim("No connectors yet. Add one with `antbot mcp add`."));
|
|
1299
1383
|
return 0;
|
|
1300
1384
|
}
|
|
1385
|
+
const roster = await getJson(port, "/api/bots").catch(() => []);
|
|
1386
|
+
const assigned = /* @__PURE__ */ new Map();
|
|
1387
|
+
for (const r of roster) {
|
|
1388
|
+
const cs = await getJson(port, `/api/bots/${r.bot.id}/connectors`).catch(() => []);
|
|
1389
|
+
for (const c of cs) assigned.set(c.name, [...assigned.get(c.name) ?? [], r.bot.name]);
|
|
1390
|
+
}
|
|
1301
1391
|
for (const c of rows) {
|
|
1302
|
-
const
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
if (c.
|
|
1307
|
-
|
|
1308
|
-
}
|
|
1392
|
+
const kind = c.kind === "builtin" ? "built-in" : String(c.config.transport ?? "?");
|
|
1393
|
+
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");
|
|
1394
|
+
const bots = assigned.get(c.name)?.length ? dim(`\u2192 ${assigned.get(c.name).join(", ")}`) : dim("\u2192 no bots");
|
|
1395
|
+
console.log(`${bold(c.name.padEnd(14))} ${kind.padEnd(9)} ${state} ${bots}`);
|
|
1396
|
+
if (c.description) console.log(` ${dim(c.description)}`);
|
|
1397
|
+
if (c.lastError && c.lastStatus !== "ready") console.log(` ${dim(c.lastError)}`);
|
|
1309
1398
|
}
|
|
1310
1399
|
return 0;
|
|
1311
1400
|
}
|
|
1312
1401
|
case "add": {
|
|
1313
1402
|
const name = argv[1];
|
|
1403
|
+
const target = argv[2] && !argv[2].startsWith("--") ? argv[2] : void 0;
|
|
1314
1404
|
if (!name || name.startsWith("--")) {
|
|
1315
|
-
console.error(red(
|
|
1405
|
+
console.error(red("Usage: antbot mcp add <name> [<command> | <url>] [--env VAR[=value]]... [--header K=V]... [--bots a,b|all]"));
|
|
1316
1406
|
return 2;
|
|
1317
1407
|
}
|
|
1318
|
-
const
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1408
|
+
const catalog = await getJson(port, "/api/connectors/catalog").catch(() => []);
|
|
1409
|
+
const entry = catalog.find((e) => e.name === name) ?? null;
|
|
1410
|
+
let body;
|
|
1411
|
+
if (!target) {
|
|
1412
|
+
if (!entry) {
|
|
1413
|
+
console.error(red(`"${name}" is not a built-in connector. Give a command or a URL:`));
|
|
1414
|
+
console.error(dim(` antbot mcp add ${name} "npx -y some-server" or antbot mcp add ${name} https://\u2026/mcp`));
|
|
1415
|
+
if (catalog.length) console.error(dim(` Built-in: ${catalog.map((e) => e.name).join(", ")}`));
|
|
1416
|
+
return 2;
|
|
1417
|
+
}
|
|
1418
|
+
console.log(`${bold(entry.displayName)} is built into ant-bot. ${entry.description}`);
|
|
1419
|
+
body = { name, builtin: name };
|
|
1420
|
+
} else if (looksLikeUrl(target)) {
|
|
1421
|
+
const headers = await storePairs(port, name, planPairs(collectFlag(argv, "--header")));
|
|
1422
|
+
const transport = flagValue(argv, "--transport") === "sse" ? "sse" : "http";
|
|
1423
|
+
body = { name, config: { transport, url: target.trim(), headers } };
|
|
1424
|
+
} else {
|
|
1425
|
+
const { command, args } = splitCommand(target);
|
|
1426
|
+
if (!command) {
|
|
1427
|
+
console.error(red("The command is empty."));
|
|
1428
|
+
return 2;
|
|
1429
|
+
}
|
|
1430
|
+
const env = await storePairs(port, name, planPairs(collectFlag(argv, "--env")));
|
|
1431
|
+
body = { name, config: { transport: "stdio", command, args, env } };
|
|
1432
|
+
}
|
|
1433
|
+
if (flagValue(argv, "--desc")) body.description = flagValue(argv, "--desc");
|
|
1434
|
+
const roster = await getJson(port, "/api/bots").catch(() => []);
|
|
1435
|
+
let botIds = resolveBots(flagValue(argv, "--bots"), roster);
|
|
1436
|
+
if (botIds === "ask") {
|
|
1437
|
+
if (roster.length) {
|
|
1438
|
+
const names = roster.map((r) => r.bot.name).join(", ");
|
|
1439
|
+
const ans = await ask(` Give it to which bots? [${names}, all, none] `);
|
|
1440
|
+
botIds = resolveBots(ans || "none", roster);
|
|
1441
|
+
} else botIds = null;
|
|
1322
1442
|
}
|
|
1443
|
+
if (botIds && botIds.length) body.botIds = botIds;
|
|
1444
|
+
let created;
|
|
1323
1445
|
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;
|
|
1446
|
+
created = await postJson(port, "/api/connectors", body);
|
|
1332
1447
|
} catch (err) {
|
|
1333
1448
|
console.error(red(err.message));
|
|
1334
1449
|
return 1;
|
|
1335
1450
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1451
|
+
console.log(describeVerdict(name, created.check));
|
|
1452
|
+
if (created.check.status === "needs-sign-in") {
|
|
1453
|
+
let creds = {};
|
|
1454
|
+
if (!created.check.selfRegistration) {
|
|
1455
|
+
const c = await collectClientCredentials(entry, created.check.provider);
|
|
1456
|
+
if (!c) {
|
|
1457
|
+
console.log(dim(` Later: antbot mcp login ${name} --client-id ID --client-secret SECRET`));
|
|
1458
|
+
return 0;
|
|
1459
|
+
}
|
|
1460
|
+
creds = c;
|
|
1461
|
+
}
|
|
1462
|
+
const ok = await signIn(port, created, creds);
|
|
1463
|
+
if (ok) {
|
|
1464
|
+
const check = await postJson(port, `/api/connectors/${created.id}/check`, {});
|
|
1465
|
+
console.log(describeVerdict(name, check));
|
|
1466
|
+
}
|
|
1349
1467
|
}
|
|
1350
|
-
|
|
1351
|
-
console.log(
|
|
1468
|
+
if (botIds && botIds.length) console.log(dim(` Assigned to ${botIds.length} bot${botIds.length === 1 ? "" : "s"}.`));
|
|
1469
|
+
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
1470
|
return 0;
|
|
1353
1471
|
}
|
|
1354
|
-
case "
|
|
1472
|
+
case "check": {
|
|
1355
1473
|
const name = argv[1];
|
|
1356
1474
|
if (!name) {
|
|
1357
|
-
console.error(red("Usage: antbot
|
|
1475
|
+
console.error(red("Usage: antbot mcp check <name>"));
|
|
1358
1476
|
return 2;
|
|
1359
1477
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
console.log(green(`Removed connector "${name}". Bot assignments for it are gone too.`));
|
|
1368
|
-
return 0;
|
|
1478
|
+
const row = await byName(name);
|
|
1479
|
+
if (!row) return 1;
|
|
1480
|
+
const check = await postJson(port, `/api/connectors/${row.id}/check`, {});
|
|
1481
|
+
console.log(describeVerdict(name, check));
|
|
1482
|
+
for (const t of check.tools) console.log(` ${bold(`mcp__${name}__${t.name}`)}${t.description ? dim(` ${t.description.slice(0, 90)}`) : ""}`);
|
|
1483
|
+
if (check.status === "needs-sign-in") console.log(dim(` Sign in with: antbot mcp login ${name}`));
|
|
1484
|
+
return check.status === "ready" ? 0 : 1;
|
|
1369
1485
|
}
|
|
1370
1486
|
case "login": {
|
|
1371
1487
|
const name = argv[1];
|
|
@@ -1373,29 +1489,20 @@ async function runConnectorCommand(argv, port) {
|
|
|
1373
1489
|
console.error(red("Usage: antbot mcp login <name> [--client-id ID] [--client-secret SECRET]"));
|
|
1374
1490
|
return 2;
|
|
1375
1491
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
console.error(red(`No connector named "${name}".`));
|
|
1380
|
-
return 1;
|
|
1381
|
-
}
|
|
1382
|
-
const body = {};
|
|
1492
|
+
const row = await byName(name);
|
|
1493
|
+
if (!row) return 1;
|
|
1494
|
+
const creds = {};
|
|
1383
1495
|
const cid = flagValue(argv, "--client-id");
|
|
1384
1496
|
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));
|
|
1497
|
+
if (cid) creds.clientId = cid;
|
|
1498
|
+
if (csec) creds.clientSecret = csec;
|
|
1499
|
+
const ok = await signIn(port, row, creds);
|
|
1500
|
+
if (ok) {
|
|
1501
|
+
const check = await postJson(port, `/api/connectors/${row.id}/check`, {});
|
|
1502
|
+
console.log(describeVerdict(name, check));
|
|
1394
1503
|
return 0;
|
|
1395
|
-
} catch (err) {
|
|
1396
|
-
console.error(red(err.message));
|
|
1397
|
-
return 1;
|
|
1398
1504
|
}
|
|
1505
|
+
return 1;
|
|
1399
1506
|
}
|
|
1400
1507
|
case "logout": {
|
|
1401
1508
|
const name = argv[1];
|
|
@@ -1403,49 +1510,89 @@ async function runConnectorCommand(argv, port) {
|
|
|
1403
1510
|
console.error(red("Usage: antbot mcp logout <name>"));
|
|
1404
1511
|
return 2;
|
|
1405
1512
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
console.error(red(`No connector named "${name}".`));
|
|
1410
|
-
return 1;
|
|
1411
|
-
}
|
|
1412
|
-
await deleteJson(port, `/api/connectors/${match.id}/login`);
|
|
1513
|
+
const row = await byName(name);
|
|
1514
|
+
if (!row) return 1;
|
|
1515
|
+
await deleteJson(port, `/api/connectors/${row.id}/login`);
|
|
1413
1516
|
console.log(green(`Signed out of "${name}".`));
|
|
1414
1517
|
return 0;
|
|
1415
1518
|
}
|
|
1416
|
-
case "
|
|
1519
|
+
case "enable":
|
|
1520
|
+
case "disable": {
|
|
1521
|
+
const name = argv[1];
|
|
1522
|
+
if (!name) {
|
|
1523
|
+
console.error(red(`Usage: antbot mcp ${sub} <name>`));
|
|
1524
|
+
return 2;
|
|
1525
|
+
}
|
|
1526
|
+
const row = await byName(name);
|
|
1527
|
+
if (!row) return 1;
|
|
1528
|
+
await patchJson(port, `/api/connectors/${row.id}`, { enabled: sub === "enable" });
|
|
1529
|
+
console.log(green(`Connector "${name}" ${sub}d.`));
|
|
1530
|
+
return 0;
|
|
1531
|
+
}
|
|
1532
|
+
case "remove": {
|
|
1417
1533
|
const name = argv[1];
|
|
1418
1534
|
if (!name) {
|
|
1419
|
-
console.error(red("Usage: antbot
|
|
1535
|
+
console.error(red("Usage: antbot mcp remove <name>"));
|
|
1420
1536
|
return 2;
|
|
1421
1537
|
}
|
|
1422
|
-
|
|
1538
|
+
const row = await byName(name);
|
|
1539
|
+
if (!row) return 1;
|
|
1540
|
+
await deleteJson(port, `/api/connectors/${row.id}`);
|
|
1541
|
+
console.log(green(`Removed "${name}". Bot assignments for it are gone too.`));
|
|
1542
|
+
return 0;
|
|
1543
|
+
}
|
|
1544
|
+
default:
|
|
1545
|
+
console.error(red(`Unknown subcommand "${sub}".`));
|
|
1546
|
+
hint();
|
|
1547
|
+
return 2;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
async function runSecretCommand(argv, port) {
|
|
1551
|
+
const sub = argv[0];
|
|
1552
|
+
switch (sub) {
|
|
1553
|
+
case "list": {
|
|
1423
1554
|
try {
|
|
1424
|
-
|
|
1555
|
+
const r = await getJson(port, "/api/secrets");
|
|
1556
|
+
if (!r.names.length) console.log(dim(`No secrets stored (backend: ${r.backend}).`));
|
|
1557
|
+
for (const n of r.names) console.log(n);
|
|
1558
|
+
return 0;
|
|
1425
1559
|
} catch (err) {
|
|
1426
|
-
console.error(red(
|
|
1560
|
+
console.error(red(err.message));
|
|
1427
1561
|
return 1;
|
|
1428
1562
|
}
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1563
|
+
}
|
|
1564
|
+
case "set": {
|
|
1565
|
+
const name = argv[1];
|
|
1566
|
+
if (!name) {
|
|
1567
|
+
console.error(red("Usage: antbot secret set <NAME>"));
|
|
1568
|
+
return 2;
|
|
1569
|
+
}
|
|
1570
|
+
const value = await ask(` Value for ${name}: `, true);
|
|
1571
|
+
if (!value) {
|
|
1572
|
+
console.error(red("A value is required."));
|
|
1573
|
+
return 2;
|
|
1432
1574
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
console.
|
|
1575
|
+
try {
|
|
1576
|
+
await postJson(port, "/api/secrets", { name, value });
|
|
1577
|
+
console.log(green(`Stored ${name} in the keychain.`));
|
|
1578
|
+
return 0;
|
|
1579
|
+
} catch (err) {
|
|
1580
|
+
console.error(red(err.message));
|
|
1436
1581
|
return 1;
|
|
1437
1582
|
}
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1583
|
+
}
|
|
1584
|
+
case "remove": {
|
|
1585
|
+
const name = argv[1];
|
|
1586
|
+
if (!name) {
|
|
1587
|
+
console.error(red("Usage: antbot secret remove <NAME>"));
|
|
1588
|
+
return 2;
|
|
1443
1589
|
}
|
|
1590
|
+
await deleteJson(port, `/api/secrets/${encodeURIComponent(name)}`);
|
|
1591
|
+
console.log(green(`Removed ${name}.`));
|
|
1444
1592
|
return 0;
|
|
1445
1593
|
}
|
|
1446
1594
|
default:
|
|
1447
|
-
console.error(red(
|
|
1448
|
-
hint();
|
|
1595
|
+
console.error(red("Usage: antbot secret list | set <NAME> | remove <NAME>"));
|
|
1449
1596
|
return 2;
|
|
1450
1597
|
}
|
|
1451
1598
|
}
|
|
@@ -1453,7 +1600,7 @@ async function runConnectorCommand(argv, port) {
|
|
|
1453
1600
|
// cli/src/updateCommand.ts
|
|
1454
1601
|
import fs9 from "node:fs";
|
|
1455
1602
|
import path10 from "node:path";
|
|
1456
|
-
import
|
|
1603
|
+
import readline2 from "node:readline/promises";
|
|
1457
1604
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1458
1605
|
|
|
1459
1606
|
// cli/src/update.ts
|
|
@@ -1628,7 +1775,7 @@ async function runUpdateCommand(opts) {
|
|
|
1628
1775
|
const pm = detectPackageManager(here);
|
|
1629
1776
|
const { cmd, args } = updateCommand(pm);
|
|
1630
1777
|
if (!opts.yes) {
|
|
1631
|
-
const rl =
|
|
1778
|
+
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
1632
1779
|
const answer = await rl.question(`Run \`${cmd} ${args.join(" ")}\`? [y/N] `);
|
|
1633
1780
|
rl.close();
|
|
1634
1781
|
if (!/^y(es)?$/i.test(answer.trim())) {
|
|
@@ -1744,7 +1891,7 @@ async function runRestoreCommand(archivePath, skipConfirm) {
|
|
|
1744
1891
|
}
|
|
1745
1892
|
const paths = loadPaths();
|
|
1746
1893
|
if (!skipConfirm) {
|
|
1747
|
-
const rl =
|
|
1894
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
1748
1895
|
const answer = await rl.question(
|
|
1749
1896
|
`This will overwrite files under ${paths.root} with the contents of ${archivePath}. Continue? [y/N] `
|
|
1750
1897
|
);
|
|
@@ -1829,7 +1976,12 @@ async function main() {
|
|
|
1829
1976
|
case "mcp":
|
|
1830
1977
|
case "connector": {
|
|
1831
1978
|
const cfg = loadConfig();
|
|
1832
|
-
process.exit(await
|
|
1979
|
+
process.exit(await runMcpCommand(parsed.positionals, cfg.port));
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
case "secret": {
|
|
1983
|
+
const cfg = loadConfig();
|
|
1984
|
+
process.exit(await runSecretCommand(parsed.positionals, cfg.port));
|
|
1833
1985
|
return;
|
|
1834
1986
|
}
|
|
1835
1987
|
case "backup":
|