@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/README.md CHANGED
@@ -22,12 +22,27 @@ npm install @paybond/kit
22
22
  - A `paybond_sk_sandbox_...` or `paybond_sk_live_...` service-account API key
23
23
  - For intent creation or evidence submission: 32-byte Ed25519 signing seeds owned by your application
24
24
 
25
- Minimal environment for the quick start:
25
+ Create a sandbox key for local development:
26
26
 
27
27
  ```bash
28
- export PAYBOND_API_KEY="paybond_sk_sandbox_..."
28
+ npx -p @paybond/kit paybond login
29
29
  ```
30
30
 
31
+ `paybond login` writes a sandbox `PAYBOND_API_KEY` to `.env.local` with file mode `0600`, adds the default `.env.local` target to `.gitignore` when needed, and refuses to overwrite an existing key unless `--force` is passed. Custom env-file paths inside a git repo must already be ignored. Live production keys are created by tenant admins in Console and stored in deployment secret managers.
32
+
33
+ ## First guardrail scaffold
34
+
35
+ Use this first when you have a paid tool and want Paybond guardrails in the sandbox:
36
+
37
+ ```bash
38
+ npx -p @paybond/kit paybond-init \
39
+ --preset paid-tool-guard \
40
+ --framework provider-agnostic \
41
+ --out paybond-paid-tool-guard.ts
42
+ ```
43
+
44
+ The generated integration opens Paybond from the environment, loads `.env.local` when `PAYBOND_API_KEY` is not already present, bootstraps a sandbox guardrail intent, wraps your paid-tool handler, and submits sandbox evidence. It does not generate a paid-tool implementation. Free Developer is sandbox-only; live settlement rails start on paid production plans.
45
+
31
46
  ## Tenant isolation
32
47
 
33
48
  Every session is bound to the tenant realm echoed by gateway-authenticated service-account introspection.
@@ -73,31 +88,37 @@ const paybond = await Paybond.open({
73
88
  expectedEnvironment: "sandbox",
74
89
  });
75
90
 
