@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.
- package/dist/cli/audit-export.d.ts +7 -0
- package/dist/cli/audit-export.js +120 -0
- package/dist/cli/automation.d.ts +25 -0
- package/dist/cli/automation.js +297 -0
- package/dist/cli/body.d.ts +7 -0
- package/dist/cli/body.js +22 -0
- package/dist/cli/color.d.ts +15 -0
- package/dist/cli/color.js +39 -0
- package/dist/cli/command-spec.d.ts +12 -0
- package/dist/cli/command-spec.js +330 -0
- package/dist/cli/commands/discovery.d.ts +8 -0
- package/dist/cli/commands/discovery.js +194 -0
- package/dist/cli/commands/setup.d.ts +15 -0
- package/dist/cli/commands/setup.js +397 -0
- package/dist/cli/commands/workflows.d.ts +7 -0
- package/dist/cli/commands/workflows.js +209 -0
- package/dist/cli/config.d.ts +14 -0
- package/dist/cli/config.js +96 -0
- package/dist/cli/context.d.ts +22 -0
- package/dist/cli/context.js +109 -0
- package/dist/cli/credentials.d.ts +21 -0
- package/dist/cli/credentials.js +141 -0
- package/dist/cli/doctor-agent.d.ts +15 -0
- package/dist/cli/doctor-agent.js +311 -0
- package/dist/cli/envelope.d.ts +14 -0
- package/dist/cli/envelope.js +46 -0
- package/dist/cli/globals.d.ts +22 -0
- package/dist/cli/globals.js +238 -0
- package/dist/cli/help.d.ts +3 -0
- package/dist/cli/help.js +51 -0
- package/dist/cli/mcp-install.d.ts +41 -0
- package/dist/cli/mcp-install.js +95 -0
- package/dist/cli/mcp-policy.d.ts +23 -0
- package/dist/cli/mcp-policy.js +104 -0
- package/dist/cli/mcp-verify-config.d.ts +37 -0
- package/dist/cli/mcp-verify-config.js +174 -0
- package/dist/cli/redact.d.ts +4 -0
- package/dist/cli/redact.js +67 -0
- package/dist/cli/request-id.d.ts +2 -0
- package/dist/cli/request-id.js +5 -0
- package/dist/cli/router.d.ts +2 -0
- package/dist/cli/router.js +275 -0
- package/dist/cli/suggest.d.ts +4 -0
- package/dist/cli/suggest.js +58 -0
- package/dist/cli/support-diagnostics.d.ts +19 -0
- package/dist/cli/support-diagnostics.js +47 -0
- package/dist/cli/types.d.ts +72 -0
- package/dist/cli/types.js +60 -0
- package/dist/cli/ux.d.ts +8 -0
- package/dist/cli/ux.js +164 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +38 -0
- package/dist/init.js +9 -14
- package/dist/login.d.ts +14 -1
- package/dist/login.js +123 -63
- package/dist/mcp-server.d.ts +4 -0
- package/dist/mcp-server.js +204 -76
- package/package.json +5 -2
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { access } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { defaultMcpInstallFormat, defaultMcpServerCommand, parseMcpInstallHost, planMcpInstall, } from "./mcp-install.js";
|
|
6
|
+
import { validateMcpToolSchema } from "./mcp-policy.js";
|
|
7
|
+
import { validateMcpHostConfig } from "./mcp-verify-config.js";
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const packageJson = require("../../package.json");
|
|
10
|
+
export function packageVersion() {
|
|
11
|
+
return packageJson.version;
|
|
12
|
+
}
|
|
13
|
+
export function encodeMcpMessage(payload) {
|
|
14
|
+
const body = JSON.stringify(payload);
|
|
15
|
+
return Buffer.from(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`, "utf8");
|
|
16
|
+
}
|
|
17
|
+
function consumeMcpMessages(raw) {
|
|
18
|
+
const messages = [];
|
|
19
|
+
let offset = 0;
|
|
20
|
+
while (true) {
|
|
21
|
+
const headerEnd = raw.indexOf("\r\n\r\n", offset);
|
|
22
|
+
if (headerEnd < 0) {
|
|
23
|
+
return { messages, remainder: raw.subarray(offset) };
|
|
24
|
+
}
|
|
25
|
+
const headerText = raw.subarray(offset, headerEnd).toString("ascii");
|
|
26
|
+
const contentLength = Number.parseInt(headerText
|
|
27
|
+
.split("\r\n")
|
|
28
|
+
.find((line) => line.toLowerCase().startsWith("content-length:"))
|
|
29
|
+
?.split(":", 2)[1]
|
|
30
|
+
?.trim() ?? "", 10);
|
|
31
|
+
if (!Number.isFinite(contentLength) || contentLength <= 0) {
|
|
32
|
+
throw new Error("MCP response missing Content-Length");
|
|
33
|
+
}
|
|
34
|
+
const bodyStart = headerEnd + 4;
|
|
35
|
+
const bodyEnd = bodyStart + contentLength;
|
|
36
|
+
if (raw.length < bodyEnd) {
|
|
37
|
+
return { messages, remainder: raw.subarray(offset) };
|
|
38
|
+
}
|
|
39
|
+
const body = raw.subarray(bodyStart, bodyEnd).toString("utf8");
|
|
40
|
+
const parsed = JSON.parse(body);
|
|
41
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
42
|
+
throw new Error("MCP response was not a JSON object");
|
|
43
|
+
}
|
|
44
|
+
messages.push(parsed);
|
|
45
|
+
offset = bodyEnd;
|
|
46
|
+
if (offset >= raw.length) {
|
|
47
|
+
return { messages, remainder: Buffer.alloc(0) };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function readMcpMessage(stream, deadlineMs, rawBuffer) {
|
|
52
|
+
const started = Date.now();
|
|
53
|
+
while (true) {
|
|
54
|
+
const joined = Buffer.concat(rawBuffer);
|
|
55
|
+
const { messages, remainder } = consumeMcpMessages(joined);
|
|
56
|
+
rawBuffer.length = 0;
|
|
57
|
+
if (remainder.length > 0) {
|
|
58
|
+
rawBuffer.push(remainder);
|
|
59
|
+
}
|
|
60
|
+
if (messages[0]) {
|
|
61
|
+
return messages[0];
|
|
62
|
+
}
|
|
63
|
+
if (Date.now() - started > deadlineMs) {
|
|
64
|
+
throw new Error("timed out waiting for MCP response");
|
|
65
|
+
}
|
|
66
|
+
const chunk = await new Promise((resolve, reject) => {
|
|
67
|
+
const onData = (data) => {
|
|
68
|
+
cleanup();
|
|
69
|
+
resolve(data);
|
|
70
|
+
};
|
|
71
|
+
const onEnd = () => {
|
|
72
|
+
cleanup();
|
|
73
|
+
resolve(null);
|
|
74
|
+
};
|
|
75
|
+
const onError = (err) => {
|
|
76
|
+
cleanup();
|
|
77
|
+
reject(err);
|
|
78
|
+
};
|
|
79
|
+
const cleanup = () => {
|
|
80
|
+
stream.off("data", onData);
|
|
81
|
+
stream.off("end", onEnd);
|
|
82
|
+
stream.off("error", onError);
|
|
83
|
+
};
|
|
84
|
+
stream.once("data", onData);
|
|
85
|
+
stream.once("end", onEnd);
|
|
86
|
+
stream.once("error", onError);
|
|
87
|
+
});
|
|
88
|
+
if (!chunk) {
|
|
89
|
+
throw new Error("MCP server closed stdout before responding");
|
|
90
|
+
}
|
|
91
|
+
rawBuffer.push(chunk);
|
|
92
|
+
if (Buffer.concat(rawBuffer).length > 1_048_576) {
|
|
93
|
+
throw new Error("MCP stdout buffer too large");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function stdoutIsMcpPure(rawStdout) {
|
|
98
|
+
if (rawStdout.length === 0) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const { messages, remainder } = consumeMcpMessages(rawStdout);
|
|
103
|
+
if (remainder.toString("utf8").trim()) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return messages.length > 0;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export async function runAgentMcpChecks(input) {
|
|
113
|
+
const checks = [];
|
|
114
|
+
const installHost = parseMcpInstallHost(input.host ?? "generic");
|
|
115
|
+
const format = defaultMcpInstallFormat(installHost);
|
|
116
|
+
const plan = planMcpInstall({
|
|
117
|
+
host: installHost,
|
|
118
|
+
scope: "local",
|
|
119
|
+
format,
|
|
120
|
+
envFile: input.envFile,
|
|
121
|
+
cwd: input.cwd,
|
|
122
|
+
home: process.env.HOME ?? process.env.USERPROFILE ?? input.cwd,
|
|
123
|
+
serverCommand: input.serverCommand ?? defaultMcpServerCommand(),
|
|
124
|
+
});
|
|
125
|
+
const configResult = validateMcpHostConfig({
|
|
126
|
+
host: installHost,
|
|
127
|
+
format,
|
|
128
|
+
payload: plan.payload,
|
|
129
|
+
cwd: input.cwd,
|
|
130
|
+
expectedEnvFile: input.envFile,
|
|
131
|
+
});
|
|
132
|
+
checks.push({
|
|
133
|
+
name: "mcp_host_config",
|
|
134
|
+
ok: configResult.ok,
|
|
135
|
+
message: configResult.message,
|
|
136
|
+
details: { host: installHost, format },
|
|
137
|
+
});
|
|
138
|
+
const envPath = path.isAbsolute(input.envFile)
|
|
139
|
+
? path.resolve(input.envFile)
|
|
140
|
+
: path.resolve(input.cwd, input.envFile);
|
|
141
|
+
let envOk = false;
|
|
142
|
+
try {
|
|
143
|
+
await access(envPath);
|
|
144
|
+
envOk = true;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
envOk = false;
|
|
148
|
+
}
|
|
149
|
+
checks.push({
|
|
150
|
+
name: "mcp_env_resolution",
|
|
151
|
+
ok: envOk,
|
|
152
|
+
message: envOk ? envPath : `env file not found: ${envPath}`,
|
|
153
|
+
details: { env_file: input.envFile, resolved: envPath },
|
|
154
|
+
});
|
|
155
|
+
if (!envOk) {
|
|
156
|
+
checks.push({ name: "mcp_launch", ok: false, message: "skipped (env file missing)" }, { name: "mcp_initialize", ok: false, message: "skipped (env file missing)" }, { name: "mcp_tools_list", ok: false, message: "skipped (env file missing)" }, { name: "mcp_tool_schemas", ok: false, message: "skipped (env file missing)" }, { name: "mcp_stdout_purity", ok: false, message: "skipped (env file missing)" });
|
|
157
|
+
return checks;
|
|
158
|
+
}
|
|
159
|
+
const command = input.serverCommand ?? defaultMcpServerCommand();
|
|
160
|
+
const env = {
|
|
161
|
+
...process.env,
|
|
162
|
+
PAYBOND_ENV_FILE: envPath,
|
|
163
|
+
};
|
|
164
|
+
delete env.PAYBOND_API_KEY;
|
|
165
|
+
return await new Promise((resolve) => {
|
|
166
|
+
let child;
|
|
167
|
+
try {
|
|
168
|
+
child = spawn(command[0], command.slice(1), {
|
|
169
|
+
cwd: input.cwd,
|
|
170
|
+
env,
|
|
171
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
resolve([
|
|
176
|
+
{ name: "mcp_launch", ok: false, message: `unable to launch MCP server: ${err instanceof Error ? err.message : String(err)}` },
|
|
177
|
+
{ name: "mcp_initialize", ok: false, message: "skipped (launch failed)" },
|
|
178
|
+
{ name: "mcp_tools_list", ok: false, message: "skipped (launch failed)" },
|
|
179
|
+
{ name: "mcp_tool_schemas", ok: false, message: "skipped (probe failed)" },
|
|
180
|
+
{ name: "mcp_stdout_purity", ok: false, message: "skipped (launch failed)" },
|
|
181
|
+
]);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const timeoutMs = input.timeoutMs ?? 10_000;
|
|
185
|
+
const stderrChunks = [];
|
|
186
|
+
const rawStdout = [];
|
|
187
|
+
child.stderr?.on("data", (chunk) => {
|
|
188
|
+
stderrChunks.push(chunk);
|
|
189
|
+
});
|
|
190
|
+
child.stdout?.on("data", (chunk) => {
|
|
191
|
+
rawStdout.push(chunk);
|
|
192
|
+
});
|
|
193
|
+
void (async () => {
|
|
194
|
+
try {
|
|
195
|
+
if (!child.stdin || !child.stdout) {
|
|
196
|
+
throw new Error("MCP server stdio pipes unavailable");
|
|
197
|
+
}
|
|
198
|
+
checks.push({
|
|
199
|
+
name: "mcp_launch",
|
|
200
|
+
ok: true,
|
|
201
|
+
message: `launched ${command.join(" ")}`,
|
|
202
|
+
});
|
|
203
|
+
child.stdin.write(encodeMcpMessage({
|
|
204
|
+
jsonrpc: "2.0",
|
|
205
|
+
id: 1,
|
|
206
|
+
method: "initialize",
|
|
207
|
+
params: {
|
|
208
|
+
protocolVersion: "2025-11-25",
|
|
209
|
+
capabilities: {},
|
|
210
|
+
clientInfo: { name: "paybond-doctor", version: packageVersion() },
|
|
211
|
+
},
|
|
212
|
+
}));
|
|
213
|
+
const initResponse = await readMcpMessage(child.stdout, timeoutMs, rawStdout);
|
|
214
|
+
if (initResponse.error) {
|
|
215
|
+
checks.push({
|
|
216
|
+
name: "mcp_initialize",
|
|
217
|
+
ok: false,
|
|
218
|
+
message: "MCP initialize failed",
|
|
219
|
+
details: { error: initResponse.error },
|
|
220
|
+
});
|
|
221
|
+
checks.push({ name: "mcp_tools_list", ok: false, message: "skipped (initialize failed)" }, { name: "mcp_tool_schemas", ok: false, message: "skipped (initialize failed)" }, { name: "mcp_stdout_purity", ok: false, message: "skipped (initialize failed)" });
|
|
222
|
+
resolve(checks);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const serverInfo = initResponse.result?.serverInfo;
|
|
226
|
+
const initOk = Boolean(serverInfo && typeof serverInfo === "object");
|
|
227
|
+
checks.push({
|
|
228
|
+
name: "mcp_initialize",
|
|
229
|
+
ok: initOk,
|
|
230
|
+
message: initOk ? "MCP initialize succeeded" : "MCP initialize response missing serverInfo",
|
|
231
|
+
details: initOk
|
|
232
|
+
? {
|
|
233
|
+
server_name: serverInfo.name,
|
|
234
|
+
server_version: serverInfo.version,
|
|
235
|
+
}
|
|
236
|
+
: undefined,
|
|
237
|
+
});
|
|
238
|
+
if (!initOk) {
|
|
239
|
+
checks.push({ name: "mcp_tools_list", ok: false, message: "skipped (initialize failed)" }, { name: "mcp_tool_schemas", ok: false, message: "skipped (initialize failed)" }, { name: "mcp_stdout_purity", ok: false, message: "skipped (initialize failed)" });
|
|
240
|
+
resolve(checks);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
child.stdin.write(encodeMcpMessage({
|
|
244
|
+
jsonrpc: "2.0",
|
|
245
|
+
method: "notifications/initialized",
|
|
246
|
+
}));
|
|
247
|
+
child.stdin.write(encodeMcpMessage({
|
|
248
|
+
jsonrpc: "2.0",
|
|
249
|
+
id: 2,
|
|
250
|
+
method: "tools/list",
|
|
251
|
+
params: {},
|
|
252
|
+
}));
|
|
253
|
+
const toolsResponse = await readMcpMessage(child.stdout, timeoutMs, rawStdout);
|
|
254
|
+
if (toolsResponse.error) {
|
|
255
|
+
checks.push({
|
|
256
|
+
name: "mcp_tools_list",
|
|
257
|
+
ok: false,
|
|
258
|
+
message: "MCP tools/list failed",
|
|
259
|
+
details: { error: toolsResponse.error },
|
|
260
|
+
});
|
|
261
|
+
checks.push({ name: "mcp_tool_schemas", ok: false, message: "skipped (tools/list failed)" }, { name: "mcp_stdout_purity", ok: false, message: "skipped (tools/list failed)" });
|
|
262
|
+
resolve(checks);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const tools = toolsResponse.result?.tools;
|
|
266
|
+
const toolCount = Array.isArray(tools) ? tools.length : 0;
|
|
267
|
+
checks.push({
|
|
268
|
+
name: "mcp_tools_list",
|
|
269
|
+
ok: toolCount > 0,
|
|
270
|
+
message: `${toolCount} tools listed`,
|
|
271
|
+
details: { tool_count: toolCount },
|
|
272
|
+
});
|
|
273
|
+
const schemaErrors = [];
|
|
274
|
+
if (Array.isArray(tools)) {
|
|
275
|
+
for (const tool of tools) {
|
|
276
|
+
if (tool && typeof tool === "object" && !Array.isArray(tool)) {
|
|
277
|
+
schemaErrors.push(...validateMcpToolSchema(tool));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
checks.push({
|
|
282
|
+
name: "mcp_tool_schemas",
|
|
283
|
+
ok: toolCount > 0 && schemaErrors.length === 0,
|
|
284
|
+
message: schemaErrors.length === 0 ? "all listed tools have valid schemas" : schemaErrors[0],
|
|
285
|
+
details: { invalid_count: schemaErrors.length, errors: schemaErrors.slice(0, 5) },
|
|
286
|
+
});
|
|
287
|
+
const pure = stdoutIsMcpPure(Buffer.concat(rawStdout));
|
|
288
|
+
checks.push({
|
|
289
|
+
name: "mcp_stdout_purity",
|
|
290
|
+
ok: pure,
|
|
291
|
+
message: pure ? "stdout contains only MCP-framed JSON-RPC" : "stdout contains non-MCP material",
|
|
292
|
+
});
|
|
293
|
+
resolve(checks);
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
297
|
+
const suffix = stderr ? `: ${stderr}` : "";
|
|
298
|
+
checks.push({
|
|
299
|
+
name: "mcp_initialize",
|
|
300
|
+
ok: false,
|
|
301
|
+
message: `MCP stdio probe failed: ${err instanceof Error ? err.message : String(err)}${suffix}`,
|
|
302
|
+
});
|
|
303
|
+
checks.push({ name: "mcp_tools_list", ok: false, message: "skipped (probe failed)" }, { name: "mcp_tool_schemas", ok: false, message: "skipped (probe failed)" }, { name: "mcp_stdout_purity", ok: false, message: "skipped (probe failed)" });
|
|
304
|
+
resolve(checks);
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
child.kill("SIGTERM");
|
|
308
|
+
}
|
|
309
|
+
})();
|
|
310
|
+
});
|
|
311
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CliEnvelope, CliErrorShape, CommandResult, GlobalOptions, Writable } from "./types.js";
|
|
2
|
+
export declare function mergeWarnings(result: CommandResult, extra?: string[]): string[];
|
|
3
|
+
export declare function prepareCommandOutput(command: string, globals: GlobalOptions, result: CommandResult): {
|
|
4
|
+
data: unknown;
|
|
5
|
+
warnings: string[];
|
|
6
|
+
automationPlain: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function successEnvelope<T>(command: string, globals: GlobalOptions, result: CommandResult<T> | {
|
|
9
|
+
data: T;
|
|
10
|
+
warnings?: string[];
|
|
11
|
+
}): CliEnvelope<T>;
|
|
12
|
+
export declare function failureEnvelope(command: string, globals: GlobalOptions, error: CliErrorShape): CliEnvelope<null>;
|
|
13
|
+
export declare function writeEnvelope<T>(stdout: Writable, envelope: CliEnvelope<T>): void;
|
|
14
|
+
export declare function writeTableLines(stdout: Writable, lines: string[]): void;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { applyAutomationTransforms } from "./automation.js";
|
|
2
|
+
export function mergeWarnings(result, extra = []) {
|
|
3
|
+
const merged = [...(result.warnings ?? []), ...extra];
|
|
4
|
+
return [...new Set(merged)];
|
|
5
|
+
}
|
|
6
|
+
export function prepareCommandOutput(command, globals, result) {
|
|
7
|
+
const warnings = mergeWarnings(result);
|
|
8
|
+
const automationRequested = Boolean(globals.jsonFields || globals.jqExpr);
|
|
9
|
+
const data = applyAutomationTransforms(command, result.data, {
|
|
10
|
+
jsonFields: globals.jsonFields,
|
|
11
|
+
jqExpr: globals.jqExpr,
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
data,
|
|
15
|
+
warnings,
|
|
16
|
+
automationPlain: automationRequested && globals.format !== "json",
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function successEnvelope(command, globals, result) {
|
|
20
|
+
return {
|
|
21
|
+
ok: true,
|
|
22
|
+
command,
|
|
23
|
+
data: result.data,
|
|
24
|
+
warnings: result.warnings ?? [],
|
|
25
|
+
request_id: globals.requestId,
|
|
26
|
+
error: null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function failureEnvelope(command, globals, error) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
command,
|
|
33
|
+
data: null,
|
|
34
|
+
warnings: [],
|
|
35
|
+
request_id: globals.requestId,
|
|
36
|
+
error,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function writeEnvelope(stdout, envelope) {
|
|
40
|
+
stdout.write(`${JSON.stringify(envelope, null, 2)}\n`);
|
|
41
|
+
}
|
|
42
|
+
export function writeTableLines(stdout, lines) {
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
stdout.write(`${line}\n`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type GlobalOptions } from "./types.js";
|
|
2
|
+
export declare const DEFAULT_GATEWAY = "https://api.paybond.ai";
|
|
3
|
+
export declare const DEFAULT_ENV_FILE = ".env.local";
|
|
4
|
+
export declare const TENANT_OVERRIDE_FLAGS: readonly ["--tenant-id", "--tenant", "--tenant_id"];
|
|
5
|
+
export declare function rejectsTenantOverrideFlag(arg: string): boolean;
|
|
6
|
+
export declare function parseRequiredNonNegativeInt(raw: string, field: string): number;
|
|
7
|
+
export declare function parseOptionalNonNegativeInt(raw: string | undefined, field: string): number;
|
|
8
|
+
export declare function defaultGlobalOptions(): GlobalOptions;
|
|
9
|
+
export type ParsedCliArgv = {
|
|
10
|
+
globals: GlobalOptions;
|
|
11
|
+
command: string[];
|
|
12
|
+
};
|
|
13
|
+
export declare function parseCliArgv(argv: string[]): ParsedCliArgv;
|
|
14
|
+
export declare function consumeFlag(argv: string[], flag: string): {
|
|
15
|
+
present: boolean;
|
|
16
|
+
value?: string;
|
|
17
|
+
rest: string[];
|
|
18
|
+
};
|
|
19
|
+
export declare function consumeBooleanFlag(argv: string[], flag: string): {
|
|
20
|
+
present: boolean;
|
|
21
|
+
rest: string[];
|
|
22
|
+
};
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { generateRequestId } from "./request-id.js";
|
|
2
|
+
import { parseColorMode, resolveColorModeFromEnv } from "./color.js";
|
|
3
|
+
import { formatUnknownGlobalFlagMessage } from "./suggest.js";
|
|
4
|
+
import { CliError } from "./types.js";
|
|
5
|
+
export const DEFAULT_GATEWAY = "https://api.paybond.ai";
|
|
6
|
+
export const DEFAULT_ENV_FILE = ".env.local";
|
|
7
|
+
export const TENANT_OVERRIDE_FLAGS = ["--tenant-id", "--tenant", "--tenant_id"];
|
|
8
|
+
export function rejectsTenantOverrideFlag(arg) {
|
|
9
|
+
for (const flag of TENANT_OVERRIDE_FLAGS) {
|
|
10
|
+
if (arg === flag || arg.startsWith(`${flag}=`)) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
export function parseRequiredNonNegativeInt(raw, field) {
|
|
17
|
+
const text = raw.trim();
|
|
18
|
+
if (!text) {
|
|
19
|
+
throw new CliError(`invalid ${field} (expected non-negative integer)`, {
|
|
20
|
+
category: "validation",
|
|
21
|
+
code: "cli.validation.invalid_integer",
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const value = Number(text);
|
|
25
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
26
|
+
throw new CliError(`invalid ${field} (expected non-negative integer)`, {
|
|
27
|
+
category: "validation",
|
|
28
|
+
code: "cli.validation.invalid_integer",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
export function parseOptionalNonNegativeInt(raw, field) {
|
|
34
|
+
if (!raw?.trim()) {
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
return parseRequiredNonNegativeInt(raw, field);
|
|
38
|
+
}
|
|
39
|
+
const GLOBAL_FLAGS = new Set([
|
|
40
|
+
"--gateway",
|
|
41
|
+
"--env-file",
|
|
42
|
+
"--format",
|
|
43
|
+
"--json",
|
|
44
|
+
"--jq",
|
|
45
|
+
"--profile",
|
|
46
|
+
"--request-id",
|
|
47
|
+
"--yes",
|
|
48
|
+
"--no-open",
|
|
49
|
+
"--color",
|
|
50
|
+
"--no-color",
|
|
51
|
+
]);
|
|
52
|
+
export function defaultGlobalOptions() {
|
|
53
|
+
return {
|
|
54
|
+
gateway: DEFAULT_GATEWAY,
|
|
55
|
+
envFile: DEFAULT_ENV_FILE,
|
|
56
|
+
format: "table",
|
|
57
|
+
color: resolveColorModeFromEnv(),
|
|
58
|
+
requestId: generateRequestId(),
|
|
59
|
+
yes: false,
|
|
60
|
+
noOpen: false,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function parseFormat(raw) {
|
|
64
|
+
const value = raw.trim().toLowerCase();
|
|
65
|
+
if (value === "table" || value === "json") {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
throw new CliError("invalid --format (expected table|json)", { category: "usage", code: "cli.usage.invalid_format" });
|
|
69
|
+
}
|
|
70
|
+
function readFlagValue(argv, index, flag) {
|
|
71
|
+
const arg = argv[index];
|
|
72
|
+
if (arg.startsWith(`${flag}=`)) {
|
|
73
|
+
return { value: arg.slice(flag.length + 1), consumed: 1 };
|
|
74
|
+
}
|
|
75
|
+
const next = argv[index + 1];
|
|
76
|
+
if (!next || next.startsWith("-")) {
|
|
77
|
+
throw new CliError(`missing value for ${flag}`, { category: "usage", code: "cli.usage.missing_flag_value" });
|
|
78
|
+
}
|
|
79
|
+
return { value: next, consumed: 2 };
|
|
80
|
+
}
|
|
81
|
+
export function parseCliArgv(argv) {
|
|
82
|
+
for (const arg of argv) {
|
|
83
|
+
if (rejectsTenantOverrideFlag(arg)) {
|
|
84
|
+
throw new CliError("tenant scope comes from authenticated credentials; do not pass --tenant-id", {
|
|
85
|
+
category: "usage",
|
|
86
|
+
code: "cli.usage.tenant_override_forbidden",
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const globals = defaultGlobalOptions();
|
|
91
|
+
const command = [];
|
|
92
|
+
let i = 0;
|
|
93
|
+
while (i < argv.length) {
|
|
94
|
+
const arg = argv[i];
|
|
95
|
+
if (arg === "--help" || arg === "-h") {
|
|
96
|
+
command.push(arg);
|
|
97
|
+
i += 1;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (arg === "--yes") {
|
|
101
|
+
globals.yes = true;
|
|
102
|
+
i += 1;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (arg === "--no-open") {
|
|
106
|
+
globals.noOpen = true;
|
|
107
|
+
i += 1;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (arg === "--no-color") {
|
|
111
|
+
globals.color = "never";
|
|
112
|
+
i += 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (arg === "--color" || arg.startsWith("--color=")) {
|
|
116
|
+
const { value, consumed } = readFlagValue(argv, i, "--color");
|
|
117
|
+
try {
|
|
118
|
+
globals.color = parseColorMode(value);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
throw new CliError(err instanceof Error ? err.message : String(err), {
|
|
122
|
+
category: "usage",
|
|
123
|
+
code: "cli.usage.invalid_color",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
i += consumed;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (arg === "--gateway" || arg.startsWith("--gateway=")) {
|
|
130
|
+
const { value, consumed } = readFlagValue(argv, i, "--gateway");
|
|
131
|
+
if (!value.trim()) {
|
|
132
|
+
throw new CliError("invalid --gateway", { category: "usage", code: "cli.usage.invalid_gateway" });
|
|
133
|
+
}
|
|
134
|
+
globals.gateway = value.trim();
|
|
135
|
+
i += consumed;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (arg === "--env-file" || arg.startsWith("--env-file=")) {
|
|
139
|
+
const { value, consumed } = readFlagValue(argv, i, "--env-file");
|
|
140
|
+
if (!value.trim()) {
|
|
141
|
+
throw new CliError("invalid --env-file", { category: "usage", code: "cli.usage.invalid_env_file" });
|
|
142
|
+
}
|
|
143
|
+
globals.envFile = value.trim();
|
|
144
|
+
i += consumed;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (arg === "--format" || arg.startsWith("--format=")) {
|
|
148
|
+
const { value, consumed } = readFlagValue(argv, i, "--format");
|
|
149
|
+
globals.format = parseFormat(value);
|
|
150
|
+
i += consumed;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (arg === "--profile" || arg.startsWith("--profile=")) {
|
|
154
|
+
const { value, consumed } = readFlagValue(argv, i, "--profile");
|
|
155
|
+
if (!value.trim()) {
|
|
156
|
+
throw new CliError("invalid --profile", { category: "usage", code: "cli.usage.invalid_profile" });
|
|
157
|
+
}
|
|
158
|
+
globals.profile = value.trim();
|
|
159
|
+
i += consumed;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (arg === "--request-id" || arg.startsWith("--request-id=")) {
|
|
163
|
+
const { value, consumed } = readFlagValue(argv, i, "--request-id");
|
|
164
|
+
if (!value.trim()) {
|
|
165
|
+
throw new CliError("invalid --request-id", { category: "usage", code: "cli.usage.invalid_request_id" });
|
|
166
|
+
}
|
|
167
|
+
globals.requestId = value.trim();
|
|
168
|
+
i += consumed;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (arg === "--json" || arg.startsWith("--json=")) {
|
|
172
|
+
const { value, consumed } = readFlagValue(argv, i, "--json");
|
|
173
|
+
if (!value.trim()) {
|
|
174
|
+
throw new CliError("invalid --json (expected comma-separated field names)", {
|
|
175
|
+
category: "usage",
|
|
176
|
+
code: "cli.usage.invalid_json_fields",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
globals.jsonFields = value.trim();
|
|
180
|
+
i += consumed;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (arg === "--jq" || arg.startsWith("--jq=")) {
|
|
184
|
+
const { value, consumed } = readFlagValue(argv, i, "--jq");
|
|
185
|
+
if (!value.trim()) {
|
|
186
|
+
throw new CliError("invalid --jq (expected filter expression)", { category: "usage", code: "cli.usage.invalid_jq" });
|
|
187
|
+
}
|
|
188
|
+
globals.jqExpr = value.trim();
|
|
189
|
+
i += consumed;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (arg.startsWith("--") && !GLOBAL_FLAGS.has(arg.split("=")[0])) {
|
|
193
|
+
if (command.length === 0) {
|
|
194
|
+
throw new CliError(formatUnknownGlobalFlagMessage(arg), {
|
|
195
|
+
category: "usage",
|
|
196
|
+
code: "cli.usage.unknown_flag",
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
command.push(arg);
|
|
201
|
+
i += 1;
|
|
202
|
+
}
|
|
203
|
+
return { globals, command };
|
|
204
|
+
}
|
|
205
|
+
export function consumeFlag(argv, flag) {
|
|
206
|
+
const rest = [];
|
|
207
|
+
let present = false;
|
|
208
|
+
let value;
|
|
209
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
210
|
+
const arg = argv[i];
|
|
211
|
+
if (arg === flag) {
|
|
212
|
+
present = true;
|
|
213
|
+
const next = argv[i + 1];
|
|
214
|
+
if (!next || next.startsWith("-")) {
|
|
215
|
+
throw new CliError(`missing value for ${flag}`, { category: "usage", code: "cli.usage.missing_flag_value" });
|
|
216
|
+
}
|
|
217
|
+
value = next;
|
|
218
|
+
i += 1;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (arg.startsWith(`${flag}=`)) {
|
|
222
|
+
present = true;
|
|
223
|
+
value = arg.slice(flag.length + 1);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
rest.push(arg);
|
|
227
|
+
}
|
|
228
|
+
return { present, value, rest };
|
|
229
|
+
}
|
|
230
|
+
export function consumeBooleanFlag(argv, flag) {
|
|
231
|
+
const rest = argv.filter((arg) => {
|
|
232
|
+
if (arg === flag) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
});
|
|
237
|
+
return { present: argv.length !== rest.length, rest };
|
|
238
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const ROOT_HELP = "Usage: paybond [--global-flags] <command> [<subcommand> ...] [args]\n\nGlobal flags:\n --gateway <url> Gateway base URL (default: https://api.paybond.ai)\n --env-file <path> Local secrets file (default: .env.local)\n --format table|json Human table or JSON envelope output (default: table)\n --json <fields> Field-selectable JSON for list/read commands (plain JSON; use --format json for envelope)\n --jq <expr> jq-style filter for list/read command output\n --profile <name> Named credential profile from the Paybond CLI config file\n --request-id <id> Correlation ID for Gateway calls and JSON output\n --yes Skip confirmation for destructive operations\n --no-open Do not open a browser for device login\n --color auto|always|never Colorize human table output and help text (default: auto)\n --no-color Disable color output (also honors NO_COLOR)\n\nCommands:\n onboarding Guided, non-destructive first-run checks\n help Detailed help for a command\n examples Copy-paste examples from the command spec\n completion bash|zsh|fish Shell completion scripts\n login Sandbox device login\n init guardrail Scaffold a sandbox paid-tool guardrail integration file\n mcp serve|install|verify-config|tools MCP server and host configuration\n doctor Validate local runtime, credentials, and optional agent setup\n version Print package version (use --verbose for runtime details)\n diagnose Emit a redacted support bundle with --redacted\n config get|set|unset|list\n Manage CLI configuration values\n whoami Resolve the authenticated principal and tenant realm\n keys list|create|rotate|revoke\n Manage tenant service-account API keys\n intents list|get|create|fund|evidence|settlement-confirm\n Harbor intent workflows\n guardrails bootstrap|evidence\n Sandbox guardrail helpers\n spend authorize Authorize delegated spend for a tool call\n signal reputation|portfolio|fraud\n Signal summaries and fraud reads\n receipts get|verify\n Protocol settlement receipts\n mandates verify|import\n Agent mandate verification and import\n a2a card|contracts\n Agent card discovery and task contracts\n audit exports list|get|verify|delete\n Compliance audit export jobs and local bundle verification\n\nGetting started:\n Sandbox setup\n Authenticate, validate the runtime, and confirm tenant context.\n $ paybond login\n $ paybond doctor --format json\n Next: paybond whoami\n Agent + MCP host\n Preview MCP host config and list tools exposed to agents.\n $ paybond mcp install --host claude --scope local\n $ paybond mcp verify-config --host claude\n $ paybond mcp tools\n Next: paybond doctor --agent\n Paid-tool guardrail\n Scaffold a sandbox guardrail integration file for your agent runtime.\n $ paybond init guardrail\n $ paybond guardrails bootstrap --operation paid-tool --requested-spend-cents 100\n Next: paybond spend authorize --help\n\nDocumentation: https://docs.paybond.ai/kit\n\nLearn more:\n paybond help <command> Detailed help for a command\n paybond examples Copy-paste command examples\n paybond onboarding Guided first-run checks\n paybond completion <shell> Shell completions (bash|zsh|fish)\n\nLegacy aliases: paybond-kit-login, paybond-init, paybond-kit-init, paybond-mcp-server\n";
|
|
2
|
+
export declare const COMMAND_HELP: Record<string, string>;
|
|
3
|
+
export declare function helpForCommand(path: string): string;
|