@go-labs-sg/bb 1.21.0 → 2.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/README.md +74 -50
- package/command-manifest.json +6606 -0
- package/command-reference.md +1153 -0
- package/dist/api-client.js +44 -8
- package/dist/commands.js +20 -2
- package/dist/index.js +264 -24
- package/dist/registry/generate-command-artifacts.js +36 -0
- package/dist/registry/index.js +583 -0
- package/dist/runtime/confirmation.js +76 -0
- package/dist/runtime/error.js +143 -0
- package/dist/runtime/index.js +6 -0
- package/dist/runtime/output.js +36 -0
- package/dist/runtime/process-runtime.js +28 -0
- package/dist/runtime/sanitize.js +50 -0
- package/dist/runtime/session.js +50 -0
- package/dist/runtime/types.js +1 -0
- package/package.json +12 -18
- package/role-aware-agent-guide.md +169 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative command catalogue for the Budget Builder CLI.
|
|
3
|
+
*
|
|
4
|
+
* The first v2 integration step uses `legacyTarget` to dispatch through the
|
|
5
|
+
* established command implementation. New command handlers can replace that
|
|
6
|
+
* target incrementally by supplying `load` instead.
|
|
7
|
+
*/
|
|
8
|
+
export const globalCommandOptions = [
|
|
9
|
+
{ name: "--help", description: "Show help for this command." },
|
|
10
|
+
{ name: "--quiet", description: "Suppress non-error diagnostics." },
|
|
11
|
+
{ name: "--debug", description: "Emit sanitized diagnostic traces." },
|
|
12
|
+
{
|
|
13
|
+
name: "--api-url",
|
|
14
|
+
description: "Override the Budget Builder API base URL.",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "--allow-state-change",
|
|
18
|
+
description: "Allow a Budget Builder state change in non-interactive use.",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "--allow-email",
|
|
22
|
+
description: "Allow sending email in non-interactive use.",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "--allow-external-write",
|
|
26
|
+
description: "Allow writes to external systems in non-interactive use.",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "--allow-delete",
|
|
30
|
+
description: "Allow deleting data in non-interactive use.",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "--allow-financial-write",
|
|
34
|
+
description: "Allow financial-record changes in non-interactive use.",
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
const mutationPrefixes = [
|
|
38
|
+
"add-",
|
|
39
|
+
"approve-",
|
|
40
|
+
"attach-",
|
|
41
|
+
"cleanup-",
|
|
42
|
+
"complete-",
|
|
43
|
+
"create-",
|
|
44
|
+
"delete-",
|
|
45
|
+
"discard-",
|
|
46
|
+
"import-",
|
|
47
|
+
"mark-",
|
|
48
|
+
"patch-",
|
|
49
|
+
"reactivate-",
|
|
50
|
+
"reconcile-",
|
|
51
|
+
"reject-",
|
|
52
|
+
"remove-",
|
|
53
|
+
"rename-",
|
|
54
|
+
"reorder-",
|
|
55
|
+
"restore-",
|
|
56
|
+
"retry-",
|
|
57
|
+
"revoke-",
|
|
58
|
+
"send-",
|
|
59
|
+
"stage-",
|
|
60
|
+
"submit-",
|
|
61
|
+
"sync-",
|
|
62
|
+
"update-",
|
|
63
|
+
"upload-",
|
|
64
|
+
"void-",
|
|
65
|
+
];
|
|
66
|
+
const emailTargets = new Set([
|
|
67
|
+
"approve-bill",
|
|
68
|
+
"approve-budget",
|
|
69
|
+
"approve-customer-invoice",
|
|
70
|
+
"approve-supplier",
|
|
71
|
+
"create-bill",
|
|
72
|
+
"create-bill-approval",
|
|
73
|
+
"create-budget-approval",
|
|
74
|
+
"create-customer-invoice",
|
|
75
|
+
"create-supplier",
|
|
76
|
+
"reject-bill",
|
|
77
|
+
"reject-budget",
|
|
78
|
+
"reject-customer-invoice",
|
|
79
|
+
"reject-supplier",
|
|
80
|
+
"send-estimate-email",
|
|
81
|
+
"send-estimate-to-contact-person",
|
|
82
|
+
"send-customer-invoice-to-contact-person",
|
|
83
|
+
"update-bill-status",
|
|
84
|
+
"update-supplier",
|
|
85
|
+
]);
|
|
86
|
+
const externalWriteTargets = new Set([
|
|
87
|
+
"approve-bill",
|
|
88
|
+
"approve-customer-invoice",
|
|
89
|
+
"approve-supplier",
|
|
90
|
+
"complete-project",
|
|
91
|
+
"create-bill",
|
|
92
|
+
"create-company",
|
|
93
|
+
"create-estimate",
|
|
94
|
+
"create-placeholder-bill",
|
|
95
|
+
"create-customer-invoice",
|
|
96
|
+
"create-item",
|
|
97
|
+
"create-item-category",
|
|
98
|
+
"create-project",
|
|
99
|
+
"create-supplier",
|
|
100
|
+
"delete-bill",
|
|
101
|
+
"delete-budget",
|
|
102
|
+
"delete-company",
|
|
103
|
+
"delete-customer-invoice",
|
|
104
|
+
"delete-item",
|
|
105
|
+
"delete-project",
|
|
106
|
+
"delete-suppliers",
|
|
107
|
+
"discard-customer-invoice",
|
|
108
|
+
"import-qbo-project",
|
|
109
|
+
"mark-budget-items-not-utilized",
|
|
110
|
+
"mark-budget-won",
|
|
111
|
+
"mark-customer-invoice-paid",
|
|
112
|
+
"reconcile-project",
|
|
113
|
+
"reject-customer-invoice",
|
|
114
|
+
"reactivate-suppliers",
|
|
115
|
+
"restore-budget-item",
|
|
116
|
+
"restore-budget-version",
|
|
117
|
+
"retry-integration-operation",
|
|
118
|
+
"sync-customer-invoice",
|
|
119
|
+
"update-bill",
|
|
120
|
+
"update-budget-status",
|
|
121
|
+
"update-company",
|
|
122
|
+
"update-item",
|
|
123
|
+
"update-item-category",
|
|
124
|
+
"update-project",
|
|
125
|
+
"update-project-status",
|
|
126
|
+
"update-supplier",
|
|
127
|
+
"void-customer-invoice",
|
|
128
|
+
]);
|
|
129
|
+
const financialWriteTargets = new Set([
|
|
130
|
+
"approve-bill",
|
|
131
|
+
"approve-customer-invoice",
|
|
132
|
+
"complete-project",
|
|
133
|
+
"create-bill",
|
|
134
|
+
"create-customer-invoice",
|
|
135
|
+
"create-estimate",
|
|
136
|
+
"create-placeholder-bill",
|
|
137
|
+
"delete-budget-commission",
|
|
138
|
+
"delete-budget-discount",
|
|
139
|
+
"delete-bill",
|
|
140
|
+
"delete-budget",
|
|
141
|
+
"delete-customer-invoice",
|
|
142
|
+
"mark-budget-items-not-utilized",
|
|
143
|
+
"mark-budget-won",
|
|
144
|
+
"mark-customer-invoice-paid",
|
|
145
|
+
"patch-bill-payment",
|
|
146
|
+
"reject-customer-invoice",
|
|
147
|
+
"restore-budget-item",
|
|
148
|
+
"restore-budget-version",
|
|
149
|
+
"sync-customer-invoice",
|
|
150
|
+
"update-bill",
|
|
151
|
+
"update-bill-payment-evidence",
|
|
152
|
+
"update-bill-status",
|
|
153
|
+
"update-budget-commission",
|
|
154
|
+
"update-budget-discount",
|
|
155
|
+
"update-budget-status",
|
|
156
|
+
"update-project-status",
|
|
157
|
+
"void-customer-invoice",
|
|
158
|
+
]);
|
|
159
|
+
const deleteTargets = new Set([
|
|
160
|
+
"cleanup-staged-bill-attachments",
|
|
161
|
+
"cleanup-staged-quotation-attachments",
|
|
162
|
+
"discard-customer-invoice",
|
|
163
|
+
"complete-project",
|
|
164
|
+
"mark-budget-items-not-utilized",
|
|
165
|
+
"remove-budget-item",
|
|
166
|
+
"restore-budget-version",
|
|
167
|
+
"update-project-status",
|
|
168
|
+
]);
|
|
169
|
+
const effectsFor = (legacyTarget) => {
|
|
170
|
+
const effects = new Set();
|
|
171
|
+
if (mutationPrefixes.some((prefix) => legacyTarget.startsWith(prefix))) {
|
|
172
|
+
effects.add("state-change");
|
|
173
|
+
}
|
|
174
|
+
if (legacyTarget.startsWith("delete-") || deleteTargets.has(legacyTarget)) {
|
|
175
|
+
effects.add("delete");
|
|
176
|
+
}
|
|
177
|
+
if (emailTargets.has(legacyTarget))
|
|
178
|
+
effects.add("email");
|
|
179
|
+
if (externalWriteTargets.has(legacyTarget))
|
|
180
|
+
effects.add("external-write");
|
|
181
|
+
if (financialWriteTargets.has(legacyTarget))
|
|
182
|
+
effects.add("financial-write");
|
|
183
|
+
return [...effects];
|
|
184
|
+
};
|
|
185
|
+
const titleCase = (value) => value
|
|
186
|
+
.split("-")
|
|
187
|
+
.map((word) => `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`)
|
|
188
|
+
.join(" ");
|
|
189
|
+
const summaryFor = (legacyTarget) => {
|
|
190
|
+
if (legacyTarget === "whoami")
|
|
191
|
+
return "Show the active API-key identity.";
|
|
192
|
+
if (legacyTarget.startsWith("list-")) {
|
|
193
|
+
return `List ${legacyTarget.slice("list-".length).replaceAll("-", " ")}.`;
|
|
194
|
+
}
|
|
195
|
+
if (legacyTarget.startsWith("get-")) {
|
|
196
|
+
return `Get ${legacyTarget.slice("get-".length).replaceAll("-", " ")}.`;
|
|
197
|
+
}
|
|
198
|
+
return `${titleCase(legacyTarget)}.`;
|
|
199
|
+
};
|
|
200
|
+
const aliasesFor = (legacyTarget, aliases) => [
|
|
201
|
+
...new Set([legacyTarget, ...aliases].flatMap((alias) => [
|
|
202
|
+
alias,
|
|
203
|
+
alias.replaceAll("-", "_"),
|
|
204
|
+
])),
|
|
205
|
+
];
|
|
206
|
+
const legacyCommand = (legacyTarget, path, aliases = []) => ({
|
|
207
|
+
path,
|
|
208
|
+
legacyAliases: aliasesFor(legacyTarget, aliases),
|
|
209
|
+
summary: summaryFor(legacyTarget),
|
|
210
|
+
options: globalCommandOptions,
|
|
211
|
+
effects: effectsFor(legacyTarget),
|
|
212
|
+
legacyTarget,
|
|
213
|
+
});
|
|
214
|
+
const registry = [
|
|
215
|
+
legacyCommand("whoami", ["auth", "whoami"]),
|
|
216
|
+
legacyCommand("list-budgets", ["budget", "list"]),
|
|
217
|
+
legacyCommand("get-budget", ["budget", "get"]),
|
|
218
|
+
legacyCommand("upload-budget-attachment", ["budget", "attachment", "upload"]),
|
|
219
|
+
legacyCommand("mark-budget-won", ["budget", "won", "mark"]),
|
|
220
|
+
legacyCommand("get-budget-items", ["budget", "item", "list"]),
|
|
221
|
+
legacyCommand("get-budget-details", ["budget", "detail", "get"]),
|
|
222
|
+
legacyCommand("get-budget-categories", ["budget", "category", "list"]),
|
|
223
|
+
legacyCommand("get-budget-versions", ["budget", "version", "list"]),
|
|
224
|
+
legacyCommand("rename-budget-version", ["budget", "version", "rename"]),
|
|
225
|
+
legacyCommand("restore-budget-version", ["budget", "version", "restore"]),
|
|
226
|
+
legacyCommand("update-budget-status", ["budget", "status", "update"]),
|
|
227
|
+
legacyCommand("create-budget-approval", ["budget", "approval", "create"]),
|
|
228
|
+
legacyCommand("create-estimate", ["budget", "estimate", "create"]),
|
|
229
|
+
legacyCommand("send-estimate-to-contact-person", ["budget", "estimate", "send"], ["send-estimate-email"]),
|
|
230
|
+
legacyCommand("create-budget", ["budget", "create"]),
|
|
231
|
+
legacyCommand("update-budget", ["budget", "update"]),
|
|
232
|
+
legacyCommand("delete-budget", ["budget", "delete"]),
|
|
233
|
+
legacyCommand("add-budget-items", ["budget", "item", "add"]),
|
|
234
|
+
legacyCommand("update-budget-item", ["budget", "item", "update"]),
|
|
235
|
+
legacyCommand("remove-budget-item", ["budget", "item", "remove"]),
|
|
236
|
+
legacyCommand("reorder-budget-items", ["budget", "item", "reorder"]),
|
|
237
|
+
legacyCommand("update-budget-item-supplier", [
|
|
238
|
+
"budget",
|
|
239
|
+
"item",
|
|
240
|
+
"supplier",
|
|
241
|
+
"update",
|
|
242
|
+
]),
|
|
243
|
+
legacyCommand("create-budget-category", ["budget", "category", "create"]),
|
|
244
|
+
legacyCommand("update-budget-category", ["budget", "category", "update"]),
|
|
245
|
+
legacyCommand("delete-budget-category", ["budget", "category", "delete"]),
|
|
246
|
+
legacyCommand("update-budget-commission", ["budget", "commission", "update"]),
|
|
247
|
+
legacyCommand("update-budget-discount", ["budget", "discount", "update"]),
|
|
248
|
+
legacyCommand("delete-budget-commission", ["budget", "commission", "delete"]),
|
|
249
|
+
legacyCommand("delete-budget-discount", ["budget", "discount", "delete"]),
|
|
250
|
+
legacyCommand("mark-budget-items-not-utilized", [
|
|
251
|
+
"budget",
|
|
252
|
+
"item",
|
|
253
|
+
"mark-not-utilized",
|
|
254
|
+
]),
|
|
255
|
+
legacyCommand("restore-budget-item", ["budget", "item", "restore"]),
|
|
256
|
+
legacyCommand("create-placeholder-bill", [
|
|
257
|
+
"budget",
|
|
258
|
+
"item",
|
|
259
|
+
"placeholder-bill",
|
|
260
|
+
"create",
|
|
261
|
+
]),
|
|
262
|
+
legacyCommand("list-bills", ["bill", "list"]),
|
|
263
|
+
legacyCommand("list-claims", ["claim", "list"]),
|
|
264
|
+
legacyCommand("create-bill-approval", ["bill", "approval", "create"]),
|
|
265
|
+
legacyCommand("create-bill", ["bill", "create"]),
|
|
266
|
+
legacyCommand("validate-bill-selection", ["bill", "selection", "validate"]),
|
|
267
|
+
legacyCommand("update-bill", ["bill", "update"]),
|
|
268
|
+
legacyCommand("update-bill-payment-evidence", [
|
|
269
|
+
"bill",
|
|
270
|
+
"payment-evidence",
|
|
271
|
+
"update",
|
|
272
|
+
]),
|
|
273
|
+
legacyCommand("delete-bill", ["bill", "delete"]),
|
|
274
|
+
legacyCommand("update-bill-status", ["bill", "status", "update"]),
|
|
275
|
+
legacyCommand("patch-bill-payment", ["bill", "payment", "patch"]),
|
|
276
|
+
legacyCommand("patch-bill-invoice-number", [
|
|
277
|
+
"bill",
|
|
278
|
+
"invoice-number",
|
|
279
|
+
"patch",
|
|
280
|
+
]),
|
|
281
|
+
legacyCommand("get-bill-attachments", ["bill", "attachment", "list"]),
|
|
282
|
+
legacyCommand("stage-bill-attachment", ["bill", "attachment", "stage"]),
|
|
283
|
+
legacyCommand("cleanup-staged-bill-attachments", [
|
|
284
|
+
"bill",
|
|
285
|
+
"attachment",
|
|
286
|
+
"staged",
|
|
287
|
+
"cleanup",
|
|
288
|
+
]),
|
|
289
|
+
legacyCommand("upload-bill-attachment", ["bill", "attachment", "upload"], ["attach-bill-attachment"]),
|
|
290
|
+
legacyCommand("attach-bill-payment-receipt", [
|
|
291
|
+
"bill",
|
|
292
|
+
"payment-receipt",
|
|
293
|
+
"attach",
|
|
294
|
+
]),
|
|
295
|
+
legacyCommand("attach-bill-documents", ["bill", "document", "attach"]),
|
|
296
|
+
legacyCommand("get-bill-details", ["bill", "get"]),
|
|
297
|
+
legacyCommand("list-quotations", ["quotation", "list"]),
|
|
298
|
+
legacyCommand("get-quotation", ["quotation", "get"]),
|
|
299
|
+
legacyCommand("upload-quotation-attachment", [
|
|
300
|
+
"quotation",
|
|
301
|
+
"attachment",
|
|
302
|
+
"upload",
|
|
303
|
+
]),
|
|
304
|
+
legacyCommand("cleanup-staged-quotation-attachments", [
|
|
305
|
+
"quotation",
|
|
306
|
+
"attachment",
|
|
307
|
+
"staged",
|
|
308
|
+
"cleanup",
|
|
309
|
+
]),
|
|
310
|
+
legacyCommand("create-quotation", ["quotation", "create"]),
|
|
311
|
+
legacyCommand("update-quotation", ["quotation", "update"]),
|
|
312
|
+
legacyCommand("delete-quotation", ["quotation", "delete"]),
|
|
313
|
+
legacyCommand("submit-quotation", ["quotation", "submit"]),
|
|
314
|
+
legacyCommand("approve-quotation", ["quotation", "approve"]),
|
|
315
|
+
legacyCommand("reject-quotation", ["quotation", "reject"]),
|
|
316
|
+
legacyCommand("download-quotation-pdf", ["quotation", "pdf", "download"]),
|
|
317
|
+
legacyCommand("check-customer-invoice-readiness", [
|
|
318
|
+
"customer-invoice",
|
|
319
|
+
"readiness",
|
|
320
|
+
"check",
|
|
321
|
+
]),
|
|
322
|
+
legacyCommand("list-eligible-customer-invoice-budgets", [
|
|
323
|
+
"customer-invoice",
|
|
324
|
+
"eligible-budget",
|
|
325
|
+
"list",
|
|
326
|
+
]),
|
|
327
|
+
legacyCommand("list-customer-invoices", ["customer-invoice", "list"]),
|
|
328
|
+
legacyCommand("get-customer-invoice", ["customer-invoice", "get"]),
|
|
329
|
+
legacyCommand("get-customer-invoice-email-context", [
|
|
330
|
+
"customer-invoice",
|
|
331
|
+
"email-context",
|
|
332
|
+
"get",
|
|
333
|
+
]),
|
|
334
|
+
legacyCommand("create-customer-invoice", ["customer-invoice", "create"]),
|
|
335
|
+
legacyCommand("discard-customer-invoice", ["customer-invoice", "discard"]),
|
|
336
|
+
legacyCommand("delete-customer-invoice", ["customer-invoice", "delete"]),
|
|
337
|
+
legacyCommand("void-customer-invoice", ["customer-invoice", "void"]),
|
|
338
|
+
legacyCommand("approve-customer-invoice", ["customer-invoice", "approve"]),
|
|
339
|
+
legacyCommand("reject-customer-invoice", ["customer-invoice", "reject"]),
|
|
340
|
+
legacyCommand("send-customer-invoice-to-contact-person", [
|
|
341
|
+
"customer-invoice",
|
|
342
|
+
"send",
|
|
343
|
+
]),
|
|
344
|
+
legacyCommand("mark-customer-invoice-paid", [
|
|
345
|
+
"customer-invoice",
|
|
346
|
+
"payment",
|
|
347
|
+
"mark-paid",
|
|
348
|
+
]),
|
|
349
|
+
legacyCommand("download-customer-invoice-payment-proof", [
|
|
350
|
+
"customer-invoice",
|
|
351
|
+
"payment-proof",
|
|
352
|
+
"download",
|
|
353
|
+
]),
|
|
354
|
+
legacyCommand("download-customer-invoice-pdf", [
|
|
355
|
+
"customer-invoice",
|
|
356
|
+
"pdf",
|
|
357
|
+
"download",
|
|
358
|
+
]),
|
|
359
|
+
legacyCommand("sync-customer-invoice", ["customer-invoice", "sync"]),
|
|
360
|
+
legacyCommand("list-approvals", ["approval", "list"], ["get-pending-approvals"]),
|
|
361
|
+
legacyCommand("approve-bill", ["bill", "approve"]),
|
|
362
|
+
legacyCommand("reject-bill", ["bill", "reject"]),
|
|
363
|
+
legacyCommand("approve-budget", ["budget", "approve"]),
|
|
364
|
+
legacyCommand("reject-budget", ["budget", "reject"]),
|
|
365
|
+
legacyCommand("approve-supplier", ["supplier", "approve"]),
|
|
366
|
+
legacyCommand("reject-supplier", ["supplier", "reject"]),
|
|
367
|
+
legacyCommand("get-supplier-details", ["supplier", "detail", "get"]),
|
|
368
|
+
legacyCommand("list-companies", ["company", "list"]),
|
|
369
|
+
legacyCommand("get-company", ["company", "get"]),
|
|
370
|
+
legacyCommand("create-company", ["company", "create"]),
|
|
371
|
+
legacyCommand("update-company", ["company", "update"]),
|
|
372
|
+
legacyCommand("delete-company", ["company", "delete"]),
|
|
373
|
+
legacyCommand("list-projects", ["project", "list"]),
|
|
374
|
+
legacyCommand("get-project", ["project", "get"]),
|
|
375
|
+
legacyCommand("create-project", ["project", "create"]),
|
|
376
|
+
legacyCommand("update-project", ["project", "update"]),
|
|
377
|
+
legacyCommand("delete-project", ["project", "delete"]),
|
|
378
|
+
legacyCommand("check-project-reconciliation", [
|
|
379
|
+
"project",
|
|
380
|
+
"reconciliation",
|
|
381
|
+
"check",
|
|
382
|
+
]),
|
|
383
|
+
legacyCommand("reconcile-project", ["project", "reconcile"]),
|
|
384
|
+
legacyCommand("complete-project", ["project", "complete"]),
|
|
385
|
+
legacyCommand("import-qbo-project", ["project", "quickbooks", "import"]),
|
|
386
|
+
legacyCommand("update-project-status", ["project", "status", "update"]),
|
|
387
|
+
legacyCommand("list-contacts", ["contact", "list"]),
|
|
388
|
+
legacyCommand("create-contact-person", ["contact", "create"]),
|
|
389
|
+
legacyCommand("update-contact-person", ["contact", "update"]),
|
|
390
|
+
legacyCommand("list-suppliers", ["supplier", "list"]),
|
|
391
|
+
legacyCommand("create-supplier", ["supplier", "create"]),
|
|
392
|
+
legacyCommand("update-supplier", ["supplier", "update"]),
|
|
393
|
+
legacyCommand("delete-suppliers", ["supplier", "delete"]),
|
|
394
|
+
legacyCommand("reactivate-suppliers", ["supplier", "reactivate"]),
|
|
395
|
+
legacyCommand("create-certification", [
|
|
396
|
+
"supplier",
|
|
397
|
+
"certification",
|
|
398
|
+
"create",
|
|
399
|
+
]),
|
|
400
|
+
legacyCommand("create-payment-method", [
|
|
401
|
+
"supplier",
|
|
402
|
+
"payment-method",
|
|
403
|
+
"create",
|
|
404
|
+
]),
|
|
405
|
+
legacyCommand("create-supplier-role", ["supplier", "role", "create"]),
|
|
406
|
+
legacyCommand("create-supplier-tag", ["supplier", "tag", "create"]),
|
|
407
|
+
legacyCommand("get-supplier-analytics", ["supplier", "analytics", "get"]),
|
|
408
|
+
legacyCommand("list-items", ["item", "list"]),
|
|
409
|
+
legacyCommand("get-item", ["item", "get"]),
|
|
410
|
+
legacyCommand("create-item", ["item", "create"]),
|
|
411
|
+
legacyCommand("update-item", ["item", "update"]),
|
|
412
|
+
legacyCommand("delete-item", ["item", "delete"]),
|
|
413
|
+
legacyCommand("list-item-categories", ["item", "category", "list"]),
|
|
414
|
+
legacyCommand("create-item-category", ["item", "category", "create"]),
|
|
415
|
+
legacyCommand("update-item-category", ["item", "category", "update"]),
|
|
416
|
+
legacyCommand("delete-item-categories", ["item", "category", "delete"]),
|
|
417
|
+
legacyCommand("list-users", ["user", "list"]),
|
|
418
|
+
legacyCommand("create-user", ["user", "create"]),
|
|
419
|
+
legacyCommand("create-api-key", ["user", "api-key", "create"]),
|
|
420
|
+
legacyCommand("list-api-keys", ["user", "api-key", "list"]),
|
|
421
|
+
legacyCommand("revoke-api-key", ["user", "api-key", "revoke"]),
|
|
422
|
+
legacyCommand("get-user-performance", ["user", "performance", "get"]),
|
|
423
|
+
legacyCommand("get-dashboard", ["dashboard", "get"]),
|
|
424
|
+
legacyCommand("get-monthly-metrics", ["dashboard", "monthly-metrics", "get"]),
|
|
425
|
+
legacyCommand("get-system-overview", ["dashboard", "system-overview", "get"]),
|
|
426
|
+
legacyCommand("get-estimate-performance", [
|
|
427
|
+
"dashboard",
|
|
428
|
+
"estimate-performance",
|
|
429
|
+
"get",
|
|
430
|
+
]),
|
|
431
|
+
legacyCommand("get-financial-overview", [
|
|
432
|
+
"dashboard",
|
|
433
|
+
"financial-overview",
|
|
434
|
+
"get",
|
|
435
|
+
]),
|
|
436
|
+
legacyCommand("get-recent-errors", ["error", "recent", "list"]),
|
|
437
|
+
legacyCommand("get-error-metrics", ["error", "metrics", "get"]),
|
|
438
|
+
legacyCommand("list-integration-operations", [
|
|
439
|
+
"integration",
|
|
440
|
+
"operation",
|
|
441
|
+
"list",
|
|
442
|
+
]),
|
|
443
|
+
legacyCommand("retry-integration-operation", [
|
|
444
|
+
"integration",
|
|
445
|
+
"operation",
|
|
446
|
+
"retry",
|
|
447
|
+
]),
|
|
448
|
+
legacyCommand("get-approved-budgets", ["budget", "approved", "list"]),
|
|
449
|
+
legacyCommand("get-budget-category-benchmarks", [
|
|
450
|
+
"budget",
|
|
451
|
+
"category",
|
|
452
|
+
"benchmark",
|
|
453
|
+
"get",
|
|
454
|
+
]),
|
|
455
|
+
legacyCommand("get-item-pricing-history", [
|
|
456
|
+
"item",
|
|
457
|
+
"pricing-history",
|
|
458
|
+
"list",
|
|
459
|
+
]),
|
|
460
|
+
legacyCommand("get-supplier-pricing-history", [
|
|
461
|
+
"supplier",
|
|
462
|
+
"pricing-history",
|
|
463
|
+
"list",
|
|
464
|
+
]),
|
|
465
|
+
];
|
|
466
|
+
const normalizeToken = (value) => value.replaceAll("_", "-");
|
|
467
|
+
const normalizedPath = (path) => path.map(normalizeToken);
|
|
468
|
+
const assertRegistry = (commands) => {
|
|
469
|
+
const paths = new Set();
|
|
470
|
+
const aliases = new Set();
|
|
471
|
+
for (const command of commands) {
|
|
472
|
+
const path = command.path.join(" ");
|
|
473
|
+
if (paths.has(path))
|
|
474
|
+
throw new Error(`Duplicate canonical command path: ${path}`);
|
|
475
|
+
paths.add(path);
|
|
476
|
+
const commandAliases = new Set();
|
|
477
|
+
for (const alias of command.legacyAliases) {
|
|
478
|
+
const normalizedAlias = normalizeToken(alias);
|
|
479
|
+
if (commandAliases.has(normalizedAlias))
|
|
480
|
+
continue;
|
|
481
|
+
commandAliases.add(normalizedAlias);
|
|
482
|
+
if (aliases.has(normalizedAlias)) {
|
|
483
|
+
throw new Error(`Duplicate legacy command alias: ${alias}`);
|
|
484
|
+
}
|
|
485
|
+
aliases.add(normalizedAlias);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
assertRegistry(registry);
|
|
490
|
+
export const commandRegistry = registry;
|
|
491
|
+
export const resolveCommand = (argv) => {
|
|
492
|
+
const normalizedArgv = argv.map(normalizeToken);
|
|
493
|
+
const canonical = [...commandRegistry]
|
|
494
|
+
.sort((left, right) => right.path.length - left.path.length)
|
|
495
|
+
.find((command) => {
|
|
496
|
+
const path = normalizedPath(command.path);
|
|
497
|
+
return path.every((segment, index) => normalizedArgv[index] === segment);
|
|
498
|
+
});
|
|
499
|
+
if (canonical) {
|
|
500
|
+
return {
|
|
501
|
+
command: canonical,
|
|
502
|
+
argv: argv.slice(canonical.path.length),
|
|
503
|
+
invokedAs: argv.slice(0, canonical.path.length),
|
|
504
|
+
isLegacyAlias: false,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
const rawAlias = argv[0];
|
|
508
|
+
if (!rawAlias)
|
|
509
|
+
return undefined;
|
|
510
|
+
const normalizedAlias = normalizeToken(rawAlias);
|
|
511
|
+
const legacy = commandRegistry.find((command) => command.legacyAliases.some((alias) => normalizeToken(alias) === normalizedAlias));
|
|
512
|
+
if (!legacy)
|
|
513
|
+
return undefined;
|
|
514
|
+
return {
|
|
515
|
+
command: legacy,
|
|
516
|
+
argv: argv.slice(1),
|
|
517
|
+
invokedAs: [rawAlias],
|
|
518
|
+
isLegacyAlias: true,
|
|
519
|
+
};
|
|
520
|
+
};
|
|
521
|
+
export const createCommandManifest = () => commandRegistry.map((command) => ({
|
|
522
|
+
path: command.path,
|
|
523
|
+
legacyAliases: command.legacyAliases,
|
|
524
|
+
summary: command.summary,
|
|
525
|
+
globalOptions: command.options,
|
|
526
|
+
argumentMode: "legacyTarget" in command ? "legacy-passthrough" : "typed",
|
|
527
|
+
effects: command.effects,
|
|
528
|
+
...("legacyTarget" in command
|
|
529
|
+
? { legacyTarget: command.legacyTarget }
|
|
530
|
+
: {}),
|
|
531
|
+
}));
|
|
532
|
+
export const createHumanHelp = () => {
|
|
533
|
+
const lines = commandRegistry.map((command) => {
|
|
534
|
+
const canonical = `bb ${command.path.join(" ")}`;
|
|
535
|
+
const aliases = command.legacyAliases
|
|
536
|
+
.filter((alias) => alias.includes("-"))
|
|
537
|
+
.map((alias) => `bb ${alias}`)
|
|
538
|
+
.join(", ");
|
|
539
|
+
return ` ${canonical}\n ${command.summary}${aliases ? ` Legacy: ${aliases}.` : ""}`;
|
|
540
|
+
});
|
|
541
|
+
return [
|
|
542
|
+
"bb — Budget Builder CLI",
|
|
543
|
+
"",
|
|
544
|
+
"Usage: bb <resource> <operation> [options] [args]",
|
|
545
|
+
"",
|
|
546
|
+
"Global options",
|
|
547
|
+
...globalCommandOptions.map((option) => ` ${option.name}\n ${option.description}`),
|
|
548
|
+
"",
|
|
549
|
+
"Canonical commands",
|
|
550
|
+
...lines,
|
|
551
|
+
"",
|
|
552
|
+
"Use `bb completion <shell>` to generate shell completion scripts.",
|
|
553
|
+
].join("\n");
|
|
554
|
+
};
|
|
555
|
+
const completionWords = () => {
|
|
556
|
+
const words = new Set();
|
|
557
|
+
for (const command of commandRegistry) {
|
|
558
|
+
for (const segment of command.path)
|
|
559
|
+
words.add(segment);
|
|
560
|
+
for (const alias of command.legacyAliases)
|
|
561
|
+
words.add(alias);
|
|
562
|
+
}
|
|
563
|
+
words.add("help");
|
|
564
|
+
words.add("version");
|
|
565
|
+
words.add("completion");
|
|
566
|
+
return [...words].sort();
|
|
567
|
+
};
|
|
568
|
+
export const createCompletionScript = (shell) => {
|
|
569
|
+
const words = completionWords().join(" ");
|
|
570
|
+
switch (shell) {
|
|
571
|
+
case "bash":
|
|
572
|
+
return `# bash completion for bb\n_bb() {\n COMPREPLY=( $(compgen -W '${words}' -- "${"$"}{COMP_WORDS[COMP_CWORD]}") )\n}\ncomplete -F _bb bb\n`;
|
|
573
|
+
case "zsh":
|
|
574
|
+
return `#compdef bb\n_bb() {\n local -a commands\n commands=(${words})\n _describe 'bb command' commands\n}\ncompdef _bb bb\n`;
|
|
575
|
+
case "fish":
|
|
576
|
+
return completionWords()
|
|
577
|
+
.map((word) => `complete -c bb -f -a '${word}'`)
|
|
578
|
+
.join("\n")
|
|
579
|
+
.concat("\n");
|
|
580
|
+
case "powershell":
|
|
581
|
+
return `Register-ArgumentCompleter -Native -CommandName bb -ScriptBlock {\n param($wordToComplete)\n '${words}'.Split(' ') | Where-Object { $_ -like "$wordToComplete*" }\n}\n`;
|
|
582
|
+
}
|
|
583
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { CliRuntimeError } from "./error.js";
|
|
2
|
+
const effectLabels = {
|
|
3
|
+
"state-change": "changes Budget Builder state",
|
|
4
|
+
email: "sends email",
|
|
5
|
+
"external-write": "writes to an external system",
|
|
6
|
+
delete: "deletes data",
|
|
7
|
+
"financial-write": "creates or changes financial records",
|
|
8
|
+
};
|
|
9
|
+
const isEffectAllowed = (effect, permissions) => {
|
|
10
|
+
switch (effect) {
|
|
11
|
+
case "state-change":
|
|
12
|
+
return permissions.allowStateChange === true;
|
|
13
|
+
case "email":
|
|
14
|
+
return permissions.allowEmail === true;
|
|
15
|
+
case "external-write":
|
|
16
|
+
return permissions.allowExternalWrite === true;
|
|
17
|
+
case "delete":
|
|
18
|
+
return permissions.allowDelete === true;
|
|
19
|
+
case "financial-write":
|
|
20
|
+
return permissions.allowFinancialWrite === true;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const uniqueEffects = (effects) => [...new Set(effects)];
|
|
24
|
+
export const getRequiredEffectPermissions = (effects, permissions) => uniqueEffects(effects).filter((effect) => !isEffectAllowed(effect, permissions));
|
|
25
|
+
export const formatConfirmationPreview = (request) => [
|
|
26
|
+
"Budget Builder workflow preview",
|
|
27
|
+
`Action: ${request.action}`,
|
|
28
|
+
`Target: ${request.target?.trim() || "not specified"}`,
|
|
29
|
+
`Effects: ${uniqueEffects(request.effects)
|
|
30
|
+
.map((effect) => effectLabels[effect])
|
|
31
|
+
.join("; ") || "none"}`,
|
|
32
|
+
...(request.details === undefined ? [] : [`Details: ${request.details}`]),
|
|
33
|
+
].join("\n");
|
|
34
|
+
export const confirmSensitiveCommand = async (runtime, request) => {
|
|
35
|
+
const requiredPermissions = getRequiredEffectPermissions(request.effects, request.permissions);
|
|
36
|
+
if (request.effects.length === 0) {
|
|
37
|
+
return { confirmed: true, requiredPermissions };
|
|
38
|
+
}
|
|
39
|
+
const target = request.target?.trim();
|
|
40
|
+
if (!runtime.isTTY) {
|
|
41
|
+
if (target === undefined) {
|
|
42
|
+
throw new CliRuntimeError("CONFIRMATION_REQUIRED", "Non-interactive sensitive commands require an explicit target.", {
|
|
43
|
+
details: {
|
|
44
|
+
action: request.action,
|
|
45
|
+
effects: requiredPermissions,
|
|
46
|
+
missingFlags: requiredPermissions.map((effect) => `--allow-${effect}`),
|
|
47
|
+
target: "required",
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (requiredPermissions.length === 0) {
|
|
52
|
+
return { confirmed: true, requiredPermissions };
|
|
53
|
+
}
|
|
54
|
+
const missing = requiredPermissions.map((effect) => `--allow-${effect}`);
|
|
55
|
+
throw new CliRuntimeError("CONFIRMATION_REQUIRED", "Non-interactive sensitive commands require an explicit target and all applicable allow flags.", {
|
|
56
|
+
details: {
|
|
57
|
+
action: request.action,
|
|
58
|
+
effects: requiredPermissions,
|
|
59
|
+
missingFlags: missing,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (requiredPermissions.length === 0) {
|
|
64
|
+
return { confirmed: true, requiredPermissions };
|
|
65
|
+
}
|
|
66
|
+
const preview = formatConfirmationPreview(request);
|
|
67
|
+
runtime.stderr.write(`${preview}\n`);
|
|
68
|
+
if (runtime.prompt === undefined) {
|
|
69
|
+
throw new CliRuntimeError("CONFIRMATION_REQUIRED", "This command needs an interactive confirmation prompt.");
|
|
70
|
+
}
|
|
71
|
+
const answer = await runtime.prompt("Type CONFIRM to continue: ");
|
|
72
|
+
if (answer !== "CONFIRM") {
|
|
73
|
+
throw new CliRuntimeError("ABORTED", "Command aborted by user.");
|
|
74
|
+
}
|
|
75
|
+
return { confirmed: true, preview, requiredPermissions };
|
|
76
|
+
};
|