76
- const created = await paybond.intents.create({
77
- // principal, payee, budget, predicate, evidence schema, deadline...
78
- allowedTools: ["travel.book_hotel"],
79
- settlementRail: "stripe_connect",
91
+ const guardrail = await paybond.guardrails.bootstrapSandbox({
92
+ operation: "travel.book_hotel",
93
+ requestedSpendCents: 20_000,
94
+ currency: "usd",
80
95
  });
81
96
 
82
- const intentId = String(created.intent_id);
83
- const capabilityToken = String(created.capability_token ?? "");
84
- if (!capabilityToken) {
85
- throw new Error("fund the intent before guarding tools");
86
- }
87
-
88
- const guard = paybond.spendGuard(intentId, capabilityToken);
97
+ const guard = paybond.spendGuard(guardrail.intent_id, guardrail.capability_token);
89
98
  const guardedTool = guard.guardTool(
90
- { operation: "travel.book_hotel", requestedSpendCents: 20_000 },
99
+ {
100
+ operation: guardrail.operation,
101
+ requestedSpendCents: guardrail.requested_spend_cents,
102
+ },
91
103
  async (input) => bookHotel(input),
92
104
  );
105
+
106
+ const result = await guardedTool({ hotelId: "hotel_123", maxPriceCents: 20_000 });
107
+ await paybond.guardrails.submitSandboxEvidence({
108
+ intentId: guardrail.intent_id,
109
+ payload: { result, sandbox: true },
110
+ });
93
111
  ```
94
112
 
95
- The `paybond.harbor` client is created by `Paybond.open(...)` and bound to the tenant resolved from the service-account API key. Normal integrations read `capability_token` from `paybond.intents.create(...)`, or from `paybond.intents.fund(...)` after an `x402_usdc_base` payment challenge is satisfied.
113
+ The `paybond.harbor` and `paybond.guardrails` clients are created by `Paybond.open(...)` and bound to the tenant resolved from the service-account API key. Production integrations read `capability_token` from `paybond.intents.create(...)`, or from `paybond.intents.fund(...)` after an `x402_usdc_base` payment challenge is satisfied.
96
114
 
97
- Scaffold a wrapper:
115
+ Scaffold a guardrail integration:
98
116
 
99
117
  ```bash
100
- npx -p @paybond/kit paybond-init --framework provider-agnostic --out paybond-spend-guard.ts
118
+ npx -p @paybond/kit paybond-init \
119
+ --preset paid-tool-guard \
120
+ --framework provider-agnostic \
121
+ --out paybond-paid-tool-guard.ts
101
122
  ```
102
123
 
103
124
  ## What the package includes
@@ -117,8 +138,9 @@ Gateway and trust helpers:
117
138
  - `GatewaySignalClient` and `ServiceAccountSignalSession` for tenant-scoped Signal reads and signed portfolio artifacts
118
139
  - `GatewayFraudClient` and `ServiceAccountFraudSession` for tenant-scoped fraud assessments, review queues, review events, metrics, and release-gate config
119
140
  - Protocol-v2 helpers for mandate verification, replay-safe recognition proof verification, receipt reads, and A2A discovery
141
+ - `paybond login` for sandbox device approval and local `.env.local` API-key setup
120
142
  - `paybond-mcp-server` for tenant-bound MCP tool exposure to any MCP-compatible host
121
- - `paybond-init` for generating a small spend guard wrapper
143
+ - `paybond-init` for generating a Paybond guardrail integration helper
122
144
 
123
145
  Agent-facing surfaces are model-provider agnostic. Paybond verifies tool operations and tenant scope, not whether a tool call came from OpenAI, Anthropic, Gemini, a local model, or another runtime.
124
146
 
@@ -143,6 +165,7 @@ Gateway-backed protocol helpers throw `ProtocolHttpError` with parsed `errorCode
143
165
  ## Docs
144
166
 
145
167
  - Long-form docs: https://paybond.ai/docs/kit
168
+ - One-command guardrails: https://paybond.ai/docs/kit/one-command-guardrails
146
169
  - TypeScript quickstart: https://paybond.ai/docs/kit/quickstart-typescript
147
170
  - TypeScript SDK reference: https://paybond.ai/docs/kit/sdk-reference-typescript
148
171
  - MCP server guide: https://paybond.ai/docs/kit/mcp-server
package/dist/index.d.ts CHANGED
@@ -72,6 +72,49 @@ export type FundIntentResult = {
72
72
  capabilityToken?: string;
73
73
  funding?: IntentFundingResult;
74
74
  };
75
+ export type SandboxGuardrailBootstrapInput = {
76
+ operation: string;
77
+ requestedSpendCents: number;
78
+ currency?: string;
79
+ evidenceSchema?: Record<string, unknown>;
80
+ metadata?: Record<string, unknown>;
81
+ idempotencyKey?: string;
82
+ };
83
+ export type SandboxGuardrailEvidenceInput = {
84
+ intentId: string;
85
+ payload?: Record<string, unknown>;
86
+ artifacts?: string[];
87
+ operation?: string;
88
+ requestedSpendCents?: number;
89
+ metadata?: Record<string, unknown>;
90
+ idempotencyKey?: string;
91
+ };
92
+ export type SandboxGuardrailBootstrapResult = {
93
+ tenant_id: string;
94
+ intent_id: string;
95
+ capability_token: string;
96
+ operation: string;
97
+ requested_spend_cents: number;
98
+ sandbox_lifecycle_status: string;
99
+ currency?: string;
100
+ settlement_rail?: string;
101
+ settlement_mode?: string;
102
+ simulator_event?: unknown;
103
+ };
104
+ export type SandboxGuardrailEvidenceResult = {
105
+ tenant_id: string;
106
+ intent_id: string;
107
+ capability_token?: string;
108
+ operation: string;
109
+ requested_spend_cents: number;
110
+ sandbox_lifecycle_status: string;
111
+ settlement_rail?: string;
112
+ settlement_mode?: string;
113
+ predicate_passed?: boolean | null;
114
+ payload_digest?: string;
115
+ artifacts_digest?: string;
116
+ simulator_event?: unknown;
117
+ };
75
118
  export declare const DEFAULT_PAYBOND_GATEWAY_BASE_URL = "https://api.paybond.ai";
76
119
  /** Async supplier for upstream Harbor bearer tokens on low-level direct clients. */
77
120
  export type HarborBearerSupplier = () => Promise<string | null | undefined>;
@@ -878,6 +921,28 @@ export declare class GatewayHarborClient {
878
921
  }): Promise<FundIntentResult>;
879
922
  submitEvidence(intentId: string, evidenceBody: Record<string, unknown>, options: GatewayHarborMutationOptions): Promise<SubmitEvidenceResult>;
880
923
  }
