@ghostlytools/cli 1.2.4 → 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.
Files changed (100) hide show
  1. package/README.md +25 -9
  2. package/dist/assets/api-Dq2QXQDk.js +4172 -0
  3. package/dist/assets/api-Dq2QXQDk.js.map +1 -0
  4. package/dist/assets/api-TiB5QSG9.js +3 -0
  5. package/dist/assets/apiKit-CA2x5GJF.js +200 -0
  6. package/dist/assets/apiKit-CA2x5GJF.js.map +1 -0
  7. package/dist/assets/apps-BxV9i_Y4.js +786 -0
  8. package/dist/assets/apps-BxV9i_Y4.js.map +1 -0
  9. package/dist/assets/apps-C-T8jeji.js +2 -0
  10. package/dist/assets/{arkade-CdnKGDbg.js → arkade-C_qDmHnm.js} +2 -2
  11. package/dist/assets/{arkade-CdnKGDbg.js.map → arkade-C_qDmHnm.js.map} +1 -1
  12. package/dist/assets/backup-Bugnn3Ue.js +2 -0
  13. package/dist/assets/backup-CnMN2oUK.js +731 -0
  14. package/dist/assets/backup-CnMN2oUK.js.map +1 -0
  15. package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
  16. package/dist/assets/bitcoind-CZchoh43.js +349 -0
  17. package/dist/assets/bitcoind-CZchoh43.js.map +1 -0
  18. package/dist/assets/bitcoindProvider-DGw3xBSa.js +97 -0
  19. package/dist/assets/bitcoindProvider-DGw3xBSa.js.map +1 -0
  20. package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
  21. package/dist/assets/buttons-Bj7Vf7Pn.js +2 -0
  22. package/dist/assets/buttons-CvNBzh2S.js +496 -0
  23. package/dist/assets/buttons-CvNBzh2S.js.map +1 -0
  24. package/dist/assets/{endpoint-pgFkR2AS.js → endpoint-BOTFMpfS.js} +56 -18
  25. package/dist/assets/endpoint-BOTFMpfS.js.map +1 -0
  26. package/dist/assets/envelope-WjingYSc.js +90 -0
  27. package/dist/assets/envelope-WjingYSc.js.map +1 -0
  28. package/dist/assets/errors-DO5z6h_l.js +105 -0
  29. package/dist/assets/errors-DO5z6h_l.js.map +1 -0
  30. package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
  31. package/dist/assets/{fileBytes-Dbifu5I5.js → fileBytes-CfSXjTFa.js} +2 -2
  32. package/dist/assets/{fileBytes-Dbifu5I5.js.map → fileBytes-CfSXjTFa.js.map} +1 -1
  33. package/dist/assets/fileBytes-CgbibpbV.js +2 -0
  34. package/dist/assets/{fileBytes-BcYZhNgF.js → fileBytes-Di7VZtmU.js} +6 -19
  35. package/dist/assets/fileBytes-Di7VZtmU.js.map +1 -0
  36. package/dist/assets/fileBytesIdb-CFP-DSkZ.js +2 -0
  37. package/dist/assets/{fileBytesIdb-DzSaqI0A.js → fileBytesIdb-D5u6H8X_.js} +4 -4
  38. package/dist/assets/{fileBytesIdb-DzSaqI0A.js.map → fileBytesIdb-D5u6H8X_.js.map} +1 -1
  39. package/dist/assets/{fileBytesOpfs-Dx8RZiV5.js → fileBytesOpfs-Dd1N4ERH.js} +3 -3
  40. package/dist/assets/{fileBytesOpfs-Dx8RZiV5.js.map → fileBytesOpfs-Dd1N4ERH.js.map} +1 -1
  41. package/dist/assets/groupFiles-IEUW3DQz.js +3202 -0
  42. package/dist/assets/groupFiles-IEUW3DQz.js.map +1 -0
  43. package/dist/assets/host-CWt-jtqz.js +4673 -0
  44. package/dist/assets/host-CWt-jtqz.js.map +1 -0
  45. package/dist/assets/hyperdhtRelay-BT_wRxab.js +21 -0
  46. package/dist/assets/hyperdhtRelay-BT_wRxab.js.map +1 -0
  47. package/dist/assets/{hyperdhtRelay-C1p0nkle.js → hyperdhtRelay-CPYwSLLS.js} +25 -10
  48. package/dist/assets/hyperdhtRelay-CPYwSLLS.js.map +1 -0
  49. package/dist/assets/{idb-CTF6LF5B.js → idb-B5L8wuOm.js} +7 -5
  50. package/dist/assets/idb-B5L8wuOm.js.map +1 -0
  51. package/dist/assets/irohWeb-4oChIOIp.js +544 -0
  52. package/dist/assets/irohWeb-4oChIOIp.js.map +1 -0
  53. package/dist/assets/{joinTrace-CO8mlhNO.js → joinTrace-CjRDFy85.js} +2 -7
  54. package/dist/assets/{joinTrace-CO8mlhNO.js.map → joinTrace-CjRDFy85.js.map} +1 -1
  55. package/dist/assets/joinTrace-D-fnt2p3.js +2 -0
  56. package/dist/assets/main-DbqcdShN.js +4409 -0
  57. package/dist/assets/main-DbqcdShN.js.map +1 -0
  58. package/dist/assets/{mainline-BH6s8HVi.js → mainline-ZkiOnevs.js} +11 -6
  59. package/dist/assets/mainline-ZkiOnevs.js.map +1 -0
  60. package/dist/assets/mints-BrlY773j.js +74 -0
  61. package/dist/assets/mints-BrlY773j.js.map +1 -0
  62. package/dist/assets/mints-CqTdA4Gk.js +2 -0
  63. package/dist/assets/networkReason-CetKkTPo.js +154 -0
  64. package/dist/assets/networkReason-CetKkTPo.js.map +1 -0
  65. package/dist/assets/nwc-CgkqyvsE.js +612 -0
  66. package/dist/assets/nwc-CgkqyvsE.js.map +1 -0
  67. package/dist/assets/nwcProvider-4eVz_8HP.js +77 -0
  68. package/dist/assets/nwcProvider-4eVz_8HP.js.map +1 -0
  69. package/dist/assets/privateFolder-Bk9C9oFv.js +38 -0
  70. package/dist/assets/privateFolder-Bk9C9oFv.js.map +1 -0
  71. package/dist/assets/profiles-BHc9pEYg.js +139 -0
  72. package/dist/assets/profiles-BHc9pEYg.js.map +1 -0
  73. package/dist/assets/registry-DlAKO0Fy.js +3793 -0
  74. package/dist/assets/registry-DlAKO0Fy.js.map +1 -0
  75. package/dist/assets/registry-OBJBnHVQ.js +2 -0
  76. package/dist/assets/resolve-CouHhCpJ.js +591 -0
  77. package/dist/assets/resolve-CouHhCpJ.js.map +1 -0
  78. package/dist/assets/{restoredRows-C_84q0Pd.js → restoredRows-Db3oUTko.js} +2 -2
  79. package/dist/assets/{restoredRows-C_84q0Pd.js.map → restoredRows-Db3oUTko.js.map} +1 -1
  80. package/dist/assets/{server-0zfNpABQ.js → server-Ss0gJ-nw.js} +1275 -1404
  81. package/dist/assets/server-Ss0gJ-nw.js.map +1 -0
  82. package/dist/assets/{src-CbLkHfz8.js → src-DDzSERyc.js} +882 -134
  83. package/dist/assets/src-DDzSERyc.js.map +1 -0
  84. package/dist/assets/turnRefs-B87dtrHM.js +323 -0
  85. package/dist/assets/turnRefs-B87dtrHM.js.map +1 -0
  86. package/dist/assets/types-C4_KafL_.js +122 -0
  87. package/dist/assets/types-C4_KafL_.js.map +1 -0
  88. package/dist/assets/{usdt-Qf4UuygS.js → usdt-Fj8CQO2z.js} +3 -3
  89. package/dist/assets/{usdt-Qf4UuygS.js.map → usdt-Fj8CQO2z.js.map} +1 -1
  90. package/dist/ghostly.mjs +4 -22077
  91. package/dist/ghostly.mjs.map +1 -1
  92. package/package.json +1 -1
  93. package/dist/assets/endpoint-pgFkR2AS.js.map +0 -1
  94. package/dist/assets/fileBytes-BcYZhNgF.js.map +0 -1
  95. package/dist/assets/hyperdhtRelay-C1p0nkle.js.map +0 -1
  96. package/dist/assets/idb-CTF6LF5B.js.map +0 -1
  97. package/dist/assets/mainline-BH6s8HVi.js.map +0 -1
  98. package/dist/assets/rolldown-runtime-8H4AJuhK.js +0 -14
  99. package/dist/assets/server-0zfNpABQ.js.map +0 -1
  100. package/dist/assets/src-CbLkHfz8.js.map +0 -1
