@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,377 @@
1
+ // SPDX-License-Identifier: MPL-2.0
2
+ // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
3
+ // If a copy was not distributed with this file, see https://mozilla.org/MPL/2.0/.
4
+ import { displaySeconds } from "./local-time.mjs";
5
+ import { randomBytes } from "node:crypto";
6
+ import { parseUnits, getAddress, formatUnits } from "ethers";
7
+ import { buildSafePolicyPlan } from "./safe-policy-plan.mjs";
8
+ import { authorizationFields } from "./authorization-question-checkpoint.mjs";
9
+
10
+ export function authorizationContext(deployment, delegate) {
11
+ return JSON.stringify(
12
+ Object.fromEntries(
13
+ [
14
+ "chainId",
15
+ "rpcUrl",
16
+ "module",
17
+ "token",
18
+ "factory",
19
+ "registry",
20
+ "tokenDecimals",
21
+ "tokenSymbol",
22
+ "ownerAddress",
23
+ ]
24
+ .map((key) => [key, deployment[key] ?? null])
25
+ .concat([["delegate", delegate]]),
26
+ ),
27
+ );
28
+ }
29
+ export function showAuthorizationSummary(plan, { write, currency, decimals }) {
30
+ write(
31
+ `Safe: ${plan.safe}\nNetwork: eip155:${plan.chainId}\nTotal budget: ${formatUnits(plan.totalBudget, decimals)} ${currency}\nPer-purchase limit: ${formatUnits(plan.maxPerPayment, decimals)} ${currency}\nExpires: ${displaySeconds(plan.validUntil)}`,
32
+ );
33
+ write(
34
+ "\nYour Agent can make purchases within these limits without asking you each time.\nIt could spend the full budget.",
35
+ );
36
+ }
37
+ export async function runPolicyTerminalGuide(options) {
38
+ let checkpoint = options.checkpoint;
39
+ let current = {
40
+ ...options,
41
+ saveInputs: async (value) => {
42
+ await options.saveInputs?.(value);
43
+ checkpoint = value;
44
+ },
45
+ };
46
+ for (;;) {
47
+ try {
48
+ return await runGuideOnce(current);
49
+ } catch (error) {
50
+ if (error.code === "AUTHORIZATION_BACK_REVIEW") {
51
+ const { days, ...previous } = checkpoint.fields;
52
+ const edited = { ...checkpoint, fields: previous };
53
+ await current.saveInputs(edited);
54
+ current = { ...current, checkpoint: edited };
55
+ continue;
56
+ }
57
+ if (error.code !== "AUTHORIZATION_BACK") throw error;
58
+ current = { ...current, checkpoint: error.checkpoint };
59
+ checkpoint = error.checkpoint;
60
+ }
61
+ }
62
+ }
63
+ async function runGuideOnce({
64
+ deployment,
65
+ delegate,
66
+ demo = false,
67
+ collectOnly = false,
68
+ defaults = {},
69
+ boundSafe = null,
70
+ migration = false,
71
+ checkpoint = null,
72
+ saveInputs = async () => {},
73
+ restoreExisting = null,
74
+ ask,
75
+ write,
76
+ inspect,
77
+ }) {
78
+ if (
79
+ !Number.isInteger(deployment.tokenDecimals) ||
80
+ deployment.tokenDecimals < 0 ||
81
+ deployment.tokenDecimals > 18 ||
82
+ typeof deployment.source !== "string" ||
83
+ !deployment.source.trim()
84
+ )
85
+ throw Error("Public deployment data requires tokenDecimals and source");
86
+ const rpc = new URL(deployment.rpcUrl);
87
+ if (rpc.username || rpc.password)
88
+ throw Error("RPC URL must not contain credentials");
89
+ const publicDeployment = Object.fromEntries(
90
+ ["chainId", "rpcUrl", "module", "token", "factory", "registry"].map(
91
+ (key) => [key, deployment[key]],
92
+ ),
93
+ );
94
+ const currency = demo
95
+ ? "demo USDC"
96
+ : deployment.tokenSymbol || "payment tokens";
97
+ const suggested = boundSafe
98
+ ? getAddress(boundSafe)
99
+ : demo
100
+ ? "0x6666666666666666666666666666666666666666"
101
+ : deployment.mode === "private-anvil" && deployment.chainId === 31337
102
+ ? deployment.safe
103
+ : null;
104
+ const details = {
105
+ ...publicDeployment,
106
+ ...(suggested ? { safe: getAddress(suggested) } : {}),
107
+ delegate,
108
+ tokenDecimals: deployment.tokenDecimals,
109
+ tokenSymbol: currency,
110
+ };
111
+ const previousChoice = checkpoint?.fields?.policyChoice;
112
+ const editableCheckpoint =
113
+ previousChoice && previousChoice !== "new"
114
+ ? {
115
+ ...checkpoint,
116
+ fields: Object.fromEntries(
117
+ Object.entries(checkpoint.fields).filter(
118
+ ([key]) => key !== "policyChoice",
119
+ ),
120
+ ),
121
+ }
122
+ : checkpoint;
123
+ const field = authorizationFields({
124
+ context: authorizationContext(deployment, delegate),
125
+ checkpoint: editableCheckpoint,
126
+ ask,
127
+ save: saveInputs,
128
+ write,
129
+ details,
130
+ });
131
+ write(
132
+ "\n------------------------------------------------------------\n2/4 Set spending permission\n------------------------------------------------------------",
133
+ );
134
+ if (demo) write("Demo only: no real authorization or payment.");
135
+ if (boundSafe) write(`Using your connected Safe: ${getAddress(boundSafe)}`);
136
+ else if (demo && suggested) write(`Demo Safe: ${getAddress(suggested)}`);
137
+ else if (!suggested)
138
+ write(
139
+ `Use your Safe account address on chain ${deployment.chainId}. Find it at https://app.safe.global/.`,
140
+ );
141
+ const safe = await field(
142
+ "safe",
143
+ suggested
144
+ ? "Safe address (Enter to use the address above; type 'create' if you need a Safe)"
145
+ : "Enter your Safe account address, or type 'create' for guidance",
146
+ (value) => {
147
+ if (value.toLowerCase() === "create")
148
+ throw Error(
149
+ `This guide cannot create a Safe. ${deployment.chainId === 31337 ? "Ask the operator for the private Anvil (31337) test Safe; the official app does not offer this network." : `Open https://app.safe.global/ on chain ${deployment.chainId}.`} Use the Safe account address, not its owner address.`,
150
+ );
151
+ try {
152
+ return getAddress(value || suggested);
153
+ } catch {
154
+ throw Error("Enter a full Safe account address starting with 0x.");
155
+ }
156
+ },
157
+ !demo && suggested ? getAddress(suggested) : undefined,
158
+ );
159
+ if (restoreExisting && !demo) {
160
+ write(
161
+ previousChoice && previousChoice !== "new"
162
+ ? `Original Policy: ${previousChoice}. Press Enter to retry it, or enter a corrected Policy ID.`
163
+ : "Press Enter to create new spending permission, or enter your original Policy ID to restore it.",
164
+ );
165
+ const choice = await field(
166
+ "policyChoice",
167
+ "New permission or original Policy ID",
168
+ (v) => {
169
+ v = v || previousChoice || "new";
170
+ if (v === "new") return "new";
171
+ if (!/^0x[0-9a-f]{64}$/i.test(v) || /^0x0+$/.test(v))
172
+ throw Error(
173
+ "Enter a full Policy ID, or press Enter for new permission.",
174
+ );
175
+ return v.toLowerCase();
176
+ },
177
+ checkpoint?.fields?.budget && !checkpoint?.fields?.policyChoice
178
+ ? "new"
179
+ : undefined,
180
+ );
181
+ if (choice !== "new") {
182
+ const result = await restoreExisting({
183
+ chainId: deployment.chainId,
184
+ rpcUrl: deployment.rpcUrl,
185
+ module: deployment.module,
186
+ safe,
187
+ policyId: choice,
188
+ });
189
+ return {
190
+ restored: true,
191
+ policyConfig: {
192
+ ...publicDeployment,
193
+ safe,
194
+ policyId: choice,
195
+ totalBudget: result.terms.totalBudget,
196
+ maxPerPayment: result.terms.maxPerPayment,
197
+ validAfter: result.terms.validAfter,
198
+ validUntil: result.terms.validUntil,
199
+ },
200
+ };
201
+ }
202
+ }
203
+ const amount = (value) => {
204
+ if (
205
+ !/^\d+(\.\d+)?$/.test(value) ||
206
+ parseUnits(value, deployment.tokenDecimals) <= 0n
207
+ )
208
+ throw Error(`Enter an amount greater than zero in ${currency}.`);
209
+ return value;
210
+ };
211
+ write(
212
+ migration
213
+ ? "Your remaining allowance is the default budget for the updated permission."
214
+ : "Set the total amount your Agent may spend without asking you to approve each payment.",
215
+ );
216
+ if (demo || deployment.mode === "private-anvil")
217
+ write(`${currency} is the test currency for this rehearsal.`);
218
+ write(`Currency: ${currency}`);
219
+ const budget = await field(
220
+ "budget",
221
+ defaults.budget
222
+ ? `Total budget · Enter to keep ${defaults.budget} ${currency}`
223
+ : "Enter total budget",
224
+ (v) => amount(v || defaults.budget || ""),
225
+ );
226
+ write(
227
+ "\n------------------------------------------------------------\nPer-purchase limit\n------------------------------------------------------------",
228
+ );
229
+ write("Set the most your Agent may spend on one purchase.");
230
+ write(`Currency: ${currency} · Maximum: ${budget}`);
231
+ const previousLimit =
232
+ defaults.perPayment &&
233
+ parseUnits(defaults.perPayment, deployment.tokenDecimals) <=
234
+ parseUnits(budget, deployment.tokenDecimals)
235
+ ? defaults.perPayment
236
+ : null;
237
+ const perPayment = await field(
238
+ "perPayment",
239
+ previousLimit
240
+ ? `Per-purchase limit · Enter to keep ${previousLimit} ${currency}`
241
+ : "Enter per-purchase limit",
242
+ (value) => {
243
+ value = value || previousLimit || "";
244
+ amount(value);
245
+ if (
246
+ parseUnits(value, deployment.tokenDecimals) >
247
+ parseUnits(budget, deployment.tokenDecimals)
248
+ )
249
+ throw Error(
250
+ `Enter an amount no greater than your total budget of ${budget} ${currency}.`,
251
+ );
252
+ return value;
253
+ },
254
+ );
255
+ write(
256
+ "\n------------------------------------------------------------\nAuthorization expiry\n------------------------------------------------------------",
257
+ );
258
+ write(
259
+ migration && defaults.validUntil
260
+ ? `Keep the original expiry: ${displaySeconds(defaults.validUntil)}. Enter a different number of days to change it.`
261
+ : "Allow your Agent to make purchases for this many days, starting now.",
262
+ );
263
+ write(
264
+ "When this period ends, new purchases stop until you authorize them again.",
265
+ );
266
+ if (defaults.daysRounded)
267
+ write(
268
+ "The previous duration is rounded up to whole days for this default.",
269
+ );
270
+ const days = await field(
271
+ "days",
272
+ migration && defaults.validUntil
273
+ ? `Enter to keep the original expiry, or enter new validity in days`
274
+ : defaults.days
275
+ ? `Validity in days · Enter to keep ${defaults.days}`
276
+ : "Enter validity in days",
277
+ (value) => {
278
+ value = value || defaults.days || "";
279
+ const end = Math.floor(Date.now() / 1000) + Number(value) * 86400;
280
+ if (
281
+ !/^[1-9][0-9]*$/.test(value) ||
282
+ !Number.isSafeInteger(end) ||
283
+ end * 1000 > 253402300799999
284
+ )
285
+ throw Error("Enter a positive whole number of days.");
286
+ return value;
287
+ },
288
+ );
289
+ const startsAt = Math.floor(Date.now() / 1000);
290
+ const policyConfig = {
291
+ ...publicDeployment,
292
+ safe,
293
+ policyId: `0x${randomBytes(32).toString("hex")}`,
294
+ totalBudget: parseUnits(budget, deployment.tokenDecimals).toString(),
295
+ maxPerPayment: parseUnits(perPayment, deployment.tokenDecimals).toString(),
296
+ validAfter: startsAt,
297
+ validUntil:
298
+ migration && defaults.validUntil && days === defaults.days
299
+ ? defaults.validUntil
300
+ : startsAt + Number(days) * 86400,
301
+ };
302
+ const plan = buildSafePolicyPlan(policyConfig, delegate);
303
+ let chainState = null,
304
+ chainError = null;
305
+ if (!demo)
306
+ write(
307
+ "Checking the network, Safe and contracts… This may take a moment. No transaction is being submitted.",
308
+ );
309
+ if (!demo)
310
+ try {
311
+ chainState = await inspect(plan);
312
+ } catch {
313
+ chainError =
314
+ "Chain check failed. Check the RPC, network, Safe and deployment before retrying.";
315
+ }
316
+ if (chainError) {
317
+ write(chainError);
318
+ return { chainError, readyToSign: false, readyToPurchase: false };
319
+ }
320
+ if (!collectOnly) {
321
+ showAuthorizationSummary(plan, {
322
+ write,
323
+ currency,
324
+ decimals: deployment.tokenDecimals,
325
+ });
326
+ while (true) {
327
+ const value = (
328
+ await ask(
329
+ "Type 'confirm' to finish this preview, 'back' to change the draft, 'details' for transaction data, or 'q' to return\n> ",
330
+ )
331
+ )
332
+ .trim()
333
+ .toLowerCase();
334
+ if (value === "back")
335
+ throw Object.assign(Error("Change draft"), {
336
+ code: "AUTHORIZATION_BACK_REVIEW",
337
+ });
338
+ if (value === "q")
339
+ throw Object.assign(
340
+ Error("Preview cancelled; no authorization was submitted."),
341
+ { code: "CANCELLED" },
342
+ );
343
+ if (value === "details") {
344
+ write(
345
+ JSON.stringify(
346
+ { policyId: plan.policyId, calls: plan.calls },
347
+ null,
348
+ 2,
349
+ ),
350
+ );
351
+ continue;
352
+ }
353
+ if (value === "confirm") break;
354
+ }
355
+ write("Authorization preview complete. No permission was granted.");
356
+ }
357
+ return {
358
+ mode: demo ? "demo" : "read_only",
359
+ readyToSign: false,
360
+ readyToPurchase: false,
361
+ chainState,
362
+ chainError,
363
+ policyConfig,
364
+ calls: plan.calls,
365
+ draft: {
366
+ safe,
367
+ module: plan.module,
368
+ chainId: plan.chainId,
369
+ budget,
370
+ perPayment,
371
+ currency,
372
+ expiry: displaySeconds(plan.validUntil),
373
+ delegate: plan.delegate,
374
+ calls: plan.calls,
375
+ },
376
+ };
377
+ }
@@ -0,0 +1,143 @@
1
+ import { deploymentFilePath, importSetupFiles } from "./installation-paths.mjs";
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 { ensureRuntimeConnection } from "./runtime-connection-settings.mjs";
6
+ import { constants } from "node:fs";
7
+ import { open } from "node:fs/promises";
8
+ import { dirname, join } from "node:path";
9
+ import { createInterface } from "node:readline/promises";
10
+ import { installedUserDir } from "./local-data-directory.mjs";
11
+ import {
12
+ checkServiceDirectory,
13
+ readServiceSettings,
14
+ saveServiceSettings,
15
+ serviceSettings,
16
+ } from "./local-services.mjs";
17
+
18
+ async function privateJson(path) {
19
+ const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
20
+ try {
21
+ const st = await file.stat();
22
+ if (
23
+ !st.isFile() ||
24
+ st.uid !== process.getuid() ||
25
+ st.mode & 0o077 ||
26
+ st.size > 65536
27
+ )
28
+ throw Error(
29
+ "Saved User settings must be private, regular files owned by you.",
30
+ );
31
+ return JSON.parse(await file.readFile("utf8"));
32
+ } finally {
33
+ await file.close();
34
+ }
35
+ }
36
+ async function optionalJson(path) {
37
+ try {
38
+ return await privateJson(path);
39
+ } catch (e) {
40
+ if (e.code === "ENOENT") return null;
41
+ throw e;
42
+ }
43
+ }
44
+ export async function askServiceSetting(prompt) {
45
+ if (!process.stdin.isTTY)
46
+ throw Error(
47
+ "Startup settings are incomplete. Run agentpay service start in your terminal to finish configuration.",
48
+ );
49
+ const terminal = createInterface({
50
+ input: process.stdin,
51
+ output: process.stdout,
52
+ });
53
+ try {
54
+ return await terminal.question(`${prompt}\n> `);
55
+ } finally {
56
+ terminal.close();
57
+ }
58
+ }
59
+ // Only participant-owned settings are consulted. No platform environment file,
60
+ // default endpoint or business-specific Skill is a source of startup authority.
61
+ export async function ensureLocalServices({
62
+ dataDir,
63
+ userDir = installedUserDir,
64
+ ask = askServiceSetting,
65
+ write = console.log,
66
+ connectionOptions = {},
67
+ } = {}) {
68
+ let saved;
69
+ try {
70
+ saved = await readServiceSettings(dataDir);
71
+ } catch (e) {
72
+ if (e.code !== "ENOENT") throw e;
73
+ }
74
+ if (saved) {
75
+ await ensureRuntimeConnection({ dataDir, write, ...connectionOptions });
76
+ return saved;
77
+ }
78
+ await checkServiceDirectory(dataDir);
79
+ const key = await optionalJson(
80
+ join(dataDir, "wallet", "session.keystore.json"),
81
+ );
82
+ if (!key) throw Error("Wallet is not initialized. Run agentpay setup first.");
83
+ const connection = await optionalJson(
84
+ join(dataDir, "runtime", "connection.json"),
85
+ );
86
+ if (
87
+ connection &&
88
+ (connection.version !== "agentpay.runtime-connection.v1" ||
89
+ connection.callerTokenFile !== join(dataDir, "runtime", "caller.token"))
90
+ )
91
+ throw Error(
92
+ "The saved Runtime connection does not match this User directory. Review it before starting.",
93
+ );
94
+ const setup = await optionalJson(
95
+ await deploymentFilePath(dataDir, userDir, "anvil-onboarding.json"),
96
+ );
97
+ const policy = await optionalJson(join(dataDir, "wallet", "policy.json"));
98
+ if (
99
+ setup &&
100
+ policy &&
101
+ (["safe", "module"].some(
102
+ (k) => String(setup[k]).toLowerCase() !== String(policy[k]).toLowerCase(),
103
+ ) ||
104
+ Number(setup.chainId) !== Number(policy.chainId))
105
+ )
106
+ throw Error(
107
+ "Saved setup belongs to a different authorization. Your current Wallet was kept; review its startup addresses first.",
108
+ );
109
+ let runtimeUrl = connection?.runtimeUrl || setup?.runtimeUrl;
110
+ if (
111
+ connection &&
112
+ setup &&
113
+ new URL(connection.runtimeUrl).origin !== new URL(setup.runtimeUrl).origin
114
+ )
115
+ throw Error(
116
+ "Saved Runtime addresses disagree. Review your local settings before starting.",
117
+ );
118
+ let settlementUrl = setup?.settlementUrl;
119
+ write(
120
+ "Preparing background services for your existing Wallet. Your key and spending permission stay unchanged.",
121
+ );
122
+ const request = async (label) => {
123
+ const value = (await ask(label + " (type 'q' to cancel)")).trim();
124
+ if (value === "q" || !value)
125
+ throw Error(
126
+ "Startup preparation cancelled. Your Wallet data is unchanged.",
127
+ );
128
+ return value;
129
+ };
130
+ if (!runtimeUrl)
131
+ runtimeUrl = await request(
132
+ "Local Runtime address, including its port (for example http://127.0.0.1:6490)",
133
+ );
134
+ if (!settlementUrl)
135
+ settlementUrl = await request(
136
+ "Settlement address supplied by your AgentPay platform",
137
+ );
138
+ const settings = serviceSettings({ version: 1, runtimeUrl, settlementUrl });
139
+ await saveServiceSettings({ dataDir, ...settings });
140
+ await ensureRuntimeConnection({ dataDir, write, ...connectionOptions });
141
+ write("Startup settings saved. Login startup preference was not changed.");
142
+ return settings;
143
+ }