@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
package/progress.md ADDED
@@ -0,0 +1,178 @@
1
+ # AgentPay User progress
2
+
3
+ ## User 0.3.0 first publication prepared — 2026-11-07
4
+
5
+ Owner authorized the first npm publication. The package's private gate is removed;
6
+ README now describes the npm entry and exact Wallet dependency. GitHub `publish.yml`
7
+ uses the `npm-publish` environment and OIDC for subsequent releases. Initial Token
8
+ upload, public-registry verification and Trusted Publisher validation are pending.
9
+ No published-success claim is made by the prepared package or workflow.
10
+
11
+
12
+ ## npm update and migration review fixes — 2026-11-07
13
+
14
+ Approved `agentpay update [-f]`: query npm, confirm the exact User release and its
15
+ pinned Wallet, check requirements, stop managed services, install and invoke the new
16
+ repair/official-test digest. Other Skills, original test requests, Policies and keys
17
+ are preserved. No paid test is triggered. Source/npm-link checkouts are not replaced.
18
+
19
+ Review fixes: relocation-aware preparation validates without rewriting the client
20
+ path before services stop; explicit data purge also retires matching legacy source
21
+ checkpoints/configuration; packaged documentation includes its referenced public
22
+ entry files. Independent regression suite: 374 passed; final targeted checks: 35
23
+ passed. Real npm replacement between two local unpublished tarballs and new-process
24
+ repair passed with controlled registry metadata/service readiness. User npm release
25
+ and real published-version upgrade acceptance remain pending; no participant services changed.
26
+
27
+
28
+ ## User npm integration candidate complete — 2026-11-07
29
+
30
+ User 0.3.0 now pins public Wallet 0.3.0 and Protocol 0.14.3, resolves public Wallet
31
+ exports and exposes `agentpay`. Installer/setup, managed service paths, repair and
32
+ explicit uninstall modes no longer require sibling Wallet source or a compiler.
33
+ Private data remains outside program files. Old custom data selections are retained;
34
+ repair can refresh a moved client-module path only with matching URL/credential.
35
+ Service changes are covered by the installer lock.
36
+
37
+ Independent User tests passed (366 in the complete suite, followed by targeted
38
+ migration/concurrency regressions). A packed User installed from its tarball with
39
+ public dependencies and passed real isolated Wallet/Runtime restart checks without
40
+ sibling source. No participant service, Policy, key or Order was changed. User is
41
+ still `private: true`; no User npm upload or Market guide deployment occurred.
42
+ See the [User package integration record](doc/reference/npm-integration-verification.md) for evidence and remaining limits.
43
+
44
+ Next: review the candidate, then participant install/upgrade/restart/uninstall
45
+ acceptance. Running-daemon version negotiation, User OIDC publication and Market
46
+ install instructions remain open. Wallet Trusted Publisher is configured by the
47
+ owner and pending its first OIDC publication. Earlier sections below are historical
48
+ milestones; their sibling-build and unpublished-Wallet statements are superseded.
49
+
50
+
51
+ ## Wallet first npm publication complete — 2026-11-07
52
+
53
+ `@c4roa0/agentpay-wallet@0.3.0` is public; npm `latest` is 0.3.0. The owner
54
+ published the verified artifact using a hidden-input Token. Registry SHA-512
55
+ integrity matches the local artifact. A fresh public-registry installation with
56
+ scripts disabled passed installed CLI and real IPC permission/unlock/restart checks;
57
+ disposable test data was removed. Source revision: Wallet `082a8db`.
58
+
59
+ The publication workflow is on GitHub, but Wallet's npm Trusted Publisher setup
60
+ and first OIDC publication remain unverified. User 0.2.0 still rejects this Wallet
61
+ pair; next development loop is User package adoption. No live Wallet services,
62
+ keys, Policies or Orders were changed. Earlier candidate sections record pre-release
63
+ evidence and are superseded for publication status by this section.
64
+
65
+
66
+ ## Wallet package review follow-up — 2026-11-07
67
+
68
+ Review found a missing prepack build guard and stale internal-path examples in
69
+ packaged documentation. Wallet now rebuilds compiled files before normal npm pack;
70
+ installed tarballs need no build hook. Public client examples and package reading
71
+ paths are corrected. Clean independent pack/install, a TypeScript consumer and real
72
+ CLI/IPC unlock/restart checks passed. User integration remains the next loop;
73
+ Wallet 0.3.0 is still unpublished and intentionally rejected by User 0.2.0 pairing.
74
+
75
+
76
+ ## Wallet npm candidate verified — 2026-11-07
77
+
78
+ Steps 1–2 complete: Protocol migration baseline committed; Wallet 0.3.0 candidate
79
+ provides public client/local-administration/CLI entries and compiled declarations.
80
+ Independent frozen install/build and 29 behavioral tests passed. The packed artifact
81
+ installed without sibling sources; real CLI/IPC checks passed for capability denial,
82
+ no generic signing, unlock and restart preserving the key while locking the service.
83
+ No npm publication, service restart or participant data change was performed.
84
+
85
+ Next: User consumes this reviewed Wallet package and resolves public entries instead
86
+ of sibling `dist/src` files. Current User 0.2.0 pair checks deliberately reject Wallet
87
+ 0.3.0; do not update a participant Wallet alone. Publication remains blocked by
88
+ `private: true`; package and User integration must be completed before synchronization.
89
+
90
+
91
+ ## npm client distribution plan — 2026-11-07
92
+
93
+ Approved npm-first distribution for Protocol, Wallet and User; separate repositories
94
+ and processes remain. Ordinary users will install User, which supplies a reviewed
95
+ compiled Wallet dependency. Combined source ZIP is optional later work. See the
96
+ local distribution plan for public entries, upgrades, service paths, private data
97
+ and uninstall boundaries. Steps 1–2 are authorized: commit the verified Protocol
98
+ dependency baseline, then prepare/test Wallet's public package. User package
99
+ integration and npm uploads are later loops; present sibling installation remains
100
+ until replaced and verified.
101
+
102
+
103
+ ## Public Protocol npm migration verified — 2026-11-06
104
+
105
+ User now pins `@c4roa0/agentpay-protocol@0.14.3`; production imports, test fixtures
106
+ and the public notice use that package. No unrelated locked dependencies changed.
107
+ The v1 update-pair metadata retains its `typesDependency` field and pins 0.14.3;
108
+ checks require both User and Wallet to use the new package at that exact version.
109
+ Old archive-based Wallet and different Protocol versions are rejected. Version-range
110
+ compatibility remains planned; no legacy alias or bypass was added.
111
+
112
+ Installer/repair fingerprints no longer require a vendor directory. Manifests and
113
+ lockfiles still bind dependency changes. Removed the old archive/license/manifest
114
+ after independent verification; historical review records describe the earlier state.
115
+ With only sibling User/Wallet source in a temporary directory, frozen Wallet install,
116
+ Wallet build, User npm ci and all 364 tests passed (zero failures/skips). Real installer
117
+ prepare-only execution also passed twice: first preparation ran the package managers
118
+ and build, unchanged retry skipped them. Actual pair checks/snapshot reads passed
119
+ without vendor directories. This proves preparation and controlled local behavior,
120
+ not participant setup, owner approval or real-network payment acceptance.
121
+ No running services, key files, Policies or Orders were modified.
122
+
123
+
124
+ ## Public-source preparation — 2026-11-06
125
+
126
+ User's original code is MPL 2.0, as selected by the owner; public Protocol and
127
+ attributed reference fixtures retain MIT. Wallet and Protocol are public. User
128
+ history was replaced by the approved single-commit baseline `2a80f4a`; the owner
129
+ confirmed public visibility. Anonymous refs were verified on 2026-11-06. Release
130
+ assets and full anonymous clone/ZIP installation remain separate work.
131
+
132
+ Current preparation removes private platform harnesses and historical dependency
133
+ archives from the public tree, uses the reviewed public Protocol 0.14.1 archive,
134
+ and makes tests independent of sibling Agent/Protocol/private platform checkouts.
135
+ User still requires a built sibling Wallet. The exact version pair and internal
136
+ Wallet imports remain current constraints, not the planned compatibility contract.
137
+ See [source review](doc/security/public-source-review-2026-11-06.md).
138
+
139
+ Current documentation is organized by role and feature. Private chronological records
140
+ and previous TODO snapshots are preserved outside the repository; removing them from
141
+ publication does not cancel outstanding acceptance. Independent User + built public Wallet verification passed all 364 tests with no
142
+ failures or skips. Offline installation, 112-file syntax-tree comparison, local
143
+ Markdown links and whitespace checks passed. This does not establish live-network
144
+ or fresh participant installation acceptance.
145
+
146
+ Follow-up public-document review corrected stale Market per-Policy grouping claims,
147
+ expired-Policy connection guidance and owner-signing descriptions across setup,
148
+ renewal, migration and deployment references. Installation prerequisites now include
149
+ npm/Corepack. The old preview module is labelled as such; executable behavior is
150
+ unchanged. Local links, stale-wording scan, syntax and whitespace checks passed.
151
+
152
+ ## Implemented product scope
153
+
154
+ - One grouped `./run` entry for installation, repair, Wallet/Policy operations,
155
+ Linux user services, Market connection, Orders and local cache management.
156
+ - External-owner browser approval with resumable operation plans; separate guarded
157
+ Anvil signer. Policy renewal/deployment replacement preserves key and Order history.
158
+ - Per-call service-neutral Runtime, private caller authentication, original request
159
+ recovery, durable input/result snapshots and explicitly managed attachment cache.
160
+ - Stable Wallet display identity with individually verified Policy scopes. Local
161
+ status distinguishes saved metadata, live observations and unresolved results.
162
+ - Official digest-pinned paid connectivity test, with explicit price consent and
163
+ original-request continuation after interruption.
164
+
165
+ The recorded private single-owner MetaMask/Anvil authorization and payment journey
166
+ passed, including participant Market Order viewing and independent payout reconciliation.
167
+ This is scoped private evidence, not public-network, multisig, real-funds or production
168
+ approval. Detailed operational records are not distributed in this repository.
169
+
170
+ ## Next work
171
+
172
+ Public source preparation is complete within the recorded review scope. Next is the approved
173
+ [distribution plan](doc/plans/user-wallet-distribution.md): a supported public Wallet
174
+ client/version boundary and combined release archive. No automatic source updater.
175
+
176
+ Official test network mapping, deployment/artifact handoff, complete interrupted owner
177
+ signing and real-network acceptance remain open. macOS and graphical unlock remain
178
+ deferred. [TODO](doc/TODO.md) tracks these gates and the retained acceptance backlog.
package/run ADDED
@@ -0,0 +1,29 @@
1
+ #!/bin/sh
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
+ # Use an explicit Node installation without requiring a global installation.
6
+ if [ -n "${AGENTPAY_NODE:-}" ]; then
7
+ case "$AGENTPAY_NODE" in
8
+ /*) node_path=$AGENTPAY_NODE ;;
9
+ *) printf '%s\n' 'AGENTPAY_NODE must be the full path to your Node executable.' >&2; exit 1 ;;
10
+ esac
11
+ else
12
+ node_path=$(command -v node) || {
13
+ printf '%s\n' 'Node was not found. Set AGENTPAY_NODE to its full path, then retry.' 'Example: AGENTPAY_NODE=/path/to/node ./run help' >&2
14
+ exit 1
15
+ }
16
+ fi
17
+ if [ ! -f "$node_path" ] || [ ! -x "$node_path" ]; then
18
+ printf '%s\n' 'The selected Node executable does not exist or is not executable. Check AGENTPAY_NODE.' >&2
19
+ exit 1
20
+ fi
21
+ case "$node_path" in
22
+ /*) ;;
23
+ *) node_path="$PWD/$node_path" ;;
24
+ esac
25
+ # npm and its child scripts must see the same selected Node installation.
26
+ PATH="${node_path%/*}:${PATH:-/usr/bin:/bin}"
27
+ export PATH
28
+ run_dir=$(CDPATH= cd -- "${0%/*}" && pwd -P) || exit 1
29
+ exec "$node_path" "$run_dir/scripts/agentpay-cli.mjs" "$@"
@@ -0,0 +1,11 @@
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 { runUserCli } from "../src/user-cli.mjs";
6
+ try {
7
+ process.exitCode = await runUserCli(process.argv.slice(2));
8
+ } catch (error) {
9
+ console.error(error.message || "Local operation failed. Run agentpay help.");
10
+ process.exitCode = 1;
11
+ }
@@ -0,0 +1,211 @@
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-Anvil existing-Safe authorization. Never accepts an owner key.
6
+ import { readFile, stat } from "node:fs/promises";
7
+ import { join, resolve } from "node:path";
8
+ import { createInterface } from "node:readline/promises";
9
+ import { Contract, getAddress, JsonRpcProvider } from "ethers";
10
+ import { Phase0Wallet } from "@c4roa0/agentpay-wallet/local";
11
+ import {
12
+ buildSafePolicyPlan,
13
+ inspectSafePolicy,
14
+ safeEthSignSignature,
15
+ } from "../src/safe-policy-plan.mjs";
16
+
17
+ const [command, privateDirArg, configArg] = process.argv.slice(2);
18
+ if (
19
+ !["preview", "authorize", "bind", "check"].includes(command) ||
20
+ !privateDirArg ||
21
+ !configArg ||
22
+ process.argv.length !== 5
23
+ )
24
+ throw new Error(
25
+ "usage: authorize-existing-safe.mjs <preview|authorize|bind|check> <private-user-data-dir> <public-policy-config.json>",
26
+ );
27
+ const dataDir = resolve(privateDirArg);
28
+ const config = JSON.parse(await readFile(resolve(configArg), "utf8"));
29
+ const walletDir = join(dataDir, "wallet");
30
+ const wallet = await Phase0Wallet.open(walletDir);
31
+ const delegate = await wallet.address();
32
+ const plan = buildSafePolicyPlan(config, delegate);
33
+ const ownerAddress = getAddress(config.ownerAddress);
34
+ const rpc = new JsonRpcProvider(plan.rpcUrl, plan.chainId, {
35
+ staticNetwork: true,
36
+ });
37
+ try {
38
+ const state = await inspectSafePolicy(rpc, plan);
39
+ if (!state.owners.includes(ownerAddress) || state.threshold !== 1) {
40
+ throw new Error(
41
+ "C6 first rehearsal requires the selected Anvil owner to be the sole Safe signature threshold",
42
+ );
43
+ }
44
+ const publicPlan = {
45
+ chainId: plan.chainId,
46
+ safe: plan.safe,
47
+ ownerAddress,
48
+ delegate: plan.delegate,
49
+ module: plan.module,
50
+ factory: plan.factory,
51
+ registry: plan.registry,
52
+ token: plan.token,
53
+ policyId: plan.policyId,
54
+ maxPerPaymentAtomic: plan.maxPerPayment.toString(),
55
+ totalBudgetAtomic: plan.totalBudget.toString(),
56
+ validAfter: plan.validAfter,
57
+ validUntil: plan.validUntil,
58
+ dynamicQualification:
59
+ "AgentPay Registry at the fixed address; future qualified Skills need no new Policy",
60
+ currentState: state,
61
+ safeCalls: plan.calls,
62
+ };
63
+ if (command === "preview") {
64
+ process.stdout.write(
65
+ `${JSON.stringify({ ok: true, plan: publicPlan }, null, 2)}\n`,
66
+ );
67
+ } else {
68
+ if (command === "authorize") {
69
+ const url = new URL(plan.rpcUrl);
70
+ if (
71
+ plan.chainId !== 31337 ||
72
+ url.protocol !== "http:" ||
73
+ !["127.0.0.1", "localhost", "[::1]"].includes(url.hostname)
74
+ ) {
75
+ throw new Error("ANVIL_OWNER_SIGNING_TEST_ONLY");
76
+ }
77
+ if (state.policyExists && !state.policyMatches)
78
+ throw new Error("EXISTING_POLICY_CONFLICT");
79
+ if (!state.moduleEnabled || !state.policyExists) {
80
+ process.stdout.write(
81
+ `${JSON.stringify({ ok: true, authorizationPreview: publicPlan }, null, 2)}\n`,
82
+ );
83
+ const expected = `AUTHORIZE ${plan.policyId}`;
84
+ if (!process.stdin.isTTY || !process.stderr.isTTY)
85
+ throw new Error("OWNER_AUTHORIZATION_TTY_REQUIRED");
86
+ const prompt = createInterface({
87
+ input: process.stdin,
88
+ output: process.stderr,
89
+ });
90
+ let answer;
91
+ try {
92
+ answer = await prompt.question(
93
+ `Type '${expected}' to submit the displayed Safe calls: `,
94
+ );
95
+ } finally {
96
+ prompt.close();
97
+ }
98
+ if (answer.trim() !== expected)
99
+ throw new Error("OWNER_AUTHORIZATION_NOT_CONFIRMED");
100
+ const calls = [
101
+ ...(!state.moduleEnabled ? [plan.calls[0]] : []),
102
+ ...(!state.policyExists ? [plan.calls[1]] : []),
103
+ ];
104
+ for (const call of calls)
105
+ await executeSafeCall(rpc, plan.safe, ownerAddress, call);
106
+ }
107
+ }
108
+ const final = await inspectSafePolicy(rpc, plan);
109
+ if (!final.moduleEnabled || !final.policyMatches)
110
+ throw new Error("CHAIN_POLICY_NOT_VERIFIED");
111
+ if (command === "bind") {
112
+ if (
113
+ await stat(join(walletDir, "wallet.sock")).then(
114
+ () => true,
115
+ (error) => {
116
+ if (error.code === "ENOENT") return false;
117
+ throw error;
118
+ },
119
+ )
120
+ )
121
+ throw new Error("STOP_WALLET_SERVICE_BEFORE_LOCAL_BIND");
122
+ if (wallet.policy) {
123
+ if (JSON.stringify(wallet.policy) !== JSON.stringify(plan.walletPolicy))
124
+ throw new Error("LOCAL_POLICY_CONFLICT");
125
+ } else await wallet.bindPolicy(plan.walletPolicy);
126
+ }
127
+ process.stdout.write(
128
+ `${JSON.stringify(
129
+ {
130
+ ok: true,
131
+ chainPolicyVerified: true,
132
+ moduleEnabled: final.moduleEnabled,
133
+ policyMatches: final.policyMatches,
134
+ localWalletBound: command === "bind" ? true : wallet.policy !== null,
135
+ remainingBudgetAtomic: (
136
+ plan.totalBudget - BigInt(final.policySpent)
137
+ ).toString(),
138
+ },
139
+ null,
140
+ 2,
141
+ )}\n`,
142
+ );
143
+ }
144
+ } finally {
145
+ rpc.destroy();
146
+ }
147
+
148
+ async function executeSafeCall(rpc, safeAddress, ownerAddress, call) {
149
+ const zero = "0x0000000000000000000000000000000000000000";
150
+ const safe = new Contract(
151
+ safeAddress,
152
+ [
153
+ "function nonce() view returns (uint256)",
154
+ "function getTransactionHash(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce) view returns (bytes32)",
155
+ "function execTransaction(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,bytes signatures) returns (bool)",
156
+ "event ExecutionSuccess(bytes32 indexed txHash,uint256 payment)",
157
+ "event ExecutionFailure(bytes32 indexed txHash,uint256 payment)",
158
+ ],
159
+ await rpc.getSigner(ownerAddress),
160
+ );
161
+ const nonce = await safe.nonce();
162
+ const hash = await safe.getTransactionHash(
163
+ call.to,
164
+ 0,
165
+ call.data,
166
+ 0,
167
+ 0,
168
+ 0,
169
+ 0,
170
+ zero,
171
+ zero,
172
+ nonce,
173
+ );
174
+ const signedMessage = await rpc.send("eth_sign", [ownerAddress, hash]);
175
+ const safeSignature = safeEthSignSignature(hash, signedMessage, ownerAddress);
176
+ const receipt = await (
177
+ await safe.execTransaction(
178
+ call.to,
179
+ 0,
180
+ call.data,
181
+ 0,
182
+ 0,
183
+ 0,
184
+ 0,
185
+ zero,
186
+ zero,
187
+ safeSignature,
188
+ )
189
+ ).wait();
190
+ const events = receipt.logs.map((log) => {
191
+ try {
192
+ return safe.interface.parseLog(log);
193
+ } catch {
194
+ return null;
195
+ }
196
+ });
197
+ if (
198
+ receipt.status !== 1 ||
199
+ !events.some((event) => event?.name === "ExecutionSuccess") ||
200
+ events.some((event) => event?.name === "ExecutionFailure")
201
+ )
202
+ throw new Error("SAFE_OWNER_CALL_FAILED");
203
+ process.stdout.write(
204
+ `${JSON.stringify({
205
+ safeCall: call.purpose,
206
+ transactionHash: receipt.hash,
207
+ safeNonce: nonce.toString(),
208
+ confirmed: true,
209
+ })}\n`,
210
+ );
211
+ }
@@ -0,0 +1,186 @@
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 { randomUUID } from "node:crypto";
6
+ import { execFileSync } from "node:child_process";
7
+ import { lstat, mkdir, readFile, writeFile, unlink, rename } from "node:fs/promises";
8
+ import { join } from "node:path";
9
+ import { homedir } from "node:os";
10
+ import {
11
+ resolveUserDataDir,
12
+ installedUserDir,
13
+ } from "../src/local-data-directory.mjs";
14
+ import {
15
+ autostartFiles,
16
+ isManagedUnit,
17
+ units,
18
+ runtimeUnits,
19
+ } from "../src/autostart-files.mjs";
20
+ import {
21
+ readServiceSettings,
22
+ saveServiceSettings,
23
+ } from "../src/local-services.mjs";
24
+ import { ensureLocalServices } from "../src/prepare-local-services.mjs";
25
+ const ctl = (...args) =>
26
+ execFileSync("systemctl", ["--user", ...args], {
27
+ stdio: "inherit",
28
+ timeout: 30000,
29
+ });
30
+ try {
31
+ const [action, ...args] = process.argv.slice(2);
32
+ if (
33
+ ![
34
+ "prepare",
35
+ "prepare-repair",
36
+ "enable",
37
+ "disable",
38
+ "remove",
39
+ "start",
40
+ "restart",
41
+ "stop",
42
+ "status",
43
+ "configure",
44
+ ].includes(action) ||
45
+ args.length !== (action === "configure" ? 2 : 0)
46
+ )
47
+ throw Error(
48
+ "Usage: node scripts/autostart.mjs <prepare|enable|disable|remove|start|restart|stop|status> or configure RUNTIME_URL SETTLEMENT_URL",
49
+ );
50
+ if (process.platform !== "linux")
51
+ throw Error(
52
+ "Automatic startup currently supports Linux with systemd user services. macOS support is pending.",
53
+ );
54
+ const dataDir = await resolveUserDataDir();
55
+ if (action === "configure") {
56
+ await saveServiceSettings({
57
+ dataDir,
58
+ runtimeUrl: args[0],
59
+ settlementUrl: args[1],
60
+ });
61
+ console.log("Local startup settings saved. No service was started.");
62
+ } else {
63
+ const files = autostartFiles({
64
+ node: process.execPath,
65
+ userDir: installedUserDir,
66
+ dataDir,
67
+ });
68
+ const config = join(homedir(), ".config"),
69
+ unitDir = join(config, "systemd/user");
70
+ for (const directory of [config, join(config, "systemd"), unitDir]) {
71
+ if (["enable", "prepare", "prepare-repair"].includes(action))
72
+ await mkdir(directory, { mode: 0o700 }).catch((e) => {
73
+ if (e.code !== "EEXIST") throw e;
74
+ });
75
+ const info = await lstat(directory);
76
+ if (
77
+ !info.isDirectory() ||
78
+ info.isSymbolicLink() ||
79
+ info.uid !== process.getuid() ||
80
+ info.mode & 0o022
81
+ )
82
+ throw Error(
83
+ `User configuration directory is not safely owned: ${directory}`,
84
+ );
85
+ }
86
+ const paths = Object.entries(files).map(([name, body]) => ({
87
+ name,
88
+ path: join(unitDir, name),
89
+ body,
90
+ }));
91
+ for (const { path, body, name } of paths) {
92
+ try {
93
+ const info = await lstat(path);
94
+ if (
95
+ !info.isFile() ||
96
+ info.isSymbolicLink() ||
97
+ info.uid !== process.getuid() ||
98
+ info.mode & 0o022 ||
99
+ !isManagedUnit(await readFile(path, "utf8"), name, dataDir)
100
+ )
101
+ throw Error(
102
+ "An existing startup file belongs to another configuration. Preserve it and review before switching.",
103
+ );
104
+ } catch (e) {
105
+ if (e.code !== "ENOENT") throw e;
106
+ }
107
+ }
108
+ if (action === "status") {
109
+ ctl("status", "--no-pager", ...runtimeUnits);
110
+ console.log(
111
+ "The login reminder runs once per graphical session; it can be inactive outside a desktop login.",
112
+ );
113
+ } else if (["start", "restart", "stop"].includes(action)) {
114
+ if (action !== "stop") await readServiceSettings(dataDir);
115
+ for (const { path } of paths) await lstat(path);
116
+ ctl(action, ...(action !== "stop" ? runtimeUnits : units));
117
+ console.log(
118
+ action !== "stop"
119
+ ? `${action === "restart" ? "Restart" : "Start"} requested through systemd. This does not confirm readiness.\nNext: agentpay status`
120
+ : "Stop requested through systemd. Login startup and saved Wallet data are unchanged.\nNext: agentpay status",
121
+ );
122
+ } else if (["enable", "prepare", "prepare-repair"].includes(action)) {
123
+ await ensureLocalServices({ dataDir, connectionOptions: action === "prepare-repair" ? { refreshClientModule: true, checkOnly: true } : {} });
124
+ // No --now: avoid replacing a Wallet currently owned by terminal setup/system services.
125
+ for (const { path, body } of paths)
126
+ try {
127
+ await writeFile(path, body, { flag: "wx", mode: 0o600 });
128
+ } catch (e) {
129
+ if (e.code !== "EEXIST") throw e;
130
+ const temporary = path + '.' + randomUUID();
131
+ try {
132
+ await writeFile(temporary, body, {flag:'wx',mode:0o600});
133
+ await rename(temporary,path);
134
+ } finally { await unlink(temporary).catch(e => { if(e.code !== 'ENOENT') throw e; }); }
135
+ }
136
+ ctl("daemon-reload");
137
+ if (action === "enable") {
138
+ ctl("enable", ...units);
139
+ await saveServiceSettings({
140
+ dataDir,
141
+ ...(await readServiceSettings(dataDir)),
142
+ startupChoice: "enabled",
143
+ });
144
+ console.log(
145
+ "Login startup enabled for Wallet and Runtime. Current processes were not changed.\nTo start now: agentpay service start",
146
+ );
147
+ } else
148
+ console.log(
149
+ "Local service files prepared. Automatic startup was not changed.",
150
+ );
151
+ } else if (action === "disable") {
152
+ await readServiceSettings(dataDir);
153
+ ctl("disable", ...units);
154
+ await saveServiceSettings({
155
+ dataDir,
156
+ ...(await readServiceSettings(dataDir)),
157
+ startupChoice: "disabled",
158
+ });
159
+ console.log(
160
+ "Login startup disabled for Wallet and Runtime. Current processes were not stopped.\nTo stop now: agentpay service stop",
161
+ );
162
+ } else {
163
+ await saveServiceSettings({
164
+ dataDir,
165
+ ...(await readServiceSettings(dataDir)),
166
+ startupChoice: "disabled",
167
+ });
168
+ ctl("disable", "--now", ...units);
169
+ for (const { path } of paths)
170
+ await unlink(path).catch((e) => {
171
+ if (e.code !== "ENOENT") throw e;
172
+ });
173
+ ctl("daemon-reload");
174
+ console.log(
175
+ "Automatic startup and its reminder removed. Wallet data, Policy and request history are kept.",
176
+ );
177
+ }
178
+ }
179
+ } catch (error) {
180
+ console.error(
181
+ error.status === undefined
182
+ ? error.message
183
+ : "The systemd command failed; some service changes may already have applied. Wallet keys and request history were kept. Run agentpay status before retrying.",
184
+ );
185
+ process.exitCode = 1;
186
+ }