@paybond/kit 0.9.3 → 0.9.4

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-guardrail-demo.ts] [--force]",
29
30
  "",
30
- "Scaffolds a Paybond spend guard wrapper for delegated agent spend controls.",
31
+ "Scaffolds a production-shaped Paybond guardrail integration with a sandbox smoke path.",
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-guardrail-demo.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,173 @@ 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";
87
+
88
+ const DEFAULT_OPERATION = "paid_tool.smoke_test";
89
+ const DEFAULT_REQUESTED_SPEND_CENTS = 500;
72
90
 
73
- type ToolInput = {
74
- city: string;
91
+ export type PaidToolHandler<TInput, TResult> = (input: TInput) => TResult | Promise<TResult>;
92
+
93
+ export type SandboxGuardrailIntentOptions = {
94
+ operation?: string;
95
+ requestedSpendCents?: number;
96
+ currency?: string;
97
+ evidenceSchema?: Record<string, unknown>;
98
+ metadata?: Record<string, unknown>;
99
+ idempotencyKey?: string;
100
+ };
101
+
102
+ export type SubmitSandboxEvidenceOptions = {
103
+ operation?: string;
104
+ requestedSpendCents?: number;
105
+ metadata?: Record<string, unknown>;
106
+ artifacts?: string[];
107
+ idempotencyKey?: string;
108
+ };
109
+
110
+ export type SmokePaidToolInput = {
111
+ itemId: string;
75
112
  maxPriceCents: number;
76
113
  };
77
114
 
78
- type FundedIntent = {
79
- intentId: string;
80
- capabilityToken: string;
115
+ export type SmokePaidToolResult = {
116
+ confirmationId: string;
117
+ itemId: string;
118
+ chargedCents: number;
119
+ sandbox: true;
81
120
  };
82
121
 
83
- export async function openPaybond(): Promise<Paybond> {
122
+ export async function openPaybondFromEnv(): Promise<Paybond> {
123
+ const apiKey = process.env.PAYBOND_API_KEY?.trim();
124
+ if (!apiKey) {
125
+ throw new Error("PAYBOND_API_KEY is required");
126
+ }
127
+
84
128
  return Paybond.open({
85
- apiKey: process.env.PAYBOND_API_KEY!,
129
+ apiKey,
130
+ gatewayBaseUrl: process.env.PAYBOND_GATEWAY_BASE_URL,
86
131
  expectedEnvironment: "sandbox",
87
132
  });
88
133
  }
89
134
 
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");
95
- }
96
- if (!capabilityToken) {
97
- throw new Error("intent is not funded yet; call paybond.intents.fund(...) before guarding tools");
98
- }
99
- return { intentId, capabilityToken };
135
+ export async function bootstrapSandboxGuardrailIntent(
136
+ paybond: Paybond,
137
+ options: SandboxGuardrailIntentOptions = {},
138
+ ): Promise<SandboxGuardrailBootstrapResult> {
139
+ return paybond.guardrails.bootstrapSandbox({
140
+ operation: options.operation ?? DEFAULT_OPERATION,
141
+ requestedSpendCents: options.requestedSpendCents ?? DEFAULT_REQUESTED_SPEND_CENTS,
142
+ currency: options.currency ?? "usd",
143
+ evidenceSchema: options.evidenceSchema ?? {
144
+ type: "object",
145
+ required: ["confirmation_id", "charged_cents"],
146
+ properties: {
147
+ confirmation_id: { type: "string" },
148
+ charged_cents: { type: "integer" },
149
+ },
150
+ },
151
+ metadata: options.metadata,
152
+ idempotencyKey: options.idempotencyKey,
153
+ });
100
154
  }
101
155
 
102
- export function fundedIntentFromFund(funded: FundIntentResult): FundedIntent {
103
- if (!funded.capabilityToken) {
104
- throw new Error("intent funding did not return a capabilityToken yet");
156
+ export function wrapPaidTool<TInput, TResult>(
157
+ paybond: Paybond,
158
+ guardrail: Pick<
159
+ SandboxGuardrailBootstrapResult,
160
+ "intent_id" | "capability_token" | "operation" | "requested_spend_cents"
161
+ >,
162
+ handler: PaidToolHandler<TInput, TResult>,
163
+ ): (input: TInput) => Promise<Awaited<TResult>> {
164
+ if (!guardrail.capability_token.trim()) {
165
+ throw new Error("sandbox guardrail bootstrap did not return a capability token");
105
166
  }
106
- return { intentId: funded.intentId, capabilityToken: funded.capabilityToken };
107
- }
108
167
 
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}\` };
112
- }
113
-
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");
121
- }
122
- const guard = params.paybond.spendGuard(params.intentId, params.capabilityToken);
168
+ const guard = paybond.spendGuard(guardrail.intent_id, guardrail.capability_token);
123
169
 
124
170
  // ${FRAMEWORK_NOTES[framework]}
125
171
  return guard.guardTool(
126
172
  {
127
- operation: "travel.book_hotel",
128
- requestedSpendCents: 20_000,
173
+ operation: guardrail.operation,
174
+ requestedSpendCents: guardrail.requested_spend_cents,
129
175
  },
130
- bookHotel,
176
+ handler,
131
177
  );
132
178
  }
179
+
180
+ export async function submitSandboxEvidence(
181
+ paybond: Paybond,
182
+ guardrail: Pick<SandboxGuardrailBootstrapResult, "intent_id" | "operation" | "requested_spend_cents">,
183
+ payload: Record<string, unknown>,
184
+ options: SubmitSandboxEvidenceOptions = {},
185
+ ): Promise<SandboxGuardrailEvidenceResult> {
186
+ return paybond.guardrails.submitSandboxEvidence({
187
+ intentId: guardrail.intent_id,
188
+ payload,
189
+ artifacts: options.artifacts,
190
+ operation: options.operation ?? guardrail.operation,
191
+ requestedSpendCents: options.requestedSpendCents ?? guardrail.requested_spend_cents,
192
+ metadata: options.metadata,
193
+ idempotencyKey: options.idempotencyKey,
194
+ });
195
+ }
196
+
197
+ export async function replaceableSmokeTestPaidTool(
198
+ input: SmokePaidToolInput,
199
+ ): Promise<SmokePaidToolResult> {
200
+ // Replace this sandbox smoke-test function with the real paid side-effecting tool.
201
+ return {
202
+ confirmationId: "sandbox-confirmation-" + input.itemId,
203
+ itemId: input.itemId,
204
+ chargedCents: Math.min(input.maxPriceCents, DEFAULT_REQUESTED_SPEND_CENTS),
205
+ sandbox: true,
206
+ };
207
+ }
208
+
209
+ export async function runSandboxSmokePath(): Promise<{
210
+ guardrail: SandboxGuardrailBootstrapResult;
211
+ toolResult: SmokePaidToolResult;
212
+ evidence: SandboxGuardrailEvidenceResult;
213
+ }> {
214
+ const paybond = await openPaybondFromEnv();
215
+ const guardrail = await bootstrapSandboxGuardrailIntent(paybond);
216
+ const guardedTool = wrapPaidTool(paybond, guardrail, replaceableSmokeTestPaidTool);
217
+ const toolResult = await guardedTool({
218
+ itemId: "replace-with-your-tool-input",
219
+ maxPriceCents: DEFAULT_REQUESTED_SPEND_CENTS,
220
+ });
221
+ const evidence = await submitSandboxEvidence(paybond, guardrail, {
222
+ confirmation_id: toolResult.confirmationId,
223
+ charged_cents: toolResult.chargedCents,
224
+ item_id: toolResult.itemId,
225
+ sandbox: toolResult.sandbox,
226
+ });
227
+ return { guardrail, toolResult, evidence };
228
+ }
229
+
230
+ async function main(): Promise<void> {
231
+ const result = await runSandboxSmokePath();
232
+ console.log(JSON.stringify(result, null, 2));
233
+ }
234
+
235
+ function normalizeFileURL(url: string): string {
236
+ return url.startsWith("file:///var/") ? url.replace("file:///var/", "file:///private/var/") : url;
237
+ }
238
+
239
+ const invokedPath = process.argv[1] ? normalizeFileURL(new URL("file://" + process.argv[1]).href) : "";
240
+ if (invokedPath && normalizeFileURL(import.meta.url) === invokedPath) {
241
+ main().catch((err) => {
242
+ console.error(err instanceof Error ? err.message : String(err));
243
+ process.exitCode = 1;
244
+ });
245
+ }
133
246
  `;
134
247
  }
135
248
  async function writeScaffold(out, body, force) {
@@ -162,13 +275,28 @@ export async function main(argv = process.argv.slice(2)) {
162
275
  if (argv.includes("--help") || argv.includes("-h")) {
163
276
  return 0;
164
277
  }
165
- await writeScaffold(parsed.out, template(parsed.framework), parsed.force);
166
- process.stdout.write(`Created ${parsed.out}\n`);
278
+ try {
279
+ await writeScaffold(parsed.out, template(parsed.framework), parsed.force);
280
+ }
281
+ catch (err) {
282
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
283
+ return 1;
284
+ }
285
+ process.stdout.write(`Created Paybond guardrail integration: ${parsed.out}\n`);
167
286
  return 0;
168
287
  }
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
- });
288
+ function normalizeFileURL(url) {
289
+ return url.startsWith("file:///var/") ? url.replace("file:///var/", "file:///private/var/") : url;
290
+ }
291
+ function invokedFromCLI() {
292
+ const invokedPath = process.argv[1] ? normalizeFileURL(new URL("file://" + process.argv[1]).href) : "";
293
+ return Boolean(invokedPath && normalizeFileURL(import.meta.url) === invokedPath);
294
+ }
295
+ if (invokedFromCLI()) {
296
+ main().then((code) => {
297
+ process.exitCode = code;
298
+ }, (err) => {
299
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
300
+ process.exitCode = 1;
301
+ });
302
+ }
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ type Writable = {
3
+ write(chunk: string): boolean;
4
+ };
5
+ export type DeviceEnvironment = "sandbox" | "live";
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 {};