@paybond/kit 0.9.8 → 0.10.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.
Files changed (58) hide show
  1. package/dist/cli/audit-export.d.ts +7 -0
  2. package/dist/cli/audit-export.js +120 -0
  3. package/dist/cli/automation.d.ts +25 -0
  4. package/dist/cli/automation.js +297 -0
  5. package/dist/cli/body.d.ts +7 -0
  6. package/dist/cli/body.js +22 -0
  7. package/dist/cli/color.d.ts +15 -0
  8. package/dist/cli/color.js +39 -0
  9. package/dist/cli/command-spec.d.ts +12 -0
  10. package/dist/cli/command-spec.js +330 -0
  11. package/dist/cli/commands/discovery.d.ts +8 -0
  12. package/dist/cli/commands/discovery.js +194 -0
  13. package/dist/cli/commands/setup.d.ts +15 -0
  14. package/dist/cli/commands/setup.js +397 -0
  15. package/dist/cli/commands/workflows.d.ts +7 -0
  16. package/dist/cli/commands/workflows.js +209 -0
  17. package/dist/cli/config.d.ts +14 -0
  18. package/dist/cli/config.js +96 -0
  19. package/dist/cli/context.d.ts +22 -0
  20. package/dist/cli/context.js +109 -0
  21. package/dist/cli/credentials.d.ts +21 -0
  22. package/dist/cli/credentials.js +141 -0
  23. package/dist/cli/doctor-agent.d.ts +15 -0
  24. package/dist/cli/doctor-agent.js +311 -0
  25. package/dist/cli/envelope.d.ts +14 -0
  26. package/dist/cli/envelope.js +46 -0
  27. package/dist/cli/globals.d.ts +22 -0
  28. package/dist/cli/globals.js +238 -0
  29. package/dist/cli/help.d.ts +3 -0
  30. package/dist/cli/help.js +51 -0
  31. package/dist/cli/mcp-install.d.ts +41 -0
  32. package/dist/cli/mcp-install.js +95 -0
  33. package/dist/cli/mcp-policy.d.ts +23 -0
  34. package/dist/cli/mcp-policy.js +104 -0
  35. package/dist/cli/mcp-verify-config.d.ts +37 -0
  36. package/dist/cli/mcp-verify-config.js +174 -0
  37. package/dist/cli/redact.d.ts +4 -0
  38. package/dist/cli/redact.js +67 -0
  39. package/dist/cli/request-id.d.ts +2 -0
  40. package/dist/cli/request-id.js +5 -0
  41. package/dist/cli/router.d.ts +2 -0
  42. package/dist/cli/router.js +275 -0
  43. package/dist/cli/suggest.d.ts +4 -0
  44. package/dist/cli/suggest.js +58 -0
  45. package/dist/cli/support-diagnostics.d.ts +19 -0
  46. package/dist/cli/support-diagnostics.js +47 -0
  47. package/dist/cli/types.d.ts +72 -0
  48. package/dist/cli/types.js +60 -0
  49. package/dist/cli/ux.d.ts +8 -0
  50. package/dist/cli/ux.js +164 -0
  51. package/dist/cli.d.ts +2 -0
  52. package/dist/cli.js +38 -0
  53. package/dist/init.js +9 -14
  54. package/dist/login.d.ts +14 -1
  55. package/dist/login.js +123 -63
  56. package/dist/mcp-server.d.ts +4 -0
  57. package/dist/mcp-server.js +204 -76
  58. package/package.json +5 -2
