@c4roa0/agentpay-user 0.0.0-stage → 0.3.1

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 (175) hide show
  1. package/AGENTS.md +137 -0
  2. package/CONTRIBUTING.md +16 -0
  3. package/LICENSE +373 -0
  4. package/NOTICE.md +20 -0
  5. package/README.md +85 -2
  6. package/SECURITY.md +16 -0
  7. package/doc/README.md +52 -0
  8. package/doc/TODO.md +107 -0
  9. package/doc/architecture.md +99 -0
  10. package/doc/design.md +60 -0
  11. package/doc/develope.md +68 -0
  12. package/doc/plans/user-wallet-distribution.md +91 -0
  13. package/doc/reference/harness-skill-runtime.md +120 -0
  14. package/doc/reference/local-cache-foundation.md +176 -0
  15. package/doc/reference/local-safe-owner-ui.md +25 -0
  16. package/doc/reference/local-update.md +18 -0
  17. package/doc/reference/multi-service-runtime.md +119 -0
  18. package/doc/reference/npm-integration-verification.md +103 -0
  19. package/doc/reference/onboarding-writing.md +48 -0
  20. package/doc/reference/original-order-recovery.md +97 -0
  21. package/doc/reference/private-anvil-onboarding-config.example.json +18 -0
  22. package/doc/reference/public-deployment-settings.md +56 -0
  23. package/doc/reference/random-skill-contract.md +18 -0
  24. package/doc/reference/result-file-cache.md +166 -0
  25. package/doc/reference/runtime-address-recovery.md +42 -0
  26. package/doc/reference/runtime-result-cache.md +157 -0
  27. package/doc/reference/runtime-startup-recovery.md +141 -0
  28. package/doc/reference/skill-input-output-cache.md +93 -0
  29. package/doc/reference/wallet-identity-policy-history.md +23 -0
  30. package/doc/runbooks/commands.md +103 -0
  31. package/doc/runbooks/deployment-authorization-update.md +42 -0
  32. package/doc/runbooks/emergency-payment-stop.md +80 -0
  33. package/doc/runbooks/local-autostart.md +212 -0
  34. package/doc/runbooks/local-cache.md +115 -0
  35. package/doc/runbooks/local-update.md +84 -0
  36. package/doc/runbooks/market-connection.md +73 -0
  37. package/doc/runbooks/npm-publication.md +56 -0
  38. package/doc/runbooks/official-test-package.md +62 -0
  39. package/doc/runbooks/order-cancellation.md +56 -0
  40. package/doc/runbooks/order-support.md +41 -0
  41. package/doc/runbooks/policy-renewal.md +80 -0
  42. package/doc/runbooks/private-anvil-terminal-setup.md +58 -0
  43. package/doc/runbooks/runtime-skill-client.md +155 -0
  44. package/doc/runbooks/safe-owner-web.md +104 -0
  45. package/doc/runbooks/safe-policy-migration.md +36 -0
  46. package/doc/runbooks/safe-policy-setup.md +52 -0
  47. package/doc/runbooks/terminal-authorization-preview.md +38 -0
  48. package/doc/runbooks/unlock-wallet.md +68 -0
  49. package/doc/runbooks/user-onboarding.md +87 -0
  50. package/doc/runbooks/wallet-identity.md +59 -0
  51. package/doc/security/public-source-review-2026-11-06.md +71 -0
  52. package/doc/usage.md +54 -0
  53. package/package.json +49 -4
  54. package/progress.md +210 -0
  55. package/run +29 -0
  56. package/scripts/agentpay-cli.mjs +11 -0
  57. package/scripts/authorize-existing-safe.mjs +211 -0
  58. package/scripts/autostart.mjs +186 -0
  59. package/scripts/cancel-order.mjs +76 -0
  60. package/scripts/check-installation.mjs +26 -0
  61. package/scripts/check-official-test-package.mjs +35 -0
  62. package/scripts/check-purchase-readiness.mjs +275 -0
  63. package/scripts/configure-runtime-client.mjs +16 -0
  64. package/scripts/guide-policy.mjs +64 -0
  65. package/scripts/initialize-caller-credential.mjs +11 -0
  66. package/scripts/install-skill-bundle.mjs +176 -0
  67. package/scripts/install-user.mjs +24 -0
  68. package/scripts/local-service.mjs +60 -0
  69. package/scripts/lock.mjs +11 -0
  70. package/scripts/login-notify.mjs +39 -0
  71. package/scripts/migrate-safe-policy.mjs +165 -0
  72. package/scripts/official-paid-test.mjs +9 -0
  73. package/scripts/poll-unconfirmed-notice.mjs +75 -0
  74. package/scripts/prepare-onboarding.mjs +33 -0
  75. package/scripts/preview-policy.mjs +65 -0
  76. package/scripts/random-model-ack-smoke.mjs +235 -0
  77. package/scripts/random-reject-smoke.mjs +147 -0
  78. package/scripts/renew.mjs +9 -0
  79. package/scripts/repair.mjs +10 -0
  80. package/scripts/revoke.mjs +9 -0
  81. package/scripts/select-skill-bundle.mjs +39 -0
  82. package/scripts/setup-local.mjs +199 -0
  83. package/scripts/status.mjs +13 -0
  84. package/scripts/test-package.mjs +35 -0
  85. package/scripts/unlock.mjs +13 -0
  86. package/scripts/update.mjs +7 -0
  87. package/scripts/user-setup.mjs +219 -0
  88. package/scripts/verify-npm-release.mjs +32 -0
  89. package/src/active-skill.mjs +160 -0
  90. package/src/anvil-onboarding-state.mjs +96 -0
  91. package/src/anvil-onboarding.mjs +1036 -0
  92. package/src/authorization-question-checkpoint.mjs +114 -0
  93. package/src/autostart-files.mjs +50 -0
  94. package/src/cache-management-cli.mjs +302 -0
  95. package/src/cache-management.mjs +47 -0
  96. package/src/caller-auth.mjs +58 -0
  97. package/src/client-request-lock.mjs +44 -0
  98. package/src/connect-market.mjs +408 -0
  99. package/src/demo-unlock-secret.mjs +64 -0
  100. package/src/deployment-status.mjs +198 -0
  101. package/src/file-cache-cli.mjs +83 -0
  102. package/src/file-cache-owner.mjs +74 -0
  103. package/src/file-cache-settings.mjs +50 -0
  104. package/src/file-cache.mjs +790 -0
  105. package/src/file-download.mjs +281 -0
  106. package/src/finalization-disclosure.mjs +16 -0
  107. package/src/guided-setup.mjs +194 -0
  108. package/src/index.mjs +1233 -0
  109. package/src/install-and-setup.mjs +38 -0
  110. package/src/install-preflight.mjs +201 -0
  111. package/src/install-user.mjs +279 -0
  112. package/src/installation-paths.mjs +31 -0
  113. package/src/invocation-result.mjs +148 -0
  114. package/src/invocation-store.mjs +385 -0
  115. package/src/local-data-directory.mjs +117 -0
  116. package/src/local-info.mjs +181 -0
  117. package/src/local-services.mjs +156 -0
  118. package/src/local-time.mjs +36 -0
  119. package/src/local-update.mjs +316 -0
  120. package/src/local-wallet-ipc.mjs +93 -0
  121. package/src/login-reminder.mjs +28 -0
  122. package/src/market-download-url.mjs +21 -0
  123. package/src/node-version.mjs +12 -0
  124. package/src/npm-update.mjs +126 -0
  125. package/src/official-paid-test.mjs +407 -0
  126. package/src/official-test-package.mjs +41 -0
  127. package/src/official-test-update.mjs +162 -0
  128. package/src/order-cancellation.mjs +241 -0
  129. package/src/order-closure.mjs +42 -0
  130. package/src/order-list-panel.mjs +110 -0
  131. package/src/order-review.mjs +424 -0
  132. package/src/order-status-panel.mjs +65 -0
  133. package/src/order-support.mjs +365 -0
  134. package/src/owner-approval-server.mjs +332 -0
  135. package/src/owner-signer.mjs +67 -0
  136. package/src/policy-info.mjs +38 -0
  137. package/src/policy-migration.mjs +84 -0
  138. package/src/policy-operations.mjs +67 -0
  139. package/src/policy-preview-server.mjs +236 -0
  140. package/src/policy-terminal-guide.mjs +377 -0
  141. package/src/prepare-local-services.mjs +143 -0
  142. package/src/prepare-onboarding.mjs +210 -0
  143. package/src/private-journal-files.mjs +29 -0
  144. package/src/protocol-package.mjs +25 -0
  145. package/src/readiness-profile.mjs +30 -0
  146. package/src/renew-policy.mjs +605 -0
  147. package/src/repair-market-wallet.mjs +137 -0
  148. package/src/restore-wallet.mjs +255 -0
  149. package/src/result-cache-view.mjs +276 -0
  150. package/src/runtime-client.mjs +1145 -0
  151. package/src/runtime-connection-settings.mjs +101 -0
  152. package/src/runtime-recovery.mjs +667 -0
  153. package/src/runtime-status.mjs +32 -0
  154. package/src/safe-owner-anvil.mjs +115 -0
  155. package/src/safe-policy-plan.mjs +195 -0
  156. package/src/service-reference.mjs +233 -0
  157. package/src/service-url.mjs +52 -0
  158. package/src/settlement-response.mjs +69 -0
  159. package/src/setup-autostart.mjs +197 -0
  160. package/src/setup-demo.mjs +152 -0
  161. package/src/skill-bundle.mjs +142 -0
  162. package/src/skill-client.mjs +198 -0
  163. package/src/skill-install-prompt.mjs +31 -0
  164. package/src/skill-package.mjs +50 -0
  165. package/src/skill-zip.mjs +220 -0
  166. package/src/software-versions.mjs +48 -0
  167. package/src/status-panel.mjs +603 -0
  168. package/src/transport.mjs +48 -0
  169. package/src/uninstall-local.mjs +299 -0
  170. package/src/user-cli.mjs +354 -0
  171. package/src/user-setup-guide.mjs +193 -0
  172. package/src/wallet-control.mjs +182 -0
  173. package/src/wallet-ipc-error.mjs +16 -0
  174. package/src/wallet-package.mjs +28 -0
  175. package/update-pair.json +6 -0