924
+ type GatewaySandboxGuardrailsClientOptions = {
925
+ staticGatewayBearerToken: string;
926
+ maxRetries?: number;
927
+ };
928
+ /**
929
+ * Gateway sandbox guardrail helpers.
930
+ *
931
+ * These routes derive tenant scope only from the service-account bearer token and intentionally
932
+ * reject caller-supplied tenant IDs, including the normal `x-tenant-id` Gateway Harbor header.
933
+ */
934
+ export declare class PaybondGuardrails {
935
+ private readonly base;
936
+ readonly tenantId: string;
937
+ private readonly bearerToken;
938
+ private readonly maxRetries;
939
+ constructor(gatewayBaseUrl: string, tenantId: string, options: GatewaySandboxGuardrailsClientOptions);
940
+ private headers;
941
+ private fetchWithRetries;
942
+ private postJSON;
943
+ bootstrapSandbox(input: SandboxGuardrailBootstrapInput): Promise<SandboxGuardrailBootstrapResult>;
944
+ submitSandboxEvidence(input: SandboxGuardrailEvidenceInput): Promise<SandboxGuardrailEvidenceResult>;
945
+ }
881
946
  type GatewaySignalClientOptions = {
882
947
  staticGatewayBearerToken: string;
883
948
  maxRetries?: number;
@@ -1071,6 +1136,7 @@ export declare class PaybondIntents {
1071
1136
  */
1072
1137
  export declare class Paybond {
1073
1138
  readonly harbor: GatewayHarborClient;
1139
+ readonly guardrails: PaybondGuardrails;
1074
1140
  readonly signal: GatewaySignalClient;
1075
1141
  readonly fraud: GatewayFraudClient;
1076
1142
  readonly a2a: GatewayA2AClient;
package/dist/index.js CHANGED
@@ -801,6 +801,132 @@ export class GatewayHarborClient {
801
801
  return parseSubmitEvidenceResponse(JSON.parse(text));
802
802
  }
803
803
  }
804
+ /**
805
+ * Gateway sandbox guardrail helpers.
806
+ *
807
+ * These routes derive tenant scope only from the service-account bearer token and intentionally
808
+ * reject caller-supplied tenant IDs, including the normal `x-tenant-id` Gateway Harbor header.
809
+ */
810
+ export class PaybondGuardrails {
811
+ base;
812
+ tenantId;
813
+ bearerToken;
814
+ maxRetries;
815
+ constructor(gatewayBaseUrl, tenantId, options) {
816
+ this.base = normalizeBase(gatewayBaseUrl) + "/";
817
+ this.tenantId = tenantId.trim();
818
+ this.bearerToken = options.staticGatewayBearerToken.trim();
819
+ this.maxRetries = Math.max(1, options.maxRetries ?? 3);
820
+ }
821
+ headers(extra) {
822
+ const headers = new Headers(extra);
823
+ headers.set("accept", "application/json");
824
+ headers.set("authorization", `Bearer ${this.bearerToken}`);
825
+ return headers;
826
+ }
827
+ async fetchWithRetries(url, init) {
828
+ let lastErr;
829
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
830
+ let res;
831
+ try {
832
+ res = await fetch(url, init);
833
+ }
834
+ catch (e) {
835
+ lastErr = e;
836
+ if (attempt + 1 >= this.maxRetries)
837
+ throw e;
838
+ await new Promise((r) => setTimeout(r, backoffMs(attempt)));
839
+ continue;
840
+ }
841
+ if ([429, 500, 502, 503, 504].includes(res.status) && attempt + 1 < this.maxRetries) {
842
+ const raSec = parseRetryAfterSeconds(res.headers.get("retry-after"));
843
+ const delayMs = raSec != null ? raSec * 1000 : backoffMs(attempt);
844
+ await new Promise((r) => setTimeout(r, delayMs));
845
+ continue;
846
+ }
847
+ return res;
848
+ }
849
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
850
+ }
851
+ async postJSON(path, payload, options) {
852
+ const url = `${this.base}${path.replace(/^\/+/, "")}`;
853
+ const extraHeaders = {
854
+ "content-type": "application/json",
855
+ };
856
+ if (options?.idempotencyKey?.trim()) {
857
+ extraHeaders["idempotency-key"] = options.idempotencyKey.trim();
858
+ }
859
+ const res = await this.fetchWithRetries(url, {
860
+ method: "POST",
861
+ headers: this.headers(extraHeaders),
862
+ body: JSON.stringify(payload),
863
+ });
864
+ const text = await res.text();
865
+ return { res, text, url };
866
+ }
867
+ async bootstrapSandbox(input) {
868
+ const payload = {
869
+ operation: input.operation,
870
+ requested_spend_cents: input.requestedSpendCents,
871
+ };
872
+ if (input.currency !== undefined) {
873
+ payload.currency = input.currency;
874
+ }
875
+ if (input.evidenceSchema !== undefined) {
876
+ payload.evidence_schema = input.evidenceSchema;
877
+ }
878
+ if (input.metadata !== undefined) {
879
+ payload.metadata = input.metadata;
880
+ }
881
+ const { res, text, url } = await this.postJSON("/v1/sandbox/guardrails/bootstrap", payload, { idempotencyKey: input.idempotencyKey });
882
+ if (!res.ok) {
883
+ throw new HarborHttpError(`Gateway sandbox guardrail bootstrap HTTP ${res.status}: ${text}`, {
884
+ statusCode: res.status,
885
+ url,
886
+ bodyText: text,
887
+ });
888
+ }
889
+ return parseSandboxGuardrailBootstrapResponse(JSON.parse(text), {
890
+ tenantId: this.tenantId,
891
+ url,
892
+ bodyText: text,
893
+ statusCode: res.status,
894
+ });
895
+ }
896
+ async submitSandboxEvidence(input) {
897
+ const payload = {};
898
+ if (input.payload !== undefined) {
899
+ payload.payload = input.payload;
900
+ }
901
+ if (input.artifacts !== undefined) {
902
+ payload.artifacts = input.artifacts;
903
+ }
904
+ if (input.operation !== undefined) {
905
+ payload.operation = input.operation;
906
+ }
907
+ if (input.requestedSpendCents !== undefined) {
908
+ payload.requested_spend_cents = input.requestedSpendCents;
909
+ }
910
+ if (input.metadata !== undefined) {
911
+ payload.metadata = input.metadata;
912
+ }
913
+ const { res, text, url } = await this.postJSON(`/v1/sandbox/guardrails/${encodeURIComponent(input.intentId)}/evidence`, payload, { idempotencyKey: input.idempotencyKey });
914
+ if (!res.ok) {
915
+ throw new HarborHttpError(`Gateway sandbox guardrail evidence HTTP ${res.status}: ${text}`, {
916
+ statusCode: res.status,
917
+ url,
918
+ bodyText: text,
919
+ });
920
+ }
921
+ return parseSandboxGuardrailEvidenceResponse(JSON.parse(text), {
922
+ tenantId: this.tenantId,
923
+ intentId: input.intentId,
924
+ url,
925
+ bodyText: text,
926
+ statusCode: res.status,
927
+ });
928
+ }
929
+ }
804
930
  const DEFAULT_PRINCIPAL_PATH = "/v1/auth/principal";
805
931
  const SETTLEMENT_RAIL_VALUES = new Set(["stripe_connect", "stripe_ach_debit", "x402_usdc_base"]);
806
932
  const FRAUD_REVIEW_EVENT_TYPES = new Set([
@@ -878,6 +1004,78 @@ function parseSubmitEvidenceResponse(value) {
878
1004
  predicatePassed: typeof body.predicate_passed === "boolean" ? body.predicate_passed : undefined,
879
1005
  };
880
1006
  }
1007
+ function parseSandboxGuardrailBootstrapResponse(value, init) {
1008
+ const body = assertJSONObject(value);
1009
+ const tenant = sandboxGuardrailString(body, "tenant_id", init);
1010
+ if (tenant !== init.tenantId) {
1011
+ throw new Error(`sandbox guardrail tenant mismatch: client=${init.tenantId} gateway=${tenant}`);
1012
+ }
1013
+ return {
1014
+ tenant_id: tenant,
1015
+ intent_id: sandboxGuardrailString(body, "intent_id", init),
1016
+ capability_token: sandboxGuardrailString(body, "capability_token", init),
1017
+ operation: sandboxGuardrailString(body, "operation", init),
1018
+ requested_spend_cents: sandboxGuardrailNumber(body, "requested_spend_cents", init),
1019
+ sandbox_lifecycle_status: sandboxGuardrailString(body, "sandbox_lifecycle_status", init),
1020
+ currency: sandboxGuardrailOptionalString(body.currency),
1021
+ settlement_rail: sandboxGuardrailOptionalString(body.settlement_rail),
1022
+ settlement_mode: sandboxGuardrailOptionalString(body.settlement_mode),
1023
+ simulator_event: body.simulator_event,
1024
+ };
1025
+ }
1026
+ function parseSandboxGuardrailEvidenceResponse(value, init) {
1027
+ const body = assertJSONObject(value);
1028
+ const tenant = sandboxGuardrailString(body, "tenant_id", init);
1029
+ if (tenant !== init.tenantId) {
1030
+ throw new Error(`sandbox guardrail tenant mismatch: client=${init.tenantId} gateway=${tenant}`);
1031
+ }
1032
+ const intentId = sandboxGuardrailString(body, "intent_id", init);
1033
+ if (intentId !== init.intentId) {
1034
+ throw new Error(`sandbox guardrail intent mismatch: requested=${init.intentId} gateway=${intentId}`);
1035
+ }
1036
+ const predicatePassedRaw = body.predicate_passed;
1037
+ return {
1038
+ tenant_id: tenant,
1039
+ intent_id: intentId,
1040
+ capability_token: sandboxGuardrailOptionalString(body.capability_token),
1041
+ operation: sandboxGuardrailString(body, "operation", init),
1042
+ requested_spend_cents: sandboxGuardrailNumber(body, "requested_spend_cents", init),
1043
+ sandbox_lifecycle_status: sandboxGuardrailString(body, "sandbox_lifecycle_status", init),
1044
+ settlement_rail: sandboxGuardrailOptionalString(body.settlement_rail),
1045
+ settlement_mode: sandboxGuardrailOptionalString(body.settlement_mode),
1046
+ predicate_passed: typeof predicatePassedRaw === "boolean" || predicatePassedRaw === null
1047
+ ? predicatePassedRaw
1048
+ : undefined,
1049
+ payload_digest: sandboxGuardrailOptionalString(body.payload_digest),
1050
+ artifacts_digest: sandboxGuardrailOptionalString(body.artifacts_digest),
1051
+ simulator_event: body.simulator_event,
1052
+ };
1053
+ }
1054
+ function sandboxGuardrailString(body, field, init) {
1055
+ const value = body[field];
1056
+ if (typeof value === "string" && value.trim()) {
1057
+ return value;
1058
+ }
1059
+ throw new HarborHttpError(`Gateway sandbox guardrail response missing ${field}`, {
1060
+ statusCode: init.statusCode,
1061
+ url: init.url,
1062
+ bodyText: init.bodyText,
1063
+ });
1064
+ }
1065
+ function sandboxGuardrailOptionalString(value) {
1066
+ return typeof value === "string" && value.trim() ? value : undefined;
1067
+ }
1068
+ function sandboxGuardrailNumber(body, field, init) {
1069
+ const value = body[field];
1070
+ if (typeof value === "number" && Number.isFinite(value)) {
1071
+ return value;
1072
+ }
1073
+ throw new HarborHttpError(`Gateway sandbox guardrail response missing ${field}`, {
1074
+ statusCode: init.statusCode,
1075
+ url: init.url,
1076
+ bodyText: init.bodyText,
1077
+ });
1078
+ }
881
1079
  function parseIntentFundingResult(value) {
882
1080
  const body = assertJSONObject(value);
883
1081
  const onchainRaw = body.onchain_transaction_hashes;
@@ -1795,13 +1993,15 @@ export class PaybondIntents {
1795
1993
  */
1796
1994
  export class Paybond {
1797
1995
  harbor;
1996
+ guardrails;
1798
1997
  signal;
1799
1998
  fraud;
1800
1999
  a2a;
1801
2000
  protocol;
1802
2001
  intents;
1803
- constructor(harbor, signal, fraud, a2a, protocol) {
2002
+ constructor(harbor, guardrails, signal, fraud, a2a, protocol) {
1804
2003
  this.harbor = harbor;
2004
+ this.guardrails = guardrails;
1805
2005
  this.signal = signal;
1806
2006
  this.fraud = fraud;
1807
2007
  this.a2a = a2a;
@@ -1817,6 +2017,10 @@ export class Paybond {
1817
2017
  staticGatewayBearerToken: init.apiKey,
1818
2018
  maxRetries,
1819
2019
  });
2020
+ const guardrails = new PaybondGuardrails(gatewayBaseUrl, tenantId, {
2021
+ staticGatewayBearerToken: init.apiKey,
2022
+ maxRetries,
2023
+ });
1820
2024
  const signal = new GatewaySignalClient(gatewayBaseUrl, tenantId, {
1821
2025
  staticGatewayBearerToken: init.apiKey,
1822
2026
  maxRetries,
@@ -1833,7 +2037,7 @@ export class Paybond {
1833
2037
  staticGatewayBearerToken: init.apiKey,
1834
2038
  maxRetries,
1835
2039
  });
1836
- return new Paybond(harbor, signal, fraud, a2a, protocol);
2040
+ return new Paybond(harbor, guardrails, signal, fraud, a2a, protocol);
1837
2041
  }
1838
2042
  /** Reserved for future HTTP client cleanup; safe to call after work completes. */
1839
2043
  async aclose() {