@defdone/annon 0.1.0-alpha.1 → 0.1.0-alpha.3

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 (87) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/CLI.md +173 -13
  3. package/README.md +47 -10
  4. package/dist/api/annon-types.d.ts +2 -1
  5. package/dist/api/annon.js +9 -2
  6. package/dist/browser/index.d.ts +1 -1
  7. package/dist/browser/login-bundle.js +61 -60
  8. package/dist/browser/login-dom.d.ts +13 -0
  9. package/dist/browser/login-dom.js +71 -0
  10. package/dist/browser/login-dropdown.d.ts +32 -0
  11. package/dist/browser/login-dropdown.js +129 -0
  12. package/dist/browser/login-format.d.ts +16 -0
  13. package/dist/browser/login-format.js +52 -0
  14. package/dist/browser/login-status.d.ts +70 -0
  15. package/dist/browser/login-status.js +85 -0
  16. package/dist/browser/login-steps.d.ts +6 -0
  17. package/dist/browser/login-steps.js +35 -0
  18. package/dist/browser/login-terms.d.ts +10 -0
  19. package/dist/browser/login-terms.js +69 -0
  20. package/dist/browser/login-view.d.ts +34 -0
  21. package/dist/browser/login-view.js +227 -0
  22. package/dist/browser/login-wallet.d.ts +28 -0
  23. package/dist/browser/login-wallet.js +179 -0
  24. package/dist/browser/login.js +154 -142
  25. package/dist/browser/purchase-bundle.js +29 -29
  26. package/dist/browser/purchases.d.ts +4 -0
  27. package/dist/browser/purchases.js +14 -0
  28. package/dist/browser/reconcile.d.ts +41 -0
  29. package/dist/browser/reconcile.js +77 -0
  30. package/dist/node/adapter-checks.d.ts +3 -1
  31. package/dist/node/adapter-checks.js +3 -1
  32. package/dist/node/adapter.js +10 -3
  33. package/dist/node/agent.js +2 -1
  34. package/dist/node/approval-status.d.ts +34 -0
  35. package/dist/node/approval-status.js +71 -0
  36. package/dist/node/backup-secret-init.d.ts +9 -0
  37. package/dist/node/backup-secret-init.js +103 -0
  38. package/dist/node/backup-secret.d.ts +35 -0
  39. package/dist/node/backup-secret.js +104 -0
  40. package/dist/node/browser-login.d.ts +2 -0
  41. package/dist/node/browser-login.js +95 -16
  42. package/dist/node/buy-commands.js +17 -9
  43. package/dist/node/buy-flow.d.ts +18 -0
  44. package/dist/node/buy-flow.js +89 -0
  45. package/dist/node/cli-flags.d.ts +10 -0
  46. package/dist/node/cli-flags.js +68 -0
  47. package/dist/node/cli.d.ts +9 -1
  48. package/dist/node/cli.js +87 -95
  49. package/dist/node/command-context.js +1 -1
  50. package/dist/node/config.d.ts +4 -0
  51. package/dist/node/config.js +6 -2
  52. package/dist/node/core-adapter.js +4 -0
  53. package/dist/node/login-page.d.ts +7 -0
  54. package/dist/node/login-page.js +77 -0
  55. package/dist/node/main.js +20 -2
  56. package/dist/node/output/discovery.d.ts +23 -0
  57. package/dist/node/output/discovery.js +52 -0
  58. package/dist/node/output/human.d.ts +10 -0
  59. package/dist/node/output/human.js +46 -0
  60. package/dist/node/output/purchase.d.ts +10 -0
  61. package/dist/node/output/purchase.js +78 -0
  62. package/dist/node/output/text.d.ts +10 -0
  63. package/dist/node/output/text.js +43 -0
  64. package/dist/node/purchase-commands.d.ts +2 -2
  65. package/dist/node/purchase-commands.js +14 -6
  66. package/dist/node/purchase-ref.d.ts +15 -0
  67. package/dist/node/purchase-ref.js +93 -0
  68. package/dist/node/saved-state.d.ts +4 -0
  69. package/dist/node/saved-state.js +50 -0
  70. package/dist/node/shell/complete.d.ts +2 -0
  71. package/dist/node/shell/complete.js +17 -0
  72. package/dist/node/shell/session.d.ts +20 -0
  73. package/dist/node/shell/session.js +219 -0
  74. package/dist/node/shell/welcome.d.ts +9 -0
  75. package/dist/node/shell/welcome.js +39 -0
  76. package/dist/node/shell/words.d.ts +1 -0
  77. package/dist/node/shell/words.js +36 -0
  78. package/dist/node/shops.d.ts +5 -0
  79. package/dist/node/shops.js +22 -0
  80. package/dist/node/version.d.ts +1 -0
  81. package/dist/node/version.js +3 -0
  82. package/dist/node/wallets.d.ts +1 -1
  83. package/dist/node/wallets.js +2 -10
  84. package/dist/pay/purchase.d.ts +2 -1
  85. package/dist/wallet/uncertain.d.ts +6 -3
  86. package/dist/wallet/uncertain.js +4 -1
  87. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @defdone/annon
2
2
 
