@c4roa0/agentpay-user 0.0.0-stage → 0.3.0

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 (173) 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 +84 -2
  6. package/SECURITY.md +16 -0
  7. package/doc/README.md +52 -0
  8. package/doc/TODO.md +97 -0
  9. package/doc/architecture.md +96 -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 +67 -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 +89 -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 +178 -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 +1231 -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 +306 -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/readiness-profile.mjs +30 -0
  145. package/src/renew-policy.mjs +605 -0
  146. package/src/repair-market-wallet.mjs +137 -0
  147. package/src/restore-wallet.mjs +255 -0
  148. package/src/result-cache-view.mjs +276 -0
  149. package/src/runtime-client.mjs +1145 -0
  150. package/src/runtime-connection-settings.mjs +101 -0
  151. package/src/runtime-recovery.mjs +667 -0
  152. package/src/runtime-status.mjs +32 -0
  153. package/src/safe-owner-anvil.mjs +115 -0
  154. package/src/safe-policy-plan.mjs +195 -0
  155. package/src/service-reference.mjs +233 -0
  156. package/src/service-url.mjs +52 -0
  157. package/src/settlement-response.mjs +69 -0
  158. package/src/setup-autostart.mjs +197 -0
  159. package/src/setup-demo.mjs +152 -0
  160. package/src/skill-bundle.mjs +142 -0
  161. package/src/skill-client.mjs +198 -0
  162. package/src/skill-install-prompt.mjs +31 -0
  163. package/src/skill-package.mjs +50 -0
  164. package/src/skill-zip.mjs +220 -0
  165. package/src/status-panel.mjs +589 -0
  166. package/src/transport.mjs +48 -0
  167. package/src/uninstall-local.mjs +299 -0
  168. package/src/user-cli.mjs +354 -0
  169. package/src/user-setup-guide.mjs +193 -0
  170. package/src/wallet-control.mjs +182 -0
  171. package/src/wallet-ipc-error.mjs +16 -0
  172. package/src/wallet-package.mjs +22 -0
  173. package/update-pair.json +6 -0
