@ghostlytools/cli 1.3.1 → 1.3.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 (72) hide show
  1. package/README.md +6 -6
  2. package/dist/assets/{api-Dq2QXQDk.js → api-B1fADtqd.js} +65 -20
  3. package/dist/assets/api-B1fADtqd.js.map +1 -0
  4. package/dist/assets/{api-TiB5QSG9.js → api-Bg7bh_6E.js} +2 -2
  5. package/dist/assets/{apiKit-CA2x5GJF.js → apiKit-BsUZPHLn.js} +44 -33
  6. package/dist/assets/apiKit-BsUZPHLn.js.map +1 -0
  7. package/dist/assets/{apps-BxV9i_Y4.js → apps-BaUNt-5c.js} +91 -24
  8. package/dist/assets/apps-BaUNt-5c.js.map +1 -0
  9. package/dist/assets/{apps-C-T8jeji.js → apps-DepomCiX.js} +1 -1
  10. package/dist/assets/{arkade-C_qDmHnm.js → arkade-CNnMaoEQ.js} +4 -4
  11. package/dist/assets/arkade-CNnMaoEQ.js.map +1 -0
  12. package/dist/assets/{backup-Bugnn3Ue.js → backup-B5Q1W-Ey.js} +1 -1
  13. package/dist/assets/{backup-CnMN2oUK.js → backup-Bbz3lyC5.js} +6 -6
  14. package/dist/assets/{backup-CnMN2oUK.js.map → backup-Bbz3lyC5.js.map} +1 -1
  15. package/dist/assets/buttons-CUd7j0ZA.js +2 -0
  16. package/dist/assets/{buttons-CvNBzh2S.js → buttons-DS1iURp8.js} +4 -4
  17. package/dist/assets/{buttons-CvNBzh2S.js.map → buttons-DS1iURp8.js.map} +1 -1
  18. package/dist/assets/{envelope-WjingYSc.js → envelope-fNC941z4.js} +2 -2
  19. package/dist/assets/{envelope-WjingYSc.js.map → envelope-fNC941z4.js.map} +1 -1
  20. package/dist/assets/{errors-DO5z6h_l.js → errors-B-zMxbcd.js} +6 -5
  21. package/dist/assets/{errors-DO5z6h_l.js.map → errors-B-zMxbcd.js.map} +1 -1
  22. package/dist/assets/{ghostly_iroh_web-Dvzh3Y6E.js → ghostly_iroh_web-BsAEFIjl.js} +30 -36
  23. package/dist/assets/ghostly_iroh_web-BsAEFIjl.js.map +1 -0
  24. package/dist/assets/ghostly_iroh_web_bg.wasm +0 -0
  25. package/dist/assets/{groupFiles-IEUW3DQz.js → groupFiles-6qnLCnOm.js} +82 -29
  26. package/dist/assets/groupFiles-6qnLCnOm.js.map +1 -0
  27. package/dist/assets/{host-CWt-jtqz.js → host-Dj3C1fn3.js} +305 -68
  28. package/dist/assets/host-Dj3C1fn3.js.map +1 -0
  29. package/dist/assets/{hyperdhtRelay-CPYwSLLS.js → hyperdhtRelay-CrgYQN-g.js} +2 -2
  30. package/dist/assets/{hyperdhtRelay-CPYwSLLS.js.map → hyperdhtRelay-CrgYQN-g.js.map} +1 -1
  31. package/dist/assets/{irohWeb-4oChIOIp.js → irohWeb-DSyLp5Re.js} +3 -3
  32. package/dist/assets/{irohWeb-4oChIOIp.js.map → irohWeb-DSyLp5Re.js.map} +1 -1
  33. package/dist/assets/{main-DbqcdShN.js → main-BDd11Yd1.js} +159 -54
  34. package/dist/assets/main-BDd11Yd1.js.map +1 -0
  35. package/dist/assets/{mainline-ZkiOnevs.js → mainline-CLTo0nGa.js} +2 -2
  36. package/dist/assets/{mainline-ZkiOnevs.js.map → mainline-CLTo0nGa.js.map} +1 -1
  37. package/dist/assets/{networkReason-CetKkTPo.js → networkReason-DwcadZz5.js} +2 -2
  38. package/dist/assets/{networkReason-CetKkTPo.js.map → networkReason-DwcadZz5.js.map} +1 -1
  39. package/dist/assets/{nwc-CgkqyvsE.js → nwc-BNFyyECH.js} +3 -3
  40. package/dist/assets/{nwc-CgkqyvsE.js.map → nwc-BNFyyECH.js.map} +1 -1
  41. package/dist/assets/{nwcProvider-4eVz_8HP.js → nwcProvider-BbjJOfLD.js} +2 -2
  42. package/dist/assets/{nwcProvider-4eVz_8HP.js.map → nwcProvider-BbjJOfLD.js.map} +1 -1
  43. package/dist/assets/{profiles-BHc9pEYg.js → profiles-gPCjLOQq.js} +2 -2
  44. package/dist/assets/{profiles-BHc9pEYg.js.map → profiles-gPCjLOQq.js.map} +1 -1
  45. package/dist/assets/{registry-DlAKO0Fy.js → registry-CoLHpxkb.js} +17 -32
  46. package/dist/assets/registry-CoLHpxkb.js.map +1 -0
  47. package/dist/assets/{registry-OBJBnHVQ.js → registry-DoTb6uEr.js} +1 -1
  48. package/dist/assets/{resolve-CouHhCpJ.js → resolve-BIDJ-UUl.js} +18 -3
  49. package/dist/assets/resolve-BIDJ-UUl.js.map +1 -0
  50. package/dist/assets/{server-Ss0gJ-nw.js → server-Bx3RGkBC.js} +380 -116
  51. package/dist/assets/server-Bx3RGkBC.js.map +1 -0
  52. package/dist/assets/{src-DDzSERyc.js → src-D7gcJElT.js} +470 -242
  53. package/dist/assets/src-D7gcJElT.js.map +1 -0
  54. package/dist/assets/{turnRefs-B87dtrHM.js → turnRefs-PN644M9o.js} +2 -2
  55. package/dist/assets/{turnRefs-B87dtrHM.js.map → turnRefs-PN644M9o.js.map} +1 -1
  56. package/dist/assets/{usdt-Fj8CQO2z.js → usdt-B2TLRlYa.js} +3 -3
  57. package/dist/assets/{usdt-Fj8CQO2z.js.map → usdt-B2TLRlYa.js.map} +1 -1
  58. package/dist/ghostly.mjs +1 -1
  59. package/package.json +1 -1
  60. package/dist/assets/api-Dq2QXQDk.js.map +0 -1
  61. package/dist/assets/apiKit-CA2x5GJF.js.map +0 -1
  62. package/dist/assets/apps-BxV9i_Y4.js.map +0 -1
  63. package/dist/assets/arkade-C_qDmHnm.js.map +0 -1
  64. package/dist/assets/buttons-Bj7Vf7Pn.js +0 -2
  65. package/dist/assets/ghostly_iroh_web-Dvzh3Y6E.js.map +0 -1
  66. package/dist/assets/groupFiles-IEUW3DQz.js.map +0 -1
  67. package/dist/assets/host-CWt-jtqz.js.map +0 -1
  68. package/dist/assets/main-DbqcdShN.js.map +0 -1
  69. package/dist/assets/registry-DlAKO0Fy.js.map +0 -1
  70. package/dist/assets/resolve-CouHhCpJ.js.map +0 -1
  71. package/dist/assets/server-Ss0gJ-nw.js.map +0 -1
  72. package/dist/assets/src-DDzSERyc.js.map +0 -1