3
+ ## 0.1.0-alpha.3
4
+
5
+ ### Patch Changes
6
+
7
+ - edb0ec2: Bound approval session shutdown and resume polling after accepted approvals. Keep completed review snapshots read only and clarify that the tab should stay open.
8
+ - 5d871b7: Keep accepted browser approvals successful after session shutdown and show completed approvals as read-only collapsible steps.
9
+ - f607080: The CLI is easier to use by hand. `annon` with no arguments in a terminal (or `annon shell`) starts
10
+ an interactive session that runs the usual commands without the prefix until `exit`. In a terminal
11
+ the commands print short text in place of JSON: a login line, a numbered catalog table, the buy
12
+ terms and next step, one or two lines for status and open. `--json`, piped output and agent mode
13
+ keep the JSON envelope unchanged.
14
+
15
+ `annon buy` takes a catalog row number (`buy 2 --shop demo`) and `--open <file>`, which waits for
16
+ acceptance and writes the file. `annon status` and `annon open` without an ID use the latest
17
+ purchase saved for the configured contract and network, and accept an ID prefix of 8 or more hex
18
+ digits for purchases saved for it. The `next` text of a NEAR or Base buy no
19
+ longer talks about a deposit.
20
+
21
+ ## 0.1.0-alpha.2
22
+
23
+ ### Patch Changes
24
+
25
+ - Add `BrowserPurchaseController.reconcile()` so a purchase that 1Click reported as failed can be checked again. Under the purchase lease it keeps the same purchase, deposit and recovery identity, checks NEAR acceptance and reads the existing deposit's 1Click status once. Accepted moves to `accepted`, `SUCCESS` to `accepting` and `REFUNDED` to `refunded`; anything else stays `failed` with the new status saved. If the saved deposit address or memo, the restored purchase's deposit and the saved polling checkpoint disagree, it refuses before any read or write. It never creates, starts or pays for a purchase.
26
+ - Bare `annon init` creates the CLI backup secret at `~/.config/annon/backup-secret`: 32 random bytes as hex, mode 0600 from the first write, published with a hard link so it never appears half-written and never replaces an existing file. Running it again only validates the file. It refuses to create a new secret while saved records or backups exist, never prints the secret, and sends its one-time notice to stderr. `--backup-secret-file`, `ANNON_BACKUP_SECRET_FILE` and `ANNON_BACKUP_SECRET` keep their precedence; the default file is read only when none is set, and a selected source that fails never falls back. Other commands, agent mode and MCP read the default but never create it. Reading the default checks that `~/.config/annon` is a private directory you own, refuses symlinks, FIFOs and other non-regular files without blocking, and never repairs anything. Bare `init` reports an unreadable config as a setup error (exit 2). A pending `status` now uses the default file too. `annon --version` (or `-v`) prints the installed version. The quickstart replaces the manual secret recipe with `annon init` and explains backup and restore.
27
+ - The CLI wallet approval page shows a readable summary of exactly what the wallet signs: a title per action, the pairing code to compare, amounts in USDC or NEAR, receiver, account, expiry and what signing does, with the CLI's terms shown apart and the raw request under Technical details (the NEP-413 nonce as hex). Each click opens at most one wallet window, so Meteor's web popup is no longer blocked: NEP-413 messages are signed without a sign-in, and transactions connect first and then send. After a closed, blocked or declined wallet window the page explains it and allows another try until the request expires. The terminal prints a waiting line with the expiry, the reported wallet failure and a specific final error (`cancelled`, `rejected` or `wallet_error`) instead of `operation_failed`. Once the page has called the wallet's send for a transaction, the request is never attempted again: the CLI records the send start before the wallet is called, the request ends as `transaction_uncertain` with a warning not to pay again, the saved purchase keeps that state without inventing a transaction hash, and it blocks a plain rerun of `annon buy` until it is accepted or `--force` is used. A result the CLI cannot verify after the send start also ends as uncertain, keeping a valid transaction hash when there is one. An uncertain `annon buy` exits 4, and `annon status` reports `submission: "uncertain"` with a warning not to pay again. The NEAR wallet is chosen in a styled, keyboard-accessible list instead of the browser's native menu.
28
+
3
29
  ## 0.1.0-alpha.1
4
30
 
5
31
  ### Patch Changes
package/CLI.md CHANGED
@@ -2,9 +2,38 @@
2
2
 
3
3
  Build with `pnpm build`. The `@defdone/annon` package ships the `annon` bin;
4
4
  in this checkout run `node packages/annon/dist/node/main.js` in its place.
