@paybond/kit 0.9.3 → 0.9.5

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/init.js CHANGED
@@ -11,6 +11,7 @@ const FRAMEWORKS = new Set([
11
11
  "langgraph",
12
12
  "mcp",
13
13
  ]);
14
+ const PRESETS = new Set(["paid-tool-guard"]);
14
15
  const FRAMEWORK_NOTES = {
15
16
  generic: "Wrap the returned function around any side-effecting tool handler.",
16
17
  "provider-agnostic": "Use the guarded handler with OpenAI, Gemini, Claude/Anthropic, local models, or any custom runtime.",
@@ -25,26 +26,36 @@ const FRAMEWORK_NOTES = {
25
26
  };
26
27
  function usage() {
27
28
  return [
28
- "Usage: paybond-init [--framework generic|provider-agnostic|openai|claude|anthropic|gemini|google-ai|vercel-ai|langgraph|mcp] [--out paybond-spend-guard.ts] [--force]",
29
+ "Usage: paybond-init [--preset paid-tool-guard] [--framework generic|provider-agnostic|openai|claude|anthropic|gemini|google-ai|vercel-ai|langgraph|mcp] [--out paybond-paid-tool-guard.ts] [--force]",
29
30
  "",
30
- "Scaffolds a Paybond spend guard wrapper for delegated agent spend controls.",
31
+ "Scaffolds a production-shaped Paybond guardrail integration helper.",
31
32
  ].join("\n");
32
33
  }
33
34
  function parseArgs(argv) {
34
- let framework = "generic";
35
- let out = "paybond-spend-guard.ts";
35
+ let preset = "paid-tool-guard";
36
+ let framework = "provider-agnostic";
37
+ let out = "paybond-paid-tool-guard.ts";
36
38
  let force = false;
37
39
  for (let i = 0; i < argv.length; i += 1) {
38
40
  const arg = argv[i];
39
41
  if (arg === "--help" || arg === "-h") {
40
42
  process.stdout.write(`${usage()}\n`);
41
43
  process.exitCode = 0;
42
- return { framework, out, force };
44
+ return { preset, framework, out, force };
43
45
  }
44
46
  if (arg === "--force") {
45
47
  force = true;
46
48
  continue;
47
49
  }
50
+ if (arg === "--preset") {
51
+ const raw = argv[i + 1];
52
+ i += 1;
53
+ if (!raw || !PRESETS.has(raw)) {
54
+ throw new Error("invalid --preset");
55
+ }
56
+ preset = raw;
57
+ continue;
58
+ }
48
59
  if (arg === "--framework") {
49
60
  const raw = argv[i + 1];
50
61
  i += 1;
@@ -65,71 +76,166 @@ function parseArgs(argv) {
65
76
  }
66
77
  throw new Error(`unknown argument: ${arg}`);
67
78
  }
68
- return { framework, out, force };
79
+ return { preset, framework, out, force };
69
80
  }
70
81
  function template(framework) {
71
- return `import { Paybond, type FundIntentResult } from "@paybond/kit";
82
+ return `import {
83
+ Paybond,
84
+ type SandboxGuardrailBootstrapResult,
85
+ type SandboxGuardrailEvidenceResult,
86
+ } from "@paybond/kit";
72
87
 
73
- type ToolInput = {
74
- city: string;
75
- maxPriceCents: number;
88
+ declare const process: {
89
+ env: Record<string, string | undefined>;
76
90
  };
77
91
 
78
- type FundedIntent = {
79
- intentId: string;
80
- capabilityToken: string;
92
+ // Production integration helpers only. Add your paid-tool handler in
93
+ // application code and pass it to wrapPaidTool(...).
94
+ const DEFAULT_OPERATION = "paid_tool.operation";
95
+ const DEFAULT_REQUESTED_SPEND_CENTS = 500;
96
+
97
+ export type PaidToolHandler<TInput, TResult> = (input: TInput) => TResult | Promise<TResult>;
98
+
99
+ export type SandboxGuardrailIntentOptions = {
100
+ operation?: string;
101
+ requestedSpendCents?: number;
102
+ currency?: string;
103
+ evidenceSchema?: Record<string, unknown>;
104
+ metadata?: Record<string, unknown>;
105
+ idempotencyKey?: string;
81
106
  };
82
107
 
83
- export async function openPaybond(): Promise<Paybond> {
84
- return Paybond.open({
85
- apiKey: process.env.PAYBOND_API_KEY!,
86
- expectedEnvironment: "sandbox",
87
- });
108
+ export type SubmitSandboxEvidenceOptions = {
109
+ operation?: string;
110
+ requestedSpendCents?: number;
111
+ metadata?: Record<string, unknown>;
112
+ artifacts?: string[];
113
+ idempotencyKey?: string;
114
+ };
115
+
116
+ export type OpenPaybondFromEnvOptions = {
117
+ /**
118
+ * Load PAYBOND_API_KEY from this local env file when the process environment
119
+ * does not already provide it. Pass false when your agent host injects secrets.
120
+ */
121
+ envFile?: string | false;
122
+ };
123
+
124
+ function readEnvValue(body: string, key: string): string | undefined {
125
+ const pattern = new RegExp("^\\\\s*(?:export\\\\s+)?" + key + "\\\\s*=\\\\s*(.*)$", "m");
126
+ const match = body.match(pattern);
127
+ if (!match) return undefined;
128
+ let value = (match[1] ?? "").trim();
129
+ if (value.startsWith('"') && value.endsWith('"')) {
130
+ try {
131
+ value = JSON.parse(value);
132
+ } catch {
133
+ value = value.slice(1, -1);
134
+ }
135
+ } else if (value.startsWith("'") && value.endsWith("'")) {
136
+ value = value.slice(1, -1);
137
+ }
138
+ return value.trim() || undefined;
88
139
  }
89
140
 
90
- export function fundedIntentFromCreate(created: Record<string, unknown>): FundedIntent {
91
- const intentId = String(created["intent_id"] ?? "");
92
- const capabilityToken = String(created["capability_token"] ?? "");
93
- if (!intentId) {
94
- throw new Error("intent create response missing intent_id");
141
+ async function readTextFile(envFile: string): Promise<string | undefined> {
142
+ // @ts-ignore Node builtins are available in agent and CLI Node runtimes.
143
+ const fs: { readFile(path: string, encoding: "utf8"): Promise<string> } = await import("node:fs/promises");
144
+ try {
145
+ return await fs.readFile(envFile, "utf8");
146
+ } catch (err) {
147
+ if ((err as { code?: unknown })?.code === "ENOENT") return undefined;
148
+ throw err;
95
149
  }
96
- if (!capabilityToken) {
97
- throw new Error("intent is not funded yet; call paybond.intents.fund(...) before guarding tools");
150
+ }
151
+
152
+ export async function loadPaybondEnvFile(envFile = ".env.local"): Promise<void> {
153
+ if (process.env.PAYBOND_API_KEY?.trim()) return;
154
+ const body = await readTextFile(envFile);
155
+ if (body === undefined) return;
156
+ const apiKey = readEnvValue(body, "PAYBOND_API_KEY");
157
+ if (apiKey) {
158
+ process.env.PAYBOND_API_KEY = apiKey;
98
159
  }
99
- return { intentId, capabilityToken };
100
160
  }
101
161
 
102
- export function fundedIntentFromFund(funded: FundIntentResult): FundedIntent {
103
- if (!funded.capabilityToken) {
104
- throw new Error("intent funding did not return a capabilityToken yet");
162
+ export async function openPaybondFromEnv(options: OpenPaybondFromEnvOptions = {}): Promise<Paybond> {
163
+ if (options.envFile !== false) {
164
+ await loadPaybondEnvFile(options.envFile ?? ".env.local");
105
165
  }
106
- return { intentId: funded.intentId, capabilityToken: funded.capabilityToken };
166
+ const apiKey = process.env.PAYBOND_API_KEY?.trim();
167
+ if (!apiKey) {
168
+ throw new Error("PAYBOND_API_KEY is required; run paybond login or configure your agent host to pass it");
169
+ }
170
+
171
+ return Paybond.open({
172
+ apiKey,
173
+ gatewayBaseUrl: process.env.PAYBOND_GATEWAY_BASE_URL,
174
+ expectedEnvironment: "sandbox",
175
+ });
107
176
  }
108
177
 
109
- async function bookHotel(input: ToolInput): Promise<{ confirmation: string }> {
110
- // Put the side-effecting tool call here.
111
- return { confirmation: \`demo-\${input.city}-\${input.maxPriceCents}\` };
178
+ export async function bootstrapSandboxGuardrailIntent(
179
+ paybond: Paybond,
180
+ options: SandboxGuardrailIntentOptions = {},
181
+ ): Promise<SandboxGuardrailBootstrapResult> {
182
+ return paybond.guardrails.bootstrapSandbox({
183
+ operation: options.operation ?? DEFAULT_OPERATION,
184
+ requestedSpendCents: options.requestedSpendCents ?? DEFAULT_REQUESTED_SPEND_CENTS,
185
+ currency: options.currency ?? "usd",
186
+ evidenceSchema: options.evidenceSchema ?? {
187
+ type: "object",
188
+ required: ["confirmation_id", "charged_cents"],
189
+ properties: {
190
+ confirmation_id: { type: "string" },
191
+ charged_cents: { type: "integer" },
192
+ },
193
+ },
194
+ metadata: options.metadata,
195
+ idempotencyKey: options.idempotencyKey,
196
+ });
112
197
  }
113
198
 
114
- export async function buildGuardedHotelTool(params: {
115
- paybond: Paybond;
116
- intentId: string;
117
- capabilityToken: string;
118
- }): Promise<(input: ToolInput) => Promise<{ confirmation: string }>> {
119
- if (!params.capabilityToken.trim()) {
120
- throw new Error("fund the intent before guarding tools");
199
+ export function wrapPaidTool<TInput, TResult>(
200
+ paybond: Paybond,
201
+ guardrail: Pick<
202
+ SandboxGuardrailBootstrapResult,
203
+ "intent_id" | "capability_token" | "operation" | "requested_spend_cents"
204
+ >,
205
+ handler: PaidToolHandler<TInput, TResult>,
206
+ ): (input: TInput) => Promise<Awaited<TResult>> {
207
+ if (!guardrail.capability_token.trim()) {
208
+ throw new Error("sandbox guardrail bootstrap did not return a capability token");
121
209
  }
122
- const guard = params.paybond.spendGuard(params.intentId, params.capabilityToken);
210
+
211
+ const guard = paybond.spendGuard(guardrail.intent_id, guardrail.capability_token);
123
212
 
124
213
  // ${FRAMEWORK_NOTES[framework]}
125
214
  return guard.guardTool(
126
215
  {
127
- operation: "travel.book_hotel",
128
- requestedSpendCents: 20_000,
216
+ operation: guardrail.operation,
217
+ requestedSpendCents: guardrail.requested_spend_cents,
129
218
  },
130
- bookHotel,
219
+ handler,
131
220
  );
132
221
  }
222
+
223
+ export async function submitSandboxEvidence(
224
+ paybond: Paybond,
225
+ guardrail: Pick<SandboxGuardrailBootstrapResult, "intent_id" | "operation" | "requested_spend_cents">,
226
+ payload: Record<string, unknown>,
227
+ options: SubmitSandboxEvidenceOptions = {},
228
+ ): Promise<SandboxGuardrailEvidenceResult> {
229
+ return paybond.guardrails.submitSandboxEvidence({
230
+ intentId: guardrail.intent_id,
231
+ payload,
232
+ artifacts: options.artifacts,
233
+ operation: options.operation ?? guardrail.operation,
234
+ requestedSpendCents: options.requestedSpendCents ?? guardrail.requested_spend_cents,
235
+ metadata: options.metadata,
236
+ idempotencyKey: options.idempotencyKey,
237
+ });
238
+ }
133
239
  `;
134
240
  }
135
241
  async function writeScaffold(out, body, force) {
@@ -162,13 +268,28 @@ export async function main(argv = process.argv.slice(2)) {
162
268
  if (argv.includes("--help") || argv.includes("-h")) {
163
269
  return 0;
164
270
  }
165
- await writeScaffold(parsed.out, template(parsed.framework), parsed.force);
166
- process.stdout.write(`Created ${parsed.out}\n`);
271
+ try {
272
+ await writeScaffold(parsed.out, template(parsed.framework), parsed.force);
273
+ }
274
+ catch (err) {
275
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
276
+ return 1;
277
+ }
278
+ process.stdout.write(`Created Paybond guardrail integration: ${parsed.out}\n`);
167
279
  return 0;
168
280
  }
169
- main().then((code) => {
170
- process.exitCode = code;
171
- }, (err) => {
172
- process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
173
- process.exitCode = 1;
174
- });
281
+ function normalizeFileURL(url) {
282
+ return url.startsWith("file:///var/") ? url.replace("file:///var/", "file:///private/var/") : url;
283
+ }
284
+ function invokedFromCLI() {
285
+ const invokedPath = process.argv[1] ? normalizeFileURL(new URL("file://" + process.argv[1]).href) : "";
286
+ return Boolean(invokedPath && normalizeFileURL(import.meta.url) === invokedPath);
287
+ }
288
+ if (invokedFromCLI()) {
289
+ main().then((code) => {
290
+ process.exitCode = code;
291
+ }, (err) => {
292
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
293
+ process.exitCode = 1;
294
+ });
295
+ }
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ type Writable = {
3
+ write(chunk: string): boolean;
4
+ };
5
+ export type DeviceEnvironment = "sandbox";
6
+ export type LoginOptions = {
7
+ envFile: string;
8
+ gateway: string;
9
+ environment: DeviceEnvironment;
10
+ noOpen: boolean;
11
+ force: boolean;
12
+ };
13
+ type FetchInput = string | URL;
14
+ type FetchInit = {
15
+ method?: string;
16
+ headers?: Record<string, string>;
17
+ body?: string;
18
+ };
19
+ type FetchLike = (input: FetchInput, init?: FetchInit) => Promise<Response>;
20
+ export type LoginDependencies = {
21
+ cwd?: string;
22
+ fetch?: FetchLike;
23
+ sleep?: (ms: number) => Promise<void>;
24
+ openBrowser?: (url: string) => Promise<boolean>;
25
+ stdout?: Writable;
26
+ stderr?: Writable;
27
+ now?: () => number;
28
+ };
29
+ export declare function parseArgs(argv: string[]): LoginOptions | "help";
30
+ export declare function assertGitIgnored(envPath: string, cwd: string): Promise<void>;
31
+ export declare function assertCanWriteEnvFile(envPath: string, force: boolean): Promise<void>;
32
+ export declare function writeEnvFile(envPath: string, rawKey: string, force: boolean): Promise<void>;
33
+ export declare function runLogin(options: LoginOptions, deps?: LoginDependencies): Promise<number>;
34
+ export declare function main(argv?: string[], deps?: LoginDependencies): Promise<number>;
35
+ export {};