package/README.md CHANGED
@@ -93,8 +93,8 @@ The rows are in alphabetical order of their first command, and every command of
93
93
  |---|---|
94
94
  | `app init <dir> [--name <name>] [--title <title>] [--force]` | Start an app (WISP 1200, Apps), run here with no profile or daemon: a small working one in `<dir>` (made when missing), which `app publish <dir>` signs as it is. It writes `ghostly-app.json` (`view: chat`, the `chat` permission), an `index.html` that says hello to the same app on the contact's side, typed against `@ghostlytools/sdk/app`, and a `README.md` with build, publish, verify and how to list it in a store. The name is `--name` (a lowercase letter, then up to 31 lowercase letters, digits or hyphens), else the folder's (`My App` gives `my-app`); the title is `--title` (one line, up to 40 characters), else the name's. When one of the three files is there nothing is written and it is `refused` (`details.reason` `exists`, `details.files`), unless `--force`, which replaces those three only. Answers `{dir, name, title, files, replaced?, next}` |
95
95
  | `app open <chat> <ref\|name> [--no-card]`, `app close <chat> <ref\|name>` | A served app in a chat, as a bot (WISP 1200, Apps; needs the daemon, and `app serve` first). `app open` says the app is open on this side, in the version served, and sends the contact the app's card ("opened Chess", with what their app needs to get the same app and check it), unless their app is open in that chat already or `--no-card`. The two sides talk once both opened it; while the app stays open here, the engine says so again on every session that comes back. It answers `{chat, app, version, card, peer, live}`: `card` is the card's message id or null, `peer` the version the contact has open or null. A card that cannot go leaves nothing open. `app close` ends it: `{chat, app, closed}`. What is open is kept in memory: a restarted daemon opens again. `unavailable` in a one-shot, or on a daemon that started before the first app was served (`daemon restart`); `not_found` for an app not served; `refused`, in the engine's words, for a chat that is not a paired 1:1 chat |