@@ -0,0 +1,397 @@
1
+ import { mkdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { readJsonBody } from "../automation.js";
4
+ import { listConfigEntries, resolveConfigValue, setConfigValue, unsetConfigValue } from "../config.js";
5
+ import { commandPath, withGateway } from "../context.js";
6
+ import { assertApiKeyShape, resolveApiKey, resolvedDefaultsForDoctor } from "../credentials.js";
7
+ import { packageVersion, runAgentMcpChecks } from "../doctor-agent.js";
8
+ import { consumeBooleanFlag, consumeFlag } from "../globals.js";
9
+ import { maskApiKey, redactConfigValue } from "../redact.js";
10
+ import { mergeMcpToolPolicy, parseMcpToolAllowlist, parseMcpToolPolicy, } from "../mcp-policy.js";
11
+ import { parseMcpInstallFormat, parseMcpInstallHost, parseMcpInstallScope, planMcpInstall, } from "../mcp-install.js";
12
+ import { verifyMcpInstallPlan } from "../mcp-verify-config.js";
13
+ import { buildSupportDiagnostics, formatSupportDiagnosticsTable } from "../support-diagnostics.js";
14
+ import { CliError } from "../types.js";
15
+ import { main as runInitMain } from "../../init.js";
16
+ import { parseArgs as parseLoginArgs, runLogin } from "../../login.js";
17
+ import { main as runMcpServerMain } from "../../mcp-server.js";
18
+ import { PaybondMCPServer } from "../../mcp-server.js";
19
+ async function readJsonFile(filePath) {
20
+ return readJsonBody(filePath, process.stdin);
21
+ }
22
+ function loginResultData(result) {
23
+ const data = {
24
+ env_file: result.envPath,
25
+ key_masked: result.keyMasked,
26
+ key_written: result.keyWritten,
27
+ environment: result.environment,
28
+ tenant_id: result.tenantId,
29
+ tenant_uuid: result.tenantUuid,
30
+ verification_uri: result.verificationUri,
31
+ user_code: result.userCode,
32
+ };
33
+ if (result.expiresAt) {
34
+ data.expires_at = result.expiresAt;
35
+ }
36
+ return data;
37
+ }
38
+ export async function handleLogin(ctx, argv) {
39
+ if (argv[0] === "--help" || argv[0] === "-h") {
40
+ throw new CliError("help", { category: "usage", code: "cli.help" });
41
+ }
42
+ let parsed;
43
+ try {
44
+ parsed = parseLoginArgs(["login", ...argv]);
45
+ }
46
+ catch (err) {
47
+ throw new CliError(err instanceof Error ? err.message : String(err), {
48
+ category: "validation",
49
+ code: "cli.login.rejected",
50
+ });
51
+ }
52
+ if (parsed === "help") {
53
+ throw new CliError("help", { category: "usage", code: "cli.help" });
54
+ }
55
+ const options = {
56
+ ...parsed,
57
+ gateway: ctx.globals.gateway,
58
+ envFile: ctx.globals.envFile,
59
+ noOpen: ctx.globals.noOpen,
60
+ };
61
+ let result;
62
+ try {
63
+ result = await runLogin(options, {
64
+ cwd: ctx.cwd,
65
+ fetch: ctx.fetch,
66
+ humanOutput: ctx.globals.format !== "json",
67
+ stdout: {
68
+ write(chunk) {
69
+ ctx.stdout.write(chunk);
70
+ return true;
71
+ },
72
+ },
73
+ stderr: {
74
+ write(chunk) {
75
+ ctx.stderr.write(chunk);
76
+ return true;
77
+ },
78
+ },
79
+ sleep: ctx.deps.sleep,
80
+ openBrowser: ctx.deps.openBrowser,
81
+ now: ctx.deps.now,
82
+ });
83
+ }
84
+ catch (err) {
85
+ throw new CliError(err instanceof Error ? err.message : String(err), {
86
+ category: "validation",
87
+ code: "cli.login.failed",
88
+ });
89
+ }
90
+ return { data: loginResultData(result) };
91
+ }
92
+ export async function handleInitGuardrail(ctx, argv) {
93
+ const code = await runInitMain(argv);
94
+ if (code !== 0) {
95
+ throw new CliError("init guardrail failed", { category: "validation", code: "cli.init.failed", exitCode: code });
96
+ }
97
+ const outFlag = consumeFlag(argv, "--out");
98
+ const frameworkFlag = consumeFlag(argv, "--framework");
99
+ const presetFlag = consumeFlag(argv, "--preset");
100
+ return {
101
+ data: {
102
+ out: outFlag.value ?? "paybond-paid-tool-guard.ts",
103
+ preset: presetFlag.value ?? "paid-tool-guard",
104
+ framework: frameworkFlag.value ?? "provider-agnostic",
105
+ bytes_written: true,
106
+ },
107
+ };
108
+ }
109
+ export async function handleMcpServe(ctx, argv) {
110
+ if (argv.length > 0 && argv[0] !== "--help" && argv[0] !== "-h") {
111
+ throw new CliError(`unexpected arguments: ${argv.join(" ")}`, { category: "usage", code: "cli.usage.unexpected_args" });
112
+ }
113
+ ctx.stderr.write("Starting Paybond MCP stdio server (stdout is reserved for MCP JSON-RPC).\n");
114
+ const code = runMcpServerMain([]);
115
+ if (code !== 0) {
116
+ throw new CliError("mcp serve failed", { category: "internal", code: "cli.mcp.serve_failed", exitCode: code });
117
+ }
118
+ return { data: { started: true } };
119
+ }
120
+ export async function handleMcpTools(ctx) {
121
+ const server = new PaybondMCPServer({ gatewayBaseUrl: ctx.globals.gateway, apiKey: "paybond_sk_sandbox_redacted_redacted_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" });
122
+ const tools = server.listTools().map((tool) => ({
123
+ name: tool.name,
124
+ title: tool.title ?? tool.name,
125
+ description: tool.description,
126
+ }));
127
+ return { data: { tools } };
128
+ }
129
+ export async function handleMcpInstall(ctx, argv) {
130
+ const hostFlag = consumeFlag(argv, "--host");
131
+ const formatFlag = consumeFlag(argv, "--format");
132
+ const scopeFlag = consumeFlag(argv, "--scope");
133
+ const outFlag = consumeFlag(argv, "--out");
134
+ const envFileFlag = consumeFlag(argv, "--env-file");
135
+ const toolPolicyFlag = consumeFlag(argv, "--tool-policy");
136
+ const toolAllowlistFlag = consumeFlag(argv, "--tool-allowlist");
137
+ let format;
138
+ let scope;
139
+ let installHost;
140
+ let toolPolicy;
141
+ try {
142
+ installHost = parseMcpInstallHost(hostFlag.value);
143
+ format = parseMcpInstallFormat(formatFlag.value, installHost);
144
+ scope = parseMcpInstallScope(scopeFlag.value);
145
+ toolPolicy = mergeMcpToolPolicy(parseMcpToolPolicy(toolPolicyFlag.value), parseMcpToolAllowlist(toolAllowlistFlag.value));
146
+ }
147
+ catch (err) {
148
+ throw new CliError(err instanceof Error ? err.message : String(err), {
149
+ category: "usage",
150
+ code: "cli.usage.invalid_mcp_install",
151
+ });
152
+ }
153
+ const plan = planMcpInstall({
154
+ host: installHost,
155
+ scope,
156
+ format,
157
+ envFile: envFileFlag.value ?? ctx.globals.envFile,
158
+ out: outFlag.value,
159
+ cwd: ctx.cwd,
160
+ home: process.env.HOME ?? process.env.USERPROFILE ?? ctx.cwd,
161
+ toolPolicy: toolPolicy.policy ? toolPolicy : null,
162
+ });
163
+ if (plan.printed) {
164
+ if (ctx.globals.format !== "json") {
165
+ ctx.stdout.write(plan.payload);
166
+ }
167
+ }
168
+ else {
169
+ const configPath = plan.configPath;
170
+ await mkdir(path.dirname(configPath), { recursive: true });
171
+ const { writeAtomicFileAsync } = await import("../automation.js");
172
+ await writeAtomicFileAsync(configPath, plan.payload, 0o600);
173
+ }
174
+ const data = {
175
+ host: plan.host,
176
+ scope: plan.scope,
177
+ format: plan.format,
178
+ config_path: plan.configPath,
179
+ server_command: plan.serverCommand.join(" "),
180
+ printed: plan.printed,
181
+ };
182
+ if (plan.toolPolicy?.policy) {
183
+ data.tool_policy = plan.toolPolicy.policy;
184
+ if (plan.toolPolicy.allowlist.length > 0) {
185
+ data.tool_allowlist = [...plan.toolPolicy.allowlist];
186
+ }
187
+ }
188
+ if (plan.printed && ctx.globals.format === "json") {
189
+ data.payload = plan.payload;
190
+ }
191
+ return { data };
192
+ }
193
+ export async function handleMcpVerifyConfig(ctx, argv) {
194
+ const hostFlag = consumeFlag(argv, "--host");
195
+ const formatFlag = consumeFlag(argv, "--format");
196
+ const envFileFlag = consumeFlag(argv, "--env-file");
197
+ const configFlag = consumeFlag(argv, "--config");
198
+ let installHost;
199
+ let format;
200
+ try {
201
+ installHost = parseMcpInstallHost(hostFlag.value);
202
+ format = parseMcpInstallFormat(formatFlag.value, installHost);
203
+ }
204
+ catch (err) {
205
+ throw new CliError(err instanceof Error ? err.message : String(err), {
206
+ category: "usage",
207
+ code: "cli.usage.invalid_mcp_verify_config",
208
+ });
209
+ }
210
+ const result = await verifyMcpInstallPlan({
211
+ host: installHost,
212
+ format,
213
+ envFile: envFileFlag.value ?? ctx.globals.envFile,
214
+ cwd: ctx.cwd,
215
+ home: process.env.HOME ?? process.env.USERPROFILE ?? ctx.cwd,
216
+ configPath: configFlag.value,
217
+ });
218
+ return {
219
+ data: {
220
+ ok: result.ok,
221
+ host: result.host,
222
+ source: result.source,
223
+ config_path: result.configPath,
224
+ message: result.message,
225
+ issues: result.issues,
226
+ tool_policy: result.toolPolicy?.policy ?? null,
227
+ },
228
+ };
229
+ }
230
+ export async function handleDoctor(ctx, argv) {
231
+ const agentFlag = consumeBooleanFlag(argv, "--agent");
232
+ const hostFlag = consumeFlag(agentFlag.rest, "--host");
233
+ const checks = [];
234
+ const defaults = resolvedDefaultsForDoctor(ctx.globals);
235
+ checks.push({
236
+ name: "runtime",
237
+ ok: true,
238
+ message: `node ${process.version}`,
239
+ });
240
+ checks.push({
241
+ name: "package",
242
+ ok: true,
243
+ message: "@paybond/kit",
244
+ details: { version: packageVersion() },
245
+ });
246
+ const envPath = path.isAbsolute(defaults.envFile)
247
+ ? path.resolve(defaults.envFile)
248
+ : path.resolve(ctx.cwd, defaults.envFile);
249
+ try {
250
+ await readFile(envPath, "utf8");
251
+ checks.push({ name: "env_file", ok: true, message: envPath });
252
+ }
253
+ catch {
254
+ checks.push({ name: "env_file", ok: false, message: `env file not found: ${envPath}` });
255
+ }
256
+ let apiKey = "";
257
+ try {
258
+ apiKey = await resolveApiKey(ctx.globals, ctx.cwd);
259
+ assertApiKeyShape(apiKey);
260
+ checks.push({ name: "key_shape", ok: true, message: maskApiKey(apiKey) });
261
+ }
262
+ catch (err) {
263
+ const message = err instanceof Error ? err.message : String(err);
264
+ checks.push({ name: "key_shape", ok: false, message });
265
+ }
266
+ if (apiKey) {
267
+ try {
268
+ await withGateway(ctx, async (gateway) => {
269
+ await gateway.getJson("/v1/auth/principal");
270
+ return { data: {} };
271
+ });
272
+ checks.push({ name: "principal", ok: true, message: "principal lookup succeeded" });
273
+ }
274
+ catch (err) {
275
+ checks.push({
276
+ name: "principal",
277
+ ok: false,
278
+ message: err instanceof Error ? err.message : String(err),
279
+ });
280
+ }
281
+ }
282
+ if (agentFlag.present) {
283
+ if (!apiKey) {
284
+ for (const name of [
285
+ "mcp_host_config",
286
+ "mcp_env_resolution",
287
+ "mcp_launch",
288
+ "mcp_initialize",
289
+ "mcp_tools_list",
290
+ "mcp_tool_schemas",
291
+ "mcp_stdout_purity",
292
+ ]) {
293
+ checks.push({ name, ok: false, message: "skipped MCP probe (missing API key)" });
294
+ }
295
+ }
296
+ else {
297
+ const agentChecks = await runAgentMcpChecks({
298
+ envFile: defaults.envFile,
299
+ cwd: ctx.cwd,
300
+ host: hostFlag.value ?? "generic",
301
+ });
302
+ checks.push(...agentChecks);
303
+ }
304
+ }
305
+ const summary = checks.every((check) => check.ok) ? "pass" : "fail";
306
+ return { data: { checks, summary } };
307
+ }
308
+ export async function handleConfig(ctx, subcommand, argv) {
309
+ if (subcommand === "list") {
310
+ const entries = await listConfigEntries(ctx.globals.profile);
311
+ return { data: { entries } };
312
+ }
313
+ if (subcommand === "get") {
314
+ const key = argv[0];
315
+ if (!key) {
316
+ throw new CliError("config get requires <key>", { category: "usage", code: "cli.usage.missing_key" });
317
+ }
318
+ const value = await resolveConfigValue(key, ctx.globals.profile);
319
+ if (value === undefined) {
320
+ throw new CliError(`config key not found: ${key}`, { category: "not_found", code: "cli.config.not_found" });
321
+ }
322
+ return { data: { key, value: redactConfigValue(key, value) } };
323
+ }
324
+ if (subcommand === "set") {
325
+ const key = argv[0];
326
+ const value = argv[1];
327
+ if (!key || value === undefined) {
328
+ throw new CliError("config set requires <key> <value>", { category: "usage", code: "cli.usage.missing_args" });
329
+ }
330
+ await setConfigValue(key, value, ctx.globals.profile);
331
+ return { data: { key, set: true } };
332
+ }
333
+ if (subcommand === "unset") {
334
+ const key = argv[0];
335
+ if (!key) {
336
+ throw new CliError("config unset requires <key>", { category: "usage", code: "cli.usage.missing_key" });
337
+ }
338
+ const removed = await unsetConfigValue(key, ctx.globals.profile);
339
+ return { data: { key, removed } };
340
+ }
341
+ throw new CliError(`unknown config subcommand: ${subcommand}`, { category: "usage", code: "cli.usage.unknown_command" });
342
+ }
343
+ export async function handleWhoami(ctx) {
344
+ return withGateway(ctx, async (gateway) => {
345
+ const principal = await gateway.getJson("/v1/auth/principal");
346
+ const stripped = { ...principal };
347
+ delete stripped.access_token;
348
+ delete stripped.refresh_token;
349
+ return {
350
+ data: {
351
+ tenant_id: String(principal.tenant_id ?? ""),
352
+ tenant_uuid: String(principal.tenant_uuid ?? ""),
353
+ environment: String(principal.environment ?? ""),
354
+ service_account_role: String(principal.service_account_role ?? ""),
355
+ principal: stripped,
356
+ },
357
+ };
358
+ });
359
+ }
360
+ export async function handleVersion(ctx, argv) {
361
+ const verboseFlag = consumeBooleanFlag(argv, "--verbose");
362
+ if (verboseFlag.rest.length > 0) {
363
+ throw new CliError(`unexpected arguments: ${verboseFlag.rest.join(" ")}`, {
364
+ category: "usage",
365
+ code: "cli.usage.unexpected_args",
366
+ });
367
+ }
368
+ if (verboseFlag.present) {
369
+ const diagnostics = await buildSupportDiagnostics(ctx);
370
+ return { data: diagnostics };
371
+ }
372
+ return { data: { version: packageVersion() } };
373
+ }
374
+ export async function handleDiagnose(ctx, argv) {
375
+ const redactedFlag = consumeBooleanFlag(argv, "--redacted");
376
+ if (redactedFlag.rest.length > 0) {
377
+ throw new CliError(`unexpected arguments: ${redactedFlag.rest.join(" ")}`, {
378
+ category: "usage",
379
+ code: "cli.usage.unexpected_args",
380
+ });
381
+ }
382
+ if (!redactedFlag.present) {
383
+ throw new CliError("paybond diagnose requires --redacted for support bundles", {
384
+ category: "usage",
385
+ code: "cli.diagnose.redacted_required",
386
+ });
387
+ }
388
+ const diagnostics = await buildSupportDiagnostics(ctx);
389
+ return {
390
+ data: {
391
+ redacted: true,
392
+ diagnostics,
393
+ lines: formatSupportDiagnosticsTable(diagnostics),
394
+ },
395
+ };
396
+ }
397
+ export { readJsonFile, commandPath };
@@ -0,0 +1,7 @@
1
+ import { commandPath, type CliContext } from "../context.js";
2
+ import { type CommandResult } from "../types.js";
3
+ export declare function handleKeys(ctx: CliContext, subcommand: string, argv: string[]): Promise<CommandResult>;
4
+ export declare function handleIntents(ctx: CliContext, subcommand: string, argv: string[]): Promise<CommandResult>;
5
+ export declare function handleGuardrails(ctx: CliContext, subcommand: string, argv: string[]): Promise<CommandResult>;
6
+ export declare function handleSpendAuthorize(ctx: CliContext, argv: string[]): Promise<CommandResult>;
7
+ export { commandPath };
@@ -0,0 +1,209 @@
1
+ import { resolveJsonBody } from "../body.js";
2
+ import { buildListQueryParams, extractNextCursor, partialResultsWarning, } from "../automation.js";
3
+ import { commandPath, requireConfirmation, withGateway } from "../context.js";
4
+ import { consumeFlag, parseOptionalNonNegativeInt, parseRequiredNonNegativeInt } from "../globals.js";
5
+ import { maskApiKey, redactSensitiveFields } from "../redact.js";
6
+ import { CliError } from "../types.js";
7
+ export async function handleKeys(ctx, subcommand, argv) {
8
+ return withGateway(ctx, async (gateway) => {
9
+ if (subcommand === "list") {
10
+ const limitFlag = consumeFlag(argv, "--limit");
11
+ const cursorFlag = consumeFlag(argv, "--cursor");
12
+ const params = buildListQueryParams(limitFlag.value, cursorFlag.value, { limit: "50" });
13
+ const body = await gateway.getJson(`/v1/admin/api-keys?${params.toString()}`);
14
+ const items = Array.isArray(body.items) ? body.items : [];
15
+ const keys = items.map((item) => {
16
+ const row = item;
17
+ return {
18
+ key_id: String(row.key_id ?? row.id ?? ""),
19
+ key_masked: maskApiKey(`paybond_sk_${String(row.environment ?? "sandbox")}_${String(row.key_id ?? "redacted")}_redacted`),
20
+ role: String(row.service_account_role ?? ""),
21
+ created_at: String(row.created_at ?? ""),
22
+ expires_at: row.expires_at ? String(row.expires_at) : null,
23
+ status: row.revoked_at ? "revoked" : "active",
24
+ };
25
+ });
26
+ const nextCursor = extractNextCursor(body);
27
+ const warnings = partialResultsWarning(nextCursor) ? [partialResultsWarning(nextCursor)] : undefined;
28
+ const data = { keys };
29
+ if (nextCursor) {
30
+ data.next_cursor = nextCursor;
31
+ }
32
+ return { data, warnings };
33
+ }
34
+ if (subcommand === "create") {
35
+ const nameFlag = consumeFlag(argv, "--name");
36
+ const roleFlag = consumeFlag(argv, "--role");
37
+ const labelFlag = consumeFlag(argv, "--label");
38
+ if (!nameFlag.value || !roleFlag.value) {
39
+ throw new CliError("keys create requires --name and --role", { category: "usage", code: "cli.usage.missing_args" });
40
+ }
41
+ const body = await gateway.postJson("/v1/admin/api-keys", {
42
+ service_account_name: nameFlag.value,
43
+ service_account_role: roleFlag.value,
44
+ label: labelFlag.value ?? "",
45
+ });
46
+ const item = (body.item ?? {});
47
+ const rawApiKey = typeof body.api_key === "string" ? body.api_key : "";
48
+ const data = {
49
+ key_id: String(item.key_id ?? item.id ?? ""),
50
+ key_masked: rawApiKey ? maskApiKey(rawApiKey) : maskApiKey(""),
51
+ role: String(item.service_account_role ?? roleFlag.value),
52
+ created_at: String(item.created_at ?? ""),
53
+ status: "active",
54
+ };
55
+ if (rawApiKey) {
56
+ data.api_key = rawApiKey;
57
+ }
58
+ return { data };
59
+ }
60
+ const keyId = argv[0];
61
+ if (!keyId) {
62
+ throw new CliError(`keys ${subcommand} requires <key_id>`, { category: "usage", code: "cli.usage.missing_key_id" });
63
+ }
64
+ if (subcommand === "rotate") {
65
+ requireConfirmation(ctx.globals, "rotate API key");
66
+ const body = await gateway.postJson(`/v1/admin/api-keys/${encodeURIComponent(keyId)}/rotate`);
67
+ const item = (body.item ?? {});
68
+ const rawApiKey = typeof body.api_key === "string" ? body.api_key : "";
69
+ const data = {
70
+ key_id: String(item.key_id ?? keyId),
71
+ key_masked: rawApiKey ? maskApiKey(rawApiKey) : maskApiKey(""),
72
+ rotated: true,
73
+ };
74
+ if (rawApiKey) {
75
+ data.api_key = rawApiKey;
76
+ }
77
+ return { data };
78
+ }
79
+ if (subcommand === "revoke") {
80
+ requireConfirmation(ctx.globals, "revoke API key");
81
+ await gateway.deleteJson(`/v1/admin/api-keys/${encodeURIComponent(keyId)}`);
82
+ return { data: { key_id: keyId, revoked: true } };
83
+ }
84
+ throw new CliError(`unknown keys subcommand: ${subcommand}`, { category: "usage", code: "cli.usage.unknown_command" });
85
+ });
86
+ }
87
+ export async function handleIntents(ctx, subcommand, argv) {
88
+ return withGateway(ctx, async (gateway) => {
89
+ if (subcommand === "list") {
90
+ const statusFlag = consumeFlag(argv, "--status");
91
+ const limitFlag = consumeFlag(argv, "--limit");
92
+ const cursorFlag = consumeFlag(argv, "--cursor");
93
+ const params = buildListQueryParams(limitFlag.value, cursorFlag.value);
94
+ if (statusFlag.value) {
95
+ params.set("status", statusFlag.value);
96
+ }
97
+ const body = await gateway.getJson(`/harbor/operator/v1/intents?${params.toString()}`);
98
+ const redacted = redactSensitiveFields(body);
99
+ const nextCursor = extractNextCursor(redacted);
100
+ const warnings = partialResultsWarning(nextCursor) ? [partialResultsWarning(nextCursor)] : undefined;
101
+ if (nextCursor && !redacted.next_cursor) {
102
+ redacted.next_cursor = nextCursor;
103
+ }
104
+ return { data: redacted, warnings };
105
+ }
106
+ const intentId = argv[0];
107
+ if (subcommand === "get") {
108
+ if (!intentId) {
109
+ throw new CliError("intents get requires <intent_id>", { category: "usage", code: "cli.usage.missing_intent_id" });
110
+ }
111
+ const body = await gateway.getJson(`/harbor/operator/v1/intents/${encodeURIComponent(intentId)}`);
112
+ return { data: redactSensitiveFields(body) };
113
+ }
114
+ if (subcommand === "create") {
115
+ const { payload } = await resolveJsonBody(argv, {
116
+ missingMessage: "intents create requires --body <json-file> or --stdin",
117
+ });
118
+ const body = await gateway.postJson("/harbor/intents", payload);
119
+ return { data: redactSensitiveFields(body) };
120
+ }
121
+ if (!intentId) {
122
+ throw new CliError(`intents ${subcommand} requires <intent_id>`, { category: "usage", code: "cli.usage.missing_intent_id" });
123
+ }
124
+ if (subcommand === "fund") {
125
+ const { payload } = await resolveJsonBody(argv, { required: false });
126
+ const body = await gateway.postJson(`/harbor/intents/${encodeURIComponent(intentId)}/fund`, payload);
127
+ return { data: redactSensitiveFields(body) };
128
+ }
129
+ if (subcommand === "evidence") {
130
+ const { payload } = await resolveJsonBody(argv, {
131
+ missingMessage: "intents evidence requires --body <json-file> or --stdin",
132
+ });
133
+ const body = await gateway.postJson(`/harbor/intents/${encodeURIComponent(intentId)}/evidence`, payload);
134
+ return { data: redactSensitiveFields(body) };
135
+ }
136
+ if (subcommand === "settlement-confirm") {
137
+ const { payload } = await resolveJsonBody(argv, { required: false });
138
+ const body = await gateway.postJson(`/harbor/intents/${encodeURIComponent(intentId)}/settlement/confirm`, payload);
139
+ return { data: redactSensitiveFields(body) };
140
+ }
141
+ throw new CliError(`unknown intents subcommand: ${subcommand}`, { category: "usage", code: "cli.usage.unknown_command" });
142
+ });
143
+ }
144
+ export async function handleGuardrails(ctx, subcommand, argv) {
145
+ return withGateway(ctx, async (gateway) => {
146
+ if (subcommand === "bootstrap") {
147
+ const operationFlag = consumeFlag(argv, "--operation");
148
+ const spendFlag = consumeFlag(argv, "--requested-spend-cents");
149
+ if (!operationFlag.value || !spendFlag.value) {
150
+ throw new CliError("guardrails bootstrap requires --operation and --requested-spend-cents", { category: "usage", code: "cli.usage.missing_args" });
151
+ }
152
+ const spendCents = parseRequiredNonNegativeInt(spendFlag.value, "--requested-spend-cents");
153
+ const body = await gateway.postJson("/v1/sandbox/guardrails/bootstrap", {
154
+ operation: operationFlag.value,
155
+ requested_spend_cents: spendCents,
156
+ });
157
+ return {
158
+ data: {
159
+ tenant_id: String(body.tenant_id ?? ""),
160
+ intent_id: String(body.intent_id ?? ""),
161
+ capability_token: String(body.capability_token ?? ""),
162
+ operation: String(body.operation ?? operationFlag.value),
163
+ requested_spend_cents: Number(body.requested_spend_cents ?? spendCents),
164
+ sandbox_lifecycle_status: String(body.sandbox_lifecycle_status ?? ""),
165
+ },
166
+ };
167
+ }
168
+ if (subcommand === "evidence") {
169
+ const intentFlag = consumeFlag(argv, "--intent-id");
170
+ if (!intentFlag.value) {
171
+ throw new CliError("guardrails evidence requires --intent-id and --body <json-file>", { category: "usage", code: "cli.usage.missing_args" });
172
+ }
173
+ const { payload } = await resolveJsonBody(argv, {
174
+ missingMessage: "guardrails evidence requires --intent-id and --body <json-file> or --stdin",
175
+ });
176
+ const body = await gateway.postJson(`/v1/sandbox/guardrails/${encodeURIComponent(intentFlag.value)}/evidence`, payload);
177
+ return { data: body };
178
+ }
179
+ throw new CliError(`unknown guardrails subcommand: ${subcommand}`, { category: "usage", code: "cli.usage.unknown_command" });
180
+ });
181
+ }
182
+ export async function handleSpendAuthorize(ctx, argv) {
183
+ return withGateway(ctx, async (gateway) => {
184
+ const intentFlag = consumeFlag(argv, "--intent-id");
185
+ const tokenFlag = consumeFlag(argv, "--token");
186
+ const operationFlag = consumeFlag(argv, "--operation");
187
+ const spendFlag = consumeFlag(argv, "--requested-spend-cents");
188
+ if (!intentFlag.value || !tokenFlag.value || !operationFlag.value) {
189
+ throw new CliError("spend authorize requires --intent-id, --token, and --operation", { category: "usage", code: "cli.usage.missing_args" });
190
+ }
191
+ const spendCents = parseOptionalNonNegativeInt(spendFlag.value, "--requested-spend-cents");
192
+ const body = await gateway.postJson("/verify", {
193
+ intent_id: intentFlag.value,
194
+ token: tokenFlag.value,
195
+ operation: operationFlag.value,
196
+ requested_spend_cents: spendCents,
197
+ });
198
+ return {
199
+ data: {
200
+ authorized: Boolean(body.allow),
201
+ intent_id: String(body.intent_id ?? intentFlag.value),
202
+ operation: operationFlag.value,
203
+ requested_spend_cents: spendCents,
204
+ deny_reason: body.allow ? undefined : String(body.message ?? body.code ?? "denied"),
205
+ },
206
+ };
207
+ });
208
+ }
209
+ export { commandPath };
@@ -0,0 +1,14 @@
1
+ export type CliConfigFile = {
2
+ profiles?: Record<string, {
3
+ env_file?: string;
4
+ gateway?: string;
5
+ }>;
6
+ values?: Record<string, string>;
7
+ };
8
+ export declare function configFilePath(): string;
9
+ export declare function loadConfigFile(): Promise<CliConfigFile>;
10
+ export declare function saveConfigFile(config: CliConfigFile): Promise<void>;
11
+ export declare function resolveConfigValue(key: string, profile?: string): Promise<string | undefined>;
12
+ export declare function setConfigValue(key: string, value: string, profile?: string): Promise<void>;
13
+ export declare function unsetConfigValue(key: string, profile?: string): Promise<boolean>;
14
+ export declare function listConfigEntries(profile?: string): Promise<Record<string, string>>;