5
- Run `annon --help` for all flags. All commands return JSON; `--json` uses one compact
6
- JSON line and disables interactive prompts. Success is `{ "ok": true, "result": ... }`;
7
- errors are `{ "ok": false, "error": { "code": ..., "message": ... } }`.
5
+ Run `annon --help` for all flags and `annon --version` (or `-v`) for the installed version; with
6
+ `--json` the version is `{ "ok": true, "result": { "version": ... } }`. It loads no config, wallet
7
+ or secret and also works in agent mode. With `--json` every command prints one compact JSON line
8
+ and asks nothing. Success is `{ "ok": true, "result": ... }`;
9
+ errors are `{ "ok": false, "error": { "code": ..., "message": ... } }`. Without `--json` the same
10
+ envelope is printed indented whenever standard output is not a terminal, and always in agent mode,
11
+ so scripts and pipes read JSON either way. In a terminal a person gets short text instead: one or
12
+ two lines per command, `catalog` as a numbered table, errors as `Error: message (code)`. Exit codes
13
+ are the same in every mode. Text from shops and hosts is shown with control characters removed.
14
+
15
+ ## Interactive session
16
+
17
+ `annon` with no arguments in a terminal, or `annon shell`, starts a session. It prints the version,
18
+ network, signed-in account and whether the configured contract is trusted, then reads commands at
19
+ `annon>` until `exit`, `quit` or Ctrl-D. Commands are the ones on this page without the `annon`
20
+ prefix; Tab completes names and flags, `help` lists them. Each line runs the same code as the
21
+ one-shot command, with the same trust prompt, price cap, purchase guard and confirmation, and the
22
+ session keeps no wallet, config or secret between commands.
23
+
24
+ - `catalog` numbers its rows; `buy 2` buys row 2 of the last listing shown.
25
+ - After a NEAR or Base payment `buy` waits for acceptance, then asks where to save the file.
26
+ - `status` and `open --output <file>` without an ID use the latest purchase `buy` saved for the
27
+ contract and network of that line, as the one-shot commands do. The session remembers no
28
+ purchase itself, so a line with another `--contract-id`, `--network`, `--config` or
29
+ `--state-dir` never picks up a purchase made under a different one.
30
+ - Ctrl-C cancels a prompt (the answer is no), stops a wait for acceptance, or drops a read-only
31
+ command. During `buy`, `fund`, `sell`, `share`, `stop` or `recover` it asks
32
+ `A payment may be in progress. Exit anyway? (y/N)`. Leaving removes nothing: the purchase saved
33
+ before payment is still there, so check it with `annon status` before paying again.
34
+
35
+ Without a terminal, with any argument such as `--json`, or in agent mode no session starts:
36
+ `annon` prints the help and `annon shell` fails with `invalid_arguments`.
8
37
 
9
38
  Create a public configuration file and name it with `--config /path/config.json` or
10
39
  `ANNON_CONFIG`. The default is `~/.config/annon/config.json`:
@@ -53,10 +82,9 @@ On macOS, with no NEAR file or credential env value, explicitly select both
53
82
  service `near-mainnet-alice.near`, account `alice.near:ed25519:PUBLIC_KEY`.
54
83
  Other platforms use the credential JSON file or environment. No keychain enumeration occurs.
55
84
 