96
- | `app publish <dir> --key <file> [--out <file>] [--sequence n]`, `app verify <bundle\|url>` | The publisher tools of WISP 1200 (Apps), run here with no profile or daemon. `app publish` bundles and signs an app: `<dir>/ghostly-app.json` holds the manifest's fields but `publisher`, `sequence` and `files`, and every other file in `<dir>` goes in (dot files are left out and listed in `skipped`). It writes `<dir>/app.ghostlyapp` (or `--out`), its `sequence` one more than the bundle already there, which only the same key may follow (`--sequence n` sets it, refused unless higher). `--key` names the publisher key file, a PKCS #8 PEM: made owner-only on first use (back it up: an app is updated only with its key), refused when others may read it or when it is inside `<dir>`, never printed; a folder holding any private key file is refused (`details.reason` `private-key`). `app verify` checks a bundle from a file or an `https` URL as a client does (jsDelivr only at a full commit, 16 MiB at most) and answers its manifest and digest, or `refused` (exit 1) with the WISP's reason in `details.reason` |
97
- | `app revoke <dir> --key <file> (--digest <digest>… \| --up-to <sequence>) [--reason <text>] [--bundle <file>]` | Revoke versions of your app (WISP 1200 · Revocation): by digest (base64url, as `app verify` prints it; `--digest` again for each, up to 64) or every version up to a `sequence`, with an optional one-line reason. The statement names the app of `<dir>/app.ghostlyapp` (or `--bundle`), is signed with that app's publisher key (any other key is refused, and none is made here), and is added to `<dir>/ghostly-revoke.json`, published beside the bundle; a client that reads it stops those versions. Answers `{app, digests \| upTo, current, added, revocations, file}`; `current` says whether the bundle in `<dir>` is among them |
96
+ | `app publish <dir> --key <file> [--out <file>] [--sequence n]`, `app verify <bundle\|url>` | The publisher tools of WISP 1200 (Apps), run here with no profile or daemon. `app publish` bundles and signs an app: `<dir>/ghostly-app.json` holds the manifest's fields but `publisher`, `sequence` and `files`, and every other file in `<dir>` goes in (dot files are left out and listed in `skipped`). It writes `<dir>/app.ghostlyapp` (or `--out`), its `sequence` one more than the bundle already there, which only the same key may follow (`--sequence n` sets it, refused unless higher); a sequence that `<dir>/ghostly-revoke.json` already revokes is `refused` (`details.reason` `revoked`, `details.sequence` the first one it leaves alone). `--key` names the publisher key file, a PKCS #8 PEM: made owner-only on first use (back it up: an app is updated only with its key), refused when others may read it or when it is inside `<dir>`, never printed; a folder holding any private key file is refused (`details.reason` `private-key`). `app verify` checks a bundle from a file or an `https` URL as a client does (jsDelivr only at a full commit, 16 MiB at most) and answers its manifest and digest, or `refused` (exit 1) with the WISP's reason in `details.reason` |
97
+ | `app revoke <dir> --key <file> (--digest <digest>… \| --up-to <sequence>) [--reason <text>] [--bundle <file>] [--force]` | Revoke versions of your app (WISP 1200 · Revocation): by digest (base64url, as `app verify` prints it; `--digest` again for each, up to 64) or every version up to a `sequence`, with an optional one-line reason. Clients keep a revocation for good, so `--up-to` past the bundle's own sequence is a usage error unless `--force` (versions published from elsewhere), and `app publish` refuses a sequence the folder's `ghostly-revoke.json` already stops, naming the `--sequence` that clears it. The statement names the app of `<dir>/app.ghostlyapp` (or `--bundle`), is signed with that app's publisher key (any other key is refused, and none is made here), and is added to `<dir>/ghostly-revoke.json`, published beside the bundle; a client that reads it stops those versions. Answers `{app, digests \| upTo, current, added, revocations, file}`; `current` says whether the bundle in `<dir>` is among them |
98
98
  | `app send <chat> <ref\|name> <json\|->` | One message of a served app's own protocol to the same app on the contact's side (WISP 1200, Apps; needs the daemon): any JSON value, on the command line or `-` for stdin, at most 32 KiB, at most 48 a second. It goes on the live session only, while both sides have the app open, and is never kept for later: `{chat, app, sent: true}`, or an error whose `details.reason` is the WISP's name for why it did not go: `not-open` (`refused`: `app open` first), `too-large` (`bad_request`), `offline` and `peer-closed` (`unavailable`: the chat is not live, or the contact's app is not open; wait for `app.opened`), `too-fast` (`refused`). The contact's messages come as `app.message` events |
99
99
  | `app serve <bundle\|url> --grant chat [--grant name] [--url <url>]`, `app served`, `app unserve <ref\|name>` | Serve an app as a bot (WISP 1200, Apps): the profile speaks the app's messages in its chats itself, and no app runs here. `app serve` reads the signed bundle from a file or an `https` URL and checks it as `app verify` does (`refused` with the WISP's reason in `details.reason`). Each permission the manifest asks needs its `--grant`: `chat` (talk to the same app on the contact's side) and `name` (the profile's name in that chat); one left out is `confirm` (exit 5), naming it in `details.missing`. `internet` cannot be granted (no page runs here), a permission the app does not ask is `bad_request`, and an app that does not ask for `chat` is `refused`. A newer version replaces the one served; an older one, or another bundle under the same `sequence`, is `refused`. It answers `{served: {ref, title, version, sequence, digest, url?, permissions, granted, servedAt}, offered, restart?}`; `url` is where a contact's app reads the bundle (`--url`, else the URL served from, else the manifest's first source). The list is kept in the profile's folder (`apps.json`). A profile's engine offers `apps/1` to contacts only while it serves an app, and reads the list when it starts: `offered` says whether it does now, and `restart: true` that `daemon restart` is needed for it. `app served` answers `{apps, offered, restart?}`; `app unserve` removes one by its reference or its name |
100
100
  | `button press <chat\|group> <message> <button> [--wait none\|sent\|delivered] [--timeout s]` | Press a button of someone else's question, as a tap in the app does (WISP 406 · Message Buttons): the reply carries the button's label, and the asker hears `button.pressed`. `<message>` is the question's id (its event or `chat history`), `<button>` a button's id from its `card`. Refused (exit 1) like a tap: closed buttons, a second answer past `--once`, a second press within a second. A one-shot in a chat waits until it went out, as `send`. Answers `{chat \| group, messageId, button, label, replyId, delivery \| edges}` |