@@ -0,0 +1,235 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ // If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
5
+ // Private-test model emulator: receipt acknowledgement is a separate caller
6
+ // action after the random result, never an automatic Skill/Runtime side effect.
7
+ import { createHash, randomUUID } from "node:crypto";
8
+ import { lstat, open, readFile, rename, writeFile } from "node:fs/promises";
9
+ import { join, resolve } from "node:path";
10
+ import { loadActiveSkill } from "../src/active-skill.mjs";
11
+ import {
12
+ loadRuntimeConnection,
13
+ prepareInvocation,
14
+ invokeService,
15
+ completeInvocation,
16
+ } from "../src/runtime-client.mjs";
17
+ import { callWallet } from "@c4roa0/agentpay-wallet";
18
+
19
+ const [mode, dataArg, settlementArg, userArg] = process.argv.slice(2);
20
+ if (
21
+ !["invoke", "confirm"].includes(mode) ||
22
+ !dataArg ||
23
+ !settlementArg ||
24
+ !userArg ||
25
+ process.argv.length !== 6
26
+ ) {
27
+ throw new Error(
28
+ "usage: random-model-ack-smoke.mjs <invoke|confirm> <private-user-data-dir> <settlement-url> <user-url>",
29
+ );
30
+ }
31
+ const dataDir = resolve(dataArg);
32
+ const settlementUrl = localUrl(settlementArg);
33
+ const userUrl = localUrl(userArg);
34
+ const runtimeDir = join(dataDir, "runtime");
35
+ const walletDir = join(dataDir, "wallet");
36
+ const checkpointName =
37
+ process.env.AGENTPAY_MODEL_ACK_CHECKPOINT || "model-ack-smoke.json";
38
+ if (
39
+ !["model-ack-smoke.json", "unconfirmed-smoke.json"].includes(checkpointName)
40
+ ) {
41
+ throw new Error("unsupported private-test checkpoint");
42
+ }
43
+ const path = join(runtimeDir, checkpointName);
44
+ await privateDirectory(runtimeDir);
45
+ await privateDirectory(walletDir);
46
+ const callerToken = await privateToken(join(runtimeDir, "caller.token"));
47
+ const walletToken = await privateToken(join(walletDir, "agent.token"));
48
+ let checkpoint;
49
+ try {
50
+ const info = await lstat(path);
51
+ if (
52
+ !info.isFile() ||
53
+ info.isSymbolicLink() ||
54
+ info.uid !== process.getuid() ||
55
+ info.mode & 0o077
56
+ ) {
57
+ throw new Error("unsafe model-emulator checkpoint");
58
+ }
59
+ checkpoint = JSON.parse(await readFile(path, "utf8"));
60
+ } catch (error) {
61
+ if (error.code !== "ENOENT" || mode !== "invoke") throw error;
62
+ checkpoint = {
63
+ version: "agentpay.model-ack-smoke.v1",
64
+ key: randomUUID(),
65
+ orderId: null,
66
+ outputHash: null,
67
+ confirmed: false,
68
+ routingVersion: "agentpay.runtime-call.v1",
69
+ };
70
+ const handle = await open(path, "wx", 0o600);
71
+ try {
72
+ await handle.writeFile(`${JSON.stringify(checkpoint)}\n`);
73
+ await handle.sync();
74
+ } finally {
75
+ await handle.close();
76
+ }
77
+ }
78
+ if (
79
+ checkpoint.version !== "agentpay.model-ack-smoke.v1" ||
80
+ !/^[0-9a-f-]{36}$/.test(checkpoint.key) ||
81
+ (checkpoint.confirmed && mode === "invoke")
82
+ )
83
+ throw new Error("invalid or completed model-emulator checkpoint");
84
+ if (
85
+ mode === "confirm" &&
86
+ (!/^0x[0-9a-f]{64}$/.test(checkpoint.orderId || "") ||
87
+ !/^0x[0-9a-f]{64}$/.test(checkpoint.outputHash || ""))
88
+ )
89
+ throw new Error("invoke must succeed before confirmation");
90
+
91
+ // Official test input stays here; transaction processing uses the shared client.
92
+ if (checkpoint.routingVersion !== "agentpay.runtime-call.v1")
93
+ throw Error(
94
+ "LEGACY_TEST_REVIEW_REQUIRED: preserve the existing request; do not replace its purchase",
95
+ );
96
+ const material = await loadActiveSkill(join(runtimeDir, "active-skill.json"));
97
+ const connection = await loadRuntimeConnection({ dataDir });
98
+ if (new URL(connection.runtimeUrl).href !== userUrl.href)
99
+ throw Error("TEST_RUNTIME_CONNECTION_MISMATCH");
100
+ const saved = await prepareInvocation(
101
+ connection,
102
+ material.reference,
103
+ { request: { min: 10, max: 99 }, requestedMaxUsageUnits: "1" },
104
+ checkpoint.key,
105
+ );
106
+ const body = await invokeService(connection, material.reference, saved);
107
+ await completeInvocation(connection, material.reference, saved);
108
+ const result = body.result;
109
+ if (
110
+ !/^0x[0-9a-f]{64}$/.test(result?.orderId || "") ||
111
+ result?.output?.kind !== "random" ||
112
+ !Number.isSafeInteger(result.output.value) ||
113
+ result.output.value < 10 ||
114
+ result.output.value > 99 ||
115
+ result.deliveryEvidenceVerified !== true ||
116
+ result.confirmationRequired !== false ||
117
+ !result.finalization
118
+ ) {
119
+ throw new Error("random result or delivery evidence invalid");
120
+ }
121
+ const outputHash = `0x${createHash("sha256").update(JSON.stringify(result.output)).digest("hex")}`;
122
+ if (
123
+ checkpoint.orderId &&
124
+ (checkpoint.orderId !== result.orderId ||
125
+ checkpoint.outputHash !== outputHash)
126
+ ) {
127
+ throw new Error("retry returned a different Order or random result");
128
+ }
129
+ if (mode === "invoke") {
130
+ checkpoint = { ...checkpoint, orderId: result.orderId, outputHash };
131
+ await atomicCheckpoint(path, checkpoint);
132
+ process.stdout.write(
133
+ `${JSON.stringify(
134
+ {
135
+ ok: true,
136
+ orderId: result.orderId,
137
+ outputHash,
138
+ randomResultReceived: true,
139
+ deliveryEvidenceVerified: true,
140
+ confirmationSubmitted: false,
141
+ next:
142
+ checkpointName === "unconfirmed-smoke.json"
143
+ ? "do not confirm; observe Wallet reminder and delayed release"
144
+ : "restart User Runtime, then run confirm with the same private checkpoint",
145
+ },
146
+ null,
147
+ 2,
148
+ )}\n`,
149
+ );
150
+ } else {
151
+ if (!checkpoint.confirmed) {
152
+ const deadline = Date.now() + 30_000;
153
+ while (true) {
154
+ try {
155
+ await callWallet(
156
+ join(walletDir, "wallet.sock"),
157
+ walletToken,
158
+ "confirmSettlementDelivery",
159
+ { settlementUrl: settlementUrl.toString(), orderId: result.orderId },
160
+ );
161
+ break;
162
+ } catch (error) {
163
+ if (
164
+ Date.now() >= deadline ||
165
+ error?.code !== "SETTLEMENT_REQUEST_REJECTED" ||
166
+ !String(error?.message || "").includes(
167
+ "finalization cannot be confirmed",
168
+ )
169
+ )
170
+ throw error;
171
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, 250));
172
+ }
173
+ }
174
+ checkpoint = { ...checkpoint, confirmed: true };
175
+ await atomicCheckpoint(path, checkpoint);
176
+ }
177
+ process.stdout.write(
178
+ `${JSON.stringify(
179
+ {
180
+ ok: true,
181
+ orderId: result.orderId,
182
+ outputHash,
183
+ sameOrderAndResultAfterRetry: true,
184
+ modelEmulatorAcknowledgedReceipt: true,
185
+ qualityNotJudged: true,
186
+ },
187
+ null,
188
+ 2,
189
+ )}\n`,
190
+ );
191
+ }
192
+
193
+ function localUrl(input) {
194
+ const url = new URL(input);
195
+ if (
196
+ url.protocol !== "http:" ||
197
+ !["127.0.0.1", "localhost", "[::1]"].includes(url.hostname)
198
+ ) {
199
+ throw new Error("private loopback URL required");
200
+ }
201
+ return url;
202
+ }
203
+ async function privateDirectory(directory) {
204
+ const info = await lstat(directory);
205
+ if (
206
+ !info.isDirectory() ||
207
+ info.isSymbolicLink() ||
208
+ info.uid !== process.getuid() ||
209
+ info.mode & 0o077
210
+ ) {
211
+ throw new Error("private user directory required");
212
+ }
213
+ }
214
+ async function privateToken(file) {
215
+ const info = await lstat(file);
216
+ if (
217
+ !info.isFile() ||
218
+ info.isSymbolicLink() ||
219
+ info.uid !== process.getuid() ||
220
+ info.mode & 0o077
221
+ ) {
222
+ throw new Error("private capability file required");
223
+ }
224
+ const token = (await readFile(file, "utf8")).trim();
225
+ if (!/^[0-9a-f]{64}$/.test(token)) throw new Error("capability invalid");
226
+ return token;
227
+ }
228
+ async function atomicCheckpoint(file, value) {
229
+ const temporary = `${file}.${process.pid}.tmp`;
230
+ await writeFile(temporary, `${JSON.stringify(value)}\n`, {
231
+ flag: "wx",
232
+ mode: 0o600,
233
+ });
234
+ await rename(temporary, file);
235
+ }
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ // If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
5
+ // Invoke the installed Skill with a durable key; reject/silence are expected negative responses.
6
+ import { createHash, randomUUID } from "node:crypto";
7
+ import { lstat, open, readFile } from "node:fs/promises";
8
+ import { join, resolve } from "node:path";
9
+
10
+ const [dataArg, userArg] = process.argv.slice(2);
11
+ if (!dataArg || !userArg || process.argv.length !== 4)
12
+ throw new Error("usage: random-reject-smoke.mjs <user-dir> <user-url>");
13
+ const mode = process.env.AGENTPAY_PROVIDER_NEGATIVE_CASE || "reject";
14
+ if (!["reject", "silence"].includes(mode))
15
+ throw new Error("unsupported Provider negative case");
16
+ const userUrl = new URL(userArg);
17
+ if (
18
+ userUrl.protocol !== "http:" ||
19
+ !["127.0.0.1", "localhost", "[::1]"].includes(userUrl.hostname)
20
+ )
21
+ throw new Error("loopback User URL required");
22
+ const dataDir = resolve(dataArg);
23
+ const runtimeDir = join(dataDir, "runtime");
24
+ const info = await lstat(runtimeDir);
25
+ if (
26
+ !info.isDirectory() ||
27
+ info.isSymbolicLink() ||
28
+ info.uid !== process.getuid() ||
29
+ info.mode & 0o077
30
+ )
31
+ throw new Error("unsafe User runtime directory");
32
+ const tokenPath = join(runtimeDir, "caller.token");
33
+ const tokenInfo = await lstat(tokenPath);
34
+ if (
35
+ !tokenInfo.isFile() ||
36
+ tokenInfo.isSymbolicLink() ||
37
+ tokenInfo.uid !== process.getuid() ||
38
+ tokenInfo.mode & 0o077
39
+ )
40
+ throw new Error("unsafe caller credential");
41
+ const token = (await readFile(tokenPath, "utf8")).trim();
42
+ if (!/^[0-9a-f]{64}$/.test(token)) throw new Error("invalid caller credential");
43
+ const checkpointName =
44
+ process.env.AGENTPAY_PROVIDER_NEGATIVE_CHECKPOINT ||
45
+ (mode === "silence" ? "silence-smoke.json" : "rejection-smoke.json");
46
+ if (
47
+ ![
48
+ "silence-smoke.json",
49
+ "silence-crash-smoke.json",
50
+ "rejection-smoke.json",
51
+ ].includes(checkpointName) ||
52
+ (mode === "reject") !== (checkpointName === "rejection-smoke.json")
53
+ )
54
+ throw new Error("invalid negative-case checkpoint name");
55
+ const checkpointPath = join(runtimeDir, checkpointName);
56
+ let checkpoint;
57
+ try {
58
+ const checkpointInfo = await lstat(checkpointPath);
59
+ if (
60
+ !checkpointInfo.isFile() ||
61
+ checkpointInfo.isSymbolicLink() ||
62
+ checkpointInfo.uid !== process.getuid() ||
63
+ checkpointInfo.mode & 0o077
64
+ )
65
+ throw new Error("unsafe rejection checkpoint");
66
+ checkpoint = JSON.parse(await readFile(checkpointPath, "utf8"));
67
+ } catch (error) {
68
+ if (error.code !== "ENOENT") throw error;
69
+ checkpoint = {
70
+ version:
71
+ mode === "reject"
72
+ ? "agentpay.rejection-smoke.v1"
73
+ : "agentpay.silence-smoke.v1",
74
+ key: randomUUID(),
75
+ };
76
+ const handle = await open(checkpointPath, "wx", 0o600);
77
+ try {
78
+ await handle.writeFile(`${JSON.stringify(checkpoint)}\n`);
79
+ await handle.sync();
80
+ } finally {
81
+ await handle.close();
82
+ }
83
+ }
84
+ if (
85
+ checkpoint.version !==
86
+ (mode === "reject"
87
+ ? "agentpay.rejection-smoke.v1"
88
+ : "agentpay.silence-smoke.v1") ||
89
+ !/^[0-9a-f-]{36}$/.test(checkpoint.key)
90
+ )
91
+ throw new Error("invalid negative-case checkpoint");
92
+
93
+ const referenceFile = process.env.AGENTPAY_TEST_SERVICE_REFERENCE_FILE;
94
+ if (!referenceFile)
95
+ throw Error(
96
+ "Set AGENTPAY_TEST_SERVICE_REFERENCE_FILE to the official public service reference",
97
+ );
98
+ const serviceReference = JSON.parse(await readFile(referenceFile, "utf8"));
99
+ const response = await fetch(new URL("/services/invoke", userUrl), {
100
+ method: "POST",
101
+ redirect: "error",
102
+ headers: {
103
+ "content-type": "application/json",
104
+ authorization: `Bearer ${token}`,
105
+ "idempotency-key": checkpoint.key,
106
+ },
107
+ body: JSON.stringify({
108
+ version: "agentpay.runtime-call.v1",
109
+ service: serviceReference,
110
+ request: { min: 10, max: 99 },
111
+ requestedMaxUsageUnits: "1",
112
+ }),
113
+ signal: AbortSignal.timeout(mode === "silence" ? 180_000 : 120_000),
114
+ });
115
+ const body = await response.json();
116
+ const expectedCode =
117
+ mode === "silence" ? "PROVIDER_RECOVERY_TIMEOUT" : "PROVIDER_REJECTED";
118
+ if (response.status !== 502 || body?.error?.code !== expectedCode) {
119
+ throw new Error(
120
+ `expected ${expectedCode}, got HTTP ${response.status} ${body?.error?.code || "UNKNOWN"}`,
121
+ );
122
+ }
123
+ const invocationId = `0x${createHash("sha256").update(`agentpay.user-invocation.v1:${checkpoint.key}`).digest("hex")}`;
124
+ const document = JSON.parse(
125
+ await readFile(join(runtimeDir, "invocations.json"), "utf8"),
126
+ );
127
+ const invocation = document.invocations?.[invocationId];
128
+ if (
129
+ !/^0x[0-9a-f]{64}$/.test(invocation?.orderId || "") ||
130
+ !invocation.paymentSubmitted ||
131
+ !/^0x[0-9a-f]{64}$/.test(invocation.orderRequestId || "")
132
+ )
133
+ throw new Error("funded Order invocation checkpoint missing");
134
+ process.stdout.write(
135
+ `${JSON.stringify(
136
+ {
137
+ ok: true,
138
+ orderId: invocation.orderId,
139
+ providerOutcome:
140
+ mode === "silence" ? "no_response_timeout" : "explicit_rejection",
141
+ sameKeyRecoverable: true,
142
+ paymentSubmitted: true,
143
+ },
144
+ null,
145
+ 2,
146
+ )}\n`,
147
+ );
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ // If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
5
+ import { renewPolicy } from "../src/renew-policy.mjs";
6
+ if (process.argv.length !== 2) {
7
+ console.error("Usage: agentpay policy renew");
8
+ process.exitCode = 2;
9
+ } else process.exitCode = await renewPolicy();
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ // If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
5
+ import { applyLocalUpdate } from "../src/local-update.mjs";
6
+ const args = process.argv.slice(2);
7
+ if (args.length > 1 || (args.length === 1 && args[0] !== "-f")) {
8
+ console.error("Usage: agentpay repair [-f]");
9
+ process.exitCode = 2;
10
+ } else process.exitCode = await applyLocalUpdate({ force: args[0] === "-f" });
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ // If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
5
+ import { revokePolicy } from "../src/renew-policy.mjs";
6
+ if (process.argv.length !== 2) {
7
+ console.error("Usage: agentpay policy revoke");
8
+ process.exitCode = 2;
9
+ } else process.exitCode = await revokePolicy();
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ // If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
5
+ import { confirmSkillAction } from "../src/skill-install-prompt.mjs";
6
+ import {
7
+ inspectInstalledBundle,
8
+ selectInstalledBundle,
9
+ } from "../src/active-skill.mjs";
10
+ const [mode, data, directory] = process.argv.slice(2);
11
+ if (
12
+ !["--preview", "--execute"].includes(mode) ||
13
+ !data ||
14
+ !directory ||
15
+ process.argv.length !== 5
16
+ )
17
+ throw new Error(
18
+ "Usage: select-skill-bundle.mjs <--preview|--execute> DATA BUNDLE_DIRECTORY",
19
+ );
20
+ const info = await inspectInstalledBundle(directory);
21
+ const details = { ok: true, mode, ...info, orderCreated: false };
22
+ if (mode === "--preview") console.log(JSON.stringify(details, null, 2));
23
+ if (mode === "--execute") {
24
+ if (!process.stdin.isTTY || !process.stderr.isTTY)
25
+ throw new Error("SELECTION_TTY_REQUIRED");
26
+ console.log(
27
+ `Use this Skill in your local Runtime.\n${info.locallyModified ? "These files contain local edits. Review the details before confirming." : "Package files are unchanged since installation."}\nThis selects local files; it does not start a service or make a payment.`,
28
+ );
29
+ if (
30
+ !(await confirmSkillAction({
31
+ prompt:
32
+ "Type 'confirm' to select, 'details' to view file and service information, or 'q' to cancel: ",
33
+ details,
34
+ }))
35
+ )
36
+ throw new Error("SELECTION_NOT_CONFIRMED");
37
+ await selectInstalledBundle(data, directory, info.hashes);
38
+ console.log("Skill selected. Next: start and unlock Wallet / Runtime.");
39
+ }
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ import { walletCliPath } from "../src/wallet-package.mjs";
3
+ // SPDX-License-Identifier: MPL-2.0
4
+ // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
5
+ // If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
6
+ import { createInterface } from "node:readline/promises";
7
+ import { rememberUserDataDir } from "../src/local-data-directory.mjs";
8
+ // Minimal user-side setup for the private test. No Safe or policy action.
9
+ import { spawn } from "node:child_process";
10
+ import { lstat, mkdir, readFile, stat } from "node:fs/promises";
11
+ import { join, resolve } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import { initializeCallerCredential } from "../src/caller-auth.mjs";
14
+
15
+ const command = process.argv[2];
16
+ const dataDir = process.argv[3] ? resolve(process.argv[3]) : null;
17
+ if (
18
+ !dataDir ||
19
+ !["install", "check"].includes(command) ||
20
+ !(
21
+ process.argv.length === 4 ||
22
+ (command === "install" &&
23
+ process.argv.length === 5 &&
24
+ process.argv[4] === "--restore")
25
+ )
26
+ ) {
27
+ throw new Error(
28
+ "usage: node user/scripts/setup-local.mjs <install|check> <private-data-dir>",
29
+ );
30
+ }
31
+ const walletDir = join(dataDir, "wallet");
32
+ const runtimeDir = join(dataDir, "runtime");
33
+ const credentialPath = join(runtimeDir, "caller.token");
34
+ const keystore = join(walletDir, "session.keystore.json");
35
+ const walletCli = walletCliPath();
36
+ const userRuntime = fileURLToPath(new URL("../src/index.mjs", import.meta.url));
37
+ for (const file of [walletCli, userRuntime]) await stat(file);
38
+
39
+ async function privateDirectory(path) {
40
+ const info = await lstat(path);
41
+ if (
42
+ !info.isDirectory() ||
43
+ info.isSymbolicLink() ||
44
+ info.uid !== process.getuid() ||
45
+ (info.mode & 0o077) !== 0
46
+ )
47
+ throw new Error(
48
+ `private directory ownership or permissions invalid: ${path}`,
49
+ );
50
+ }
51
+ async function runWallet(...args) {
52
+ const child = spawn(process.execPath, [walletCli, ...args], {
53
+ stdio: "inherit",
54
+ });
55
+ const code = await new Promise((resolveExit, reject) => {
56
+ child.once("error", reject);
57
+ child.once("exit", (value) => resolveExit(value));
58
+ });
59
+ if (code !== 0)
60
+ throw new Error(`Wallet ${args[0]} failed with exit status ${code}`);
61
+ }
62
+
63
+ if (command === "install") {
64
+ if (!process.stdin.isTTY || !process.stderr.isTTY) {
65
+ throw new Error(
66
+ "interactive terminal required for Wallet unlock-secret setup",
67
+ );
68
+ }
69
+ await mkdir(dataDir, { recursive: true, mode: 0o700 });
70
+ await privateDirectory(dataDir);
71
+ await mkdir(walletDir, { recursive: true, mode: 0o700 });
72
+ await mkdir(runtimeDir, { recursive: true, mode: 0o700 });
73
+ await privateDirectory(walletDir);
74
+ await privateDirectory(runtimeDir);
75
+ let exists = false;
76
+ try {
77
+ const info = await lstat(keystore);
78
+ if (!info.isFile() || info.isSymbolicLink())
79
+ throw new Error("Wallet keystore type invalid");
80
+ exists = true;
81
+ } catch (error) {
82
+ if (error.code !== "ENOENT") throw error;
83
+ }
84
+ if (!exists) {
85
+ const ask = async (message) => {
86
+ const terminal = createInterface({
87
+ input: process.stdin,
88
+ output: process.stderr,
89
+ });
90
+ try {
91
+ return (await terminal.question(message + "\n> ")).trim();
92
+ } finally {
93
+ terminal.close();
94
+ }
95
+ };
96
+ let choice =
97
+ process.argv[4] === "--restore"
98
+ ? "restore"
99
+ : await ask(
100
+ "Press Enter to create a new Wallet, type 'restore' to use your original key, or 'q' to return.",
101
+ );
102
+ while (!["", "restore", "q"].includes(choice))
103
+ choice = await ask(
104
+ "Enter 'restore', 'q', or press Enter for a new Wallet.",
105
+ );
106
+ if (choice === "q") process.exit(0);
107
+ if (choice === "restore") {
108
+ const source = await ask(
109
+ "Enter the path to your encrypted Session Key backup, or type 'key' to enter the original key privately ('q' to return).",
110
+ );
111
+ if (source === "q") process.exit(0);
112
+ if (!source) throw Error("A backup file path is required.");
113
+ await runWallet(
114
+ "import-key",
115
+ walletDir,
116
+ source === "key" ? "--private-key" : resolve(source),
117
+ );
118
+ process.stdout.write(
119
+ "Original Session Key restored. Spending permission is not bound yet.\n",
120
+ );
121
+ } else {
122
+ process.stdout.write(
123
+ "\nSet your Wallet unlock secret\n Enter it twice; input is hidden.\n\n",
124
+ );
125
+ await runWallet("init", walletDir);
126
+ }
127
+ process.stdout.write(
128
+ `\nKeep your Wallet recoverable\n Back up this encrypted file and keep its password:\n ${keystore}\n Never upload the backup to Market or share it with support.\n\n`,
129
+ );
130
+ }
131
+ try {
132
+ const info = await lstat(credentialPath);
133
+ if (
134
+ !info.isFile() ||
135
+ info.isSymbolicLink() ||
136
+ info.uid !== process.getuid() ||
137
+ (info.mode & 0o077) !== 0
138
+ )
139
+ throw new Error("existing caller credential type or permissions invalid");
140
+ } catch (error) {
141
+ if (error.code !== "ENOENT") throw error;
142
+ }
143
+ process.stdout.write(
144
+ "\nPrepare local runtime access\n The next step creates or reuses a local access credential.\n It is saved automatically; you do not need to copy it.\n\n",
145
+ );
146
+ await runWallet("prepare-access", walletDir);
147
+ await initializeCallerCredential(credentialPath);
148
+ }
149
+
150
+ await privateDirectory(dataDir);
151
+ await privateDirectory(walletDir);
152
+ await privateDirectory(runtimeDir);
153
+ const credentialInfo = await lstat(credentialPath);
154
+ if (
155
+ !credentialInfo.isFile() ||
156
+ credentialInfo.isSymbolicLink() ||
157
+ credentialInfo.uid !== process.getuid() ||
158
+ (credentialInfo.mode & 0o077) !== 0
159
+ ) {
160
+ throw new Error("User Runtime caller credential permissions invalid");
161
+ }
162
+ const token = (await readFile(credentialPath, "utf8")).trim();
163
+ if (!/^[0-9a-f]{64}$/.test(token))
164
+ throw new Error("User Runtime caller credential invalid");
165
+ const keystoreInfo = await lstat(join(walletDir, "session.keystore.json"));
166
+ if (
167
+ !keystoreInfo.isFile() ||
168
+ keystoreInfo.isSymbolicLink() ||
169
+ keystoreInfo.uid !== process.getuid() ||
170
+ (keystoreInfo.mode & 0o077) !== 0
171
+ ) {
172
+ throw new Error("Wallet keystore permissions invalid");
173
+ }
174
+ const addressFile = JSON.parse(await readFile(keystore, "utf8"));
175
+ const delegate = `0x${String(addressFile.address || "")}`;
176
+ if (!/^0x[0-9a-fA-F]{40}$/.test(delegate))
177
+ throw new Error("Wallet keystore address invalid");
178
+ let policyBoundLocalFile = false;
179
+ try {
180
+ policyBoundLocalFile = (await lstat(join(walletDir, "policy.json"))).isFile();
181
+ } catch (error) {
182
+ if (error.code !== "ENOENT") throw error;
183
+ }
184
+ if (command === "install") await rememberUserDataDir({ dataDir });
185
+ process.stdout.write(
186
+ `${JSON.stringify(
187
+ {
188
+ ok: true,
189
+ installed: true,
190
+ dataDir,
191
+ wallet: { delegate, lockState: "not_checked", policyBoundLocalFile },
192
+ userRuntime: { callerCredential: "ready", running: "not_checked" },
193
+ readyToPurchase: false,
194
+ next: "confirm the existing-Safe spending policy",
195
+ },
196
+ null,
197
+ 2,
198
+ )}\n`,
199
+ );
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MPL-2.0
3
+ // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ // If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
5
+ import { controlWallet } from "../src/wallet-control.mjs";
6
+ try {
7
+ process.exitCode = await controlWallet("status");
8
+ } catch {
9
+ console.error(
10
+ "Could not check Wallet status. Check your local installation.",
11
+ );
12
+ process.exitCode = 1;
13
+ }