@ghostlytools/cli 1.3.0 → 1.3.1
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.
- package/README.md +9 -3
- package/dist/assets/{api-CrxVxtY1.js → api-Dq2QXQDk.js} +141 -40
- package/dist/assets/api-Dq2QXQDk.js.map +1 -0
- package/dist/assets/{api-BiaX6l6f.js → api-TiB5QSG9.js} +2 -2
- package/dist/assets/{apiKit-CajiDxdK.js → apiKit-CA2x5GJF.js} +7 -3
- package/dist/assets/apiKit-CA2x5GJF.js.map +1 -0
- package/dist/assets/{apps-CbfiCSIw.js → apps-BxV9i_Y4.js} +3 -4
- package/dist/assets/{apps-CbfiCSIw.js.map → apps-BxV9i_Y4.js.map} +1 -1
- package/dist/assets/{apps-t23gkc28.js → apps-C-T8jeji.js} +1 -1
- package/dist/assets/{arkade-D4BVJFEQ.js → arkade-C_qDmHnm.js} +2 -2
- package/dist/assets/{arkade-D4BVJFEQ.js.map → arkade-C_qDmHnm.js.map} +1 -1
- package/dist/assets/{backup-Cg52WeQi.js → backup-Bugnn3Ue.js} +1 -1
- package/dist/assets/{backup-BAMi2lAq.js → backup-CnMN2oUK.js} +8 -8
- package/dist/assets/backup-CnMN2oUK.js.map +1 -0
- package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
- package/dist/assets/bitcoind-CZchoh43.js +349 -0
- package/dist/assets/bitcoind-CZchoh43.js.map +1 -0
- package/dist/assets/bitcoindProvider-DGw3xBSa.js +97 -0
- package/dist/assets/bitcoindProvider-DGw3xBSa.js.map +1 -0
- package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
- package/dist/assets/buttons-Bj7Vf7Pn.js +2 -0
- package/dist/assets/{buttons-CX1pKB0j.js → buttons-CvNBzh2S.js} +4 -5
- package/dist/assets/{buttons-CX1pKB0j.js.map → buttons-CvNBzh2S.js.map} +1 -1
- package/dist/assets/{endpoint-Dn7H8qE1.js → endpoint-BOTFMpfS.js} +44 -12
- package/dist/assets/endpoint-BOTFMpfS.js.map +1 -0
- package/dist/assets/{envelope-BvU0fLEt.js → envelope-WjingYSc.js} +2 -2
- package/dist/assets/{envelope-BvU0fLEt.js.map → envelope-WjingYSc.js.map} +1 -1
- package/dist/assets/{errors-my6VJR_0.js → errors-DO5z6h_l.js} +29 -1
- package/dist/assets/errors-DO5z6h_l.js.map +1 -0
- package/dist/assets/groupFiles-IEUW3DQz.js +3202 -0
- package/dist/assets/groupFiles-IEUW3DQz.js.map +1 -0
- package/dist/assets/{host-Cmd205Dc.js → host-CWt-jtqz.js} +41 -16
- package/dist/assets/host-CWt-jtqz.js.map +1 -0
- package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js → hyperdhtRelay-CPYwSLLS.js} +2 -3
- package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js.map → hyperdhtRelay-CPYwSLLS.js.map} +1 -1
- package/dist/assets/{irohWeb--YyEA-hX.js → irohWeb-4oChIOIp.js} +2 -3
- package/dist/assets/{irohWeb--YyEA-hX.js.map → irohWeb-4oChIOIp.js.map} +1 -1
- package/dist/assets/{main-BzNN6bkx.js → main-DbqcdShN.js} +142 -35
- package/dist/assets/main-DbqcdShN.js.map +1 -0
- package/dist/assets/{mainline-D5D5C-jT.js → mainline-ZkiOnevs.js} +2 -2
- package/dist/assets/{mainline-D5D5C-jT.js.map → mainline-ZkiOnevs.js.map} +1 -1
- package/dist/assets/{networkReason-DxZWHw6v.js → networkReason-CetKkTPo.js} +2 -2
- package/dist/assets/{networkReason-DxZWHw6v.js.map → networkReason-CetKkTPo.js.map} +1 -1
- package/dist/assets/nwc-CgkqyvsE.js +612 -0
- package/dist/assets/nwc-CgkqyvsE.js.map +1 -0
- package/dist/assets/nwcProvider-4eVz_8HP.js +77 -0
- package/dist/assets/nwcProvider-4eVz_8HP.js.map +1 -0
- package/dist/assets/{profiles-B6F4oa9K.js → profiles-BHc9pEYg.js} +2 -2
- package/dist/assets/{profiles-B6F4oa9K.js.map → profiles-BHc9pEYg.js.map} +1 -1
- package/dist/assets/registry-DlAKO0Fy.js +3793 -0
- package/dist/assets/registry-DlAKO0Fy.js.map +1 -0
- package/dist/assets/registry-OBJBnHVQ.js +2 -0
- package/dist/assets/resolve-CouHhCpJ.js +591 -0
- package/dist/assets/resolve-CouHhCpJ.js.map +1 -0
- package/dist/assets/{server-CBCEYKMN.js → server-Ss0gJ-nw.js} +184 -1181
- package/dist/assets/server-Ss0gJ-nw.js.map +1 -0
- package/dist/assets/{src-DhfGgAUu.js → src-DDzSERyc.js} +2249 -72
- package/dist/assets/src-DDzSERyc.js.map +1 -0
- package/dist/assets/{turnRefs-ChEfgbSX.js → turnRefs-B87dtrHM.js} +2 -2
- package/dist/assets/{turnRefs-ChEfgbSX.js.map → turnRefs-B87dtrHM.js.map} +1 -1
- package/dist/assets/types-C4_KafL_.js +122 -0
- package/dist/assets/types-C4_KafL_.js.map +1 -0
- package/dist/assets/{usdt-C1Xao2xH.js → usdt-Fj8CQO2z.js} +3 -3
- package/dist/assets/{usdt-C1Xao2xH.js.map → usdt-Fj8CQO2z.js.map} +1 -1
- package/dist/ghostly.mjs +1 -1
- package/package.json +1 -1
- package/dist/assets/api-CrxVxtY1.js.map +0 -1
- package/dist/assets/apiKit-CajiDxdK.js.map +0 -1
- package/dist/assets/backup-BAMi2lAq.js.map +0 -1
- package/dist/assets/buttons-CWl2yNJu.js +0 -2
- package/dist/assets/endpoint-Dn7H8qE1.js.map +0 -1
- package/dist/assets/errors-my6VJR_0.js.map +0 -1
- package/dist/assets/groupFiles-BNTSDaoq.js +0 -7685
- package/dist/assets/groupFiles-BNTSDaoq.js.map +0 -1
- package/dist/assets/groupFiles-DBbjOLuD.js +0 -2088
- package/dist/assets/groupFiles-DBbjOLuD.js.map +0 -1
- package/dist/assets/host-Cmd205Dc.js.map +0 -1
- package/dist/assets/main-BzNN6bkx.js.map +0 -1
- package/dist/assets/server-CBCEYKMN.js.map +0 -1
- package/dist/assets/src-DhfGgAUu.js.map +0 -1
package/README.md
CHANGED
|
@@ -127,7 +127,7 @@ The rows are in alphabetical order of their first command, and every command of
|
|
|
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) |
|
|
130
|
-
| `group leave <group
|
|
130
|
+
| `group leave <group> --yes`, `group forget <group> --yes`, `group accept\|decline <group>` | Membership. Leaving deletes the group's history and files on this device, so it asks for `--yes` (`confirm`, exit 5, without it), as `group forget` does; forget also deletes a group you were removed from, which keeps its history. An admin's leave hands the role to a member who is online and names them: `{left, admin: {key, name}}` |
|
|
131
131
|
| `group list [--show-secret]`, `group show <group> [--show-secret]`, `group history <group>` | Groups, members, history; a message names its author (`member` key, `nick` from the roster, or the name an author no longer in it had). The entry link prints as `<hidden>` without `--show-secret` |
|
|
132
132
|
| `group react <group> <message> <emoji> [--remove]` | React to a group's message. `refused` when the profile is not a member any more |
|
|
133
133
|
| `group rename <group> <name>` | A new name, for the admin (1 to 64 characters on one line); the picture stays. Members see it once it reaches them; until then, and on apps from before names, the group keeps the name it had when they got in |
|
|
@@ -159,7 +159,7 @@ The rows are in alphabetical order of their first command, and every command of
|
|
|
159
159
|
| `status` | The profile, its chats, whether WebRTC and calls run, the last event seq |
|
|
160
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`) |
|
|
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
|
-
| `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); a new kind or status shows at once; a message to the chat or `--stop` ends it |
|
|
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
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 |
|
|
@@ -280,6 +280,8 @@ chat (`chat list`, `chat show`, `chat rename`, …) and `engine getState` print
|
|
|
280
280
|
- A message's `timestamp` is its place in the history, which `chat history` and `group history` are ordered by: when
|
|
281
281
|
this profile sent it, or when it arrived here. A received message also has `sentAt`, the time its sender's clock
|
|
282
282
|
said, which orders nothing (a clock can be wrong).
|
|
283
|
+
- A message's `from` is `me`, `peer`, or `system` for a group's own line in `group history` (`event`: created, joined,
|
|
284
|
+
renamed, …; the stream says it as `group.event`): nobody wrote it, whatever `member` key it carries.
|
|
283
285
|
- `seq` grows by one per event in the profile, across restarts; `id` is the same whenever the same fact is reported.
|
|
284
286
|
Dedupe by `id`; resume with `--since <seq>`, or let `--cursor <file>` remember the last event handled.
|
|
285
287
|
- Types: `daemon.started`, `chat.created`, `chat.removed`, `chat.renamed`, `chat.pairing` (`stage`: publishing,
|
|
@@ -321,7 +323,8 @@ chat (`chat list`, `chat show`, `chat rename`, …) and `engine getState` print
|
|
|
321
323
|
- Pins (WISP 400 § Pinned message): `chat.pinned` (with `chat`) and `group.pinned` (with `group`) when someone else pins
|
|
322
324
|
or unpins: `{messageId, ref, by, removed}`, with `messageId` the message's id here (null when it is not here, or
|
|
323
325
|
unpinned), `ref` the id both sides know it by, and `by` `peer` or a member key.
|
|
324
|
-
- `--type message.received` keeps one type; `--type message
|
|
326
|
+
- `--type message.received` keeps one type; `--type message` (or `message.`, `message.*`) a family. A family deeper
|
|
327
|
+
down ends in "." (`group.typing.`): `group.message` is that type alone.
|
|
325
328
|
- `--from <chat|key>` and `--group <group>` (again for more): an allowlist. Only the chats (id, prefix, name, or the
|
|
326
329
|
contact's `peer` key, which also matches a chat made later) and groups named get through; any other chat's or
|
|
327
330
|
group's event stops before `--exec`, `--webhook` and stdout (the message stays in the chat; the cursor moves past
|
|
@@ -396,6 +399,9 @@ with no framing:
|
|
|
396
399
|
rest of it, then plays padded.
|
|
397
400
|
- Barge-in: `ghostly call flush` (or `call.flush` on the socket API) drops everything queued, at once. Stop writing
|
|
398
401
|
before you flush.
|
|
402
|
+
- A call that ended unanswered (you hung up while it rang, or it rang out) is followed by the next one in that chat
|
|
403
|
+
no sooner than 2 s later: `call start` refuses sooner with `busy` and `details.retryAfterMs`. The contact's app
|
|
404
|
+
would not ring a call that comes that fast after a missed one.
|
|
399
405
|
- The socket exists from `call start`/`call answer`, so the program may connect before the media is up. One program
|
|
400
406
|
at a time: a new connection replaces the old one. When the call ends, the program reads EOF and the socket is
|
|
401
407
|
removed.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Da as AWAITING_PEER_MS, Dr as TYPING_REFRESH_MS, Er as TYPING_KINDS, Fi as identityStatement, Ma as GhostlyHttpError, ac as MAX_AVATAR_LENGTH, at as groupName, kr as sanitizeTypingStatus, ls as inviteLink, m as APP_PERMISSIONS, oc as sanitizeAvatar, qr as APP_DATA_MAX_BYTES, y as isAppUrl } from "./src-DhfGgAUu.js";
|
|
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";
|
|
3
2
|
import { c as fileStore } from "./idb-B5L8wuOm.js";
|
|
4
|
-
import { O as waitingGroupFile, T as removeStored,
|
|
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";
|
|
5
4
|
import { f as FILE_BYTES_STEP, o as fileBytes, s as fileBytesOf } from "./fileBytes-Di7VZtmU.js";
|
|
6
|
-
import {
|
|
7
|
-
import { i as
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
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";
|
|
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";
|
|
@@ -76,6 +76,41 @@ function keepTyping(ctx, target, ms, word = {}) {
|
|
|
76
76
|
map.set(key, end);
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
|
+
/** How long a one-shot's `--for` waits for its chat or group to be live: a fresh one-shot connects first. */
|
|
80
|
+
var ONE_SHOT_LIVE_GRACE_MS = 15e3;
|
|
81
|
+
/**
|
|
82
|
+
* A one-shot's `--for` (it holds the profile while it runs): kept the whole time once the chat or group is live, as a
|
|
83
|
+
* daemon keeps it; ended when it is not live within `ONE_SHOT_LIVE_GRACE_MS`, since nobody would see it, and `--stop`
|
|
84
|
+
* or a send from another shell could not end it.
|
|
85
|
+
*/
|
|
86
|
+
async function keepOneShot(ctx, target, kept, live) {
|
|
87
|
+
let stop = () => {};
|
|
88
|
+
const cameLive = new Promise((resolve) => {
|
|
89
|
+
if (live(state(ctx))) return resolve(true);
|
|
90
|
+
const timer = setTimeout(() => {
|
|
91
|
+
off();
|
|
92
|
+
resolve(false);
|
|
93
|
+
}, ONE_SHOT_LIVE_GRACE_MS);
|
|
94
|
+
const off = ctx.hub.onState((next) => {
|
|
95
|
+
if (live(next)) {
|
|
96
|
+
clearTimeout(timer);
|
|
97
|
+
off();
|
|
98
|
+
resolve(true);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
stop = () => {
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
off();
|
|
104
|
+
resolve(true);
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
if (await Promise.race([kept.then(() => true), cameLive])) {
|
|
108
|
+
stop();
|
|
109
|
+
await kept;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
endTyping(ctx, target, false);
|
|
113
|
+
}
|
|
79
114
|
/** Ends a kept `--for` in the chat or group; `tell`: and says this side stopped. Whether one was kept. */
|
|
80
115
|
function endTyping(ctx, target, tell) {
|
|
81
116
|
const end = keepersOf(ctx).get(keyOf(target));
|
|
@@ -755,9 +790,12 @@ var FILE_METHODS = {
|
|
|
755
790
|
const fileId = str(params, "file", true);
|
|
756
791
|
const { linkId, where } = fileChat(ctx, params, fileId);
|
|
757
792
|
const action = oneOf(params, "action", ACTIONS, "accept");
|
|
793
|
+
const refuseAllHere = async () => {
|
|
794
|
+
if ((state(ctx).transfers[fileId] ?? (await fileStore.get(fileId).catch(() => void 0))?.transfer)?.state === "done") throw new CliError("refused", action === "accept" || action === "request" || action === "resume" ? "Nothing to ask for: the file is all here" : "Nothing to stop: the file is all here");
|
|
795
|
+
};
|
|
758
796
|
if ("group" in where) {
|
|
759
797
|
if (action === "resend") throw new CliError("bad_request", "A group's file is downloaded (file accept) or asked for again (file request)");
|
|
760
|
-
|
|
798
|
+
await refuseAllHere();
|
|
761
799
|
try {
|
|
762
800
|
await node(ctx).fileAction({
|
|
763
801
|
linkId,
|
|
@@ -780,7 +818,7 @@ var FILE_METHODS = {
|
|
|
780
818
|
const transfer = state(ctx).transfers[fileId];
|
|
781
819
|
if (!transfer || transfer.state === "done") throw new CliError("refused", outgoing ? "Nothing to send again: it arrived whole" : "Nothing to ask for: the file is all here");
|
|
782
820
|
if (transfer.state === "failed" && (!outgoing || !transfer.retry)) throw new CliError("refused", `It ended: ${transfer.error ?? "failed"}`);
|
|
783
|
-
}
|
|
821
|
+
} else await refuseAllHere();
|
|
784
822
|
await node(ctx).fileAction({
|
|
785
823
|
linkId,
|
|
786
824
|
fileId,
|
|
@@ -842,7 +880,7 @@ var FILE_METHODS = {
|
|
|
842
880
|
const stored = await fileStore.get(fileId);
|
|
843
881
|
if (!stored?.metadata) throw new CliError("not_found", `No file ${fileId}`);
|
|
844
882
|
if (stored.leftOut) throw new CliError("not_found", "Not in this backup: the light backup this profile was restored from left the file out");
|
|
845
|
-
const transfer = state(ctx).transfers[fileId] ?? stored.transfer;
|
|
883
|
+
const transfer = stored.direction === "out" ? void 0 : state(ctx).transfers[fileId] ?? stored.transfer;
|
|
846
884
|
if (transfer && transfer.state !== "done") throw new CliError("unavailable", `The file is not all here yet (${transfer.transferred} of ${transfer.size} bytes)`);
|
|
847
885
|
const target = str(params, "path") ? resolve(str(params, "path", true)) : join(resolve(str(params, "dir") ?? "."), saveName(stored.metadata.name));
|
|
848
886
|
const flags = bool(params, "force") ? "w" : "wx";
|
|
@@ -850,7 +888,9 @@ var FILE_METHODS = {
|
|
|
850
888
|
try {
|
|
851
889
|
out = await open(target, flags, 384);
|
|
852
890
|
} catch (error) {
|
|
853
|
-
|
|
891
|
+
const code = error.code;
|
|
892
|
+
if ((code === "EEXIST" || code === "EISDIR") && (await stat(target).catch(() => void 0))?.isDirectory()) throw new CliError("bad_request", `${target} is a folder: name a file, or use dir (--dir ${target})`);
|
|
893
|
+
if (code === "EEXIST") throw new CliError("confirm", `${target} exists: pass force (--force) to replace it`);
|
|
854
894
|
throw error;
|
|
855
895
|
}
|
|
856
896
|
let written = 0, complete = false;
|
|
@@ -1431,7 +1471,7 @@ function messageJson(message) {
|
|
|
1431
1471
|
return {
|
|
1432
1472
|
id: message.id,
|
|
1433
1473
|
chat: message.linkId,
|
|
1434
|
-
from: message.sender,
|
|
1474
|
+
from: message.event ? "system" : message.sender,
|
|
1435
1475
|
text: message.text,
|
|
1436
1476
|
timestamp: message.timestamp,
|
|
1437
1477
|
...message.sentAt !== void 0 ? { sentAt: message.sentAt } : {},
|
|
@@ -2613,6 +2653,33 @@ var CHAT_METHODS = [
|
|
|
2613
2653
|
* them as unavailable; creating one is refused before the engine is asked.
|
|
2614
2654
|
*/
|
|
2615
2655
|
var NODE_GAPS = { bark: "Bark's SDK ships a browser build only: its WebAssembly needs a browser window. Use it from the app." };
|
|
2656
|
+
/** Sources the engine lists that live in a browser page (`window.webln`): a headless Ghostly never offers them. */
|
|
2657
|
+
var BROWSER_SOURCES = /* @__PURE__ */ new Set(["webln"]);
|
|
2658
|
+
/** The sources of an offer a headless Ghostly can connect. */
|
|
2659
|
+
var nodeSources = (offer) => offer?.providers?.filter((p) => !BROWSER_SOURCES.has(p.id));
|
|
2660
|
+
/**
|
|
2661
|
+
* What `wallet create` was given checked against the offer's own form, before the engine is asked: a source, a field
|
|
2662
|
+
* or a flag left out, or one the wallet does not take, is the person's value, said naming the flag. Before, the engine
|
|
2663
|
+
* refused it in the app's words ("Enter connection uri", "Nothing was saved; try again") as `engine`, or the flag was
|
|
2664
|
+
* dropped without a word.
|
|
2665
|
+
*/
|
|
2666
|
+
function createChecked(type, n, offer, provider, values, params) {
|
|
2667
|
+
if (type !== "lightning" && type !== "bitcoin") {
|
|
2668
|
+
if (provider || Object.keys(values).length) throw new CliError("usage", `--provider and --value name a lightning or bitcoin source: not with ${type}`);
|
|
2669
|
+
if (offer?.needs === "invite" && !str(params, "invite")?.trim()) throw new CliError("bad_request", `${type} needs --invite <fed11…>`);
|
|
2670
|
+
if (offer?.needs === "apiKey" && !str(params, "apiKey")?.trim()) throw new CliError("bad_request", `${type} on ${n} needs --api-key <key> (or api-key=… on --stdin)`);
|
|
2671
|
+
return;
|
|
2672
|
+
}
|
|
2673
|
+
const sources = nodeSources(offer);
|
|
2674
|
+
if (!sources?.length) return;
|
|
2675
|
+
if (!provider) throw new CliError("bad_request", `${type} needs --provider: one of ${sources.map((p) => p.id).join(", ")} (ghostly wallet list)`);
|
|
2676
|
+
const source = sources.find((p) => p.id === provider);
|
|
2677
|
+
if (!source) throw new CliError("bad_request", `No ${type} source ${JSON.stringify(provider)} on ${n} (ghostly wallet list)`);
|
|
2678
|
+
const unknown = Object.keys(values).find((name) => !source.fields.some((f) => f.name === name));
|
|
2679
|
+
if (unknown) throw new CliError("bad_request", `${provider} takes no --value ${unknown}: ${source.fields.length ? `its fields are ${source.fields.map((f) => f.name).join(", ")}` : "it has no fields"}`);
|
|
2680
|
+
const missing = source.fields.find((f) => !f.optional && !f.defaults?.[n] && !values[f.name]?.trim());
|
|
2681
|
+
if (missing) throw new CliError("bad_request", `${provider} needs --value ${missing.name}=<${missing.options?.map((o) => o.value).join("|") ?? missing.placeholder ?? missing.label.toLowerCase()}>`);
|
|
2682
|
+
}
|
|
2616
2683
|
var network = (params, fallback = "testnet") => oneOf(params, "network", NETWORKS, fallback);
|
|
2617
2684
|
/** `confirmedReal` for the engine: only when the caller confirmed real money, and only then. */
|
|
2618
2685
|
var real = (params) => bool(params, "confirmReal") ? { confirmedReal: true } : {};
|
|
@@ -2775,7 +2842,7 @@ var WALLET_METHODS = Object.fromEntries(Object.entries({
|
|
|
2775
2842
|
exists: !!o.exists,
|
|
2776
2843
|
several: !!o.several,
|
|
2777
2844
|
needs: o.needs ?? null,
|
|
2778
|
-
...o.providers ? { providers: o.
|
|
2845
|
+
...o.providers ? { providers: nodeSources(o).map((p) => ({
|
|
2779
2846
|
id: p.id,
|
|
2780
2847
|
label: p.label,
|
|
2781
2848
|
fields: p.fields.map((f) => ({
|
|
@@ -2799,14 +2866,13 @@ var WALLET_METHODS = Object.fromEntries(Object.entries({
|
|
|
2799
2866
|
const raw = params.values ?? {};
|
|
2800
2867
|
if (!raw || typeof raw !== "object" || Array.isArray(raw) || Object.values(raw).some((v) => typeof v !== "string")) throw new CliError("bad_request", "values must be an object of strings");
|
|
2801
2868
|
Object.assign(values, raw);
|
|
2802
|
-
const provider = str(params, "provider");
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
if (type === "bitcoin" && (provider ?? "bdk") === "bdk" && !values.mnemonic) values.mnemonic = generateMnemonic(wordlist, 128);
|
|
2869
|
+
const provider = str(params, "provider") ?? (type === "bitcoin" ? "bdk" : void 0);
|
|
2870
|
+
if (type === "bitcoin" && provider === "bdk" && !values.mnemonic) values.mnemonic = generateMnemonic(wordlist, 128);
|
|
2871
|
+
createChecked(type, n, state(ctx).wallet.offers?.find((o) => o.type === type && o.network === n), provider, values, params);
|
|
2806
2872
|
return walletJson(await node(ctx).walletCreate({
|
|
2807
2873
|
type,
|
|
2808
2874
|
network: n,
|
|
2809
|
-
...provider ? { providerId: provider } :
|
|
2875
|
+
...provider ? { providerId: provider } : {},
|
|
2810
2876
|
...Object.keys(values).length ? { values } : {},
|
|
2811
2877
|
...str(params, "invite") ? { invite: str(params, "invite") } : {},
|
|
2812
2878
|
...str(params, "apiKey") ? { apiKey: str(params, "apiKey") } : {}
|
|
@@ -3159,6 +3225,20 @@ var SETTABLE = {
|
|
|
3159
3225
|
autoDownloads: "boolean",
|
|
3160
3226
|
serveFiles: "boolean"
|
|
3161
3227
|
};
|
|
3228
|
+
/**
|
|
3229
|
+
* A relay address the engine refuses (node.ts `updateSettings`, irohWeb.ts, hyperdhtRelay.ts): the person's value, so
|
|
3230
|
+
* `bad_request` named by its key, in a terminal's words ("give", not the app's "Enter"). Before, `engine` (exit 1). A
|
|
3231
|
+
* wrong scheme ("Use an https:// relay address") too.
|
|
3232
|
+
*/
|
|
3233
|
+
function relayRefused(error, patch) {
|
|
3234
|
+
if (!(error instanceof Error) || !/^(?:Enter (?:at least one|a) relay address|Not a relay address|Use an? \S+ relay address)\b/.test(error.message)) return error;
|
|
3235
|
+
const keys = [
|
|
3236
|
+
"relays",
|
|
3237
|
+
"irohRelays",
|
|
3238
|
+
"hyperdhtRelay"
|
|
3239
|
+
].filter((key) => Object.hasOwn(patch, key));
|
|
3240
|
+
return new CliError("bad_request", `${keys.join(", ") || "relays"}: ${error.message.replace(/^Enter /, "give ").replace(/^(Not|Use) /, (word) => word.toLowerCase())}`);
|
|
3241
|
+
}
|
|
3162
3242
|
var TRANSPORT_NAMES = {
|
|
3163
3243
|
auto: "auto",
|
|
3164
3244
|
dht: "dht",
|
|
@@ -3320,7 +3400,9 @@ var METHODS = {
|
|
|
3320
3400
|
}
|
|
3321
3401
|
patch[key] = value;
|
|
3322
3402
|
}
|
|
3323
|
-
await node(ctx).updateSettings({ settings: patch })
|
|
3403
|
+
await node(ctx).updateSettings({ settings: patch }).catch((error) => {
|
|
3404
|
+
throw relayRefused(error, patch);
|
|
3405
|
+
});
|
|
3324
3406
|
return {
|
|
3325
3407
|
...redactSettings(state(ctx).settings),
|
|
3326
3408
|
...warning ? { warning } : {}
|
|
@@ -3357,8 +3439,9 @@ var METHODS = {
|
|
|
3357
3439
|
try {
|
|
3358
3440
|
({linkId} = await node(ctx).joinLink({ inviteCode: invite }));
|
|
3359
3441
|
} catch (error) {
|
|
3442
|
+
const own = ownInviteRefusal(error);
|
|
3443
|
+
if (own) throw new CliError("refused", "This is your own invite. Share it with a contact; they join with it.", own.linkId ? { chat: own.linkId } : void 0);
|
|
3360
3444
|
const message = error instanceof Error ? error.message : String(error);
|
|
3361
|
-
if (/own invite/i.test(message)) throw new CliError("refused", message);
|
|
3362
3445
|
if (/does not look like/i.test(message)) throw new CliError("bad_request", message);
|
|
3363
3446
|
throw error;
|
|
3364
3447
|
}
|
|
@@ -3375,18 +3458,18 @@ var METHODS = {
|
|
|
3375
3458
|
return { chats: state(ctx).links.map((link) => chatJson(link, showSecret)).sort((a, b) => b.lastMessageAt - a.lastMessageAt || b.createdAt - a.createdAt) };
|
|
3376
3459
|
},
|
|
3377
3460
|
async "chat.get"(ctx, params) {
|
|
3378
|
-
const link =
|
|
3461
|
+
const link = chatNotGroup(ctx, params, "group show");
|
|
3379
3462
|
return {
|
|
3380
3463
|
...chatDetailsJson(link, bool(params, "showSecret")),
|
|
3381
3464
|
heldUntil: holdOf(ctx, link.id)
|
|
3382
3465
|
};
|
|
3383
3466
|
},
|
|
3384
3467
|
async "chat.history"(ctx, params) {
|
|
3385
|
-
const link =
|
|
3468
|
+
const link = chatNotGroup(ctx, params, "group history");
|
|
3386
3469
|
return historyOf(ctx, link.id, params, (m) => chatMessageJson(m, link));
|
|
3387
3470
|
},
|
|
3388
3471
|
async "chat.send"(ctx, params) {
|
|
3389
|
-
const link =
|
|
3472
|
+
const link = chatNotGroup(ctx, params, "group send", "[text...]");
|
|
3390
3473
|
const text = str(params, "text", true);
|
|
3391
3474
|
checkTextSize(text);
|
|
3392
3475
|
if (!bool(params, "force")) {
|
|
@@ -3430,7 +3513,7 @@ var METHODS = {
|
|
|
3430
3513
|
* once the chat is live and both apps offer edit/1; `--wait confirmed` waits for the contact to confirm it.
|
|
3431
3514
|
*/
|
|
3432
3515
|
async "chat.edit"(ctx, params) {
|
|
3433
|
-
const link =
|
|
3516
|
+
const link = chatNotGroup(ctx, params, "group edit", "<message> <text...>");
|
|
3434
3517
|
const messageId = str(params, "message", true);
|
|
3435
3518
|
const text = str(params, "text", true);
|
|
3436
3519
|
if (!bool(params, "force")) {
|
|
@@ -3461,10 +3544,11 @@ var METHODS = {
|
|
|
3461
3544
|
* WISP 401 § Typing: said on the live session only, and a start only while `sendTyping` is on; it holds 6 s there.
|
|
3462
3545
|
* `kind` (typing, recording, thinking) and `status` (a short line, e.g. "Transcribing your audio…") say what this
|
|
3463
3546
|
* side is doing; a new one reaches the contact at once.
|
|
3464
|
-
* `for`: kept that many seconds (a one-shot stays that long
|
|
3547
|
+
* `for`: kept that many seconds (a one-shot stays that long once the chat is live, and stops when it is not live
|
|
3548
|
+
* within `ONE_SHOT_LIVE_GRACE_MS`), until a message to the chat or a stop.
|
|
3465
3549
|
*/
|
|
3466
3550
|
async "chat.typing"(ctx, params) {
|
|
3467
|
-
const link =
|
|
3551
|
+
const link = chatNotGroup(ctx, params, "group typing");
|
|
3468
3552
|
const typing = !bool(params, "stop");
|
|
3469
3553
|
const { kind, status } = typingWord(params, typing);
|
|
3470
3554
|
const word = {
|
|
@@ -3484,20 +3568,21 @@ var METHODS = {
|
|
|
3484
3568
|
});
|
|
3485
3569
|
const until = seconds ? Date.now() + seconds * 1e3 : null;
|
|
3486
3570
|
const kept = seconds ? keepTyping(ctx, { linkId: link.id }, seconds * 1e3, word) : null;
|
|
3487
|
-
|
|
3571
|
+
const open = (s) => s.links.find((l) => l.id === link.id)?.dataLink === "open";
|
|
3572
|
+
if (kept && ctx.mode === "one-shot") await keepOneShot(ctx, { linkId: link.id }, kept, open);
|
|
3488
3573
|
return {
|
|
3489
3574
|
chat: link.id,
|
|
3490
3575
|
typing,
|
|
3491
3576
|
...typing ? { kind } : {},
|
|
3492
3577
|
...status ? { status } : {},
|
|
3493
|
-
live:
|
|
3578
|
+
live: open(state(ctx)),
|
|
3494
3579
|
sendTyping: state(ctx).settings.sendTyping !== false,
|
|
3495
3580
|
...until ? { until } : {}
|
|
3496
3581
|
};
|
|
3497
3582
|
},
|
|
3498
3583
|
/** WISP 400 § Reactions: one emoji per person per message, a new one replaces mine; `remove` takes mine back. */
|
|
3499
3584
|
async "chat.react"(ctx, params) {
|
|
3500
|
-
const link =
|
|
3585
|
+
const link = chatNotGroup(ctx, params, "group react", "<message> <emoji>");
|
|
3501
3586
|
return {
|
|
3502
3587
|
chat: link.id,
|
|
3503
3588
|
...await react(ctx, link.id, params)
|
|
@@ -3594,7 +3679,10 @@ var METHODS = {
|
|
|
3594
3679
|
async "chat.retry"(ctx, params) {
|
|
3595
3680
|
const link = chatOf(ctx, params);
|
|
3596
3681
|
const messageId = str(params, "message", true);
|
|
3597
|
-
|
|
3682
|
+
const message = await node(ctx).getMessage(link.id, messageId);
|
|
3683
|
+
if (!message) throw new CliError("not_found", `No message ${messageId} in this chat`);
|
|
3684
|
+
if (message.sender !== "me") throw new CliError("not_found", `No message ${messageId} of yours in this chat: only your own messages are sent again`);
|
|
3685
|
+
if (message.delivery === "delivered") throw new CliError("refused", "Nothing to send again: it was delivered");
|
|
3598
3686
|
await node(ctx).retryMessage({
|
|
3599
3687
|
linkId: link.id,
|
|
3600
3688
|
messageId
|
|
@@ -3627,7 +3715,7 @@ var METHODS = {
|
|
|
3627
3715
|
return details;
|
|
3628
3716
|
},
|
|
3629
3717
|
async "chat.rename"(ctx, params) {
|
|
3630
|
-
const link =
|
|
3718
|
+
const link = chatNotGroup(ctx, params, "group rename", "<name...>");
|
|
3631
3719
|
await node(ctx).renameLink({
|
|
3632
3720
|
linkId: link.id,
|
|
3633
3721
|
label: str(params, "name") ?? ""
|
|
@@ -3635,7 +3723,7 @@ var METHODS = {
|
|
|
3635
3723
|
return chatJson(findChat(state(ctx).links, link.id));
|
|
3636
3724
|
},
|
|
3637
3725
|
async "chat.remove"(ctx, params) {
|
|
3638
|
-
const link =
|
|
3726
|
+
const link = chatNotGroup(ctx, params, "group forget", "--yes");
|
|
3639
3727
|
if (!bool(params, "yes")) throw new CliError("confirm", "Removing a chat deletes its keys and history on this device; pass yes (--yes) to go on");
|
|
3640
3728
|
await node(ctx).removeLink({ linkId: link.id });
|
|
3641
3729
|
return { removed: link.id };
|
|
@@ -3866,21 +3954,30 @@ var METHODS = {
|
|
|
3866
3954
|
sayTyping(ctx, target, typing, word);
|
|
3867
3955
|
const until = seconds ? Date.now() + seconds * 1e3 : null;
|
|
3868
3956
|
const kept = seconds ? keepTyping(ctx, target, seconds * 1e3, word) : null;
|
|
3869
|
-
|
|
3957
|
+
const reached = (s) => (s.groups.find((g) => g.id === group.id) ?? group).members.filter((m) => !m.me && m.online && !m.viaHub).length;
|
|
3958
|
+
if (kept && ctx.mode === "one-shot") await keepOneShot(ctx, target, kept, (s) => reached(s) > 0);
|
|
3870
3959
|
return {
|
|
3871
3960
|
group: group.id,
|
|
3872
3961
|
typing,
|
|
3873
3962
|
...typing ? { kind } : {},
|
|
3874
3963
|
...status ? { status } : {},
|
|
3875
|
-
reached:
|
|
3964
|
+
reached: reached(state(ctx)),
|
|
3876
3965
|
sendTyping: state(ctx).settings.sendTyping !== false,
|
|
3877
3966
|
...until ? { until } : {}
|
|
3878
3967
|
};
|
|
3879
3968
|
},
|
|
3880
3969
|
async "group.leave"(ctx, params) {
|
|
3881
3970
|
const group = groupOf(ctx, params);
|
|
3882
|
-
|
|
3883
|
-
|
|
3971
|
+
if (!bool(params, "yes")) throw new CliError("confirm", "Leaving a group deletes its history and files on this device; pass yes (--yes) to go on");
|
|
3972
|
+
const { admin } = await node(ctx).leaveGroup({ groupId: group.id });
|
|
3973
|
+
const next = admin ? group.members.find((m) => m.key === admin) : void 0;
|
|
3974
|
+
return {
|
|
3975
|
+
left: group.id,
|
|
3976
|
+
...admin ? { admin: {
|
|
3977
|
+
key: admin,
|
|
3978
|
+
name: next?.nick ?? null
|
|
3979
|
+
} } : {}
|
|
3980
|
+
};
|
|
3884
3981
|
},
|
|
3885
3982
|
async "group.forget"(ctx, params) {
|
|
3886
3983
|
const group = groupOf(ctx, params);
|
|
@@ -3992,9 +4089,13 @@ function notAGroup(ctx, params, chat, command, rest) {
|
|
|
3992
4089
|
throw error;
|
|
3993
4090
|
}
|
|
3994
4091
|
const shown = /^[\w.:-]+$/.test(ref) ? ref : JSON.stringify(ref);
|
|
3995
|
-
throw new CliError("not_found", `${JSON.stringify(ref)} is a group, not a chat: ghostly ${command} ${shown} ${rest}`, { group: group.id });
|
|
4092
|
+
throw new CliError("not_found", `${JSON.stringify(ref)} is a group, not a chat: ghostly ${command} ${shown}${rest ? ` ${rest}` : ""}`, { group: group.id });
|
|
3996
4093
|
}
|
|
3997
4094
|
}
|
|
4095
|
+
/** The chat a chat command with a group twin names: a group's name says so, with `command`, the twin (see `notAGroup`). */
|
|
4096
|
+
function chatNotGroup(ctx, params, command, rest = "") {
|
|
4097
|
+
return notAGroup(ctx, params, () => chatOf(ctx, params), command, rest);
|
|
4098
|
+
}
|
|
3998
4099
|
/**
|
|
3999
4100
|
* An edit the engine refused. A message that is not in the chat or group at all is `not_found` (exit 3), naming the id
|
|
4000
4101
|
* to give, as `button update` and `task update` do; before, a mistyped or lost id read "Only your own text messages
|
|
@@ -4068,4 +4169,4 @@ async function callApi(ctx, method, params = {}) {
|
|
|
4068
4169
|
//#endregion
|
|
4069
4170
|
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 };
|
|
4070
4171
|
|
|
4071
|
-
//# sourceMappingURL=api-
|
|
4172
|
+
//# sourceMappingURL=api-Dq2QXQDk.js.map
|