@@ -123,7 +123,7 @@ The rows are in alphabetical order of their first command, and every command of
123
123
  | `file send <chat\|group> <path> [--name n] [--mime t] [--voice [ms] [--peaks …]] [--reply <message>]` | A file, or a voice note (its length and waveform measured from the file unless given); `--reply` quotes a message, as `send --reply` does. To a group (`group:<id>` when a chat has the same name), up to 100 MiB: every member sees it at once and each member's app fetches it from a member who has it (WISP 503) |
124
124
  | `file wait [<chat>] <file> [--timeout s]` | Wait until a transfer ends: exit `0` when the file is all here, `1` with the transfer's error when it failed (`details.retry`: `file resend` can go on), `4` on timeout (default 300 s) |
125
125
  | `forward <chat\|group> <message>… --to <chat\|group>… [--force] [--wait none\|sent] [--timeout s]` | Forward messages to up to 5 chats and groups (`--to` again for each; `group:<id>` names a group when a chat has the same name; an id that starts with a dash is taken as it is, `--to -Ab…` or `--to=-Ab…`). Each is a new message of yours that says it was forwarded (`forwarded`, the hop count) and nothing of who wrote it. Files go from the bytes this profile holds, never fetched again; a group takes texts only. A text that looks like a seed, a key or ecash needs `--force`. Answers `{from, results: [{to, kind, messageIds, error}]}`; any refusal fails the command (exit 1) with the results in `details`. `--wait sent`: each text on its way (a group's taken by an edge), each file's transfer done |
126
- | `group create <name> [--mesh]`, `group join <link>` | A community (a link anyone can open), or a private mesh. `group join` with the link of a community the profile was removed from asks to be let in again, and keeps its history |
126
+ | `group create <name> [--mesh]`, `group join [<link> \| -]` | A community (a link anyone can open), or a private mesh. `group join` reads the link from stdin when none is given or `-` (on the command line it shows in `ps` and the shell's history, and the CLI warns). `group join` with the link of a community the profile was removed from asks to be let in again, and keeps its history |
127
127
  | `group edit <group> <message> [text… \| --text <text> \| --stdin] [--mention <member>]… [--force]` | Replace the text of a message you sent to the group (`<message>` is the `messageId` `group send` gave). Every member sees it in place, marked edited; mentions whose `@name` is still in the text stay, `--mention` adds more. Answers `{group, messageId, edits, sent, edges}`; `--wait sent` and `--timeout` as for `group send`. The text the message already has is no edit: `sent: false, unchanged: true`. A private group's members whose edge is down get it when it opens, a community's with the catch-up; a member on an older app keeps the old text |
128
128
  | `group hub <group> <member> [--pin \| --exclude \| --auto]` | Past 16 members: pin a member as a hub, keep one from being a hub, or leave it to their app (a daemon offers itself) |
129
129
  | `group invite <group> <chat>`, `group remove <group> <member>`, `group admin <group> <member>` | Membership, for the admin (each prints the group; `--show-secret` for its link) |
@@ -139,7 +139,7 @@ The rows are in alphabetical order of their first command, and every command of
139
139
  | `identity providers`, `identity list` | Kinds of proof and their signers; this profile's proofs |
140
140
  | `identity share\|withdraw <chat> <id>`, `identity contact <chat>`, `identity recheck <chat> <id>`, `identity remove <id>` | Show a proof to a contact; what a contact showed, as checked here |
141
141
  | `invite create [--label <name>]` | A new chat's `ghostly1…` invite and its link |
142
- | `invite join <invite-or-link> [--label <name>]` | Join a chat (your own invite is refused) |
142
+ | `invite join [<invite-or-link> \| -] [--label <name>]` | Join a chat (your own invite is refused). The invite comes from stdin when none is given or `-`, out of `ps` and the shell's history; on the command line the CLI warns |
143
143
  | `lightning default <card>`, `lightning rename <card> <name>` | Lightning cards: which receives by default |
144
144
  | `listen [--since seq] [--cursor file] [--type t]… [--turns] [--from <chat\|key>]… [--group <group>]… [--exec cmd] [--webhook url] [--print]` | The event stream; `--from` and `--group` an allowlist, `--turns` one `agent.turn` per message to answer ([Events](#events)) |
145
145
  | `message retry\|delete\|details <chat> <message>` | One message |
@@ -157,12 +157,12 @@ The rows are in alphabetical order of their first command, and every command of
157
157
  | `service peer <chat>`, `service open <chat> <service> [--port p]`, `service close <chat> <service>` | A contact's app on a loopback port here (daemon). `url` is a link for your browser, `http://<random>.localhost:<port>/.ghostly-open/<token>`: it sets the service's cookie and shows the app on a host name of its own; any other host name, or a request without that cookie, gets 404. Chrome, Edge and curl reach `*.localhost` names by themselves; Safari and Node's `fetch` on macOS may not (add the name to `/etc/hosts` for them) |
158
158
  | `settings get [--show-secret]`, `settings set <key> <json>` | Relays, Iroh relays, the HyperDHT relay, ICE servers, `sendTyping` (false: contacts are never told you type), … |
159
159
  | `status` | The profile, its chats, whether WebRTC and calls run, the last event seq |
160
- | `store sign <index> --key <file> [--out <dir>]` | Sign a store index (WISP 1200 · Stores) with the store key file (kept offline; made when missing and the index names no `key`): writes `ghostly-store.json`, canonical, and `ghostly-store.sig` beside the index or in `--out`. Refused, with the reason, when a client would refuse the index (`expires` more than 90 days ahead, a bad field), or when the index already there, signed by the same key, has a higher `sequence` (`rollback`) or the same one with other content (`equivocation`) |
160
+ | `store sign <index> --key <file> [--out <dir>]` | Sign a store index (WISP 1200 · Stores) with the store key file (kept offline; made when missing, the index names no `key` and no signed index is in the output folder): writes `ghostly-store.json`, canonical, and `ghostly-store.sig` beside the index or in `--out`. Refused, with the reason, when a client would refuse the index (`expires` more than 90 days ahead, a bad field), or when the index already there has a higher `sequence` (`rollback`), the same one with other content (`equivocation`), or is signed by another key (`store-key`: another store is another `--out`) |
161
161
  | `task send <chat\|group> --title <title> [--status s] [--progress n \| --steps a/b] [--step "…"] [--item state:text]… [--pr-url url --pr-number n --additions n --deletions n --files n --pr-state s --pr-checks c] [--branch b] [--tag label]… [--parent task] [--link url]… [--id id] [--json json\|-\|file] [--force]`, `task update <chat\|group> <task> [fields…]` | A task card for a bot's work (WISP 405 · Status Cards): a title, a status (queued, running, blocked, done, failed, cancelled), progress, the step it is on, up to 20 steps or log lines (`--item done:Codec`; a lowercase word before the colon must be an item state: pending, running, done, failed or skipped) and its pull request, shown as a small card that apps without cards read as text. Optional, and ignored by apps from before them: where the pull request stands (`--pr-state draft`, `open`, `merged` or `closed`; an open one puts the task in the board's Review column) and its checks (`--pr-checks passing`, `failing` or `pending`), up to 3 tags of 24 characters (`--tag core --tag web`; an update's tags replace the card's), and `--parent <task>`, a task of yours in the same chat that this one is a part of (sent first; its parts stack under it on the board). `send` prints the task's id and message; `update` merges the fields given over the card (an update's `--item` list replaces the card's: give all of them) and edits its message, at most once per 2.5 s (sooner updates merge into the next). `--json` takes the whole card; `null` in it removes a field. Only the CLI sends cards: the app shows them |
162
162
  | `typing <chat> [--kind typing\|recording\|thinking] [--status "<text>"] [--for s] [--stop]` | Show the contact you are writing, recording or thinking, or a short status line in its place ("Transcribing your audio…", 40 characters, no links): live chats only, it holds 6 s there; `--for s` keeps it on that long (up to 600 s; a one-shot stays that long once the chat is live, and stops with `live: false` when it is not live within 15 s); a new kind or status shows at once; a message to the chat or `--stop` ends it |
163
163
  | `usage send [<chat\|group>] [--all] --left <percent> [--window "5 h"] [--resets <time>] [--label Claude] [--account <name>] [--also week=80@<time>]… [--used n --limit n] [--id id] [--json json\|-\|file] [--force]` | How much of a quota the bot has left (WISP 405 § Usage): the percent (or `--used` of `--limit`), what it is of, the account, the window, when it resets, and up to 3 other windows (`--also week=80@2026-10-10T00:00:00Z`). The app shows a small meter on the chat's row and in its header, amber under 20%, red under 5%, muted once 6 h old or past the reset. One card per chat: the first report sends it, later ones edit it in place (quiet, paced as `task update`'s); each report is whole, so a field it leaves out goes. `--all` reports to every 1:1 chat with a contact. See [docs/AI-AGENTS.md](../../docs/AI-AGENTS.md#reporting-usage) for where a bot gets its numbers |
164
164
  | `version` | This CLI's version, `{"version":"…"}` (also `ghostly --version`) |
165
- | `wallet create <type> [--network testnet] [--provider id] [--value name=value]… [--stdin] [--invite code] [--api-key key]` | A wallet: `cashu`, `lightning` (a card: its source's form in `--value`), `arkade`, `spark` (on Mainnet, `--api-key` is your Breez API key), `bitcoin` (BDK), `fedimint` (`--invite` is the federation's invite code), `usdt`. With `--stdin`, secret fields come as `name=value` lines on stdin (`api-key=…` too), out of `ps` and the shell's history |
165
+ | `wallet create <type> [--network testnet] [--provider id] [--value name=value]… [--stdin] [--invite code] [--api-key key]` | A wallet: `cashu`, `lightning` (a card: its source's form in `--value`), `arkade`, `spark` (on Mainnet, `--api-key` is your Breez API key), `bitcoin` (BDK), `fedimint` (`--invite` is the federation's invite code), `usdt`. With `--stdin`, secret fields come as `name=value` lines on stdin (`api-key=…` too), out of `ps` and the shell's history; a `--value` the source's form marks secret (an NWC URI, a macaroon, a rune, an RPC password) is warned about |
166
166
  | `wallet faucet <type>`, `wallet add-mint <url> [--primary]` | Test coins; another Cashu mint |
167
167
  | `wallet list [--network n]` | Wallets and balances, and what `wallet create` can make on each network |
168
168
  | `wallet receive <sats>`, `wallet address <type>`, `wallet redeem [<token>]`, `wallet history` | Receive, and what came and went. `wallet redeem` reads the token from stdin when none is given, which keeps it out of `ps` and the shell's history |
@@ -1,14 +1,14 @@
1
- import { Bo as LIMITS, Br as TYPING_REFRESH_MS, Hr as sanitizeTypingStatus, Io as GhostlyHttpError, Ji as identityStatement, Qa as cardLine, Xa as appCardId, Ya as STATUS_CARD_LIMITS, Zn as GROUP_FILE_LIMITS, _a as VOICE_LIMITS, _l as randomBytes$1, at as groupName, ba as parseVoiceMeta, do as appRef, eo as checkStatusCard, fa as readImageMeta, ga as PLAYABLE_AUDIO, gc as inviteLink, ha as sanitizeMime, ho as isAppRef, jo as AWAITING_PEER_MS, m as APP_PERMISSIONS, ma as sanitizeFileName, ml as sanitizeAvatar, oi as APP_DATA_MAX_BYTES, pl as MAX_AVATAR_LENGTH, va as baseMime, vc as ownInviteRefusal, vl as toBase64Url, xa as voicePeaksOf, y as isAppUrl, zr as TYPING_KINDS } from "./src-DDzSERyc.js";
1
+ import { $n as GROUP_FILE_LIMITS, Bo as GhostlyHttpError, Cl as randomBytes$1, Fo as AWAITING_PEER_MS, Hr as TYPING_REFRESH_MS, Qa as appCardId, Sc as ownInviteRefusal, Vr as TYPING_KINDS, Wo as LIMITS, Wr as sanitizeTypingStatus, Xi as identityStatement, Za as STATUS_CARD_LIMITS, _a as VOICE_LIMITS, _l as MAX_AVATAR_LENGTH, _o as isAppRef, at as groupName, ba as parseVoiceMeta, bc as inviteLink, ci as APP_DATA_MAX_BYTES, eo as cardLine, ga as PLAYABLE_AUDIO, ha as sanitizeMime, m as APP_PERMISSIONS, ma as sanitizeFileName, no as checkStatusCard, po as appRef, va as baseMime, vl as sanitizeAvatar, wa as readImageMeta, wl as toBase64Url, xa as voicePeaksOf, y as isAppUrl } from "./src-D7gcJElT.js";
2
2
  import { c as fileStore } from "./idb-B5L8wuOm.js";
3
- import { O as waitingGroupFile, T as removeStored, U as iceServerProblem, W as iceServerSetting, i as groupOfFile, l as replyRef, n as groupFileId, v as paymentNetwork } from "./groupFiles-IEUW3DQz.js";
3
+ import { O as waitingGroupFile, T as removeStored, U as iceServerProblem, W as iceServerSetting, i as groupOfFile, l as replyRef, n as groupFileId, v as paymentNetwork } from "./groupFiles-6qnLCnOm.js";
4
4
  import { f as FILE_BYTES_STEP, o as fileBytes, s as fileBytesOf } from "./fileBytes-Di7VZtmU.js";
5
- import { t as IDENTITY_PROVIDERS } from "./registry-DlAKO0Fy.js";
6
- import { a as waitForEdit, c as findSecret, i as pressable, n as buttonsCard, o as waitForGroupFrame, s as waitForMessage, t as BUTTON_METHODS } from "./buttons-CvNBzh2S.js";
7
- import { i as ENGINE_READS, n as withStoredIds, r as ENGINE_METHODS } from "./turnRefs-B87dtrHM.js";
8
- import { t as CliError } from "./errors-DO5z6h_l.js";
9
- import { _ as str, a as findChat, c as groupOf, d as messageOf, f as node, g as state, h as sentAt, i as chatOrGroup, l as inviteTaken, m as oneOf, n as bool, o as findChatOrGroup, p as num, r as chatOf, s as findGroup, u as list, v as waitForState } from "./apiKit-CA2x5GJF.js";
10
- import { t as BACKUP_METHODS } from "./backup-CnMN2oUK.js";
11
- import { o as checkedBundle } from "./apps-BxV9i_Y4.js";
5
+ import { t as IDENTITY_PROVIDERS } from "./registry-CoLHpxkb.js";
6
+ import { a as waitForEdit, c as findSecret, i as pressable, n as buttonsCard, o as waitForGroupFrame, s as waitForMessage, t as BUTTON_METHODS } from "./buttons-DS1iURp8.js";
7
+ import { i as ENGINE_READS, n as withStoredIds, r as ENGINE_METHODS } from "./turnRefs-PN644M9o.js";
8
+ import { i as localFileError, r as asCliError, t as CliError } from "./errors-B-zMxbcd.js";
9
+ import { _ as str, a as findChat, c as groupOf, d as messageOf, f as node, g as state, h as sentAt, i as chatOrGroup, l as inviteTaken, m as oneOf, n as bool, o as findChatOrGroup, p as num, r as chatOf, s as findGroup, u as list, v as waitForState } from "./apiKit-BsUZPHLn.js";
10
+ import { t as BACKUP_METHODS } from "./backup-Bbz3lyC5.js";
11
+ import { o as checkedBundle } from "./apps-BaUNt-5c.js";
12
12
  import { generateMnemonic } from "@scure/bip39";
13
13
  import { wordlist } from "@scure/bip39/wordlists/english.js";
14
14
  import { openAsBlob, readFileSync, renameSync, writeFileSync } from "node:fs";
@@ -1273,7 +1273,10 @@ async function reportUsage(ctx, ref, fields, params) {
1273
1273
  updated: true
1274
1274
  };
1275
1275
  }
1276
- /** `usage send`: one chat or group, or (`all`) every 1:1 chat with a contact; a chat that refuses is said, not fatal. */
1276
+ /**
1277
+ * `usage send`: one chat or group, or (`all`) every 1:1 chat with a contact; a chat that refuses is said (its
1278
+ * `{code, message}`), not fatal, unless every chat refused: then nothing was reported and the call fails.
1279
+ */
1277
1280
  async function sendUsage(ctx, params) {
1278
1281
  const fields = fieldsOf(params);
1279
1282
  if (!bool(params, "all")) return reportUsage(ctx, str(params, "chat", true), fields, params);
@@ -1284,16 +1287,31 @@ async function sendUsage(ctx, params) {
1284
1287
  kind: "usage",
1285
1288
  id: typeof fields.id === "string" && fields.id ? fields.id : USAGE_CARD_ID
1286
1289
  });
1290
+ oneOf(params, "wait", ["none", "sent"], "none");
1291
+ num(params, "timeout", 30, {
1292
+ min: 1,
1293
+ max: 3600
1294
+ });
1287
1295
  const chats = state(ctx).links.filter((link) => link.profile && link.peerPubKeyZ32);
1288
1296
  const results = [];
1297
+ const failed = [];
1289
1298
  for (const link of chats) try {
1290
1299
  results.push(await reportUsage(ctx, link.id, fields, params));
1291
1300
  } catch (error) {
1301
+ const refusal = {
1302
+ chat: link.id,
1303
+ error: asCliError(error)
1304
+ };
1305
+ failed.push(refusal);
1292
1306
  results.push({
1293
1307
  chat: link.id,
1294
- error: error instanceof Error ? error.message : String(error)
1308
+ error: refusal.error.toJSON()
1295
1309
  });
1296
1310
  }
1311
+ if (failed.length && failed.length === chats.length) {
1312
+ const codes = new Set(failed.map((f) => f.error.code));
1313
+ throw new CliError(codes.size === 1 ? failed[0].error.code : "refused", `No chat took the usage card: ${failed.map((f) => `${f.chat}: ${f.error.message}`).join("; ")}`, { chats: results });
1314
+ }
1297
1315
  return { chats: results };
1298
1316
  }
1299
1317
  var STATUS_CARD_METHODS = {
@@ -1622,7 +1640,15 @@ function findMember(group, ref) {
1622
1640
  const byKey = others.filter((m) => m.key === ref);
1623
1641
  const matches = byKey.length ? byKey : others.filter((m) => m.key.startsWith(ref) || m.nick?.trim().toLowerCase() === lower);
1624
1642
  if (matches.length === 1) return matches[0];
1625
- throw new CliError(matches.length ? "bad_request" : "not_found", matches.length ? `${JSON.stringify(ref)} names more than one member` : `No member ${JSON.stringify(ref)} in ${group.name}`);
1643
+ throw matches.length ? new CliError("bad_request", `${JSON.stringify(ref)} names more than one member`) : noMember(group, ref);
1644
+ }
1645
+ /**
1646
+ * A ref no other member answers to. When it is this profile's own key (or a prefix of it), it says so: "No member"
1647
+ * read as if this profile were not in the group.
1648
+ */
1649
+ function noMember(group, ref) {
1650
+ if (group.members.some((m) => m.me && m.key.startsWith(ref))) return new CliError("bad_request", `${JSON.stringify(ref)} is you: name another member of ${group.name}`);
1651
+ return new CliError("not_found", `No member ${JSON.stringify(ref)} in ${group.name}`);
1626
1652
  }
1627
1653
  /**
1628
1654
  * A picture as Ghostly sends it: a square JPEG data URL, checked as a contact would check it. The app scales and
@@ -1632,8 +1658,8 @@ async function pictureFrom(path) {
1632
1658
  let bytes;
1633
1659
  try {
1634
1660
  bytes = await readFile(resolve(path));
1635
- } catch {
1636
- throw new CliError("not_found", `No file ${path}`);
1661
+ } catch (error) {
1662
+ throw localFileError(error, path) ?? new CliError("not_found", `No file ${path}`);
1637
1663
  }
1638
1664
  const url = `data:image/jpeg;base64,${bytes.toString("base64")}`;
1639
1665
  const checked = sanitizeAvatar(url);
@@ -1894,7 +1920,12 @@ var IDENTITY_METHODS = {
1894
1920
  };
1895
1921
  }
1896
1922
  if (signer.kind === "redirect") throw new CliError("unavailable", `${signer.label} needs a browser window: make this proof in the app`);
1897
- const { draftId, binding } = await begin(ctx, p, str(params, "subject", true), params);
1923
+ const subject = str(params, "subject");
1924
+ if (!subject) {
1925
+ const what = p.subject ? `, its ${p.subject.label.toLowerCase()}${p.subject.placeholder ? ` (${p.subject.placeholder})` : ""}` : "";
1926
+ throw new CliError("bad_request", `${p.label} takes a subject${what}: ghostly identity add ${p.id} <subject>`);
1927
+ }
1928
+ const { draftId, binding } = await begin(ctx, p, subject, params);
1898
1929
  const statement = identityStatement(binding);
1899
1930
  drafts.set(draftId, {
1900
1931
  draftId,
@@ -3123,6 +3154,7 @@ var WALLET_METHODS = Object.fromEntries(Object.entries({
3123
3154
  const nets = params.networks === void 0 ? void 0 : (() => {
3124
3155
  const value = params.networks;
3125
3156
  if (!Array.isArray(value) || !value.every((v) => v === "mainnet" || v === "testnet")) throw new CliError("bad_request", "networks must list mainnet and/or testnet");
3157
+ if (value.length === 0) throw new CliError("bad_request", "networks must list mainnet and/or testnet: to stop taking it, --off");
3126
3158
  return value;
3127
3159
  })();
3128
3160
  await node(ctx).setChatPaymentMethods({
@@ -3249,6 +3281,18 @@ var TRANSPORT_NAMES = {
3249
3281
  "iroh/1": "iroh/1",
3250
3282
  "hyperdht/1": "hyperdht/1"
3251
3283
  };
3284
+ /** The engine keeps a chat's name to 48 UTF-16 units (renameLink). */
3285
+ var CHAT_NAME_UNITS = 48;
3286
+ /**
3287
+ * A chat's name on this side (`chat rename`, an invite's `--label`): what the engine keeps whole, on one line, refused
3288
+ * with the rule as a profile's or a group's name is. Taken as given, the engine cut it at 48 UTF-16 units without a
3289
+ * word (half an emoji left as a lone surrogate) and kept line breaks. Counted as the engine counts: an emoji is two.
3290
+ */
3291
+ function chatName(params, name) {
3292
+ const value = str(params, name);
3293
+ if (value !== void 0 && (value.trim().length > CHAT_NAME_UNITS || /\p{Cc}/u.test(value.trim()))) throw new CliError("bad_request", `A chat's name is up to ${CHAT_NAME_UNITS} characters on one line (an emoji counts as two)`);
3294
+ return value;
3295
+ }
3252
3296
  /** Code points before `index` (UTF-16) in `text`: mentions count code points (WISP 902 § Mentions). */
3253
3297
  var codePoints = (text, index) => [...text.slice(0, index)].length;
3254
3298
  /**
@@ -3266,7 +3310,8 @@ function mentionsFor(text, refs, group) {
3266
3310
  const lower = ref.toLowerCase();
3267
3311
  const byKey = group.members.filter((m) => !m.me && m.key === ref);
3268
3312
  const matches = byKey.length ? byKey : group.members.filter((m) => !m.me && (m.key.startsWith(ref) || m.nick?.trim().toLowerCase() === lower));
3269
- if (matches.length !== 1) throw new CliError(matches.length ? "bad_request" : "not_found", matches.length ? `${JSON.stringify(ref)} names more than one member` : `No member ${JSON.stringify(ref)} in ${group.name}`);
3313
+ if (matches.length > 1) throw new CliError("bad_request", `${JSON.stringify(ref)} names more than one member`);
3314
+ if (!matches.length) throw noMember(group, ref);
3270
3315
  key = matches[0].key;
3271
3316
  spelled = [
3272
3317
  matches[0].nick?.trim(),
@@ -3409,7 +3454,7 @@ var METHODS = {
3409
3454
  };
3410
3455
  },
3411
3456
  async "invite.create"(ctx, params) {
3412
- const label = str(params, "label");
3457
+ const label = chatName(params, "label");
3413
3458
  const { linkId, inviteCode } = await node(ctx).createLink();
3414
3459
  if (label) await node(ctx).renameLink({
3415
3460
  linkId,
@@ -3433,6 +3478,7 @@ var METHODS = {
3433
3478
  },
3434
3479
  async "invite.join"(ctx, params) {
3435
3480
  let invite = str(params, "invite", true).trim();
3481
+ const label = chatName(params, "label");
3436
3482
  const hash = invite.indexOf("#");
3437
3483
  if (/^https?:\/\//i.test(invite) && hash !== -1) invite = invite.slice(hash + 1);
3438
3484
  let linkId;
@@ -3445,7 +3491,6 @@ var METHODS = {
3445
3491
  if (/does not look like/i.test(message)) throw new CliError("bad_request", message);
3446
3492
  throw error;
3447
3493
  }
3448
- const label = str(params, "label");
3449
3494
  if (label) await node(ctx).renameLink({
3450
3495
  linkId,
3451
3496
  label
@@ -3718,7 +3763,7 @@ var METHODS = {
3718
3763
  const link = chatNotGroup(ctx, params, "group rename", "<name...>");
3719
3764
  await node(ctx).renameLink({
3720
3765
  linkId: link.id,
3721
- label: str(params, "name") ?? ""
3766
+ label: chatName(params, "name") ?? ""
3722
3767
  });
3723
3768
  return chatJson(findChat(state(ctx).links, link.id));
3724
3769
  },
@@ -4169,4 +4214,4 @@ async function callApi(ctx, method, params = {}) {
4169
4214
  //#endregion
4170
4215
  export { flushCardUpdates as _, hideStateSecrets as a, paymentJson as c, nodeLocalFetch as d, chatJson as f, resumeHolds as g, groupMessageJson as h, groupSendRefused as i, reportAppFrames as l, groupJson as m, SETTABLE as n, mentionsFor as o, chatMessageJson as p, callApi as r, redactSettings as s, API_METHODS as t, servedApps as u };
4171
4216
 
4172
- //# sourceMappingURL=api-Dq2QXQDk.js.map
4217
+ //# sourceMappingURL=api-B1fADtqd.js.map