@@ -0,0 +1,589 @@
1
+ import { walletCliPath } from "./wallet-package.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 { inspectDeploymentStatus } from "./deployment-status.mjs";
6
+ import { constants } from "node:fs";
7
+ import { open } from "node:fs/promises";
8
+ import { execFile } from "node:child_process";
9
+ import { promisify } from "node:util";
10
+ import { join } from "node:path";
11
+ import {
12
+ installedUserDir,
13
+ resolveUserDataDir,
14
+ } from "./local-data-directory.mjs";
15
+ import { readRuntimeStatus } from "./runtime-status.mjs";
16
+ const execute = promisify(execFile);
17
+ async function service(role) {
18
+ let stdout;
19
+ try {
20
+ ({ stdout } = await execute(
21
+ "systemctl",
22
+ [
23
+ "--user",
24
+ "show",
25
+ `agentpay-local-${role}.service`,
26
+ "--property=LoadState,ActiveState,SubState,UnitFileState",
27
+ ],
28
+ { timeout: 4000, maxBuffer: 4096 },
29
+ ));
30
+ } catch (error) {
31
+ if (
32
+ typeof error.stdout === "string" &&
33
+ /^LoadState=not-found$/m.test(error.stdout)
34
+ )
35
+ stdout = error.stdout;
36
+ else throw error;
37
+ }
38
+ return Object.fromEntries(
39
+ stdout
40
+ .trim()
41
+ .split("\n")
42
+ .map((line) => line.split("=")),
43
+ );
44
+ }
45
+ export async function readAuthorizationStatus(dataDir) {
46
+ let file;
47
+ try {
48
+ file = await open(
49
+ join(dataDir, "wallet/policy.json"),
50
+ constants.O_RDONLY | constants.O_NOFOLLOW,
51
+ );
52
+ const info = await file.stat();
53
+ if (
54
+ !info.isFile() ||
55
+ info.uid !== process.getuid() ||
56
+ info.mode & 0o077 ||
57
+ info.size > 16384
58
+ )
59
+ throw Error("Unsafe Policy file");
60
+ const p = JSON.parse(await file.readFile("utf8"));
61
+ if (
62
+ !Number.isSafeInteger(p.validUntil) ||
63
+ p.validUntil <= 0 ||
64
+ !/^0x[0-9a-f]{64}$/i.test(p.policyId)
65
+ )
66
+ throw Error("Invalid Policy");
67
+ let revoked = false,
68
+ marker;
69
+ try {
70
+ marker = await open(
71
+ join(dataDir, "wallet/policy-revocation.json"),
72
+ constants.O_RDONLY | constants.O_NOFOLLOW,
73
+ );
74
+ const st = await marker.stat();
75
+ if (
76
+ !st.isFile() ||
77
+ st.uid !== process.getuid() ||
78
+ st.mode & 0o077 ||
79
+ st.size > 16384
80
+ )
81
+ throw Error("Unsafe revocation record");
82
+ const record = JSON.parse(await marker.readFile("utf8"));
83
+ revoked = record.revoked === true && record.policyId === p.policyId;
84
+ } catch (e) {
85
+ if (e.code !== "ENOENT") throw e;
86
+ } finally {
87
+ await marker?.close();
88
+ }
89
+ return { policyId: p.policyId, validUntil: p.validUntil, revoked };
90
+ } finally {
91
+ await file?.close();
92
+ }
93
+ }
94
+ // Local proof receipt only; this does not claim the server is online or that
95
+ // the connection has not subsequently been disconnected on the website.
96
+ export async function readMarketStatus(dataDir) {
97
+ async function saved(path) {
98
+ let h;
99
+ try {
100
+ h = await open(
101
+ join(dataDir, path),
102
+ constants.O_RDONLY | constants.O_NOFOLLOW,
103
+ );
104
+ const st = await h.stat();
105
+ if (
106
+ !st.isFile() ||
107
+ st.uid !== process.getuid() ||
108
+ st.mode & 0o077 ||
109
+ st.size > 16384
110
+ )
111
+ throw Error("Unsafe connection settings");
112
+ return JSON.parse(await h.readFile("utf8"));
113
+ } catch (e) {
114
+ if (e.code === "ENOENT") return null;
115
+ throw e;
116
+ } finally {
117
+ await h?.close();
118
+ }
119
+ }
120
+ const receipt = await saved("market-connection.json");
121
+ if (!receipt) return { state: "none" };
122
+ const policy = await saved("wallet/policy.json"),
123
+ c = receipt.connection;
124
+ if (!receipt.marketOrigin || !c || !policy) return { state: "unknown" };
125
+ const expected = {
126
+ network: "eip155:" + policy.chainId,
127
+ module: policy.module,
128
+ safe: policy.safe,
129
+ delegate: policy.delegate,
130
+ policyId: policy.policyId,
131
+ };
132
+ if (Object.values(expected).some((v) => typeof v !== "string" || !v))
133
+ return { state: "unknown" };
134
+ return {
135
+ state: Object.entries(expected).every(
136
+ ([k, v]) =>
137
+ typeof c[k] === "string" && c[k].toLowerCase() === v.toLowerCase(),
138
+ )
139
+ ? "saved"
140
+ : "pending",
141
+ };
142
+ }
143
+ export async function collectLocalStatus() {
144
+ let dataDir;
145
+ try {
146
+ dataDir = await resolveUserDataDir();
147
+ } catch {
148
+ return { configurationError: true };
149
+ }
150
+ const results = await Promise.allSettled([
151
+ execute(
152
+ process.execPath,
153
+ [
154
+ walletCliPath(),
155
+ "status",
156
+ join(dataDir, "wallet"),
157
+ ],
158
+ { timeout: 3000, maxBuffer: 16384 },
159
+ ).then(({ stdout }) => {
160
+ const value = JSON.parse(stdout);
161
+ if (typeof value.locked !== "boolean" || typeof value.bound !== "boolean")
162
+ throw Error();
163
+ return value;
164
+ }),
165
+ service("wallet"),
166
+ service("runtime"),
167
+ readRuntimeStatus(dataDir),
168
+ readAuthorizationStatus(dataDir),
169
+ readMarketStatus(dataDir),
170
+ inspectDeploymentStatus(dataDir),
171
+ ]);
172
+ const value = (i) =>
173
+ results[i].status === "fulfilled" ? results[i].value : null;
174
+ return {
175
+ deployment: value(6) || { state: "unknown" },
176
+ market: value(5) || { state: "unknown" },
177
+ authorization: value(4),
178
+ wallet: value(0),
179
+ walletService: value(1),
180
+ runtimeService: value(2),
181
+ runtime: value(3),
182
+ runtimeUnsupported:
183
+ results[3].status === "rejected" &&
184
+ results[3].reason?.code === "STATUS_UNSUPPORTED",
185
+ };
186
+ }
187
+ export function loginStartupLabel(service) {
188
+ if (!service) return "Could not check";
189
+ if (service.LoadState === "not-found") return "Not configured for this user";
190
+ if (service.LoadState && !["loaded", "masked"].includes(service.LoadState))
191
+ return "Could not check";
192
+ const labels = {
193
+ enabled: "Enabled",
194
+ disabled: "Disabled",
195
+ "enabled-runtime": "Enabled until reboot",
196
+ masked: "Blocked",
197
+ "masked-runtime": "Blocked until reboot",
198
+ static: "No independent startup setting",
199
+ indirect: "Controlled by another unit",
200
+ alias: "Controlled by another unit",
201
+ generated: "Generated by the system",
202
+ transient: "Temporary service",
203
+ linked: "Not enabled for login",
204
+ "linked-runtime": "Not enabled for login",
205
+ };
206
+ return Object.hasOwn(labels, service.UnitFileState)
207
+ ? labels[service.UnitFileState]
208
+ : "Could not check";
209
+ }
210
+ const plainText = (text) => text.replace(/\x1b\[[0-9;]*m/g, "");
211
+ function wrapLine(line, width) {
212
+ if (plainText(line).length <= width) return line;
213
+ // Wrapped explanatory text remains plain; ANSI padding must not change layout.
214
+ line = plainText(line);
215
+ const indent = line.match(/^ */)[0],
216
+ words = line.trim().split(/ +/),
217
+ lines = [];
218
+ let current = indent;
219
+ for (const word of words) {
220
+ const candidate = current.trim() ? current + " " + word : indent + word;
221
+ if (plainText(candidate).length > width && current.trim()) {
222
+ lines.push(current);
223
+ current = indent + word;
224
+ } else current = candidate;
225
+ }
226
+ if (current.trim()) lines.push(current);
227
+ return lines.join("\n");
228
+ }
229
+ export function renderStatusPanel(
230
+ model,
231
+ {
232
+ color = false,
233
+ columns = 80,
234
+ nowSeconds = Math.floor(Date.now() / 1000),
235
+ } = {},
236
+ ) {
237
+ const width = Math.max(
238
+ 40,
239
+ Math.min(72, Number.isFinite(columns) ? Math.floor(columns) : 80),
240
+ );
241
+ const paint = (s, n) => (color ? `\x1b[${n}m${s}\x1b[0m` : s);
242
+ const badge = (level, width = 0) =>
243
+ paint(
244
+ (level === "ok" ? "[OK]" : level === "wait" ? "[WAIT]" : "[!]").padEnd(
245
+ width,
246
+ ),
247
+ level === "ok" ? 32 : level === "wait" ? 33 : 31,
248
+ );
249
+ const {
250
+ wallet: w,
251
+ runtime: r,
252
+ walletService: ws,
253
+ runtimeService: rs,
254
+ } = model;
255
+ const authorization = model.authorization;
256
+ const authorizationMatches =
257
+ authorization &&
258
+ w?.bound !== false &&
259
+ (!w?.policyId ||
260
+ w.policyId.toLowerCase() === authorization.policyId?.toLowerCase());
261
+ const expired =
262
+ authorizationMatches &&
263
+ Number.isSafeInteger(authorization.validUntil) &&
264
+ authorization.validUntil <= nowSeconds;
265
+ const revoked = authorizationMatches && authorization.revoked;
266
+ const issues = Array.isArray(r?.issues) ? r.issues : [];
267
+ const onlyRetryable =
268
+ r?.orders.review > 0 &&
269
+ issues.length === r.orders.review &&
270
+ issues.every((issue) => issue.code === "ORDER_CHECK_UNAVAILABLE");
271
+ const stopped = (s) =>
272
+ s && ["failed", "inactive", "deactivating"].includes(s.ActiveState);
273
+ let title = "Local checks passed",
274
+ level = "ok",
275
+ detail = "Wallet and service checks still apply before payment.",
276
+ next = null;
277
+ if (model.configurationError) {
278
+ title = "Local settings need attention";
279
+ level = "error";
280
+ detail =
281
+ "Your saved installation could not be read. Keep your existing Wallet data.";
282
+ next = "agentpay service logs runtime";
283
+ } else if (model.deployment?.state === "changed") {
284
+ title = "Spending permission needs updating";
285
+ level = "error";
286
+ detail =
287
+ "AgentPay deployment changed. New purchases need updated permission; existing Orders remain accessible.";
288
+ next = "agentpay policy renew";
289
+ } else if (revoked) {
290
+ title = "Spending permission revoked";
291
+ level = "error";
292
+ detail =
293
+ "New payments are stopped. Existing Orders and support remain available.";
294
+ next = "agentpay policy renew";
295
+ } else if (expired) {
296
+ title = "Spending permission expired";
297
+ level = "error";
298
+ detail =
299
+ "New payments are blocked. Your existing Orders and records are kept.";
300
+ next = "agentpay policy renew";
301
+ } else if (!r) {
302
+ title = stopped(rs)
303
+ ? "Runtime is not running"
304
+ : "Runtime status unavailable";
305
+ level = "error";
306
+ detail = model.runtimeUnsupported
307
+ ? "This Runtime version does not provide startup checks."
308
+ : stopped(rs)
309
+ ? "Start the background services, then check again."
310
+ : "Check the Runtime log for the cause.";
311
+ next = model.runtimeUnsupported
312
+ ? "Update User, then run agentpay service restart."
313
+ : stopped(rs)
314
+ ? "agentpay service start"
315
+ : "agentpay service logs runtime";
316
+ } else if (r.inventory === "blocked" || r.storageError) {
317
+ title = "History needs attention";
318
+ level = "error";
319
+ detail =
320
+ "New payments are paused. Keep your history; do not repeat purchases.";
321
+ next = "agentpay service logs runtime";
322
+ } else if (!w) {
323
+ title = stopped(ws) ? "Wallet is not running" : "Wallet status unavailable";
324
+ level = "error";
325
+ detail =
326
+ "Runtime is running. Order checks will continue when Wallet is available.";
327
+ next = stopped(ws) ? "agentpay service start" : "agentpay service logs wallet";
328
+ } else if (w.policyRenewalPending) {
329
+ title = "Spending permission renewal incomplete";
330
+ level = "wait";
331
+ detail = "Finish the saved renewal before making new payments.";
332
+ next = "agentpay policy renew";
333
+ } else if (w.policyMigrationPending) {
334
+ title = "Authorization update needed";
335
+ level = "wait";
336
+ detail = "New payments are paused until the update is complete.";
337
+ next = "agentpay policy renew";
338
+ } else if (w.locked) {
339
+ title = "Waiting for unlock";
340
+ level = "wait";
341
+ detail =
342
+ "Your services are running. Unlock Wallet to continue Order checks.";
343
+ next = "agentpay wallet unlock";
344
+ } else if (!w.bound) {
345
+ title = "Spending permission needed";
346
+ level = "wait";
347
+ detail = "Choose your Safe and spending limits in setup.";
348
+ next = "agentpay setup";
349
+ } else if (r.inventory !== "complete" || r.wallet !== "available") {
350
+ title = "Checks in progress";
351
+ level = "wait";
352
+ detail =
353
+ "Wallet is available. Runtime will refresh its checks automatically.";
354
+ next =
355
+ "Leave the services running. Wait about 15 seconds, then run agentpay status. Large histories may take longer.";
356
+ } else if (model.deployment?.state === "downloaded") {
357
+ title = "Platform deployment not checked online";
358
+ level = "wait";
359
+ detail =
360
+ "Only downloaded settings were compared. Connect Market to check current deployment.";
361
+ next = "agentpay market connect";
362
+ } else if (model.deployment?.state === "unknown") {
363
+ title = "Payment availability could not be confirmed";
364
+ level = "wait";
365
+ detail =
366
+ "Platform deployment check is unavailable. Check your network and retry.";
367
+ next = "agentpay status";
368
+ } else if (onlyRetryable) {
369
+ title = "Order checks temporarily unavailable";
370
+ level = "wait";
371
+ detail = "Runtime retries automatically. See the Order list for details.";
372
+ next = "agentpay orders list";
373
+ } else if (r.orders.review > 0) {
374
+ title = "Some Orders need review";
375
+ level = "wait";
376
+ detail = "Open your local Order list to review the warnings.";
377
+ next = "agentpay orders list";
378
+ } else if (r.orders.missingResult > 0) {
379
+ title = "Payment complete; local result unavailable";
380
+ level = "wait";
381
+ detail = "See the Order list for recovery options. Do not pay again.";
382
+ next = "agentpay orders list";
383
+ } else if (r.orders.archived > 0 && r.orders.waiting === 0) {
384
+ title = "Local review complete";
385
+ detail =
386
+ "Archived records are retained; archiving does not confirm their financial state.";
387
+ next = "agentpay orders list";
388
+ } else if (r.orders.expired > 0 && r.orders.waiting === 0) {
389
+ title = "Unpaid Orders expired";
390
+ level = "ok";
391
+ detail =
392
+ "These Orders ended without payment. No service result is expected.";
393
+ next =
394
+ "No action is needed for expired Orders. They will not be paid again automatically.";
395
+ } else if (r.orders.waiting > 0) {
396
+ title = "Orders still in progress";
397
+ level = "wait";
398
+ detail =
399
+ "Runtime checks their status automatically; it does not run the service for you.";
400
+ next =
401
+ "Keep services running. Check progress in the original Agent conversation. If continuing a supported request, keep its original identifier; never create a replacement purchase.";
402
+ }
403
+ const marketPending = ["pending", "unknown"].includes(model.market?.state);
404
+ if (marketPending && level === "ok") {
405
+ title =
406
+ model.market.state === "pending"
407
+ ? "Market connection needs updating"
408
+ : "Market connection could not be checked";
409
+ level = "wait";
410
+ detail =
411
+ "Website access to this Policy is not confirmed. Local payments are unaffected.";
412
+ next = "agentpay market connect";
413
+ }
414
+ const divider = "-".repeat(Math.min(60, width));
415
+ const lines = [
416
+ "",
417
+ divider,
418
+ `> AgentPay status`,
419
+ divider,
420
+ `${badge(level)} ${paint(title, 1)}`,
421
+ ` ${detail}`,
422
+ "",
423
+ ];
424
+ if (!model.configurationError) {
425
+ lines.push("> Services");
426
+ const row = (label, text, state) =>
427
+ lines.push(` ${badge(state, 6)} ${label.padEnd(8)} ${text}`);
428
+ row(
429
+ "Wallet",
430
+ w
431
+ ? w.locked
432
+ ? "Running · locked"
433
+ : "Running · unlocked"
434
+ : stopped(ws)
435
+ ? "Stopped"
436
+ : "Not checked",
437
+ w ? (w.locked ? "wait" : "ok") : "error",
438
+ );
439
+ row(
440
+ "Runtime",
441
+ r ? "Running" : stopped(rs) ? "Stopped" : "Not checked",
442
+ r ? "ok" : "error",
443
+ );
444
+ if (r)
445
+ row(
446
+ "History",
447
+ r.storageError
448
+ ? "Checks not saved"
449
+ : r.inventory === "complete"
450
+ ? "Local check complete"
451
+ : r.inventory === "blocked"
452
+ ? "Needs review"
453
+ : "Checking",
454
+ r.storageError || r.inventory === "blocked"
455
+ ? "error"
456
+ : r.inventory === "complete"
457
+ ? "ok"
458
+ : "wait",
459
+ );
460
+ lines.push("", "> Policy");
461
+ const policyState =
462
+ model.deployment?.state === "changed"
463
+ ? ["error", "Update required · deployment changed"]
464
+ : w?.policyRenewalPending
465
+ ? ["wait", "Renewal incomplete"]
466
+ : w?.policyMigrationPending
467
+ ? ["wait", "Migration incomplete"]
468
+ : w?.bound === false
469
+ ? ["wait", "Not configured"]
470
+ : revoked
471
+ ? ["error", "Revoked (last verified)"]
472
+ : expired
473
+ ? ["error", "Expired"]
474
+ : authorizationMatches
475
+ ? ["ok", "Within saved validity period"]
476
+ : ["wait", "Could not confirm"];
477
+ lines.push(` ${badge(policyState[0])} ${policyState[1]}`);
478
+ if (authorizationMatches)
479
+ lines.push(
480
+ ` Valid until: ${new Date(authorization.validUntil * 1000).toLocaleString()}`,
481
+ );
482
+ if (model.deployment?.state === "unknown")
483
+ lines.push(` ${badge("wait")} Platform compatibility not confirmed`);
484
+ if (model.deployment?.state === "downloaded")
485
+ lines.push(
486
+ " Deployment matches downloaded settings; live compatibility not confirmed.",
487
+ );
488
+ if (policyState[1] === "Could not confirm")
489
+ lines.push(" Details: agentpay policy info");
490
+ const startup = loginStartupLabel;
491
+ lines.push("", "> Login startup");
492
+ const startupBadge = (s) =>
493
+ startup(s) === "Enabled"
494
+ ? "ok"
495
+ : ["Could not check", "Blocked", "Blocked until reboot"].includes(
496
+ startup(s),
497
+ )
498
+ ? "error"
499
+ : "wait";
500
+ if (startup(ws) === startup(rs)) {
501
+ const shared = startup(ws);
502
+ lines.push(
503
+ ` ${badge(startupBadge(ws))} ${shared === "Not configured for this user" ? "Neither service configured" : shared === "Could not check" ? "Could not check either service" : shared + " for both services"}`,
504
+ );
505
+ } else {
506
+ row("Wallet", startup(ws), startupBadge(ws));
507
+ row("Runtime", startup(rs), startupBadge(rs));
508
+ }
509
+ if (model.market) {
510
+ lines.push("", "> Market");
511
+ const state = model.market.state;
512
+ lines.push(
513
+ state === "saved"
514
+ ? ` ${badge("ok")} Current Policy connection saved locally`
515
+ : state === "none"
516
+ ? " Not connected from this installation"
517
+ : state === "pending"
518
+ ? ` ${badge("wait")} Current Policy not connected`
519
+ : ` ${badge("wait")} Could not check saved connection`,
520
+ );
521
+ if (marketPending) lines.push(" Continue: agentpay market connect");
522
+ }
523
+ }
524
+ if (r && r.inventory !== "complete")
525
+ lines.push(
526
+ "",
527
+ "> Orders",
528
+ " Counts unavailable until history is checked.",
529
+ );
530
+ if (r && r.inventory === "complete") {
531
+ const count = (v) => (Number.isSafeInteger(v) && v >= 0 ? v : "?");
532
+ lines.push(
533
+ "",
534
+ "> Orders",
535
+ r.orders.total === 0
536
+ ? " No saved Orders."
537
+ : ` ${count(r.orders.total)} saved · ${count(r.orders.settled)} settled${r.orders.expired ? " · " + count(r.orders.expired) + " expired unpaid" : ""} · ${count(r.orders.waiting)} waiting`,
538
+ );
539
+ if (r.orders.archived > 0)
540
+ lines.push(
541
+ ` ${count(r.orders.archived)} archived locally · agentpay orders list`,
542
+ );
543
+ if (r.orders.review > 0)
544
+ lines.push(
545
+ ` ${badge("wait")} ${paint(`${count(r.orders.review)} need review`, "1;33")}`,
546
+ );
547
+ if (r.orders.missingResult > 0)
548
+ lines.push(
549
+ ` ${badge("wait")} ${paint(`${count(r.orders.missingResult)} local ${r.orders.missingResult === 1 ? "result needs" : "results need"} recovery`, "1;33")}`,
550
+ );
551
+ if (r.orders.legacy)
552
+ lines.push(
553
+ ` ${count(r.orders.legacy)} older ${r.orders.legacy === 1 ? "request" : "requests"} preserved.`,
554
+ );
555
+ }
556
+ if (next)
557
+ lines.push(
558
+ "",
559
+ "> Next",
560
+ ` ${next.startsWith("./") ? paint(next, 36) : next}`,
561
+ );
562
+ else if (level === "ok") lines.push("", " Status check complete.");
563
+ lines.push("");
564
+ return {
565
+ text: lines
566
+ .filter(
567
+ (line, index) => line !== "" || index === 0 || lines[index - 1] !== "",
568
+ )
569
+ .map((line) => wrapLine(line, width))
570
+ .join("\n"),
571
+ code: level === "ok" ? 0 : 1,
572
+ };
573
+ }
574
+ export async function showStatusPanel(
575
+ write = console.log,
576
+ {
577
+ collect = collectLocalStatus,
578
+ color = Boolean(process.stdout.isTTY) &&
579
+ process.env.TERM !== "dumb" &&
580
+ !Object.hasOwn(process.env, "NO_COLOR"),
581
+ } = {},
582
+ ) {
583
+ const panel = renderStatusPanel(await collect(), {
584
+ color,
585
+ columns: process.stdout.columns || 80,
586
+ });
587
+ write(panel.text);
588
+ return panel.code;
589
+ }
@@ -0,0 +1,48 @@
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
+ export async function fetchJsonWithTimeout(url, options, timeoutMs) {
5
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1)
6
+ throw new Error("HTTP timeout must be positive");
7
+ const controller = new AbortController();
8
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
9
+ try {
10
+ const response = await fetch(url, {
11
+ ...options,
12
+ redirect: "manual",
13
+ signal: controller.signal,
14
+ });
15
+ if (response.status >= 300 && response.status < 400) {
16
+ await response.body?.cancel();
17
+ throw Object.assign(Error("HTTP redirects are not allowed"), {
18
+ code: "TRANSPORT_REDIRECT_REJECTED",
19
+ });
20
+ }
21
+ const chunks = [];
22
+ let length = 0;
23
+ for await (const chunk of response.body) {
24
+ length += chunk.length;
25
+ if (length > 2 * 1024 * 1024)
26
+ throw Object.assign(
27
+ Error("HTTP response exceeds the JSON size limit"),
28
+ { code: "TRANSPORT_RESPONSE_TOO_LARGE" },
29
+ );
30
+ chunks.push(chunk);
31
+ }
32
+ let result;
33
+ try {
34
+ result = JSON.parse(Buffer.concat(chunks).toString("utf8"));
35
+ } catch {
36
+ result = null;
37
+ }
38
+ return { response, result };
39
+ } catch (error) {
40
+ if (controller.signal.aborted)
41
+ throw Object.assign(new Error("HTTP peer did not complete in time"), {
42
+ code: "TRANSPORT_TIMEOUT",
43
+ });
44
+ throw error;
45
+ } finally {
46
+ clearTimeout(timer);
47
+ }
48
+ }