56
- Sell, buy, open, recover and stop also require a user-held random 32-byte backup secret,
57
- encoded as 64 lowercase hex characters in `ANNON_BACKUP_SECRET`, or in the file named by
58
- `--backup-secret-file` / `ANNON_BACKUP_SECRET_FILE`. Create it with a cryptographic random
59
- generator and keep it outside the repository. It is never derived from a wallet signature.
85
+ Sell, buy, open, recover and stop also require a random 32-byte backup secret. It encrypts the
86
+ local purchase, recovery and sell records and the portable backups. It is never derived from a
87
+ wallet signature or key. See [Backup secret](#backup-secret).
60
88
  `verify` and accepted/read-only `status` need neither a wallet nor this secret. A pending Base `status` with saved state and a backup secret may send `unlock` through the configured relayer or NEAR wallet. Status can update encrypted local state and backups: expired/canceled Base payments are marked with `paymentState`, preserving the purchase, attempts and recovery key. It never deletes the purchase record.
61
89
 
62
90
  State directories are created with mode 0700 and records/outputs with 0600. Keep existing
@@ -64,8 +92,99 @@ state directories private. Purchases and recovery grants are encrypted by the SD
64
92
  encrypted backups are under `stateDir/backups`. Public headers and resource URLs are saved
65
93
  separately for later downloading. Preserve state after timeouts. Losing local state does not
66
94
  lose recovery rights: the same recovery wallet can create a fresh grant on a new device with
67
- a new backup secret. Keys, signed payment payloads and decrypted file contents are never
68
- returned in CLI/MCP results. Plaintext is written only to the explicitly named output.
95
+ a new backup secret ([Loss](#backup-secret) explains when `init` creates one). Keys, signed payment
96
+ payloads and decrypted file contents are never returned in CLI/MCP results. Plaintext is written only to the explicitly named output.
97
+
98
+ ### Backup secret
99
+
100
+ Run `annon init` once, before buying, selling or enabling agent mode. It creates
101
+ `~/.config/annon/backup-secret`: 32 random bytes as 64 lowercase hex characters and a newline,
102
+ mode 0600, in a directory created with mode 0700. The file is written to an exclusive temporary
103
+ file and published with a hard link, so it never appears half-written and an existing file is
104
+ never replaced. Running `init` again only validates the file and reports `"created": false`. An
105
+ unsafe default is refused and never repaired: a symlink, FIFO or other non-regular file, a file not
106
+ owned by you or with group or other permission bits, malformed content, or an `~/.config/annon`
107
+ that is a symlink, is not owned by you, or is writable by group or others. An existing 0755
108
+ directory is fine. Every command that reads the default applies the same checks.
109
+
110
+ The location is fixed. `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, `XDG_STATE_HOME`, `--config`,
111
+ `ANNON_CONFIG` and `--state-dir` do not move it. For another private location, name an existing file with the explicit inputs below.
112
+
113
+ Sources, in order, as before this file existed:
114
+
115
+ 1. `--backup-secret-file` (or, when the flag is absent, `ANNON_BACKUP_SECRET_FILE`), a file with the
116
+ 64 hex characters.
117
+ 2. `ANNON_BACKUP_SECRET`, the hex value itself. Prefer a file: environment values are inherited by
118
+ child processes.
119
+ 3. The default file, only when neither is set.
120
+
121
+ An empty value counts as unset, as before: an empty `--backup-secret-file` selects no file even when
122
+ `ANNON_BACKUP_SECRET_FILE` is set, and moves on to `ANNON_BACKUP_SECRET`. Hex must be lowercase in
123
+ every source; an optional `0x` prefix and surrounding whitespace are accepted. A selected source that is missing,
124
+ unreadable or malformed fails with `backup_secret_required` (exit 2); there is no fallback to a
125
+ later source, the default file or a new secret. `annon init` with an explicit source only validates
126
+ it and never writes, creates or changes that file. Commands other than `init` read the secret but
127
+ never create it, and agent mode and MCP never create it. Bare `init` accepts only `--json`,
128
+ `--config`, `--state-dir` and `--backup-secret-file`; signer options need `--agent`.
129
+
130
+ Output: stdout carries the usual single JSON result, for example
131
+ `{"ok":true,"result":{"configured":true,"backupSecret":{"source":"default-file","path":"/home/me/.config/annon/backup-secret","created":true}}}`.
132
+ `source` is `default-file`, `flag-file`, `env-file` or `env`; `path` is shown for files only. The
133
+ one-time creation notice goes to stderr, also with `--json`. The secret itself is never printed, in
134
+ results, errors or notices.
135
+
136
+ **Back it up.** The secret is not part of the encrypted backups in `stateDir/backups`; copy the file
137
+ itself, separately, to private encrypted storage. Never `cat` it or paste it into a terminal. This
138
+ copies without displaying it and refuses an existing destination:
139
+
140
+ ```sh
141
+ node --input-type=module -e '
142
+ import { constants } from "node:fs";
143
+ import { copyFile } from "node:fs/promises";
144
+ await copyFile(process.argv[1], process.argv[2], constants.COPYFILE_EXCL);
145
+ ' "$HOME/.config/annon/backup-secret" /mounted-private-encrypted-backup/annon-backup-secret
146
+ ```
147
+
148
+ The copy keeps mode 0600 when the destination supports POSIX permissions; check it. A secret manager
149
+ that imports files is also fine.
150
+
151
+ **Restore.** On a new machine, put the saved file in a private directory (directory mode 0700, file
152
+ mode 0600), copy the encrypted record backups separately, and select the file. Do not run `init`
153
+ first:
154
+
155
+ ```sh
156
+ export ANNON_BACKUP_SECRET_FILE=/absolute/private/restored-annon-backup-secret
157
+ annon open /absolute/private/purchase-backup.json --url https://host.example/file --output restored-file
158
+ ```
159
+
160
+ Opening one known backup proves the restore. To restore to the default location instead, create
161
+ `~/.config/annon` with mode 0700 and copy in the other direction, still refusing an existing file:
162
+
163
+ ```sh
164
+ mkdir -p -m 700 "$HOME/.config/annon"
165
+ node --input-type=module -e '
166
+ import { constants } from "node:fs";
167
+ import { copyFile } from "node:fs/promises";
168
+ await copyFile(process.argv[1], process.argv[2], constants.COPYFILE_EXCL);
169
+ ' /mounted-private-encrypted-backup/annon-backup-secret "$HOME/.config/annon/backup-secret"
170
+ ```
171
+
172
+ **Loss.** If the secret disappears, commands fail with `backup_secret_required` and a pending
173
+ `status` stays pending; nothing recreates it. Restore the same bytes. Bare `init` creates a new
174
+ secret only when both checked state locations are empty: the effective state directory and the
175
+ default `~/.local/share/annon`, each in `records` and `backups`. If every copy is lost and you want
176
+ to recover with a new secret, first move the whole old state, including `~/.local/share/annon`, to
177
+ a separate private archive and keep it. Then use an empty state directory, run `init`, and run
178
+ `recover` with the same recovery wallet. That creates a fresh grant and new records; it cannot
179
+ decrypt the archived records or resume pending sell jobs. An operator can instead provision a
180
+ separate secret file and select it with `ANNON_BACKUP_SECRET_FILE`. Use one secret per state
181
+ directory.
182
+
183
+ **Agents.** `init --agent` does not create or reference a backup secret. Run bare `init` before it,
184
+ or give the agent an operator-managed file with an absolute `ANNON_BACKUP_SECRET_FILE` in the
185
+ launcher. A launcher that sets `ANNON_BACKUP_SECRET_FILE` must point it at a file that exists; a
186
+ missing file fails without falling back to the default. Once agent mode is active, `init` is
187
+ refused, so provision or restore the secret outside the running agent.
69
188
 
70
189
  ## Commands
71
190
 
@@ -78,6 +197,10 @@ annon sell report.pdf --price 0.2 --split near:alice.near:10000 --wallet near --
78
197
  annon sell report.pdf --resume 0xFILE_ID --wallet near --output resumed.pay2
79
198
  annon sell report.pdf --price 0.2 --split evm:0x1111111111111111111111111111111111111111:10000 --wallet evm --base
80
199
  annon buy https://host.example/report --route near --wallet near
200
+ annon buy https://host.example/report --route near --open report.pdf
201
+ annon buy 2 --shop demo --open report.pdf
202
+ annon status
203
+ annon open 0x1a2b3c4d --output report.pdf
81
204
  annon buy offer.json --url https://host.example/report --route base --wallet evm --max-price 0.2
82
205
  annon buy https://host.example/report --route 1click --wallet solana --from-chain solana
83
206
  annon status 0xPURCHASE_ID --json
@@ -91,6 +214,29 @@ annon stop 0xFILE_ID --wallet near
91
214
  ```
92
215
 
93
216
  IDs in these commands are placeholders for full `0x` plus 64 lowercase hex digits.
217
+
218
+ Fewer steps for a person (agent mode refuses all three and keeps requiring full URLs and IDs):
219
+
220
+ - `buy <row> --shop <id> [--search <q>]` buys that row of the listing `catalog` prints for the same
221
+ `--shop` and `--search`. The CLI names the row's title and URL, then checks the file and its terms
222
+ as for a typed URL. A catalog can change between listing and buying, so read the terms before you
223
+ confirm. An offer file named like a number is written `./2`.
224
+ - `buy --open <file>` waits up to three minutes for acceptance and then writes the opened file. It
225
+ refuses an existing `<file>` before paying. The result is the buy result with `state: "opened"`
226
+ and `opened: { output, bytes }`. If the purchase is not accepted in time it adds
227
+ `settlement: "pending"` and exits 4; if a status check or the open fails after payment, the result
228
+ still names the purchase and adds `statusError` or `openError`, and the exit code is that
229
+ error's. A purchase that is still `state: "uncertain"` wins over the follow-up error: the result
230
+ carries `statusError`, and the exit code stays 4. Nothing is paid twice: finish with `status` and
231
+ `open`. A 1Click buy returns its deposit
232
+ instructions without waiting. In a terminal, `buy` waits for acceptance even without `--open`.
233
+ - `status` and `open --output <file>` without an ID use the latest purchase `buy` saved for the
234
+ configured contract and network. An ID may be shortened to a prefix of 8 or more hex digits, with
235
+ or without `0x`. A prefix only names purchases that `buy` or `recover` saved for the configured
236
+ contract and network; one that matches none of them, or more than one, is refused with
237
+ `purchase_required`. A purchase saved by an older version, or for another contract or network,
238
+ needs its full ID. For `open`, a name of 8 to 63 hex digits is always read as a prefix: write
239
+ such a file as `./name`.
94
240
  `sell` creates a PAY2 file; arrange hosting separately. `buy` accepts the HTTPS resource URL
95
241
  or a named file containing decoded x402 `PAYMENT-REQUIRED` JSON. An offer file requires `--url` with the independently selected resource URL; it must match the offer. It checks the public header
96
242
  and registered terms before the SDK authorizes payment. Every buy requires `--max-price`, `ANNON_MAX_PRICE` or config `maxPrice` (decimal USDC); the offer never supplies the cap. A configured cap also bounds an explicit flag. The default route is direct NEAR for
@@ -161,9 +307,23 @@ blindly retried. Raw upstream errors are intentionally suppressed to avoid leaki
161
307
  | --- | --- |
162
308
  | 0 | Command completed; buy may still require a deposit or settlement |
163
309
  | 1 | Operation failed; inspect final status and retained encrypted state before retrying |
164
- | 2 | Invalid arguments, configuration, wallet source or missing local input |
165
- | 3 | First-use contract trust required or declined |
166
- | 4 | `status` reports pending; acceptance has not been observed (not proof of payment failure) |
310
+ | 2 | Invalid arguments, configuration, wallet source or missing local input; `purchase_open` when a saved purchase of the file, including an uncertain NEAR send, still blocks a new one |
311
+ | 3 | First-use contract trust required or declined; browser approval cancelled (`cancelled`), expired (`cancelled`, or `rejected` or `wallet_error` after the last wallet failure the page reported) or declined by the wallet |
312
+ | 4 | Not settled, and not proof of failure: `status` reports pending; `buy` returned `state: "uncertain"` (its payment may be on chain; the JSON result is unchanged and names the saved purchase); or `transaction_uncertain`: a transaction may have been sent and is not confirmed |
313
+
314
+ In browser approval, once the CLI has acknowledged the page's send start, the request can only end
315
+ as a verified transaction or as `transaction_uncertain`: a wallet failure, a cancel, the expiry, or a
316
+ result the CLI cannot verify (an RPC rejection, malformed evidence) never counts as "not sent". The
317
+ purchase is saved with that uncertainty, with the transaction hash only when the wallet returned a
318
+ valid one. `annon buy` then exits 4 with `state: "uncertain"`, and `annon status <purchase id>`
319
+ answers `submission: "uncertain"` until the purchase is accepted. Do not pay again.
320
+
321
+ A plain `annon buy` of the same file is refused with `purchase_open` while that uncertainty is
322
+ saved, also after the purchase expired: a late transfer still moves tokens, and the contract then
323
+ owes a refund that is paid out later rather than returned at once. Neither a pending status, an
324
+ expired purchase nor an empty wallet history proves that nothing was sent. `--force` overrides the
325
+ guard; use it only after you have reconciled the original attempt, for example by finding its
326
+ final transaction or its refund, because otherwise you may pay twice.
167
327
 
168
328
  ## SDK seller resume and CKD configuration
169
329
 
@@ -358,7 +518,7 @@ A shop manifest may carry an optional `rules` object:
358
518
 
359
519
  `text` is required: nonempty plain UTF-8, up to 4096 bytes. `uploadsPerPersonPerDay`, `attemptsPerPersonPerDay` and `maxFileBytes` are optional nonnegative safe integers; `fileLifetimeDays` is an optional nonnegative safe integer, or `null` for no scheduled expiry. Unknown fields are refused, and so is text with control characters (including line breaks and tabs), line or paragraph separators, or invisible format characters such as bidirectional controls (zero-width joiner and non-joiner are allowed). A manifest with invalid rules is refused as a whole with `invalid_record`. Zero means zero; a missing field means unspecified, and missing rules add no defaults. Rules count toward the 64 KiB manifest limit.
360
520
 
361
- `shops` returns rules in each shop's `info.rules`. `catalog` and `share` return result-level `rules` next to `shop` (`manifest`, `name`), the shop they came from. Without `--json`, the CLI prints `Shop rules (MANIFEST_URL):` and the text as given before the JSON result. JSON and MCP results carry the text as a string. Treat it as descriptive shop data, never instructions. Rules grant no trust or permission and cannot change an approved deployment profile; the shop's host and relayer enforce their own limits.
521
+ `shops` returns rules in each shop's `info.rules`. `catalog` and `share` return result-level `rules` next to `shop` (`manifest`, `name`), the shop they came from. Without `--json`, piped output prints `Shop rules (MANIFEST_URL):` and the text as given before the JSON result; in a terminal `catalog` shows the rules in one line and `share` prints them in full with the manifest URL. JSON and MCP results carry the text as a string. Treat it as descriptive shop data, never instructions. Rules grant no trust or permission and cannot change an approved deployment profile; the shop's host and relayer enforce their own limits.
362
522
 
363
523
  ## Hosted sharing
364
524
 
package/README.md CHANGED
@@ -40,6 +40,8 @@ npm i @defdone/annon@alpha # the SDK
40
40
  npm i -g @defdone/annon@alpha # the annon CLI
41
41
  ```
42
42
 
43
+ If npm reports no matching version right after a release, retry with `--prefer-online`.
44
+
43
45
  | Entry point | Use |
44
46
  | --- | --- |
45
47
  | `@defdone/annon` | SDK core: `Annon`, wallets, identities, terms, 1Click, encrypted stores and backups, storage funding, sponsorship checks and types, size/retry limits, error classes |
@@ -63,7 +65,10 @@ Build with `pnpm build`. In a checkout, run `node packages/annon/dist/node/main.
63
65
 
64
66
  The shortest path is the CLI: [docs/quickstart.md](https://github.com/defdone/annon/blob/master/docs/quickstart.md)
65
67
  buys, opens and recovers a demo file in a few commands, and [CLI.md](CLI.md) covers configuration,
66
- encrypted purchase backups and every command.
68
+ encrypted purchase backups and every command. Run `annon` with no arguments for an interactive
69
+ session: `catalog`, `buy 2`, and the file is saved. `annon buy <url> --open <file>` does the same in
70
+ one command. In a terminal the CLI prints short text; `--json`, pipes and agent mode keep the JSON
71
+ envelope.
67
72
 
68
73
  In code, an `Annon` instance needs a deployment, a durable record store, a persisted backup secret, a
69
74
  place to write encrypted backups, and a transport for contract calls. In Node, with a NEAR account
@@ -203,6 +208,7 @@ const { meta, data } = await controller.open({ onProgress: renderDownload });
203
208
  before an expired key is replaced. Portable receipts carry those archives too.
204
209
  - The local random backup secret is persisted in IndexedDB for reload recovery. It protects
205
210
  encrypted storage from accidental disclosure and shares the embedding page's trust boundary.
211
+ This is the browser's secret; the CLI keeps its own in a file ([CLI.md](CLI.md#backup-secret)).
206
212
  - Export with `controller.exportReceipt(portableSecret)`, using a separate random 32-byte
207
213
  `newBackupSecret()` that you keep separately. Import with
208
214
  `storage.importReceipt(file, portableSecret)`. Import restores the header, resource URL and
@@ -295,6 +301,9 @@ shop-provided data: on each shop's `info` from `listShops`, and as `rules` with
295
301
 
296
302
  ## CLI browser wallet login
297
303
 
304
+ Run `annon init` once first. It creates the CLI backup secret file that encrypts local records
305
+ ([CLI.md](CLI.md#backup-secret)); login neither creates nor exports it.
306
+
298
307
  ```sh
299
308
  annon login --wallet evm
300
309
  annon login --wallet near --network near:testnet
@@ -308,21 +317,44 @@ in the wallet.
308
317
  | Wallet | How | Signing supported |
309
318
  | --- | --- | --- |
310
319
  | EVM | Injected EIP-1193 wallet on Base | `personal_sign` for the SDK's authorization texts, `eth_signTypedData_v4` for the exact EIP-3009/x402 payload |
311
- | NEAR | Meteor Wallet (extension or web popup; mainnet and testnet) or HOT Wallet (mainnet only) through Wallet Selector, picked on the page; account-only sign-in (no `contractId`, no method list) | NEP-413 and `signAndSendTransaction`, including the required deposit `"1"` on `ft_transfer_call` |
320
+ | NEAR | Meteor Wallet (extension or web popup; mainnet and testnet) or HOT Wallet (mainnet only) through Wallet Selector, picked on the page. NEP-413 messages are signed directly, with the account chosen in the wallet and no sign-in. Transactions take two clicks: an account-only sign-in (no `contractId`, no method list), then the transaction | NEP-413 and `signAndSendTransaction`, including the required deposit `"1"` on `ft_transfer_call` |
312
321
 
313
322
  Browser and remote EVM split-deployment transactions and browser and remote Solana signing are not
314
323
  included; the local-key adapters remain available for those workflows.
315
324
 
316
325
  **The local page.** The listener binds `127.0.0.1` on an OS-assigned port before opening the browser.
317
326
  Match the short pairing code with the terminal, and review the action, amount, recipients, file and
318
- chain on the page before approving in the wallet.
327
+ chain on the page before approving in the wallet. The page shows a summary built only from the
328
+ request the wallet signs (amounts in USDC or NEAR, receiver, account, expiry), the CLI's payment or
329
+ listing terms apart from it with a warning if their price differs, and the raw request under
330
+ Technical details. While a request waits, the terminal prints its expiry on stderr.
331
+
332
+ - Each click opens at most one wallet window, inside that click, so the browser's pop-up blocker
333
+ allows it. If the wallet window closes, is blocked or declines before anything is sent, the page
334
+ says so and you can try again, with the same or another wallet, until the request expires.
335
+ Nothing is retried for you.
336
+ - Once the page has called the wallet's send for a transaction, that request is never attempted
337
+ again, whatever the wallet answers, because it may already have broadcast. The request ends as
338
+ `transaction_uncertain` and the terminal says: "The wallet may have sent the transaction. Do not
339
+ pay again. Run annon status <purchase id> to check it." The page records the send start with
340
+ the CLI before it calls the wallet, so a reloaded page or the request's expiry cannot turn it
341
+ into a retryable failure, and a result the CLI cannot verify after that point also ends as
342
+ uncertain. `annon buy` exits 4, `annon status` reports `submission: "uncertain"`, and the saved
343
+ purchase blocks a plain `annon buy` of the same file (`purchase_open`). `--force` overrides it
344
+ only after you have reconciled the first attempt (see CLI.md).
345
+ - The page reports only a fixed reason name (`window_closed`, `popup_blocked`, `rejected`,
346
+ `wallet_error`, `refused`, `send_uncertain`, `send_started` or `cancelled`) to the CLI, which
347
+ prints its own line for it. Cancel ends the request at once; an expired request fails with the
348
+ last reported reason, for example `rejected` or `wallet_error`, instead of a generic error. A
349
+ report never completes a request.
319
350
 
320
351
  - Host (including port), Origin and an unpredictable RAM-only capability are checked on action and
321
352
  result API calls.
322
353
  - In open mode, a same-origin POST bootstraps the capability once into the opened page, then the
323
354
  bootstrap route closes. With `--no-open`, bootstrap is disabled and the capability exists only in
324
355
  the printed URL fragment. It is never passed to the browser launcher in argv.
325
- - Bundled scripts, a restrictive CSP, no framing, `no-store` and `no-referrer` are enforced. The page
356
+ - Bundled scripts and a stylesheet from the page's own origin (no inline styles, fonts or other
357
+ resources), a restrictive CSP, no framing, `no-store` and `no-referrer` are enforced. The page
326
358
  connects only to itself, the Wallet Selector's NEAR RPC (`free.rpc.fastnear.com` on mainnet,
327
359
  `test.rpc.fastnear.com` on testnet, fixed in the page and independent of your configured RPC) and
328
360
  HOT's relay `h4n.app`, and frames only `hot-labs.org`.
@@ -405,8 +437,11 @@ Order matters, because agent mode refuses `init`, `login` and per-call trust or
405
437
  2. Write the deployment, `maxPrice`, `workDir` and, after reviewing the contract,
406
438
  `trustContract: true` (or trust it once with a human command and `--trust-contract`). Do not set
407
439
  `agent: true` yourself.
408
- 3. Run one `annon init --agent ...` from the examples above. It saves the signer, the daily budget
409
- and `agent: true`.
440
+ 3. Run bare `annon init` to create the backup secret, or give the launcher an absolute
441
+ `ANNON_BACKUP_SECRET_FILE` you manage ([CLI.md](CLI.md#backup-secret)). Agent mode never creates
442
+ one.
443
+ 4. Run one `annon init --agent ...` from the examples above. It saves the signer, the daily budget
444
+ and `agent: true`. It does not create or reference a backup secret.
410
445
 
411
446
  - Agents never prompt. Missing trust, price cap or daily budget causes a refusal.
412
447
  - `allowedResources` is optional. Without it, any secure resource URL is allowed within the
@@ -447,7 +482,8 @@ Before a human payment the CLI confirms the current price, split recipients and
447
482
  and file, token and network. Browser mode shows these exact terms with each signing request for
448
483
  approval in the page and the wallet. Local and remote human signing require an explicit terminal
449
484
  confirmation. Purchase and recovery secrets still need the independent backup secret described in
450
- CLI.md; login does not derive one from a signature.
485
+ [CLI.md](CLI.md#backup-secret) and created by `annon init`; login does not derive one from a
486
+ signature.
451
487
 
452
488
  ## Remote signer, HTTP JSON v1
453
489
 
@@ -501,9 +537,10 @@ RPC. A returned transaction hash alone never proves success.
501
537
  - Definite mismatches and failed finalized transactions are rejected without saving the hash as
502
538
  `nearSubmission` evidence. Only RPC unavailability or a non-final status keeps the outcome
503
539
  uncertain.
504
- - If submission or finalized verification is ambiguous, the CLI returns `state: "uncertain"`,
505
- `purchaseId` and `txHash`, and keeps `nearSubmission: {state:"uncertain",txHash}` in the encrypted
506
- purchase backup. Check status for that saved purchase before buying again.
540
+ - If submission or finalized verification is ambiguous, the CLI returns `state: "uncertain"` and
541
+ `purchaseId`, and keeps `nearSubmission: {state:"uncertain"}` in the encrypted purchase backup.
542
+ Both carry `txHash` only when the wallet returned a valid one. `annon buy` exits 4. Check status
543
+ for that saved purchase before buying again.
507
544
  - Signing errors with a known hash also expose safe hash and purchase evidence, with exit code 4.
508
545
 
509
546
  **Reference handler.** `node/signer-mock` exports `signerMock`, an authenticated HTTP
@@ -100,9 +100,10 @@ export interface Bought {
100
100
  route: BuyRoute;
101
101
  record: PurchaseRecord;
102
102
  instructions?: Deposit;
103
+ /** `txHash` is absent when the wallet may have sent the transaction without returning one. */
103
104
  uncertain?: {
104
105
  purchaseId: string;
105
- txHash: string;
106
+ txHash?: string;
106
107
  };
107
108
  payment?: string;
108
109
  }
package/dist/api/annon.js CHANGED
@@ -869,11 +869,18 @@ export class Annon {
869
869
  const next = {
870
870
  ...record,
871
871
  revision: record.revision + 1,
872
- nearSubmission: { state: 'uncertain', txHash: error.txHash },
872
+ nearSubmission: {
873
+ state: 'uncertain',
874
+ ...(error.txHash ? { txHash: error.txHash } : {}),
875
+ },
873
876
  };
874
877
  if (!(await store.compareAndSet(purchaseKey(id), record, next)))
875
878
  throw new TransactionUncertain(error.txHash, id);
876
- return { route, record: next, uncertain: { purchaseId: id, txHash: error.txHash } };
879
+ return {
880
+ route,
881
+ record: next,
882
+ uncertain: { purchaseId: id, ...(error.txHash ? { txHash: error.txHash } : {}) },
883
+ };
877
884
  }
878
885
  return { route, record };
879
886
  }
@@ -18,6 +18,6 @@ export { type CatalogEntry, type CatalogQuery, type ConfiguredShop, canonicalMan
18
18
  export { TransactionUncertain } from '../wallet/uncertain.js';
19
19
  export { type EvmWallet, FileFundingRefused, type IdentityWallet, type NearWallet, } from '../wallet/wallet.js';
20
20
  export { BROWSER_DOWNLOAD_LIMIT, type BrowserDownloadOptions, type BrowserDownloadProgress, browserDownload, } from './download.js';
21
- export { type BrowserOpenOptions, BrowserPurchaseController, type BrowserPurchaseRecord, type BrowserPurchaseState, type BrowserPurchaseStorage, browserAnnon, browserPurchaseStorage, type ControllerOptions, PurchaseLeaseBusy, type PurchaseProgress, type PurchaseSdk, type PurchaseTermsReview, } from './purchases.js';
21
+ export { type BrowserOpenOptions, BrowserPurchaseController, type BrowserPurchaseRecord, type BrowserPurchaseState, type BrowserPurchaseStorage, browserAnnon, browserPurchaseStorage, type ControllerOptions, PurchaseLeaseBusy, type PurchaseProgress, type PurchaseSdk, type PurchaseTermsReview, type ReconcileOptions, type ReconcileResult, } from './purchases.js';
22
22
  export { type BrowserSaleIndex, type BrowserSellerOptions, browserSellerSpool, browserSellerStorage, createBrowserSeller, type SaleHandle, type SaleView, type SellDraft, type SellerConnection, type SellerFacade, type SellProgress, type SellQuote, type SellStep, type SellTerms, } from './selling.js';
23
23
  export { type BrowserNearProvider, type BrowserNearTransaction, BrowserWalletError, type BrowserWalletErrorCode, browserEvmWallet, browserNearWallet, browserSolanaWallet, type InjectedEvmProvider, type InjectedSolanaKey, type InjectedSolanaProvider, signNearUpload, } from './wallets.js';