@@ -0,0 +1,3 @@
1
+ import { a as findChat, s as findGroup } from "./apiKit-CA2x5GJF.js";
2
+ import { a as hideStateSecrets, i as groupSendRefused, n as SETTABLE, o as mentionsFor, r as callApi, s as redactSettings, t as API_METHODS } from "./api-Dq2QXQDk.js";
3
+ export { API_METHODS, SETTABLE, callApi, findChat, findGroup, groupSendRefused, hideStateSecrets, mentionsFor, redactSettings };
@@ -0,0 +1,200 @@
1
+ import { Wn as INVITE_TAKEN } from "./src-DDzSERyc.js";
2
+ import { t as CliError } from "./errors-DO5z6h_l.js";
3
+ //#region src/apiKit.ts
4
+ var node = (ctx) => ctx.runtime.server.node;
5
+ var state = (ctx) => node(ctx).getState();
6
+ /**
7
+ * The time of a send the engine names its row after (`me_<timestamp>`: a file, a payment, a request), its own even
8
+ * within one millisecond (a bot paying or sending several times at once over the socket, `file send` run in
9
+ * parallel), or the chat keeps only one. One clock for all of them: they share the row names. A file's resend after a
10
+ * drop finds its row by it, as a forward does.
11
+ */
12
+ var lastSentAt = 0;
13
+ var sentAt = () => lastSentAt = Math.max(Date.now(), lastSentAt + 1);
14
+ function str(params, name, required = false) {
15
+ const value = params[name];
16
+ if (value === void 0 || value === null || value === "") {
17
+ if (required) throw new CliError("bad_request", `${name} is required`);
18
+ return;
19
+ }
20
+ if (typeof value !== "string") throw new CliError("bad_request", `${name} must be a string`);
21
+ return value;
22
+ }
23
+ /** A number in `[min, max]`; `whole` takes only whole ones (a count of messages), as the engine needs them. */
24
+ function num(params, name, fallback, { min = 0, max = Number.MAX_SAFE_INTEGER, whole = false } = {}) {
25
+ const value = params[name];
26
+ if (value === void 0 || value === null) return fallback;
27
+ if (typeof value !== "number" || !Number.isFinite(value) || value < min || value > max) throw new CliError("bad_request", `${name} must be a ${whole ? "whole " : ""}number from ${min} to ${max}`);
28
+ if (whole && !Number.isSafeInteger(value)) throw new CliError("bad_request", `${name} must be a whole number from ${min} to ${max}`);
29
+ return value;
30
+ }
31
+ function bool(params, name) {
32
+ const value = params[name];
33
+ if (value === void 0 || value === null) return false;
34
+ if (typeof value !== "boolean") throw new CliError("bad_request", `${name} must be true or false`);
35
+ return value;
36
+ }
37
+ function list(params, name) {
38
+ const value = params[name];
39
+ if (value === void 0 || value === null) return [];
40
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new CliError("bad_request", `${name} must be a list of strings`);
41
+ return value;
42
+ }
43
+ function oneOf(params, name, values, fallback) {
44
+ const value = str(params, name) ?? fallback;
45
+ if (!values.includes(value)) throw new CliError("bad_request", `${name} must be one of ${values.join(", ")}`);
46
+ return value;
47
+ }
48
+ /** A contact key as chats show it (`peer`): 52 characters of z-base-32. */
49
+ var CONTACT_KEY = /^[ybndrfg8ejkmcpqxot1uwisza345h769]{52}$/;
50
+ /**
51
+ * A chat by its id; by its contact's key (only by the key: a name is never taken for one); by a unique prefix of its
52
+ * id; or by its name: the label this side gave it first, and only when no label matches, the name the contact gave
53
+ * itself (which the contact controls, so it never wins over a label).
54
+ */
55
+ function findChat(links, ref) {
56
+ return chatBy(links, ref).link;
57
+ }
58
+ /** `findChat`, and whether only the name the contact gave itself (`theirName`) found the chat. */
59
+ function chatBy(links, ref) {
60
+ const exact = links.find((link) => link.id === ref);
61
+ if (exact) return {
62
+ link: exact,
63
+ theirName: false
64
+ };
65
+ if (CONTACT_KEY.test(ref)) {
66
+ const byKey = links.filter((link) => link.peerPubKeyZ32 === ref);
67
+ if (byKey.length === 1) return {
68
+ link: byKey[0],
69
+ theirName: false
70
+ };
71
+ if (byKey.length > 1) throw new CliError("bad_request", `${JSON.stringify(ref)} names more than one chat: use its id`, { matches: byKey.map((link) => link.id) });
72
+ throw new CliError("not_found", `No chat with ${JSON.stringify(ref)}`);
73
+ }
74
+ const byPrefix = links.filter((link) => link.id.startsWith(ref));
75
+ if (byPrefix.length === 1) return {
76
+ link: byPrefix[0],
77
+ theirName: false
78
+ };
79
+ const lower = ref.toLowerCase();
80
+ const named = (name) => name?.trim().toLowerCase() === lower;
81
+ const byLabel = links.filter((link) => named(link.label));
82
+ const byName = byLabel.length ? byLabel : links.filter((link) => named(link.peerNick));
83
+ if (byName.length === 1) return {
84
+ link: byName[0],
85
+ theirName: !byLabel.length
86
+ };
87
+ if (byPrefix.length > 1 || byName.length > 1) throw new CliError("bad_request", `${JSON.stringify(ref)} names more than one chat: use its id`, { matches: [...byPrefix, ...byName].map((link) => link.id) });
88
+ throw new CliError("not_found", `No chat ${JSON.stringify(ref)}`);
89
+ }
90
+ /** Only an invitation so far: this profile is not in the group (yet), and whoever invited chose its name. */
91
+ var invitedOnly = (group) => group.status === void 0 && !!group.invitation;
92
+ /**
93
+ * A group by its id, by a unique prefix of its id, or by its name. A group this profile is in wins over an invitation
94
+ * with the same prefix or name (the one who invites chooses those); an invitation is still found by its id, and by
95
+ * prefix or name where no group of this profile has it.
96
+ */
97
+ function findGroup(groups, ref) {
98
+ const exact = groups.find((group) => group.id === ref);
99
+ if (exact) return exact;
100
+ const mineFirst = (found) => {
101
+ const mine = found.filter((group) => !invitedOnly(group));
102
+ return mine.length ? mine : found;
103
+ };
104
+ const byPrefix = mineFirst(groups.filter((group) => group.id.startsWith(ref)));
105
+ if (byPrefix.length === 1) return byPrefix[0];
106
+ const byName = mineFirst(groups.filter((group) => group.name.trim().toLowerCase() === ref.toLowerCase()));
107
+ if (byName.length === 1) return byName[0];
108
+ if (byPrefix.length > 1 || byName.length > 1) throw new CliError("bad_request", `${JSON.stringify(ref)} names more than one group: use its id`, { matches: [...byPrefix, ...byName].map((group) => group.id) });
109
+ throw new CliError("not_found", `No group ${JSON.stringify(ref)}`);
110
+ }
111
+ /**
112
+ * A chat or a group, where a command takes either: `group:<id>` is a group; anything else a chat first (`findChat`),
113
+ * then a group. A chat found only by the name its contact gave itself never takes what also names a group: a group's
114
+ * own id is that group, and a prefix or the name of a group this profile is in is refused, with both ids. With neither,
115
+ * one `not_found` names both and where to look (a contact's key: no chat with it); a name that matches more than one
116
+ * chat or group stays that error.
117
+ */
118
+ function findChatOrGroup(links, groups, ref) {
119
+ if (ref.startsWith("group:")) return { group: findGroup(groups, ref.slice(6)) };
120
+ let chat;
121
+ try {
122
+ chat = chatBy(links, ref);
123
+ } catch (error) {
124
+ if (!(error instanceof CliError) || error.code !== "not_found") throw error;
125
+ try {
126
+ return { group: findGroup(groups, ref) };
127
+ } catch (inner) {
128
+ if (!(inner instanceof CliError) || inner.code !== "not_found") throw inner;
129
+ if (CONTACT_KEY.test(ref)) throw error;
130
+ throw new CliError("not_found", `No chat or group ${JSON.stringify(ref)} (ghostly chat list, ghostly group list)`);
131
+ }
132
+ }
133
+ if (!chat.theirName) return { link: chat.link };
134
+ const exact = groups.find((group) => group.id === ref);
135
+ if (exact) return { group: exact };
136
+ const lower = ref.toLowerCase();
137
+ const also = groups.filter((group) => !invitedOnly(group) && (group.id.startsWith(ref) || group.name.trim().toLowerCase() === lower));
138
+ if (also.length) throw new CliError("bad_request", `${JSON.stringify(ref)} names a chat (the name its contact chose) and a group: use the chat's id or group:<id>`, {
139
+ chat: chat.link.id,
140
+ groups: also.map((group) => group.id)
141
+ });
142
+ return { link: chat.link };
143
+ }
144
+ var chatOf = (ctx, params) => findChat(state(ctx).links, str(params, "chat", true));
145
+ /**
146
+ * A chat whose invite nobody took yet: nobody is there to pay or to dial, and its stage says so. Before, `chat pay`
147
+ * waited 15 s and `chat connect` 25 s for the contact, then answered `engine`. A chat whose invite someone else used
148
+ * first is refused too, with the sentence `send` gives: its contact seals everything to them (`connect` waited 120 s,
149
+ * then answered `timeout`, a code a bot retries).
150
+ */
151
+ function inviteTaken(link) {
152
+ if (link.profile && link.inviteCode && !link.peerParticipationKey) throw new CliError("unavailable", "Nobody joined this chat yet: its invite is still waiting to be taken");
153
+ if (link.dhtDelivery?.inviteTaken && link.textDelivery !== "stream") throw new CliError("refused", INVITE_TAKEN);
154
+ }
155
+ /** A chat or a group as `findChatOrGroup` reads it. `linkId` is what the engine calls it. */
156
+ function chatOrGroup(ctx, ref) {
157
+ const found = findChatOrGroup(state(ctx).links, state(ctx).groups, ref);
158
+ return "group" in found ? {
159
+ id: found.group.id,
160
+ linkId: `group:${found.group.id}`,
161
+ group: true
162
+ } : {
163
+ id: found.link.id,
164
+ linkId: found.link.id,
165
+ group: false
166
+ };
167
+ }
168
+ var groupOf = (ctx, params) => findGroup(state(ctx).groups, str(params, "group", true));
169
+ /**
170
+ * A message of a chat or a group (`group:<id>`) named by its id here, read as one row; only a name that is no such
171
+ * row's key (the id both sides know, `wireId`) reads the whole history. `keep`: only a message it accepts (mine, or
172
+ * someone else's). The history is synchronous work on the daemon's one thread, about 120 ms at 10,000 messages.
173
+ */
174
+ async function messageOf(ctx, linkId, ref, keep = () => true) {
175
+ const found = await node(ctx).getMessage(linkId, ref);
176
+ if (found && keep(found)) return found;
177
+ return (linkId.startsWith("group:") ? await node(ctx).groupMessages({ groupId: linkId.slice(6) }) : await node(ctx).getMessages(linkId)).find((m) => keep(m) && m.wireId === ref);
178
+ }
179
+ /** Resolves with `check`'s first defined answer: now, or on a later state; `timeout` error after `ms`. */
180
+ function waitForState(ctx, check, ms, what) {
181
+ const now = check(state(ctx));
182
+ if (now !== void 0) return Promise.resolve(now);
183
+ return new Promise((resolve, reject) => {
184
+ const timer = setTimeout(() => {
185
+ off();
186
+ reject(new CliError("timeout", `Timed out after ${Math.round(ms / 1e3)} s waiting for ${what}`));
187
+ }, ms);
188
+ const off = ctx.hub.onState((next) => {
189
+ const value = check(next);
190
+ if (value === void 0) return;
191
+ clearTimeout(timer);
192
+ off();
193
+ resolve(value);
194
+ });
195
+ });
196
+ }
197
+ //#endregion
198
+ export { str as _, findChat as a, groupOf as c, messageOf as d, node as f, state as g, sentAt as h, chatOrGroup as i, inviteTaken as l, oneOf as m, bool as n, findChatOrGroup as o, num as p, chatOf as r, findGroup as s, CONTACT_KEY as t, list as u, waitForState as v };
199
+
200
+ //# sourceMappingURL=apiKit-CA2x5GJF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiKit-CA2x5GJF.js","names":[],"sources":["../../src/apiKit.ts"],"sourcesContent":["import { INVITE_TAKEN } from \"@ghostly/core\";\nimport type { GhostlyNode } from \"@ghostly/browser/engine/node\";\nimport type { EngineState, GroupView, LinkView, StoredMessage } from \"@ghostly/browser/shared/types\";\nimport { CliError } from \"./errors\";\nimport type { CallManager } from \"./calls/manager\";\nimport type { EventHub } from \"./events\";\nimport type { Runtime } from \"./runtime/engine\";\n\n/**\n * The methods of the local control API (WISP 1100): the daemon answers them over its socket, and a one-shot\n * command calls them in its own process. Parameters come from JSON: every one is checked here.\n */\nexport interface ApiContext {\n runtime: Runtime;\n hub: EventHub;\n /** Voice calls (WISP 1100 § Calls). */\n calls: CallManager;\n /** How the host runs: a daemon stays; a one-shot leaves when its command is done. */\n mode: \"daemon\" | \"one-shot\";\n version: string;\n /** The daemon asks to stop (answered first). */\n stop?: () => void;\n}\n\nexport type Params = Record<string, unknown>;\nexport type Method = (ctx: ApiContext, params: Params) => Promise<unknown>;\n\nexport const node = (ctx: ApiContext): GhostlyNode => ctx.runtime.server.node;\nexport const state = (ctx: ApiContext): EngineState => node(ctx).getState();\n\n/**\n * The time of a send the engine names its row after (`me_<timestamp>`: a file, a payment, a request), its own even\n * within one millisecond (a bot paying or sending several times at once over the socket, `file send` run in\n * parallel), or the chat keeps only one. One clock for all of them: they share the row names. A file's resend after a\n * drop finds its row by it, as a forward does.\n */\nlet lastSentAt = 0;\nexport const sentAt = () => (lastSentAt = Math.max(Date.now(), lastSentAt + 1));\n\n// ---------- parameters ----------\n\nexport function str(params: Params, name: string, required: true): string;\nexport function str(params: Params, name: string, required?: false): string | undefined;\nexport function str(params: Params, name: string, required = false): string | undefined {\n const value = params[name];\n if (value === undefined || value === null || value === \"\") {\n if (required) throw new CliError(\"bad_request\", `${name} is required`);\n return undefined;\n }\n if (typeof value !== \"string\") throw new CliError(\"bad_request\", `${name} must be a string`);\n return value;\n}\n/** A number in `[min, max]`; `whole` takes only whole ones (a count of messages), as the engine needs them. */\nexport function num(params: Params, name: string, fallback: number, { min = 0, max = Number.MAX_SAFE_INTEGER, whole = false } = {}): number {\n const value = params[name];\n if (value === undefined || value === null) return fallback;\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < min || value > max) throw new CliError(\"bad_request\", `${name} must be a ${whole ? \"whole \" : \"\"}number from ${min} to ${max}`);\n if (whole && !Number.isSafeInteger(value)) throw new CliError(\"bad_request\", `${name} must be a whole number from ${min} to ${max}`);\n return value;\n}\nexport function bool(params: Params, name: string): boolean {\n const value = params[name];\n if (value === undefined || value === null) return false;\n if (typeof value !== \"boolean\") throw new CliError(\"bad_request\", `${name} must be true or false`);\n return value;\n}\nexport function list(params: Params, name: string): string[] {\n const value = params[name];\n if (value === undefined || value === null) return [];\n if (!Array.isArray(value) || value.some((item) => typeof item !== \"string\")) throw new CliError(\"bad_request\", `${name} must be a list of strings`);\n return value as string[];\n}\nexport function oneOf<T extends string>(params: Params, name: string, values: readonly T[], fallback: T): T {\n const value = str(params, name) ?? fallback;\n if (!values.includes(value as T)) throw new CliError(\"bad_request\", `${name} must be one of ${values.join(\", \")}`);\n return value as T;\n}\n\n// ---------- finding things ----------\n\n/** A contact key as chats show it (`peer`): 52 characters of z-base-32. */\nexport const CONTACT_KEY = /^[ybndrfg8ejkmcpqxot1uwisza345h769]{52}$/;\n\n/**\n * A chat by its id; by its contact's key (only by the key: a name is never taken for one); by a unique prefix of its\n * id; or by its name: the label this side gave it first, and only when no label matches, the name the contact gave\n * itself (which the contact controls, so it never wins over a label).\n */\nexport function findChat(links: readonly LinkView[], ref: string): LinkView {\n return chatBy(links, ref).link;\n}\n\n/** `findChat`, and whether only the name the contact gave itself (`theirName`) found the chat. */\nfunction chatBy(links: readonly LinkView[], ref: string): { link: LinkView; theirName: boolean } {\n const exact = links.find((link) => link.id === ref);\n if (exact) return { link: exact, theirName: false };\n if (CONTACT_KEY.test(ref)) {\n const byKey = links.filter((link) => link.peerPubKeyZ32 === ref);\n if (byKey.length === 1) return { link: byKey[0], theirName: false };\n if (byKey.length > 1) throw new CliError(\"bad_request\", `${JSON.stringify(ref)} names more than one chat: use its id`, { matches: byKey.map((link) => link.id) });\n throw new CliError(\"not_found\", `No chat with ${JSON.stringify(ref)}`);\n }\n const byPrefix = links.filter((link) => link.id.startsWith(ref));\n if (byPrefix.length === 1) return { link: byPrefix[0], theirName: false };\n const lower = ref.toLowerCase();\n const named = (name: string | null | undefined) => name?.trim().toLowerCase() === lower;\n const byLabel = links.filter((link) => named(link.label));\n const byName = byLabel.length ? byLabel : links.filter((link) => named(link.peerNick));\n if (byName.length === 1) return { link: byName[0], theirName: !byLabel.length };\n if (byPrefix.length > 1 || byName.length > 1) throw new CliError(\"bad_request\", `${JSON.stringify(ref)} names more than one chat: use its id`, { matches: [...byPrefix, ...byName].map((link) => link.id) });\n throw new CliError(\"not_found\", `No chat ${JSON.stringify(ref)}`);\n}\n\n/** Only an invitation so far: this profile is not in the group (yet), and whoever invited chose its name. */\nconst invitedOnly = (group: GroupView) => group.status === undefined && !!group.invitation;\n\n/**\n * A group by its id, by a unique prefix of its id, or by its name. A group this profile is in wins over an invitation\n * with the same prefix or name (the one who invites chooses those); an invitation is still found by its id, and by\n * prefix or name where no group of this profile has it.\n */\nexport function findGroup(groups: readonly GroupView[], ref: string): GroupView {\n const exact = groups.find((group) => group.id === ref);\n if (exact) return exact;\n const mineFirst = (found: GroupView[]) => { const mine = found.filter((group) => !invitedOnly(group)); return mine.length ? mine : found; };\n const byPrefix = mineFirst(groups.filter((group) => group.id.startsWith(ref)));\n if (byPrefix.length === 1) return byPrefix[0];\n const byName = mineFirst(groups.filter((group) => group.name.trim().toLowerCase() === ref.toLowerCase()));\n if (byName.length === 1) return byName[0];\n if (byPrefix.length > 1 || byName.length > 1) throw new CliError(\"bad_request\", `${JSON.stringify(ref)} names more than one group: use its id`, { matches: [...byPrefix, ...byName].map((group) => group.id) });\n throw new CliError(\"not_found\", `No group ${JSON.stringify(ref)}`);\n}\n\n/**\n * A chat or a group, where a command takes either: `group:<id>` is a group; anything else a chat first (`findChat`),\n * then a group. A chat found only by the name its contact gave itself never takes what also names a group: a group's\n * own id is that group, and a prefix or the name of a group this profile is in is refused, with both ids. With neither,\n * one `not_found` names both and where to look (a contact's key: no chat with it); a name that matches more than one\n * chat or group stays that error.\n */\nexport function findChatOrGroup(links: readonly LinkView[], groups: readonly GroupView[], ref: string): { link: LinkView } | { group: GroupView } {\n if (ref.startsWith(\"group:\")) return { group: findGroup(groups, ref.slice(\"group:\".length)) };\n let chat: ReturnType<typeof chatBy>;\n try {\n chat = chatBy(links, ref);\n } catch (error) {\n if (!(error instanceof CliError) || error.code !== \"not_found\") throw error;\n try { return { group: findGroup(groups, ref) }; } catch (inner) {\n if (!(inner instanceof CliError) || inner.code !== \"not_found\") throw inner;\n if (CONTACT_KEY.test(ref)) throw error;\n throw new CliError(\"not_found\", `No chat or group ${JSON.stringify(ref)} (ghostly chat list, ghostly group list)`);\n }\n }\n if (!chat.theirName) return { link: chat.link };\n const exact = groups.find((group) => group.id === ref);\n if (exact) return { group: exact };\n const lower = ref.toLowerCase();\n const also = groups.filter((group) => !invitedOnly(group) && (group.id.startsWith(ref) || group.name.trim().toLowerCase() === lower));\n if (also.length) throw new CliError(\"bad_request\", `${JSON.stringify(ref)} names a chat (the name its contact chose) and a group: use the chat's id or group:<id>`, { chat: chat.link.id, groups: also.map((group) => group.id) });\n return { link: chat.link };\n}\n\nexport const chatOf = (ctx: ApiContext, params: Params) => findChat(state(ctx).links, str(params, \"chat\", true));\n\n/**\n * A chat whose invite nobody took yet: nobody is there to pay or to dial, and its stage says so. Before, `chat pay`\n * waited 15 s and `chat connect` 25 s for the contact, then answered `engine`. A chat whose invite someone else used\n * first is refused too, with the sentence `send` gives: its contact seals everything to them (`connect` waited 120 s,\n * then answered `timeout`, a code a bot retries).\n */\nexport function inviteTaken(link: LinkView): void {\n if (link.profile && link.inviteCode && !link.peerParticipationKey) throw new CliError(\"unavailable\", \"Nobody joined this chat yet: its invite is still waiting to be taken\");\n if (link.dhtDelivery?.inviteTaken && link.textDelivery !== \"stream\") throw new CliError(\"refused\", INVITE_TAKEN);\n}\n\n/** A chat or a group as `findChatOrGroup` reads it. `linkId` is what the engine calls it. */\nexport function chatOrGroup(ctx: ApiContext, ref: string): { id: string; linkId: string; group: boolean } {\n const found = findChatOrGroup(state(ctx).links, state(ctx).groups, ref);\n return \"group\" in found ? { id: found.group.id, linkId: `group:${found.group.id}`, group: true } : { id: found.link.id, linkId: found.link.id, group: false };\n}\n\nexport const groupOf = (ctx: ApiContext, params: Params) => findGroup(state(ctx).groups, str(params, \"group\", true));\n\n/**\n * A message of a chat or a group (`group:<id>`) named by its id here, read as one row; only a name that is no such\n * row's key (the id both sides know, `wireId`) reads the whole history. `keep`: only a message it accepts (mine, or\n * someone else's). The history is synchronous work on the daemon's one thread, about 120 ms at 10,000 messages.\n */\nexport async function messageOf(ctx: ApiContext, linkId: string, ref: string, keep: (message: StoredMessage) => boolean = () => true): Promise<StoredMessage | undefined> {\n const found = await node(ctx).getMessage(linkId, ref);\n if (found && keep(found)) return found;\n const all = linkId.startsWith(\"group:\") ? await node(ctx).groupMessages({ groupId: linkId.slice(\"group:\".length) }) : await node(ctx).getMessages(linkId);\n return all.find((m) => keep(m) && m.wireId === ref);\n}\n\n// ---------- waiting ----------\n\n/** Resolves with `check`'s first defined answer: now, or on a later state; `timeout` error after `ms`. */\nexport function waitForState<T>(ctx: ApiContext, check: (state: EngineState) => T | undefined, ms: number, what: string): Promise<T> {\n const now = check(state(ctx));\n if (now !== undefined) return Promise.resolve(now);\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => { off(); reject(new CliError(\"timeout\", `Timed out after ${Math.round(ms / 1000)} s waiting for ${what}`)); }, ms);\n const off = ctx.hub.onState((next) => {\n const value = check(next);\n if (value === undefined) return;\n clearTimeout(timer);\n off();\n resolve(value);\n });\n });\n}\n\n"],"mappings":"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"}