@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,299 @@
1
+ import { installationDirectory, legacyInstallationDirectory } from "./installation-paths.mjs";
2
+ import { walletPackage } from "./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 { lstat, realpath, readFile, rm, unlink } from "node:fs/promises";
7
+ import { dirname, join, relative, resolve, parse, isAbsolute } from "node:path";
8
+ import { homedir } from "node:os";
9
+ import { execFile } from "node:child_process";
10
+ import { promisify } from "node:util";
11
+ import { createInterface } from "node:readline/promises";
12
+ import { autostartFiles, isManagedUnit } from "./autostart-files.mjs";
13
+ import {
14
+ resolveUserDataDir,
15
+ installedUserDir,
16
+ } from "./local-data-directory.mjs";
17
+ import { readServiceSettings, listenerPresent } from "./local-services.mjs";
18
+ const execute = promisify(execFile);
19
+ const inside = (parent, path) => {
20
+ const r = relative(parent, path);
21
+ return r === "" || (!r.startsWith("..") && !parse(r).root);
22
+ };
23
+ async function target(path, { directory = false, privateMode = false } = {}) {
24
+ try {
25
+ const info = await lstat(path);
26
+ if (
27
+ info.isSymbolicLink() ||
28
+ info.uid !== process.getuid() ||
29
+ info.mode & (privateMode ? 0o077 : 0o022) ||
30
+ (directory ? !info.isDirectory() : !info.isFile()) ||
31
+ (await realpath(path)) !== path
32
+ )
33
+ throw Error(`Cannot safely remove this path: ${path}`);
34
+ return { path, directory, dev: info.dev, ino: info.ino, privateMode };
35
+ } catch (error) {
36
+ if (error.code === "ENOENT") return null;
37
+ throw error;
38
+ }
39
+ }
40
+ export async function uninstallPlan({
41
+ dataDir,
42
+ userDir = installedUserDir,
43
+ home = homedir(),
44
+ node = process.execPath,
45
+ removeData = false,
46
+ }) {
47
+ dataDir = resolve(dataDir);
48
+ userDir = resolve(userDir);
49
+ home = resolve(home);
50
+ let walletDir;
51
+ try { walletDir = walletPackage(userDir).directory; } catch { walletDir = userDir; }
52
+ for (const protectedPath of [parse(dataDir).root, home, userDir, walletDir])
53
+ if (inside(dataDir, protectedPath))
54
+ throw Error(
55
+ "Refusing to remove a home, repository or ancestor directory.",
56
+ );
57
+ const targets = [];
58
+ const data = await target(dataDir, { directory: true, privateMode: true });
59
+ if (data && removeData) targets.push(data);
60
+ let settings;
61
+ if (data)
62
+ try {
63
+ settings = await readServiceSettings(dataDir);
64
+ } catch (e) {
65
+ if (e.code !== "ENOENT") throw e;
66
+ const pid = await lstat(
67
+ join(dataDir, "runtime/user-runtime-guide.pid"),
68
+ ).catch((x) => {
69
+ if (x.code === "ENOENT") return null;
70
+ throw x;
71
+ });
72
+ if (pid)
73
+ throw Error(
74
+ "Runtime startup settings are missing but a guide process record exists. Inspect it before uninstalling.",
75
+ );
76
+ }
77
+ const installDir = installationDirectory();
78
+ for (const reserved of [join(home, ".config"), installDir])
79
+ if (inside(dataDir, reserved) || inside(reserved, dataDir))
80
+ throw Error(
81
+ "Data directory overlaps installation or service configuration. No files were removed.",
82
+ );
83
+ const lock = await lstat(join(installDir, "install.lock")).catch((e) => {
84
+ if (e.code === "ENOENT") return null;
85
+ throw e;
86
+ });
87
+ if (lock)
88
+ throw Error(
89
+ "An installer lock exists. Finish or inspect that installation before uninstalling.",
90
+ );
91
+ const install = await target(installDir, {
92
+ directory: true,
93
+ privateMode: true,
94
+ });
95
+ if (install && removeData) {
96
+ const state = await target(join(installDir, "state.json"), {
97
+ privateMode: true,
98
+ });
99
+ if (state) {
100
+ const saved = JSON.parse(await readFile(state.path, "utf8"));
101
+ if (
102
+ saved.version !== 1 ||
103
+ typeof saved.dataDir !== "string" ||
104
+ !isAbsolute(saved.dataDir) ||
105
+ resolve(saved.dataDir) !== dataDir
106
+ )
107
+ throw Error(
108
+ "Installation record points to another data directory; no files were removed.",
109
+ );
110
+ targets.push({ ...install, checkpoint: true });
111
+
112
+ }
113
+ }
114
+ // Retire only the old source checkpoint that names this exact data directory.
115
+ // Otherwise a later setup could rediscover and reimport purged configuration.
116
+ if (removeData) {
117
+ const legacyDir = legacyInstallationDirectory(userDir);
118
+ if (legacyDir !== installDir) {
119
+ const legacy = await target(legacyDir, { directory: true, privateMode: true });
120
+ const record = legacy && await target(join(legacyDir, "state.json"), { privateMode: true });
121
+ if (record) {
122
+ const saved = JSON.parse(await readFile(record.path, "utf8"));
123
+ if (saved.version === 1 && typeof saved.dataDir === "string" && isAbsolute(saved.dataDir) && resolve(saved.dataDir) === dataDir) {
124
+ if (await lstat(join(legacyDir, "install.lock")).catch(e => { if (e.code !== "ENOENT") throw e; return null; }))
125
+ throw Error("An old installer lock exists. Finish that installation before uninstalling.");
126
+ for (const name of ["user-deployment.json", "anvil-onboarding.json"]) {
127
+ const file = await target(join(dirname(userDir), name), { privateMode: true });
128
+ if (file) targets.push(file);
129
+ }
130
+ targets.push({ ...legacy, checkpoint: true });
131
+ }
132
+ }
133
+ }
134
+ }
135
+ const unitDir = join(home, ".config/systemd/user"),
136
+ generated = autostartFiles({ node, userDir, dataDir }),
137
+ units = [];
138
+ for (const [name, body] of Object.entries(generated)) {
139
+ const file = await target(join(unitDir, name));
140
+ if (!file) continue;
141
+ for (const path of [
142
+ join(home, ".config"),
143
+ join(home, ".config/systemd"),
144
+ unitDir,
145
+ ]) {
146
+ const info = await lstat(path);
147
+ if (
148
+ !info.isDirectory() ||
149
+ info.isSymbolicLink() ||
150
+ info.uid !== process.getuid() ||
151
+ info.mode & 0o022
152
+ )
153
+ throw Error("Service configuration directory ownership is unsafe.");
154
+ }
155
+ if (!isManagedUnit(await readFile(file.path, "utf8"), name, dataDir))
156
+ throw Error(
157
+ `Service file is not this installation's managed file: ${name}`,
158
+ );
159
+ units.push({ ...file, name });
160
+ }
161
+ return { dataDir, targets, units, settings, removeData };
162
+ }
163
+ async function unchanged(item) {
164
+ const fresh = await target(item.path, item);
165
+ if (!fresh || fresh.dev !== item.dev || fresh.ino !== item.ino)
166
+ throw Error("An uninstall target changed. No further files were removed.");
167
+ }
168
+ export async function removeInstallation(
169
+ plan,
170
+ {
171
+ manage = async (args) =>
172
+ execute("systemctl", ["--user", ...args], {
173
+ timeout: 45000,
174
+ maxBuffer: 16384,
175
+ }),
176
+ probe = listenerPresent,
177
+ } = {},
178
+ ) {
179
+ // Validate all targets before stopping, and again immediately before deletion.
180
+ for (const item of [...plan.targets, ...plan.units]) await unchanged(item);
181
+ if (plan.units.length) {
182
+ await manage(["stop", ...plan.units.map((u) => u.name)]);
183
+ for (const unit of plan.units) {
184
+ const { stdout } = await manage([
185
+ "show",
186
+ unit.name,
187
+ "--property=MainPID,ActiveState",
188
+ ]);
189
+ const state = Object.fromEntries(
190
+ stdout
191
+ .trim()
192
+ .split("\n")
193
+ .map((s) => s.split("=")),
194
+ );
195
+ if (
196
+ state.MainPID !== "0" ||
197
+ !["inactive", "failed"].includes(state.ActiveState)
198
+ )
199
+ throw Error("A local service has not stopped; data was preserved.");
200
+ }
201
+ }
202
+ if (await probe(join(plan.dataDir, "wallet/wallet.sock")))
203
+ throw Error(
204
+ "A Wallet outside these managed services is still running. Stop it explicitly; data was preserved.",
205
+ );
206
+ if (
207
+ plan.settings &&
208
+ (await probe({
209
+ host: "127.0.0.1",
210
+ port: Number(new URL(plan.settings.runtimeUrl).port),
211
+ }))
212
+ )
213
+ throw Error(
214
+ "A Runtime or other process still occupies this port. Stop/review it explicitly; data was preserved.",
215
+ );
216
+ if (plan.units.length) {
217
+ await manage(["disable", ...plan.units.map((u) => u.name)]);
218
+ for (const unit of plan.units) {
219
+ await unchanged(unit);
220
+ await unlink(unit.path);
221
+ }
222
+ await manage(["daemon-reload"]);
223
+ }
224
+ // Remove the installation checkpoint last so interrupted cleanup can find its scope.
225
+ const ordered = [...plan.targets].sort(
226
+ (a, b) =>
227
+ Number(Boolean(a.checkpoint)) -
228
+ Number(Boolean(b.checkpoint)),
229
+ );
230
+ for (const item of ordered) {
231
+ await unchanged(item);
232
+ await rm(item.path, { recursive: item.directory, force: false });
233
+ }
234
+ }
235
+ export async function uninstallLocal({
236
+ ask,
237
+ write = console.log,
238
+ plan = uninstallPlan,
239
+ remove = removeInstallation,
240
+ dataDir,
241
+ terminal = Boolean(process.stdin.isTTY),
242
+ removeData = false,
243
+ } = {}) {
244
+ if (!terminal)
245
+ throw Error(
246
+ "Run agentpay uninstall in an interactive terminal. No files were removed.",
247
+ );
248
+ const selected = await plan({
249
+ dataDir: dataDir || (await resolveUserDataDir()),
250
+ removeData,
251
+ });
252
+ write("Uninstall AgentPay from this computer");
253
+ write(
254
+ removeData ? "This permanently deletes the local Session Key, credentials, installed Skills and transaction recovery history. Reinstalling cannot restore them." : "This removes background services. Your keys, Policies, Orders, configuration and cache will be kept.",
255
+ );
256
+ write(
257
+ "It does not revoke Safe permissions or cancel on-chain Orders. Resolve pending Orders and revoke the Safe permission first if you intend to retire it.",
258
+ );
259
+ write("Services will stop. Removal is limited to:");
260
+ for (const item of [...selected.units, ...selected.targets])
261
+ write(` ${item.path}`);
262
+ const confirmation = removeData ? "DELETE LOCAL AGENTPAY DATA" : "REMOVE AGENTPAY SERVICES";
263
+ let input;
264
+ if (ask)
265
+ input = await ask(
266
+ `Type ${confirmation} to continue, or anything else to cancel: `,
267
+ );
268
+ else {
269
+ const terminal = createInterface({
270
+ input: process.stdin,
271
+ output: process.stdout,
272
+ });
273
+ try {
274
+ input = await terminal.question(
275
+ `Type ${confirmation} to continue, or anything else to cancel: `,
276
+ );
277
+ } finally {
278
+ terminal.close();
279
+ }
280
+ }
281
+ if (input !== confirmation) {
282
+ write("Uninstall cancelled. Nothing was stopped or deleted.");
283
+ return 0;
284
+ }
285
+ try {
286
+ await remove(selected);
287
+ } catch (error) {
288
+ write(
289
+ "Uninstall did not finish. Some services or files may already have changed. Review the error before retrying; keep the installed User package.",
290
+ );
291
+ throw error;
292
+ }
293
+ write(
294
+ `Selected services and data removal complete. On-chain permissions remain unchanged.
295
+ To remove a global program installation: npm uninstall --global @c4roa0/agentpay-user
296
+ For a source checkout, you may now delete the User program folder.`,
297
+ );
298
+ return 0;
299
+ }
@@ -0,0 +1,354 @@
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 { spawn } from "node:child_process";
5
+ import { execFile } from "node:child_process";
6
+ import { promisify } from "node:util";
7
+ import { join } from "node:path";
8
+ import { installedUserDir } from "./local-data-directory.mjs";
9
+ const execute = promisify(execFile);
10
+ const groups = {
11
+ wallet: [
12
+ ["info", "Wallet identity and local file locations"],
13
+ ["unlock", "Unlock Wallet for payments"],
14
+ ["lock", "Lock Wallet; keep services running"],
15
+ ["restore", "Restore an existing key and Policy"],
16
+ ],
17
+ policy: [
18
+ ["info", "View current spending permission"],
19
+ ["renew", "Renew or replace spending limits and expiry"],
20
+ ["revoke", "Revoke spending permission on-chain"],
21
+ ],
22
+ service: [
23
+ ["start", "Start Wallet and Runtime"],
24
+ ["stop", "Stop Wallet and Runtime"],
25
+ ["restart", "Restart both; Wallet starts locked"],
26
+ ["autostart on|off", "Change startup at login"],
27
+ ["logs wallet|runtime", "Read recent service logs"],
28
+ ],
29
+ market: [
30
+ ["connect [--market URL]", "Sign in and connect this Wallet to Market"],
31
+ ],
32
+ orders: [
33
+ ["list", "List saved Orders and warnings"],
34
+ ["details ID", "Read saved Order details"],
35
+ ["status ID", "Check an Order online"],
36
+ ["retrieve ID", "Retrieve its original result"],
37
+ ["case ID", "Find support for an Order"],
38
+ ["archive ID", "Hide a reviewed local warning"],
39
+ ["unarchive ID", "Show its warning again"],
40
+ ["files ID [remove KEY]", "View or remove cached attachments"],
41
+ ],
42
+ cache: [
43
+ ["info", "View cache usage"],
44
+ ["list [PAGE] [input|output]", "List cached files"],
45
+ ["details ID", "Inspect one cached file"],
46
+ ["export ID PATH", "Export a verified copy"],
47
+ ["clean", "Review and confirm cleanup"],
48
+ ["settings [KEY VALUE]", "View or change cache limits"],
49
+ ],
50
+ payment: [["test", "Test the full payment path with the official Skill"]],
51
+ };
52
+ function panel(title, rows, example = "") {
53
+ return (
54
+ "\nAgentPay · " +
55
+ title +
56
+ "\n\n" +
57
+ rows
58
+ .map(([name, description]) => " " + name.padEnd(32) + " " + description)
59
+ .join("\n") +
60
+ (example ? "\n\nExample:\n " + example : "") +
61
+ "\n"
62
+ );
63
+ }
64
+ export const helpText =
65
+ panel(
66
+ "User and Wallet",
67
+ [
68
+ ["status", "Check services, permission and Order warnings"],
69
+ ["install --market URL", "Install and continue setup"],
70
+ ["setup", "Continue setup"],
71
+ ["update [-f]", "Check npm and update User, Wallet and the official test Skill"],
72
+ ["repair [-f]", "Refresh installed services; -f skips confirmation"],
73
+ ["uninstall [--delete-data]", "Remove services; optionally erase private data"],
74
+ ...Object.keys(groups).map((name) => [
75
+ name + " help",
76
+ name === "service"
77
+ ? "Background processes and login startup"
78
+ : name === "policy"
79
+ ? "Spending permission"
80
+ : name === "orders"
81
+ ? "Order history and support"
82
+ : name === "wallet"
83
+ ? "Local Wallet"
84
+ : name === "market"
85
+ ? "Website connection"
86
+ : name === "payment"
87
+ ? "Payment path checks"
88
+ : "Local file cache",
89
+ ]),
90
+ ["help", "Show this overview"],
91
+ ],
92
+ "agentpay policy help",
93
+ ) +
94
+ `
95
+ Update your installation
96
+ npm installation: agentpay update
97
+ Source checkout: update User, run npm ci, then agentpay repair.
98
+ Keep your private data. Do not uninstall to upgrade.
99
+ `;
100
+
101
+ function topicHelp(group, sub) {
102
+ if (group === "service" && sub === "autostart")
103
+ return (
104
+ panel(
105
+ "Login startup",
106
+ [
107
+ ["agentpay service autostart on", "Enable startup at future logins"],
108
+ ["agentpay service autostart off", "Disable startup at future logins"],
109
+ ],
110
+ "agentpay service autostart off",
111
+ ) + "Current processes are unchanged. Wallet starts locked.\n"
112
+ );
113
+ const notes = {
114
+ wallet: "Locking Wallet does not revoke on-chain spending permission.",
115
+ policy:
116
+ "Renewal and revocation require owner approval. Existing Orders are retained.",
117
+ service:
118
+ "Services run in the background as systemd user services. Closing your terminal does not stop them.",
119
+ orders:
120
+ "Use an Order/request ID or unique short ID. Queries need an unlocked Wallet; retrieving a result does not purchase again.",
121
+ cache:
122
+ "Add --offline when Runtime is stopped. Unfinished inputs are protected.",
123
+ market: "Connecting grants website Order access, not spending permission.",
124
+ payment:
125
+ "Each new test requires price confirmation. An interrupted test continues its original Order.",
126
+ };
127
+ return (
128
+ panel(
129
+ group[0].toUpperCase() + group.slice(1),
130
+ groups[group].map(([name, text]) => [
131
+ "agentpay " + group + " " + name,
132
+ text,
133
+ ]),
134
+ "agentpay " +
135
+ group +
136
+ " " +
137
+ (group === "orders" ? "details ORDER_ID" : groups[group][0][0]),
138
+ ) +
139
+ "\n" +
140
+ notes[group] +
141
+ "\n"
142
+ );
143
+ }
144
+ function runNode(args) {
145
+ return new Promise((resolve) => {
146
+ const child = spawn(process.execPath, args, {
147
+ stdio: "inherit",
148
+ shell: false,
149
+ });
150
+ child.once("error", () => {
151
+ console.error(
152
+ "Could not launch this command with the selected Node executable. Check AGENTPAY_NODE and run agentpay help.",
153
+ );
154
+ resolve(1);
155
+ });
156
+ child.once("exit", (code) => resolve(code ?? 1));
157
+ });
158
+ }
159
+ export async function runUserCli(
160
+ args,
161
+ {
162
+ run = runNode,
163
+ status = async (write) => {
164
+ const { showStatusPanel } = await import("./status-panel.mjs");
165
+ return showStatusPanel(write);
166
+ },
167
+ logs = async (role) => {
168
+ try {
169
+ const { stdout } = await execute(
170
+ "journalctl",
171
+ [
172
+ "--user",
173
+ "-u",
174
+ `agentpay-local-${role}.service`,
175
+ "-n",
176
+ "40",
177
+ "--no-pager",
178
+ ],
179
+ { timeout: 10000, maxBuffer: 262144 },
180
+ );
181
+ console.log(
182
+ `Recent ${role === "wallet" ? "Wallet" : "Runtime"} service logs`,
183
+ );
184
+ process.stdout.write(
185
+ !stdout.trim() || stdout.trim() === "-- No entries --"
186
+ ? "No recent entries for this user service.\n"
187
+ : stdout,
188
+ );
189
+ return 0;
190
+ } catch {
191
+ console.error(
192
+ "Could not read the user service journal. Run agentpay status to check services; verify access to your systemd user journal.",
193
+ );
194
+ return 1;
195
+ }
196
+ },
197
+ uninstall = async (options) => {
198
+ const { uninstallLocal } = await import("./uninstall-local.mjs");
199
+ return uninstallLocal(options);
200
+ },
201
+ connect = async (args, options) => {
202
+ const { connectMarket } = await import("./connect-market.mjs");
203
+ return connectMarket(args, options);
204
+ },
205
+ write = console.log,
206
+ } = {},
207
+ ) {
208
+ const [command, ...rest] = args;
209
+ const script = (name) => join(installedUserDir, "scripts", name + ".mjs");
210
+ const isHelp = (value) => ["help", "--help", "-h"].includes(value);
211
+ if (!command || isHelp(command)) {
212
+ if (rest.length && groups[rest[0]]) write(topicHelp(rest[0], rest[1]));
213
+ else write(helpText);
214
+ return 0;
215
+ }
216
+ if (
217
+ groups[command] &&
218
+ (!rest.length || (rest.length === 1 && isHelp(rest[0])))
219
+ ) {
220
+ write(topicHelp(command));
221
+ return 0;
222
+ }
223
+ const [action, ...values] = rest;
224
+ if (groups[command] && values.length === 1 && isHelp(values[0])) {
225
+ write(topicHelp(command, action));
226
+ return 0;
227
+ }
228
+ if (
229
+ ["update", "repair", "install", "setup", "status", "uninstall"].includes(command) &&
230
+ rest.length === 1 &&
231
+ isHelp(action)
232
+ ) {
233
+ write(helpText);
234
+ return 0;
235
+ }
236
+ if (command === "orders" && action === "files" && !values.length) {
237
+ write(
238
+ panel("Order files", [
239
+ ["agentpay orders files ORDER_ID", "List cached attachments"],
240
+ [
241
+ "agentpay orders files ORDER_ID remove FILE_KEY",
242
+ "Confirm removal of one copy",
243
+ ],
244
+ ]),
245
+ );
246
+ return 0;
247
+ }
248
+ if (
249
+ command === "service" &&
250
+ action === "autostart" &&
251
+ (!values.length || (values.length === 1 && isHelp(values[0])))
252
+ ) {
253
+ write(topicHelp(command, action));
254
+ return 0;
255
+ }
256
+ if (command === "install" && rest.length === 2 && action === "--market")
257
+ return run([script("install-user"), ...rest]);
258
+ if (
259
+ command === "repair" &&
260
+ (!rest.length || (rest.length === 1 && action === "-f"))
261
+ )
262
+ return run([script("repair"), ...rest]);
263
+ if (command === "update" && (!rest.length || (rest.length === 1 && action === "-f")))
264
+ return run([script("update"), ...rest]);
265
+ if (command === "setup" && !rest.length)
266
+ return run([script("user-setup"), "--guided"]);
267
+ if (command === "payment" && action === "test" && !values.length)
268
+ return run([script("official-paid-test")]);
269
+ if (command === "status" && !rest.length) return status(write);
270
+ if (command === "uninstall" && (!rest.length || (rest.length === 1 && action === "--delete-data"))) return uninstall({ removeData: action === "--delete-data" });
271
+ if (command === "wallet") {
272
+ if (["unlock", "lock"].includes(action) && !values.length)
273
+ return run([script(action)]);
274
+ if (action === "restore" && !values.length) {
275
+ const { runWalletRestore } = await import("./restore-wallet.mjs");
276
+ return runWalletRestore({ write });
277
+ }
278
+ if (action === "info" && !values.length) {
279
+ const { showLocalInfo } = await import("./local-info.mjs");
280
+ return showLocalInfo({ write });
281
+ }
282
+ }
283
+ if (command === "policy" && !values.length) {
284
+ if (["renew", "revoke"].includes(action)) return run([script(action)]);
285
+ if (action === "info") {
286
+ const { showPolicyInfo } = await import("./policy-info.mjs");
287
+ return showPolicyInfo({ write });
288
+ }
289
+ }
290
+ if (command === "service") {
291
+ if (["start", "restart"].includes(action) && !values.length) {
292
+ const prepared = await run([script("autostart"), "prepare"]);
293
+ return prepared || run([script("autostart"), action]);
294
+ }
295
+ if (action === "stop" && !values.length)
296
+ return run([script("autostart"), "stop"]);
297
+ if (
298
+ action === "autostart" &&
299
+ values.length === 1 &&
300
+ ["on", "off"].includes(values[0])
301
+ )
302
+ return run([
303
+ script("autostart"),
304
+ values[0] === "on" ? "enable" : "disable",
305
+ ]);
306
+ if (
307
+ action === "logs" &&
308
+ values.length === 1 &&
309
+ ["wallet", "runtime"].includes(values[0])
310
+ )
311
+ return logs(values[0]);
312
+ }
313
+ if (
314
+ command === "market" &&
315
+ action === "connect" &&
316
+ (!values.length || (values.length === 2 && values[0] === "--market"))
317
+ )
318
+ return connect(values, { write });
319
+ if (command === "orders") {
320
+ if (action === "files") {
321
+ const { runFileCache } = await import("./file-cache-cli.mjs");
322
+ return runFileCache(values, { write });
323
+ }
324
+ if (
325
+ (action === "list" && !values.length) ||
326
+ ([
327
+ "details",
328
+ "status",
329
+ "retrieve",
330
+ "case",
331
+ "archive",
332
+ "unarchive",
333
+ ].includes(action) &&
334
+ values.length === 1)
335
+ ) {
336
+ const { runOrderSupport } = await import("./order-support.mjs");
337
+ return runOrderSupport(action === "list" ? [] : [values[0], action], {
338
+ write,
339
+ });
340
+ }
341
+ }
342
+ if (command === "cache") {
343
+ const { runCacheManagement } = await import("./cache-management-cli.mjs");
344
+ return runCacheManagement(
345
+ [...(action === "info" ? ["summary"] : [action]), ...values],
346
+ { write },
347
+ );
348
+ }
349
+ write(
350
+ "Unknown command or arguments. " +
351
+ (groups[command] ? "Use agentpay " + command + " help." : "Use agentpay help."),
352
+ );
353
+ return 2;
354
+ }