@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,51 @@
1
+ // Generated by kit/cli-parity/generate.mjs — do not edit by hand.
2
+ export 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";
3
+ export const COMMAND_HELP = {
4
+ "onboarding": "Usage: paybond onboarding [--host claude|codex|openai|generic]\n\nRuns a guided, non-destructive first-run workflow: runtime check, login status, guardrail file status, MCP config preview, and doctor.\n\nExamples:\n $ paybond onboarding\n $ paybond onboarding --host claude --format json\n\nDocs: https://docs.paybond.ai/kit/coding-agent-setup\n\nNext: paybond login",
5
+ "help": "Usage: paybond help [<command> [<subcommand> ...]]\n\nShows detailed help for a command path. Without arguments, prints root help.\n\nExamples:\n $ paybond help\n $ paybond help login\n $ paybond help mcp install",
6
+ "examples": "Usage: paybond examples [<command> [<subcommand> ...]]\n\nLists copy-paste examples from the command spec. Filter with an optional command path.\n\nExamples:\n $ paybond examples\n $ paybond examples doctor",
7
+ "completion": "Usage: paybond completion bash|zsh|fish\n\nPrints a shell completion script generated from the command spec. Pipe to your shell config.\n\nExamples:\n $ paybond completion bash >> ~/.bashrc\n $ paybond completion zsh >> ~/.zshrc\n $ paybond completion fish | source",
8
+ "login": "Usage: paybond login [--env sandbox] [--env-file .env.local] [--gateway <url>] [--no-open] [--force]\n\nStarts sandbox device login and writes PAYBOND_API_KEY to the env file.\n\nExamples:\n $ paybond login\n $ paybond login --no-open --format json\n\nDocs: https://docs.paybond.ai/kit/authentication\n\nNext: paybond doctor",
9
+ "init guardrail": "Usage: paybond init guardrail [--preset paid-tool-guard] [--framework <name>] [--out <path>] [--force]\n\nScaffolds a sandbox paid-tool guardrail integration file.\n\nExamples:\n $ paybond init guardrail\n $ paybond init guardrail --framework openai --out guard.ts\n\nDocs: https://docs.paybond.ai/kit/index\n\nNext: paybond guardrails bootstrap --operation paid-tool --requested-spend-cents 100",
10
+ "mcp serve": "Usage: paybond mcp serve\n\nRuns the stdio MCP server until the host process exits. Diagnostics print to stderr; stdout is reserved for MCP JSON-RPC.\n\nExamples:\n $ paybond mcp serve",
11
+ "mcp install": "Usage: paybond mcp install --host claude|codex|openai|generic [--scope local|project|user] [--format json|toml] [--out <path>] [--env-file <path>] [--tool-policy readonly|spend-write|allowlist] [--tool-allowlist <names>]\n\nWrites a stdio MCP server entry that references PAYBOND_ENV_FILE (never raw API keys).\nDefault format is TOML for codex and JSON for other hosts. --scope local prints without writing.\nOptional --tool-policy controls which Paybond MCP tools the server exposes.\n\nExamples:\n $ paybond mcp install --host claude\n $ paybond mcp install --host generic --scope project --tool-policy readonly\n\nDocs: https://docs.paybond.ai/kit/coding-agent-setup\n\nNext: paybond mcp verify-config --host claude",
12
+ "mcp verify-config": "Usage: paybond mcp verify-config --host claude|codex|openai|generic [--format json|toml] [--env-file <path>] [--config <path>]\n\nValidates a generated or on-disk MCP host config without launching the MCP server.\n\nExamples:\n $ paybond mcp verify-config --host claude\n $ paybond mcp verify-config --host generic --config .paybond/mcp.json\n\nDocs: https://docs.paybond.ai/kit/coding-agent-setup\n\nNext: paybond doctor --agent",
13
+ "mcp tools": "Usage: paybond mcp tools\n\nLists tools exposed by the local MCP server.\n\nExamples:\n $ paybond mcp tools",
14
+ "doctor": "Usage: paybond doctor [--agent] [--host claude|codex|openai|generic]\n\nValidates runtime, package version, env file, key shape, principal lookup, and with --agent deep MCP checks (host config, env resolution, launch, initialize, tools/list, schema validation, stdout purity).\n\nExamples:\n $ paybond doctor\n $ paybond doctor --agent --format json\n\nDocs: https://docs.paybond.ai/kit/coding-agent-setup\n\nNext: paybond mcp install --host claude --scope local",
15
+ "version": "Usage: paybond version [--verbose]\n\nPrints the installed Kit package version. With --verbose, includes runtime, platform, config paths, gateway URL, request ID, MCP tool count, and redacted credential source.\n\nExamples:\n $ paybond version\n $ paybond version --verbose --format json\n\nDocs: https://docs.paybond.ai/kit/package-provenance",
16
+ "diagnose": "Usage: paybond diagnose --redacted\n\nEmits a redacted support bundle for troubleshooting. Never prints raw API keys.\n\nExamples:\n $ paybond diagnose --redacted\n $ paybond diagnose --redacted --format json\n\nDocs: https://docs.paybond.ai/kit/package-provenance",
17
+ "config get": "Usage: paybond config get <key>\n\nExamples:\n $ paybond config get gateway",
18
+ "config set": "Usage: paybond config set <key> <value>\n\nExamples:\n $ paybond config set gateway https://api.paybond.ai",
19
+ "config unset": "Usage: paybond config unset <key>\n\nExamples:\n $ paybond config unset gateway",
20
+ "config list": "Usage: paybond config list\n\nExamples:\n $ paybond config list --format json",
21
+ "whoami": "Usage: paybond whoami\n\nResolves the authenticated principal and tenant realm.\n\nExamples:\n $ paybond whoami\n $ paybond whoami --format json",
22
+ "keys list": "Usage: paybond keys list\n\nExamples:\n $ paybond keys list --format json",
23
+ "keys create": "Usage: paybond keys create --name <service-account> --role <role> [--label <label>]\n\nExamples:\n $ paybond keys create --name ci-bot --role operator",
24
+ "keys rotate": "Usage: paybond keys rotate <key_id> [--yes]\n\nExamples:\n $ paybond keys rotate key-123 --yes",
25
+ "keys revoke": "Usage: paybond keys revoke <key_id> [--yes]\n\nExamples:\n $ paybond keys revoke key-123 --yes",
26
+ "intents list": "Usage: paybond intents list [--status <status>] [--limit <n>]\n\nExamples:\n $ paybond intents list --limit 10",
27
+ "intents get": "Usage: paybond intents get <intent_id>\n\nExamples:\n $ paybond intents get intent-123",
28
+ "intents create": "Usage: paybond intents create --body <json-file>\n\nExamples:\n $ paybond intents create --body intent.json",
29
+ "intents fund": "Usage: paybond intents fund <intent_id> [--body <json-file>]\n\nExamples:\n $ paybond intents fund intent-123",
30
+ "intents evidence": "Usage: paybond intents evidence <intent_id> --body <json-file>\n\nExamples:\n $ paybond intents evidence intent-123 --body evidence.json",
31
+ "intents settlement-confirm": "Usage: paybond intents settlement-confirm <intent_id> [--body <json-file>]\n\nExamples:\n $ paybond intents settlement-confirm intent-123",
32
+ "guardrails bootstrap": "Usage: paybond guardrails bootstrap --operation <name> --requested-spend-cents <n>\n\nExamples:\n $ paybond guardrails bootstrap --operation paid-tool --requested-spend-cents 100",
33
+ "guardrails evidence": "Usage: paybond guardrails evidence --intent-id <id> --body <json-file>\n\nExamples:\n $ paybond guardrails evidence --intent-id intent-123 --body evidence.json",
34
+ "spend authorize": "Usage: paybond spend authorize --intent-id <id> --token <capability> --operation <name> [--requested-spend-cents <n>]\n\nExamples:\n $ paybond spend authorize --intent-id intent-123 --token cap --operation paid-tool",
35
+ "signal reputation": "Usage: paybond signal reputation --did <did>\n\nExamples:\n $ paybond signal reputation --did did:example:alice",
36
+ "signal portfolio": "Usage: paybond signal portfolio\n\nExamples:\n $ paybond signal portfolio",
37
+ "signal fraud": "Usage: paybond signal fraud --did <did>\n\nExamples:\n $ paybond signal fraud --did did:example:alice",
38
+ "receipts get": "Usage: paybond receipts get <receipt_id>\n\nExamples:\n $ paybond receipts get receipt-123",
39
+ "receipts verify": "Usage: paybond receipts verify <receipt_id>\n\nExamples:\n $ paybond receipts verify receipt-123",
40
+ "mandates verify": "Usage: paybond mandates verify --body <json-file>\n\nExamples:\n $ paybond mandates verify --body mandate.json",
41
+ "mandates import": "Usage: paybond mandates import --body <json-file>\n\nExamples:\n $ paybond mandates import --body mandate.json",
42
+ "a2a card": "Usage: paybond a2a card\n\nExamples:\n $ paybond a2a card",
43
+ "a2a contracts": "Usage: paybond a2a contracts [--contract-id <id>]\n\nExamples:\n $ paybond a2a contracts",
44
+ "audit exports list": "Usage: paybond audit exports list\n\nExamples:\n $ paybond audit exports list --format json",
45
+ "audit exports get": "Usage: paybond audit exports get <job_id> [--issue-download]\n\nExamples:\n $ paybond audit exports get job-123",
46
+ "audit exports verify": "Usage: paybond audit exports verify <path>\n\nExamples:\n $ paybond audit exports verify ./bundle",
47
+ "audit exports delete": "Usage: paybond audit exports delete <job_id> [--yes]\n\nExamples:\n $ paybond audit exports delete job-123 --yes",
48
+ };
49
+ export function helpForCommand(path) {
50
+ return COMMAND_HELP[path] ?? ROOT_HELP;
51
+ }
@@ -0,0 +1,41 @@
1
+ import { type McpToolPolicyConfig } from "./mcp-policy.js";
2
+ export type McpInstallFormat = "json" | "toml";
3
+ export type McpInstallScope = "local" | "project" | "user";
4
+ export type McpInstallHost = "claude" | "codex" | "openai" | "generic";
5
+ export declare const MCP_INSTALL_HOSTS: readonly McpInstallHost[];
6
+ export type McpServerEntry = {
7
+ command: string;
8
+ args: string[];
9
+ env: Record<string, string>;
10
+ };
11
+ export type McpInstallPlan = {
12
+ host: string;
13
+ scope: McpInstallScope;
14
+ format: McpInstallFormat;
15
+ envFile: string;
16
+ configPath: string | null;
17
+ serverCommand: string[];
18
+ payload: string;
19
+ printed: boolean;
20
+ toolPolicy?: McpToolPolicyConfig | null;
21
+ };
22
+ export declare function resolvePackageLocalMcpServerCommand(): string[];
23
+ export declare function defaultMcpServerCommand(): string[];
24
+ export declare function buildMcpServerEntry(envFile: string, serverCommand: string[], toolPolicy?: McpToolPolicyConfig | null): McpServerEntry;
25
+ export declare function serializeMcpInstallPayload(format: McpInstallFormat, entry: McpServerEntry): string;
26
+ export declare function resolveMcpInstallPath(scope: McpInstallScope, format: McpInstallFormat, out: string | undefined, cwd: string, home: string): string | null;
27
+ export declare function defaultMcpInstallFormat(host: McpInstallHost): McpInstallFormat;
28
+ export declare function planMcpInstall(input: {
29
+ host: McpInstallHost;
30
+ scope: McpInstallScope;
31
+ format: McpInstallFormat;
32
+ envFile: string;
33
+ out?: string;
34
+ cwd: string;
35
+ home: string;
36
+ serverCommand?: string[];
37
+ toolPolicy?: McpToolPolicyConfig | null;
38
+ }): McpInstallPlan;
39
+ export declare function parseMcpInstallHost(raw: string | undefined): McpInstallHost;
40
+ export declare function parseMcpInstallFormat(raw: string | undefined, host: McpInstallHost): McpInstallFormat;
41
+ export declare function parseMcpInstallScope(raw: string | undefined): McpInstallScope;
@@ -0,0 +1,95 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { mcpToolPolicyEnv } from "./mcp-policy.js";
3
+ export const MCP_INSTALL_HOSTS = ["claude", "codex", "openai", "generic"];
4
+ export function resolvePackageLocalMcpServerCommand() {
5
+ const mcpServerJs = fileURLToPath(new URL("../mcp-server.js", import.meta.url));
6
+ return [process.execPath, mcpServerJs];
7
+ }
8
+ export function defaultMcpServerCommand() {
9
+ return resolvePackageLocalMcpServerCommand();
10
+ }
11
+ export function buildMcpServerEntry(envFile, serverCommand, toolPolicy) {
12
+ return {
13
+ command: serverCommand[0],
14
+ args: serverCommand.slice(1),
15
+ env: { PAYBOND_ENV_FILE: envFile, ...mcpToolPolicyEnv(toolPolicy ?? { policy: null, allowlist: [] }) },
16
+ };
17
+ }
18
+ export function serializeMcpInstallPayload(format, entry) {
19
+ if (format === "toml") {
20
+ const argsJson = JSON.stringify(entry.args);
21
+ return [
22
+ "# Paybond MCP stdio server — merge into your host MCP config",
23
+ "[mcp_servers.paybond]",
24
+ `command = ${JSON.stringify(entry.command)}`,
25
+ `args = ${argsJson}`,
26
+ "",
27
+ "[mcp_servers.paybond.env]",
28
+ ...Object.entries(entry.env).map(([key, value]) => `${key} = ${JSON.stringify(value)}`),
29
+ "",
30
+ ].join("\n");
31
+ }
32
+ return `${JSON.stringify({
33
+ mcpServers: {
34
+ paybond: entry,
35
+ },
36
+ }, null, 2)}\n`;
37
+ }
38
+ export function resolveMcpInstallPath(scope, format, out, cwd, home) {
39
+ if (out?.trim()) {
40
+ return out.trim();
41
+ }
42
+ if (scope === "local") {
43
+ return null;
44
+ }
45
+ const ext = format === "toml" ? "toml" : "json";
46
+ const base = scope === "user" ? home : cwd;
47
+ return `${base.replace(/\/+$/, "")}/.paybond/mcp.${ext}`;
48
+ }
49
+ export function defaultMcpInstallFormat(host) {
50
+ return host === "codex" ? "toml" : "json";
51
+ }
52
+ export function planMcpInstall(input) {
53
+ const serverCommand = input.serverCommand ?? defaultMcpServerCommand();
54
+ const entry = buildMcpServerEntry(input.envFile, serverCommand, input.toolPolicy);
55
+ const payload = serializeMcpInstallPayload(input.format, entry);
56
+ const configPath = resolveMcpInstallPath(input.scope, input.format, input.out, input.cwd, input.home);
57
+ return {
58
+ host: input.host,
59
+ scope: input.scope,
60
+ format: input.format,
61
+ envFile: input.envFile,
62
+ configPath,
63
+ serverCommand,
64
+ payload,
65
+ printed: configPath === null,
66
+ toolPolicy: input.toolPolicy ?? null,
67
+ };
68
+ }
69
+ export function parseMcpInstallHost(raw) {
70
+ const value = (raw ?? "").trim().toLowerCase();
71
+ if (!value) {
72
+ throw new Error("missing --host (expected claude|codex|openai|generic)");
73
+ }
74
+ if (MCP_INSTALL_HOSTS.includes(value)) {
75
+ return value;
76
+ }
77
+ throw new Error("invalid --host (expected claude|codex|openai|generic)");
78
+ }
79
+ export function parseMcpInstallFormat(raw, host) {
80
+ if (!raw?.trim()) {
81
+ return defaultMcpInstallFormat(host);
82
+ }
83
+ const value = raw.trim().toLowerCase();
84
+ if (value === "json" || value === "toml") {
85
+ return value;
86
+ }
87
+ throw new Error("invalid --format (expected json|toml)");
88
+ }
89
+ export function parseMcpInstallScope(raw) {
90
+ const value = (raw ?? "project").trim().toLowerCase();
91
+ if (value === "local" || value === "project" || value === "user") {
92
+ return value;
93
+ }
94
+ throw new Error("invalid --scope (expected local|project|user)");
95
+ }
@@ -0,0 +1,23 @@
1
+ export type McpToolPolicy = "readonly" | "spend-write" | "allowlist";
2
+ export declare const MCP_TOOL_POLICY_ENV = "PAYBOND_MCP_TOOL_POLICY";
3
+ export declare const MCP_TOOL_ALLOWLIST_ENV = "PAYBOND_MCP_TOOL_ALLOWLIST";
4
+ export declare const LIVE_MONEY_TOOL_NAMES: Set<string>;
5
+ export type McpToolPolicyConfig = {
6
+ policy: McpToolPolicy | null;
7
+ allowlist: readonly string[];
8
+ };
9
+ export type McpToolAnnotations = {
10
+ readOnlyHint?: boolean;
11
+ destructiveHint?: boolean;
12
+ };
13
+ export declare function parseMcpToolPolicy(raw: string | undefined): McpToolPolicyConfig;
14
+ export declare function parseMcpToolAllowlist(raw: string | undefined): string[];
15
+ export declare function mergeMcpToolPolicy(policy: McpToolPolicyConfig, allowlist?: readonly string[]): McpToolPolicyConfig;
16
+ export declare function mcpToolPolicyEnv(config: McpToolPolicyConfig): Record<string, string>;
17
+ export declare function toolAnnotationsFlags(annotations: McpToolAnnotations | undefined): {
18
+ readOnly: boolean;
19
+ destructive: boolean;
20
+ };
21
+ export declare function toolAllowedByPolicy(name: string, annotations: McpToolAnnotations | undefined, config: McpToolPolicyConfig): boolean;
22
+ export declare function validateMcpToolSchema(tool: Record<string, unknown>): string[];
23
+ export declare function isLiveMoneyTool(name: string, annotations: McpToolAnnotations | undefined): boolean;
@@ -0,0 +1,104 @@
1
+ export const MCP_TOOL_POLICY_ENV = "PAYBOND_MCP_TOOL_POLICY";
2
+ export const MCP_TOOL_ALLOWLIST_ENV = "PAYBOND_MCP_TOOL_ALLOWLIST";
3
+ export const LIVE_MONEY_TOOL_NAMES = new Set([
4
+ "paybond_fund_intent",
5
+ "paybond_confirm_settlement",
6
+ ]);
7
+ export function parseMcpToolPolicy(raw) {
8
+ const value = (raw ?? "").trim().toLowerCase();
9
+ if (!value) {
10
+ return { policy: null, allowlist: [] };
11
+ }
12
+ if (value === "readonly" || value === "spend-write" || value === "allowlist") {
13
+ return { policy: value, allowlist: [] };
14
+ }
15
+ throw new Error("invalid --tool-policy (expected readonly|spend-write|allowlist)");
16
+ }
17
+ export function parseMcpToolAllowlist(raw) {
18
+ if (!raw?.trim()) {
19
+ return [];
20
+ }
21
+ const items = raw
22
+ .split(",")
23
+ .map((part) => part.trim())
24
+ .filter(Boolean);
25
+ if (items.length === 0) {
26
+ throw new Error("invalid --tool-allowlist (expected comma-separated tool names)");
27
+ }
28
+ return items;
29
+ }
30
+ export function mergeMcpToolPolicy(policy, allowlist) {
31
+ const mergedAllowlist = allowlist ?? policy.allowlist;
32
+ if (policy.policy === "allowlist" && mergedAllowlist.length === 0) {
33
+ throw new Error("--tool-allowlist is required when --tool-policy allowlist");
34
+ }
35
+ if (policy.policy !== "allowlist" && mergedAllowlist.length > 0) {
36
+ throw new Error("--tool-allowlist is only valid with --tool-policy allowlist");
37
+ }
38
+ return { policy: policy.policy, allowlist: [...mergedAllowlist] };
39
+ }
40
+ export function mcpToolPolicyEnv(config) {
41
+ if (config.policy === null) {
42
+ return {};
43
+ }
44
+ const env = {
45
+ [MCP_TOOL_POLICY_ENV]: config.policy,
46
+ };
47
+ if (config.policy === "allowlist") {
48
+ env[MCP_TOOL_ALLOWLIST_ENV] = config.allowlist.join(",");
49
+ }
50
+ return env;
51
+ }
52
+ export function toolAnnotationsFlags(annotations) {
53
+ return {
54
+ readOnly: annotations?.readOnlyHint === true,
55
+ destructive: annotations?.destructiveHint === true,
56
+ };
57
+ }
58
+ export function toolAllowedByPolicy(name, annotations, config) {
59
+ if (config.policy === null) {
60
+ return true;
61
+ }
62
+ const { readOnly, destructive } = toolAnnotationsFlags(annotations);
63
+ if (config.policy === "readonly") {
64
+ return readOnly;
65
+ }
66
+ if (config.policy === "spend-write") {
67
+ return !isLiveMoneyTool(name, annotations);
68
+ }
69
+ if (config.policy === "allowlist") {
70
+ return new Set(config.allowlist).has(name);
71
+ }
72
+ return true;
73
+ }
74
+ export function validateMcpToolSchema(tool) {
75
+ const errors = [];
76
+ const name = typeof tool.name === "string" ? tool.name : "";
77
+ if (!name.trim()) {
78
+ errors.push("tool missing non-empty name");
79
+ }
80
+ const description = tool.description;
81
+ if (typeof description !== "string" || !description.trim()) {
82
+ errors.push(`${name || "<unknown>"}: missing description`);
83
+ }
84
+ const inputSchema = tool.inputSchema;
85
+ if (!inputSchema || typeof inputSchema !== "object" || Array.isArray(inputSchema)) {
86
+ errors.push(`${name || "<unknown>"}: inputSchema must be an object`);
87
+ }
88
+ else if (inputSchema.type !== "object") {
89
+ errors.push(`${name || "<unknown>"}: inputSchema.type must be object`);
90
+ }
91
+ const outputSchema = tool.outputSchema;
92
+ if (outputSchema !== undefined && (typeof outputSchema !== "object" || Array.isArray(outputSchema))) {
93
+ errors.push(`${name || "<unknown>"}: outputSchema must be an object when present`);
94
+ }
95
+ const annotations = tool.annotations;
96
+ if (annotations !== undefined && (typeof annotations !== "object" || Array.isArray(annotations))) {
97
+ errors.push(`${name || "<unknown>"}: annotations must be an object when present`);
98
+ }
99
+ return errors;
100
+ }
101
+ export function isLiveMoneyTool(name, annotations) {
102
+ const { destructive } = toolAnnotationsFlags(annotations);
103
+ return destructive || LIVE_MONEY_TOOL_NAMES.has(name);
104
+ }
@@ -0,0 +1,37 @@
1
+ import { type McpInstallFormat, type McpInstallHost, type McpInstallScope, type McpServerEntry } from "./mcp-install.js";
2
+ import { type McpToolPolicyConfig } from "./mcp-policy.js";
3
+ export type McpConfigSource = "generated" | "file";
4
+ export type McpConfigValidationIssue = {
5
+ field: string;
6
+ message: string;
7
+ };
8
+ export type McpConfigValidationResult = {
9
+ ok: boolean;
10
+ host: string;
11
+ source: McpConfigSource;
12
+ configPath: string | null;
13
+ issues: McpConfigValidationIssue[];
14
+ entry?: McpServerEntry;
15
+ toolPolicy?: McpToolPolicyConfig;
16
+ message: string;
17
+ };
18
+ export declare function parseMcpHostEntry(payload: string, format: McpInstallFormat): McpServerEntry;
19
+ export declare function validateMcpHostConfig(input: {
20
+ host: McpInstallHost;
21
+ format: McpInstallFormat;
22
+ payload: string;
23
+ cwd: string;
24
+ expectedEnvFile?: string;
25
+ source?: McpConfigSource;
26
+ configPath?: string | null;
27
+ }): McpConfigValidationResult;
28
+ export declare function verifyMcpInstallPlan(input: {
29
+ host: McpInstallHost;
30
+ scope?: McpInstallScope;
31
+ format: McpInstallFormat;
32
+ envFile: string;
33
+ cwd: string;
34
+ home: string;
35
+ configPath?: string;
36
+ toolPolicy?: McpToolPolicyConfig;
37
+ }): Promise<McpConfigValidationResult>;
@@ -0,0 +1,174 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { defaultMcpServerCommand, planMcpInstall, } from "./mcp-install.js";
5
+ import { MCP_TOOL_ALLOWLIST_ENV, MCP_TOOL_POLICY_ENV, mergeMcpToolPolicy, parseMcpToolAllowlist, parseMcpToolPolicy, } from "./mcp-policy.js";
6
+ function validationMessage(ok, issues) {
7
+ if (ok) {
8
+ return "MCP host config is valid";
9
+ }
10
+ return issues[0]?.message ?? "invalid MCP config";
11
+ }
12
+ function parseTomlPaybondEntry(payload) {
13
+ const section = "[mcp_servers.paybond]";
14
+ if (!payload.includes(section)) {
15
+ throw new Error("missing [mcp_servers.paybond] section");
16
+ }
17
+ const afterSection = payload.split(section, 1)[1] ?? "";
18
+ const envSection = "[mcp_servers.paybond.env]";
19
+ const [block, envBlock = ""] = afterSection.includes(envSection)
20
+ ? afterSection.split(envSection)
21
+ : [afterSection, ""];
22
+ const commandMatch = /command\s*=\s*(.+)/.exec(block);
23
+ const argsMatch = /args\s*=\s*(\[[\s\S]*?\])/.exec(block);
24
+ if (!commandMatch?.[1]) {
25
+ throw new Error("missing paybond command");
26
+ }
27
+ const command = JSON.parse(commandMatch[1].trim());
28
+ const args = argsMatch?.[1] ? JSON.parse(argsMatch[1]) : [];
29
+ const env = {};
30
+ for (const line of envBlock.split("\n")) {
31
+ const stripped = line.trim();
32
+ if (!stripped || stripped.startsWith("#") || !stripped.includes("=")) {
33
+ continue;
34
+ }
35
+ const [key, value] = stripped.split("=", 2);
36
+ env[key.trim()] = JSON.parse(value.trim());
37
+ }
38
+ return { command, args, env };
39
+ }
40
+ export function parseMcpHostEntry(payload, format) {
41
+ const parsed = format === "toml"
42
+ ? parseTomlPaybondEntry(payload)
43
+ : (() => {
44
+ const body = JSON.parse(payload);
45
+ const servers = body.mcpServers;
46
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
47
+ throw new Error("missing mcpServers object");
48
+ }
49
+ const paybond = servers.paybond;
50
+ if (!paybond || typeof paybond !== "object" || Array.isArray(paybond)) {
51
+ throw new Error("missing mcpServers.paybond entry");
52
+ }
53
+ return paybond;
54
+ })();
55
+ const command = parsed.command;
56
+ if (typeof command !== "string" || !command.trim()) {
57
+ throw new Error("missing paybond command");
58
+ }
59
+ const args = parsed.args;
60
+ if (!Array.isArray(args) || !args.every((item) => typeof item === "string")) {
61
+ throw new Error("paybond args must be a string array");
62
+ }
63
+ const envRaw = parsed.env;
64
+ if (envRaw !== undefined && (typeof envRaw !== "object" || Array.isArray(envRaw))) {
65
+ throw new Error("paybond env must be an object");
66
+ }
67
+ const env = {};
68
+ for (const [key, value] of Object.entries(envRaw ?? {})) {
69
+ env[key] = String(value);
70
+ }
71
+ return {
72
+ command,
73
+ args: [...args],
74
+ env,
75
+ };
76
+ }
77
+ function validateEntry(entry, cwd, expectedEnvFile) {
78
+ const issues = [];
79
+ if ("PAYBOND_API_KEY" in entry.env) {
80
+ issues.push({ field: "env", message: "config must reference PAYBOND_ENV_FILE, not PAYBOND_API_KEY" });
81
+ }
82
+ const envFile = entry.env.PAYBOND_ENV_FILE?.trim() ?? "";
83
+ if (!envFile) {
84
+ issues.push({ field: "env", message: "missing PAYBOND_ENV_FILE" });
85
+ }
86
+ else if (expectedEnvFile && envFile !== expectedEnvFile) {
87
+ issues.push({
88
+ field: "env",
89
+ message: `PAYBOND_ENV_FILE mismatch: config=${JSON.stringify(envFile)} expected=${JSON.stringify(expectedEnvFile)}`,
90
+ });
91
+ }
92
+ const resolvedEnv = path.isAbsolute(envFile) ? path.resolve(envFile) : path.resolve(cwd, envFile);
93
+ if (envFile && !existsSync(resolvedEnv)) {
94
+ issues.push({ field: "env", message: `env file not found: ${resolvedEnv}` });
95
+ }
96
+ if (!entry.command.trim()) {
97
+ issues.push({ field: "command", message: "missing MCP server command" });
98
+ }
99
+ let toolPolicy;
100
+ const rawPolicy = entry.env[MCP_TOOL_POLICY_ENV]?.trim() ?? "";
101
+ const rawAllowlist = entry.env[MCP_TOOL_ALLOWLIST_ENV]?.trim() ?? "";
102
+ if (rawPolicy || rawAllowlist) {
103
+ try {
104
+ toolPolicy = mergeMcpToolPolicy(parseMcpToolPolicy(rawPolicy || undefined), parseMcpToolAllowlist(rawAllowlist || undefined));
105
+ }
106
+ catch (err) {
107
+ issues.push({
108
+ field: "tool_policy",
109
+ message: err instanceof Error ? err.message : String(err),
110
+ });
111
+ }
112
+ }
113
+ return { issues, toolPolicy };
114
+ }
115
+ export function validateMcpHostConfig(input) {
116
+ try {
117
+ const entry = parseMcpHostEntry(input.payload, input.format);
118
+ const { issues, toolPolicy } = validateEntry(entry, input.cwd, input.expectedEnvFile);
119
+ const ok = issues.length === 0;
120
+ return {
121
+ ok,
122
+ host: input.host,
123
+ source: input.source ?? "generated",
124
+ configPath: input.configPath ?? null,
125
+ issues,
126
+ entry,
127
+ toolPolicy,
128
+ message: validationMessage(ok, issues),
129
+ };
130
+ }
131
+ catch (err) {
132
+ const issues = [{ field: "config", message: err instanceof Error ? err.message : String(err) }];
133
+ return {
134
+ ok: false,
135
+ host: input.host,
136
+ source: input.source ?? "generated",
137
+ configPath: input.configPath ?? null,
138
+ issues,
139
+ message: validationMessage(false, issues),
140
+ };
141
+ }
142
+ }
143
+ export async function verifyMcpInstallPlan(input) {
144
+ let payload;
145
+ let source = "generated";
146
+ let configPath = input.configPath ?? null;
147
+ if (input.configPath) {
148
+ payload = await readFile(input.configPath, "utf8");
149
+ source = "file";
150
+ }
151
+ else {
152
+ const plan = planMcpInstall({
153
+ host: input.host,
154
+ scope: input.scope ?? "local",
155
+ format: input.format,
156
+ envFile: input.envFile,
157
+ cwd: input.cwd,
158
+ home: input.home,
159
+ serverCommand: defaultMcpServerCommand(),
160
+ toolPolicy: input.toolPolicy,
161
+ });
162
+ payload = plan.payload;
163
+ configPath = plan.configPath;
164
+ }
165
+ return validateMcpHostConfig({
166
+ host: input.host,
167
+ format: input.format,
168
+ payload,
169
+ cwd: input.cwd,
170
+ expectedEnvFile: input.envFile,
171
+ source,
172
+ configPath,
173
+ });
174
+ }
@@ -0,0 +1,4 @@
1
+ export declare function maskApiKey(rawKey: string): string;
2
+ export declare function redactSensitiveFields(value: unknown): unknown;
3
+ export declare function isSensitiveConfigKey(key: string): boolean;
4
+ export declare function redactConfigValue(key: string, value: string): string;
@@ -0,0 +1,67 @@
1
+ export function maskApiKey(rawKey) {
2
+ const trimmed = rawKey.trim();
3
+ const parts = trimmed.split("_");
4
+ if (parts.length >= 5 && parts[0] === "paybond" && parts[1] === "sk") {
5
+ const environment = parts[2];
6
+ const keyId = parts[3];
7
+ const redactedKeyId = keyId.length > 12 ? `${keyId.slice(0, 8)}...${keyId.slice(-4)}` : "redacted";
8
+ return `paybond_sk_${environment}_${redactedKeyId}`;
9
+ }
10
+ return "paybond_sk_...";
11
+ }
12
+ export function redactSensitiveFields(value) {
13
+ if (Array.isArray(value)) {
14
+ return value.map((item) => redactSensitiveFields(item));
15
+ }
16
+ if (value && typeof value === "object") {
17
+ const out = {};
18
+ for (const [key, child] of Object.entries(value)) {
19
+ if (child && typeof child === "object") {
20
+ out[key] = redactSensitiveFields(child);
21
+ continue;
22
+ }
23
+ const lowered = key.toLowerCase();
24
+ if (lowered === "capability_token" ||
25
+ lowered === "access_token" ||
26
+ lowered === "refresh_token" ||
27
+ (lowered.endsWith("_token") && lowered !== "token_type")) {
28
+ out[key] = typeof child === "string" && child.trim() ? "[redacted]" : child;
29
+ }
30
+ else if (lowered === "api_key" || lowered.endsWith("_api_key")) {
31
+ out[key] = typeof child === "string" ? maskApiKey(child) : child;
32
+ }
33
+ else {
34
+ out[key] = child;
35
+ }
36
+ }
37
+ return out;
38
+ }
39
+ return value;
40
+ }
41
+ const SENSITIVE_CONFIG_KEY_EXACT = new Set([
42
+ "api_key",
43
+ "paybond_api_key",
44
+ "secret",
45
+ "client_secret",
46
+ "password",
47
+ ]);
48
+ const SENSITIVE_CONFIG_KEY_TOKEN_ALLOWLIST = new Set(["token_type", "token_endpoint"]);
49
+ export function isSensitiveConfigKey(key) {
50
+ const lowered = key.toLowerCase();
51
+ if (SENSITIVE_CONFIG_KEY_EXACT.has(lowered)) {
52
+ return true;
53
+ }
54
+ if (lowered.endsWith("_token") && !SENSITIVE_CONFIG_KEY_TOKEN_ALLOWLIST.has(lowered)) {
55
+ return true;
56
+ }
57
+ if (lowered.endsWith("_api_key") || lowered.endsWith("_secret") || lowered.endsWith("_password")) {
58
+ return true;
59
+ }
60
+ return false;
61
+ }
62
+ export function redactConfigValue(key, value) {
63
+ if (!isSensitiveConfigKey(key)) {
64
+ return value;
65
+ }
66
+ return value.trim() ? maskApiKey(value) : "";
67
+ }
@@ -0,0 +1,2 @@
1
+ /** Generate a correlation id suitable for Gateway request headers and JSON envelopes. */
2
+ export declare function generateRequestId(): string;
@@ -0,0 +1,5 @@
1
+ import { v4 as uuidv4 } from "uuid";
2
+ /** Generate a correlation id suitable for Gateway request headers and JSON envelopes. */
3
+ export function generateRequestId() {
4
+ return `01${uuidv4().replace(/-/g, "").slice(0, 24).toUpperCase()}`;
5
+ }
@@ -0,0 +1,2 @@
1
+ import { type CliDependencies } from "./types.js";
2
+ export declare function runCli(argv: string[], deps?: CliDependencies): Promise<number>;