@botbuddy/cli 1.5.0 → 1.5.2
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/package.json +3 -2
- package/src/botbuddy-release-repair.json +1 -0
- package/src/publish-equal.mjs +154 -6
- package/src/run.mjs +5 -1
- package/src/auth.test.mjs +0 -404
- package/src/discovery.test.mjs +0 -195
- package/src/docker-hygiene.test.mjs +0 -790
- package/src/locks.test.mjs +0 -60
- package/src/profile-bootstrap.test.mjs +0 -205
- package/src/publish-equal.test.mjs +0 -176
- package/src/publish-workflow.test.mjs +0 -122
- package/src/quiet-runner.test.mjs +0 -109
- package/src/run.test.mjs +0 -173
- package/src/stack.test.mjs +0 -756
- package/src/wait-profile.test.mjs +0 -30
- package/src/wait.test.mjs +0 -266
package/src/discovery.test.mjs
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
// BOT-876: unit coverage for the CLI's generic call + discovery commands.
|
|
2
|
-
|
|
3
|
-
import test from "node:test";
|
|
4
|
-
import assert from "node:assert/strict";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
CallUsageError,
|
|
8
|
-
discoveryUrlFor,
|
|
9
|
-
formatDiscovery,
|
|
10
|
-
parseCallArgs,
|
|
11
|
-
} from "./discovery.mjs";
|
|
12
|
-
|
|
13
|
-
const SERVER = "https://api.bot-buddy.ai/functions/v1/mcp-server";
|
|
14
|
-
|
|
15
|
-
// ─── parseCallArgs ──────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
test("call with no arguments yields an empty args object", () => {
|
|
18
|
-
assert.deepEqual(parseCallArgs(["list_agents"]), {
|
|
19
|
-
tool: "list_agents",
|
|
20
|
-
args: {},
|
|
21
|
-
usedJson: false,
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("--json supplies the whole arguments object", () => {
|
|
26
|
-
const { tool, args } = parseCallArgs([
|
|
27
|
-
"register_agent",
|
|
28
|
-
"--json",
|
|
29
|
-
'{"name":"claude-1","type":"claude"}',
|
|
30
|
-
]);
|
|
31
|
-
assert.equal(tool, "register_agent");
|
|
32
|
-
assert.deepEqual(args, { name: "claude-1", type: "claude" });
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("--json accepts nested structures", () => {
|
|
36
|
-
const { args } = parseCallArgs([
|
|
37
|
-
"register_agent",
|
|
38
|
-
"--json",
|
|
39
|
-
'{"resources":[{"resource_type":"mcp_server","subtype":"playwright_lane"}]}',
|
|
40
|
-
]);
|
|
41
|
-
assert.deepEqual(args.resources, [
|
|
42
|
-
{ resource_type: "mcp_server", subtype: "playwright_lane" },
|
|
43
|
-
]);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("--key value flags build the arguments object", () => {
|
|
47
|
-
const { args } = parseCallArgs(["create_task", "--title", "Fix the thing", "--priority", "2"]);
|
|
48
|
-
assert.deepEqual(args, { title: "Fix the thing", priority: 2 });
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("numeric and boolean flag values are coerced off the command line", () => {
|
|
52
|
-
// A tool whose schema says `number` would reject the string "2".
|
|
53
|
-
const { args } = parseCallArgs([
|
|
54
|
-
"t",
|
|
55
|
-
"--count", "2",
|
|
56
|
-
"--ratio", "1.5",
|
|
57
|
-
"--negative", "-3",
|
|
58
|
-
"--yes", "true",
|
|
59
|
-
"--no", "false",
|
|
60
|
-
"--nothing", "null",
|
|
61
|
-
]);
|
|
62
|
-
assert.deepEqual(args, {
|
|
63
|
-
count: 2, ratio: 1.5, negative: -3, yes: true, no: false, nothing: null,
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("values that only look numeric stay strings", () => {
|
|
68
|
-
const { args } = parseCallArgs([
|
|
69
|
-
"t",
|
|
70
|
-
"--ticket", "BOT-876",
|
|
71
|
-
"--version", "1.2.3",
|
|
72
|
-
"--hex", "0x10",
|
|
73
|
-
"--padded", "007",
|
|
74
|
-
]);
|
|
75
|
-
assert.equal(args.ticket, "BOT-876");
|
|
76
|
-
assert.equal(args.version, "1.2.3");
|
|
77
|
-
assert.equal(args.hex, "0x10");
|
|
78
|
-
// "007" is unambiguously numeric; losing the padding is acceptable, but a
|
|
79
|
-
// leading-zero identifier is common enough to pin the behaviour.
|
|
80
|
-
assert.equal(args.padded, 7);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("a valueless flag is a boolean true", () => {
|
|
84
|
-
const { args } = parseCallArgs(["t", "--force", "--name", "x"]);
|
|
85
|
-
assert.deepEqual(args, { force: true, name: "x" });
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("a trailing valueless flag is a boolean true", () => {
|
|
89
|
-
const { args } = parseCallArgs(["t", "--force"]);
|
|
90
|
-
assert.deepEqual(args, { force: true });
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("flags after --json override the blob", () => {
|
|
94
|
-
const { args } = parseCallArgs(["t", "--json", '{"name":"a"}', "--name", "b"]);
|
|
95
|
-
assert.equal(args.name, "b");
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("a missing tool name is a usage error", () => {
|
|
99
|
-
assert.throws(() => parseCallArgs([]), CallUsageError);
|
|
100
|
-
assert.throws(() => parseCallArgs(["--json", "{}"]), CallUsageError);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("malformed --json is reported clearly, not swallowed", () => {
|
|
104
|
-
assert.throws(() => parseCallArgs(["t", "--json", "{not json}"]), (e) => {
|
|
105
|
-
assert.ok(e instanceof CallUsageError);
|
|
106
|
-
assert.match(e.message, /not valid JSON/);
|
|
107
|
-
return true;
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
test("--json must be an object, not an array or scalar", () => {
|
|
112
|
-
for (const bad of ["[1,2]", '"a string"', "42", "null"]) {
|
|
113
|
-
assert.throws(() => parseCallArgs(["t", "--json", bad]), CallUsageError, `accepted ${bad}`);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("--json with no value is a usage error", () => {
|
|
118
|
-
assert.throws(() => parseCallArgs(["t", "--json"]), CallUsageError);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("a bare positional argument is rejected rather than silently ignored", () => {
|
|
122
|
-
assert.throws(() => parseCallArgs(["t", "oops"]), (e) => {
|
|
123
|
-
assert.match(e.message, /--key value or --json/);
|
|
124
|
-
return true;
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// ─── discoveryUrlFor ────────────────────────────────────────────
|
|
129
|
-
|
|
130
|
-
test("discovery URL is derived from the server URL", () => {
|
|
131
|
-
assert.equal(discoveryUrlFor(SERVER), `${SERVER}/discovery`);
|
|
132
|
-
assert.equal(discoveryUrlFor(`${SERVER}/`), `${SERVER}/discovery`);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test("discovery URL encodes a requested tool", () => {
|
|
136
|
-
assert.equal(discoveryUrlFor(SERVER, "get_totp_code"), `${SERVER}/discovery?tool=get_totp_code`);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// ─── formatDiscovery ────────────────────────────────────────────
|
|
140
|
-
|
|
141
|
-
const DOC = {
|
|
142
|
-
tools: [
|
|
143
|
-
{ name: "register_agent", description: "Register an agent. Extra detail here.", inputSchema: { type: "object" } },
|
|
144
|
-
{ name: "list_agents", description: "List all agents", inputSchema: { type: "object" } },
|
|
145
|
-
],
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
test("the index lists every tool, sorted, one line each", () => {
|
|
149
|
-
const out = formatDiscovery(DOC);
|
|
150
|
-
const lines = out.split("\n").filter((l) => l.startsWith(" "));
|
|
151
|
-
assert.equal(lines.length, 2);
|
|
152
|
-
assert.match(lines[0], /list_agents/);
|
|
153
|
-
assert.match(lines[1], /register_agent/);
|
|
154
|
-
assert.match(out, /2 tools available/);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("the index truncates a description to its first sentence", () => {
|
|
158
|
-
const out = formatDiscovery(DOC);
|
|
159
|
-
assert.match(out, /Register an agent\./);
|
|
160
|
-
assert.doesNotMatch(out, /Extra detail here/);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("a single tool renders its full schema and an invocation example", () => {
|
|
164
|
-
const out = formatDiscovery(DOC, { tool: "register_agent" });
|
|
165
|
-
assert.match(out, /^register_agent/);
|
|
166
|
-
assert.match(out, /botbuddy call register_agent/);
|
|
167
|
-
assert.match(out, /"type": "object"/);
|
|
168
|
-
// register_agent carries no `auth` note → no Auth section.
|
|
169
|
-
assert.doesNotMatch(out, /Auth:/);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test("a single tool with an agent-identity note renders an Auth section", () => {
|
|
173
|
-
// BOT-971: the server stamps `auth` on discovery entries for agent-gated
|
|
174
|
-
// tools; `botbuddy help <tool>` must surface it (matching help({ tool })).
|
|
175
|
-
const doc = {
|
|
176
|
-
tools: [{
|
|
177
|
-
name: "acquire_resources",
|
|
178
|
-
description: "Batch lock",
|
|
179
|
-
inputSchema: { type: "object" },
|
|
180
|
-
auth: "Requires an agent identity — call register_agent first.",
|
|
181
|
-
}],
|
|
182
|
-
};
|
|
183
|
-
const out = formatDiscovery(doc, { tool: "acquire_resources" });
|
|
184
|
-
assert.match(out, /Auth:/);
|
|
185
|
-
assert.match(out, /register_agent/);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test("an unknown tool is reported, not rendered empty", () => {
|
|
189
|
-
assert.match(formatDiscovery(DOC, { tool: "nope" }), /Unknown tool: nope/);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test("a malformed discovery document does not throw", () => {
|
|
193
|
-
assert.equal(formatDiscovery(null), "No tools found.");
|
|
194
|
-
assert.equal(formatDiscovery({}), "No tools found.");
|
|
195
|
-
});
|