@ghostlytools/cli 1.3.2 → 1.3.4

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 (73) hide show
  1. package/README.md +7 -7
  2. package/SKILL.md +2 -1
  3. package/dist/assets/{api-SYPJgFRU.js → api-BCwaCbKh.js} +2 -2
  4. package/dist/assets/{api-aup3Amud.js → api-Bp_hWAiU.js} +100 -20
  5. package/dist/assets/api-Bp_hWAiU.js.map +1 -0
  6. package/dist/assets/{apiKit-DYkVEs6r.js → apiKit-3MLUyuhL.js} +21 -6
  7. package/dist/assets/apiKit-3MLUyuhL.js.map +1 -0
  8. package/dist/assets/{apps-DcZKu29s.js → apps-B6gqk4GD.js} +1 -1
  9. package/dist/assets/{apps-Rbk2HHXb.js → apps-BXw3Ui_3.js} +91 -24
  10. package/dist/assets/apps-BXw3Ui_3.js.map +1 -0
  11. package/dist/assets/{arkade-CuWlNnCz.js → arkade-CyplNY_v.js} +2 -2
  12. package/dist/assets/{arkade-CuWlNnCz.js.map → arkade-CyplNY_v.js.map} +1 -1
  13. package/dist/assets/{backup-CJWLDHuS.js → backup-BGE4BDzf.js} +6 -6
  14. package/dist/assets/{backup-CJWLDHuS.js.map → backup-BGE4BDzf.js.map} +1 -1
  15. package/dist/assets/{backup-CSMw2GHv.js → backup-Cm8ptQQA.js} +1 -1
  16. package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
  17. package/dist/assets/{buttons-QEnAGWoC.js → buttons-52NT4sA_.js} +4 -4
  18. package/dist/assets/{buttons-QEnAGWoC.js.map → buttons-52NT4sA_.js.map} +1 -1
  19. package/dist/assets/buttons-PCvHRlkS.js +2 -0
  20. package/dist/assets/{envelope-K27mzoyu.js → envelope-CsRRCrL3.js} +2 -2
  21. package/dist/assets/{envelope-K27mzoyu.js.map → envelope-CsRRCrL3.js.map} +1 -1
  22. package/dist/assets/{errors-DO5z6h_l.js → errors-B-zMxbcd.js} +6 -5
  23. package/dist/assets/{errors-DO5z6h_l.js.map → errors-B-zMxbcd.js.map} +1 -1
  24. package/dist/assets/fedimint_client_wasm_bg-BtHHlFpp.js.map +1 -1
  25. package/dist/assets/{ghostly_iroh_web-Dvzh3Y6E.js → ghostly_iroh_web-BsAEFIjl.js} +30 -36
  26. package/dist/assets/ghostly_iroh_web-BsAEFIjl.js.map +1 -0
  27. package/dist/assets/ghostly_iroh_web_bg.wasm +0 -0
  28. package/dist/assets/{groupFiles-DHTrTA4I.js → groupFiles-BurezP4Z.js} +47 -21
  29. package/dist/assets/{groupFiles-DHTrTA4I.js.map → groupFiles-BurezP4Z.js.map} +1 -1
  30. package/dist/assets/{host-C7hA-Za7.js → host-C74Sy5jT.js} +211 -42
  31. package/dist/assets/host-C74Sy5jT.js.map +1 -0
  32. package/dist/assets/{hyperdhtRelay-BVoWY2ua.js → hyperdhtRelay-D0MvfMzX.js} +2 -2
  33. package/dist/assets/{hyperdhtRelay-BVoWY2ua.js.map → hyperdhtRelay-D0MvfMzX.js.map} +1 -1
  34. package/dist/assets/{irohWeb-BJPk9bMn.js → irohWeb-DynqwRwl.js} +3 -3
  35. package/dist/assets/{irohWeb-BJPk9bMn.js.map → irohWeb-DynqwRwl.js.map} +1 -1
  36. package/dist/assets/{main-DC8cUkDx.js → main-DNzQOdNG.js} +168 -57
  37. package/dist/assets/main-DNzQOdNG.js.map +1 -0
  38. package/dist/assets/{mainline-CxbxpoU4.js → mainline-COqCHn1R.js} +2 -2
  39. package/dist/assets/{mainline-CxbxpoU4.js.map → mainline-COqCHn1R.js.map} +1 -1
  40. package/dist/assets/{networkReason-D42iNuUB.js → networkReason-WikfjkIw.js} +2 -2
  41. package/dist/assets/{networkReason-D42iNuUB.js.map → networkReason-WikfjkIw.js.map} +1 -1
  42. package/dist/assets/{nwc-Cm4sYgLv.js → nwc-ClsjYuyP.js} +3 -3
  43. package/dist/assets/{nwc-Cm4sYgLv.js.map → nwc-ClsjYuyP.js.map} +1 -1
  44. package/dist/assets/{nwcProvider-0D7vdPDd.js → nwcProvider-ksMYZra9.js} +2 -2
  45. package/dist/assets/{nwcProvider-0D7vdPDd.js.map → nwcProvider-ksMYZra9.js.map} +1 -1
  46. package/dist/assets/{profiles-BHc9pEYg.js → profiles-gPCjLOQq.js} +2 -2
  47. package/dist/assets/{profiles-BHc9pEYg.js.map → profiles-gPCjLOQq.js.map} +1 -1
  48. package/dist/assets/{registry-CVdSZW5l.js → registry-CJh9L_kF.js} +17 -32
  49. package/dist/assets/registry-CJh9L_kF.js.map +1 -0
  50. package/dist/assets/{registry-CLolR0kM.js → registry-S7SFF_JB.js} +1 -1
  51. package/dist/assets/{resolve-BnlEajYz.js → resolve-BJxBhU5j.js} +18 -3
  52. package/dist/assets/resolve-BJxBhU5j.js.map +1 -0
  53. package/dist/assets/{server-CJNEKxzE.js → server-C05N9q4G.js} +298 -100
  54. package/dist/assets/server-C05N9q4G.js.map +1 -0
  55. package/dist/assets/{src-yMUQvdyL.js → src-RQ6OJOUO.js} +322 -104
  56. package/dist/assets/src-RQ6OJOUO.js.map +1 -0
  57. package/dist/assets/{turnRefs-B2a8to9q.js → turnRefs-u-U75DFx.js} +2 -2
  58. package/dist/assets/{turnRefs-B2a8to9q.js.map → turnRefs-u-U75DFx.js.map} +1 -1
  59. package/dist/assets/{usdt-DesFeDiG.js → usdt-A1B3r3Mq.js} +3 -3
  60. package/dist/assets/{usdt-DesFeDiG.js.map → usdt-A1B3r3Mq.js.map} +1 -1
  61. package/dist/ghostly.mjs +1 -1
  62. package/package.json +1 -1
  63. package/dist/assets/api-aup3Amud.js.map +0 -1
  64. package/dist/assets/apiKit-DYkVEs6r.js.map +0 -1
  65. package/dist/assets/apps-Rbk2HHXb.js.map +0 -1
  66. package/dist/assets/buttons-CGRqZ7z0.js +0 -2
  67. package/dist/assets/ghostly_iroh_web-Dvzh3Y6E.js.map +0 -1
  68. package/dist/assets/host-C7hA-Za7.js.map +0 -1
  69. package/dist/assets/main-DC8cUkDx.js.map +0 -1
  70. package/dist/assets/registry-CVdSZW5l.js.map +0 -1
  71. package/dist/assets/resolve-BnlEajYz.js.map +0 -1
  72. package/dist/assets/server-CJNEKxzE.js.map +0 -1
  73. package/dist/assets/src-yMUQvdyL.js.map +0 -1
@@ -1,8 +1,8 @@
1
- import { Ja as PR_STATES, Ka as ITEM_STATES, Qn as GROUP_FILE_LIMITS, Xa as STATUS_CARD_LIMITS, Ya as RUN_RESULTS, eo as cardUrl, qa as PR_CHECKS } from "./src-yMUQvdyL.js";
2
- import { a as SECRET_RESULTS, i as ENGINE_READS, r as ENGINE_METHODS, t as turnRef } from "./turnRefs-B2a8to9q.js";
3
- import { n as EXIT, r as asCliError, t as CliError } from "./errors-DO5z6h_l.js";
4
- import { t as CONTACT_KEY } from "./apiKit-DYkVEs6r.js";
5
- import { a as createProfile, c as listProfiles, d as profilePaths, f as selectProfile, i as checkProfileName, l as lockOwner, o as currentProfile, s as ghostlyHome, u as profileExists } from "./profiles-BHc9pEYg.js";
1
+ import { $n as GROUP_FILE_LIMITS, Ja as PR_CHECKS, Xa as RUN_RESULTS, Ya as PR_STATES, Za as STATUS_CARD_LIMITS, qa as ITEM_STATES, to as cardUrl } from "./src-RQ6OJOUO.js";
2
+ import { a as SECRET_RESULTS, i as ENGINE_READS, r as ENGINE_METHODS, t as turnRef } from "./turnRefs-u-U75DFx.js";
3
+ import { i as localFileError, n as EXIT, r as asCliError, t as CliError } from "./errors-B-zMxbcd.js";
4
+ import { t as CONTACT_KEY } from "./apiKit-3MLUyuhL.js";
5
+ import { a as createProfile, c as listProfiles, d as profilePaths, f as selectProfile, i as checkProfileName, l as lockOwner, o as currentProfile, s as ghostlyHome, u as profileExists } from "./profiles-gPCjLOQq.js";
6
6
  import { n as ownSocket } from "./privateFolder-Bk9C9oFv.js";
7
7
  import { accessSync, constants, existsSync, openSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
8
8
  import { dirname, resolve } from "node:path";
@@ -12,7 +12,7 @@ import { createInterface } from "node:readline";
12
12
  import { format } from "node:util";
13
13
  var package_default = {
14
14
  name: "@ghostlytools/cli",
15
- version: "1.3.2",
15
+ version: "1.3.4",
16
16
  type: "module",
17
17
  description: "Ghostly for bots and terminals: the app's own engine, headless on Node, behind a daemon and a `ghostly` command with JSON output.",
18
18
  license: "MIT",
@@ -172,7 +172,7 @@ function parseArgs(argv, spec, idSlot = () => false) {
172
172
  value = argv[++i];
173
173
  }
174
174
  if (option.type === "number") {
175
- const number = Number(value);
175
+ const number = value.trim() === "" ? NaN : Number(value);
176
176
  if (!Number.isFinite(number)) throw new CliError("usage", `--${name} takes a number, not ${JSON.stringify(value)}`);
177
177
  options[name] = number;
178
178
  } else if (option.type === "list") options[name] = [...options[name] ?? [], value];
@@ -354,10 +354,22 @@ var confirmReal = {
354
354
  type: "boolean",
355
355
  description: "Confirm a Mainnet (real money) spend"
356
356
  };
357
- /** A path as this command's working directory means it: the daemon runs elsewhere. */
357
+ /**
358
+ * A path as this command's working directory means it: the daemon runs elsewhere. "" (an unset variable in a script)
359
+ * names no file, never the current folder.
360
+ */
358
361
  function here(path) {
362
+ if (path === "") throw new CliError("usage", "A path given as \"\" names no file");
359
363
  return typeof path === "string" ? resolve(path) : void 0;
360
364
  }
365
+ /**
366
+ * A list option that names chats (`--to`, `--from`): "" or "group:" alone (an unset variable in a script) names
367
+ * nothing, and is a usage error before the profile opens; it was a prefix of every id and took the only chat or group.
368
+ */
369
+ function refs(values, flag, what) {
370
+ if (!Array.isArray(values)) return;
371
+ for (const value of values) if (typeof value === "string" && !value.replace(/^group:/, "").trim()) throw new CliError("usage", `--${flag} ${JSON.stringify(value)} names no ${what}`);
372
+ }
361
373
  /** A whole number of sats from the command line. */
362
374
  function sats(value) {
363
375
  const n = Number(value);
@@ -453,11 +465,12 @@ function cursor(value, flag) {
453
465
  function cardJson(value) {
454
466
  if (value === void 0) return {};
455
467
  const text = String(value).trim();
468
+ const file = text.startsWith("{") || text === "-" ? void 0 : here(text);
456
469
  let raw;
457
470
  try {
458
- raw = text.startsWith("{") ? text : readFileSync(text === "-" ? 0 : resolve(text), "utf8");
471
+ raw = text.startsWith("{") ? text : readFileSync(file ?? 0, "utf8");
459
472
  } catch (error) {
460
- throw new CliError("usage", `--json takes a card as JSON, - for stdin, or a file: ${error instanceof Error ? error.message : String(error)}`);
473
+ throw file !== void 0 && localFileError(error, file) || new CliError("usage", `--json takes a card as JSON, - for stdin, or a file: ${error instanceof Error ? error.message : String(error)}`);
461
474
  }
462
475
  let parsed;
463
476
  try {
@@ -869,7 +882,7 @@ var commands$8 = {
869
882
  description: "Replace the three files when they are there"
870
883
  }
871
884
  },
872
- run: async ({ options }, a) => (await import("./apps-DcZKu29s.js")).initApp(a.dir, {
885
+ run: async ({ options }, a) => (await import("./apps-B6gqk4GD.js")).initApp(a.dir, {
873
886
  name: options.name,
874
887
  title: options.title,
875
888
  force: options.force === true
@@ -909,11 +922,11 @@ var commands$8 = {
909
922
  description: "This version's sequence, higher than the bundle at --out's (default: one more than it)"
910
923
  }
911
924
  },
912
- run: async ({ options }, a) => (await import("./apps-DcZKu29s.js")).publishApp(a.dir, keyFile(options.key, "app publish <dir> --key <file> [--out <file>] [--sequence n]"), options.out, options.sequence)
925
+ run: async ({ options }, a) => (await import("./apps-B6gqk4GD.js")).publishApp(a.dir, keyFile(options.key, "app publish <dir> --key <file> [--out <file>] [--sequence n]"), here(options.out), options.sequence)
913
926
  },
914
927
  "app revoke": {
915
928
  method: "app.revoke",
916
- usage: "app revoke <dir> --key <file> (--digest <digest>... | --up-to <sequence>) [--reason <text>] [--bundle <file>]",
929
+ usage: "app revoke <dir> --key <file> (--digest <digest>... | --up-to <sequence>) [--reason <text>] [--bundle <file>] [--force]",
917
930
  summary: "Revoke versions of your app (WISP 1200 · Revocation): a ghostly-revoke/1 statement signed with its publisher key, added to <dir>/ghostly-revoke.json",
918
931
  args: ["dir"],
919
932
  options: {
@@ -936,13 +949,18 @@ var commands$8 = {
936
949
  bundle: {
937
950
  type: "string",
938
951
  description: "The bundle naming the app (default <dir>/app.ghostlyapp)"
952
+ },
953
+ force: {
954
+ type: "boolean",
955
+ description: "Allow --up-to past the bundle's own sequence (versions published from elsewhere)"
939
956
  }
940
957
  },
941
- run: async ({ options }, a) => (await import("./apps-DcZKu29s.js")).revokeApp(a.dir, keyFile(options.key, "app revoke <dir> --key <file> (--digest <digest>... | --up-to <sequence>) [--reason <text>]"), {
958
+ run: async ({ options }, a) => (await import("./apps-B6gqk4GD.js")).revokeApp(a.dir, keyFile(options.key, "app revoke <dir> --key <file> (--digest <digest>... | --up-to <sequence>) [--reason <text>]"), {
942
959
  digests: options.digest,
943
960
  upTo: options["up-to"],
944
961
  reason: options.reason,
945
- bundle: options.bundle
962
+ bundle: here(options.bundle),
963
+ force: options.force === true
946
964
  })
947
965
  },
948
966
  "app send": {
@@ -996,9 +1014,9 @@ var commands$8 = {
996
1014
  "app verify": {
997
1015
  method: "app.verify",
998
1016
  usage: "app verify <bundle|url>",
999
- summary: "Check a .ghostlyapp as a client would, from a file or an https URL: its manifest and digest, or refused (exit 1) with the reason",
1017
+ summary: "Check a .ghostlyapp as a client would, from a file or an https URL: its manifest and digest, or refused (exit 1) with the reason, revoked when the ghostly-revoke.json beside it revokes this version",
1000
1018
  args: ["bundle"],
1001
- run: async (_, a) => (await import("./apps-DcZKu29s.js")).verifyApp(a.bundle)
1019
+ run: async (_, a) => (await import("./apps-B6gqk4GD.js")).verifyApp(a.bundle)
1002
1020
  },
1003
1021
  "store sign": {
1004
1022
  method: "store.sign",
@@ -1008,14 +1026,14 @@ var commands$8 = {
1008
1026
  options: {
1009
1027
  key: {
1010
1028
  ...key,
1011
- description: "The store key file (made, owner-only, when it is missing and the index names no key; keep it offline)"
1029
+ description: "The store key file (made, owner-only, when it is missing, the index names no key and no signed index is in the output folder; keep it offline)"
1012
1030
  },
1013
1031
  out: {
1014
1032
  type: "string",
1015
1033
  description: "The folder to write ghostly-store.json and ghostly-store.sig in (default: the index's)"
1016
1034
  }
1017
1035
  },
1018
- run: async ({ options }, a) => (await import("./apps-DcZKu29s.js")).signStore(a.index, keyFile(options.key, "store sign <index> --key <file> [--out <dir>]"), options.out)
1036
+ run: async ({ options }, a) => (await import("./apps-B6gqk4GD.js")).signStore(a.index, keyFile(options.key, "store sign <index> --key <file> [--out <dir>]"), here(options.out))
1019
1037
  }
1020
1038
  };
1021
1039
  //#endregion
@@ -1048,6 +1066,7 @@ var commands$7 = {
1048
1066
  params: ({ options }, a) => {
1049
1067
  if (a.mode !== void 0 && a.mode !== "on" && a.mode !== "off") throw new CliError("usage", "ghostly call auto [on|off] [--from <chat>]... [--rate n]");
1050
1068
  if (a.mode !== "on" && (options.from !== void 0 || options.rate !== void 0)) throw new CliError("usage", "--from and --rate go with on: ghostly call auto on --from <chat>");
1069
+ refs(options.from, "from", "chat");
1051
1070
  if (a.mode === void 0) return {};
1052
1071
  return a.mode === "on" ? {
1053
1072
  on: true,
@@ -1314,6 +1333,7 @@ var commands$6 = {
1314
1333
  },
1315
1334
  params: ({ options }, a) => {
1316
1335
  if (!Array.isArray(options.to) || !options.to.length) throw new CliError("usage", "Missing --to: ghostly forward <chat|group> <message>... --to <chat|group>... [--force] [--wait none|sent] [--timeout s]");
1336
+ refs(options.to, "to", "chat or group");
1317
1337
  return {
1318
1338
  chat: a.chat,
1319
1339
  messages: (a.message ?? "").split(" ").filter(Boolean),
@@ -1336,9 +1356,9 @@ var commands$6 = {
1336
1356
  },
1337
1357
  "invite join": {
1338
1358
  method: "invite.join",
1339
- usage: "invite join <invite-or-link> [--label <name>]",
1340
- summary: "Join a chat from its invite",
1341
- args: ["invite"],
1359
+ usage: "invite join [<invite-or-link> | -] [--label <name>]",
1360
+ summary: "Join a chat from its invite; the invite on stdin keeps it out of ps",
1361
+ args: ["invite?"],
1342
1362
  options: { label: {
1343
1363
  type: "string",
1344
1364
  description: "A name for the chat on this side"
@@ -1771,10 +1791,10 @@ var commands$4 = {
1771
1791
  description: "Remove the picture"
1772
1792
  } },
1773
1793
  params: ({ options }, a) => {
1774
- if (a.path === void 0 && options.clear !== true) throw new CliError("usage", "Missing <jpeg>: ghostly profile picture <jpeg> | --clear");
1775
- if (a.path !== void 0 && options.clear === true) throw new CliError("usage", "--clear removes the picture: not with a <jpeg>");
1794
+ if (!a.path && options.clear !== true) throw new CliError("usage", "Missing <jpeg>: ghostly profile picture <jpeg> | --clear");
1795
+ if (a.path && options.clear === true) throw new CliError("usage", "--clear removes the picture: not with a <jpeg>");
1776
1796
  return {
1777
- path: here(a.path),
1797
+ path: here(a.path || void 0),
1778
1798
  clear: options.clear === true
1779
1799
  };
1780
1800
  }
@@ -1918,9 +1938,9 @@ var commands$3 = {
1918
1938
  },
1919
1939
  "group join": {
1920
1940
  method: "group.join",
1921
- usage: "group join <link>",
1922
- summary: "Join a group by its link",
1923
- args: ["link"],
1941
+ usage: "group join [<link> | -]",
1942
+ summary: "Join a group by its link; the link on stdin keeps it out of ps",
1943
+ args: ["link?"],
1924
1944
  params: (_, { link }) => ({ link })
1925
1945
  },
1926
1946
  "group leave": {
@@ -1978,11 +1998,11 @@ var commands$3 = {
1978
1998
  description: "Remove the picture"
1979
1999
  } },
1980
2000
  params: ({ options }, a) => {
1981
- if (a.path === void 0 && options.clear !== true) throw new CliError("usage", "Missing <jpeg>: ghostly group picture <group> <jpeg> | --clear");
1982
- if (a.path !== void 0 && options.clear === true) throw new CliError("usage", "--clear removes the picture: not with a <jpeg>");
2001
+ if (!a.path && options.clear !== true) throw new CliError("usage", "Missing <jpeg>: ghostly group picture <group> <jpeg> | --clear");
2002
+ if (a.path && options.clear === true) throw new CliError("usage", "--clear removes the picture: not with a <jpeg>");
1983
2003
  return {
1984
2004
  group: a.group,
1985
- path: here(a.path),
2005
+ path: here(a.path || void 0),
1986
2006
  clear: options.clear === true
1987
2007
  };
1988
2008
  }
@@ -2735,28 +2755,45 @@ function idSlot(command) {
2735
2755
  return (index) => at(index) && (shift === 0 || index + shift >= names.length || at(index + shift));
2736
2756
  }
2737
2757
  /**
2738
- * The positionals of a command, by name; missing required ones are a usage error. A leading `name?` is optional: it
2739
- * is given only when every required one is there too (`file accept [<chat>] <file>`). A trailing `name...` takes the
2740
- * rest and needs at least one word; `name...?` may take none (text from stdin, `chat rename`'s name back to the
2741
- * contact's).
2758
+ * The placeholders a usage line shows for its positionals, in order: `chat pay <chat> <sats> [--memo t]` →
2759
+ * ["chat", "sats"]. One that is not a plain `<x>` (`[text... | --text <text>]`) is undefined; the flags end them.
2760
+ */
2761
+ function usageNames(usage) {
2762
+ const words = usage.split(/\s+/);
2763
+ const first = words.findIndex((word) => word.startsWith("<") || word.startsWith("["));
2764
+ const out = [];
2765
+ for (const word of first === -1 ? [] : words.slice(first)) {
2766
+ if (/^\[?-|^\|/.test(word)) break;
2767
+ out.push(/^\[?<([^>]+?)(?:\.\.\.)?>(?:\.\.\.)?\]?$/.exec(word)?.[1]);
2768
+ }
2769
+ return out;
2770
+ }
2771
+ /**
2772
+ * The positionals of a command, by name; missing required ones are a usage error, and so are ones given as "" (an
2773
+ * unset variable in a script), before the profile opens. A leading `name?` is optional: it is given only when every
2774
+ * required one is there too (`file accept [<chat>] <file>`). A trailing `name...` takes the rest and needs at least
2775
+ * one word that is not ""; `name...?` may take none (text from stdin, `chat rename`'s name back to the contact's).
2742
2776
  */
2743
2777
  function positionals(command, values) {
2744
2778
  let names = command.args ?? [];
2745
2779
  const optional = names.filter(leadingOptional);
2780
+ let skip = 0;
2746
2781
  if (optional.length) {
2747
- const skip = Math.max(0, optional.length - Math.max(0, values.length - (names.length - optional.length)));
2782
+ skip = Math.max(0, optional.length - Math.max(0, values.length - (names.length - optional.length)));
2748
2783
  names = [...optional.slice(skip).map((name) => name.slice(0, -1)), ...names.filter((name) => !leadingOptional(name))];
2749
2784
  }
2785
+ const shown = usageNames(command.usage);
2786
+ const missing = (i, name) => new CliError("usage", `Missing <${shown[i + skip] ?? name}>: ghostly ${command.usage}`);
2750
2787
  const out = {};
2751
2788
  for (let i = 0; i < names.length; i++) {
2752
2789
  const name = names[i].replace(/\?$/, "");
2753
2790
  if (name.endsWith("...")) {
2754
2791
  const rest = values.slice(i);
2755
- if (!rest.length && !names[i].endsWith("?")) throw new CliError("usage", `Missing <${name.slice(0, -3)}>: ghostly ${command.usage}`);
2792
+ if (rest.every((word) => word === "") && !names[i].endsWith("?")) throw missing(i, name.slice(0, -3));
2756
2793
  out[name.slice(0, -3)] = rest.length ? rest.join(" ") : void 0;
2757
2794
  return out;
2758
2795
  }
2759
- if (values[i] === void 0) throw new CliError("usage", `Missing <${name}>: ghostly ${command.usage}`);
2796
+ if (values[i] === void 0 || values[i] === "") throw missing(i, name);
2760
2797
  out[name] = values[i];
2761
2798
  }
2762
2799
  noMoreThan(names.length, values, command.usage);
@@ -3131,6 +3168,20 @@ var Feed = class {
3131
3168
  * a warning. Fills `params` in place; the warning to print on stderr, if any. `tty`: stdin is a terminal, where a
3132
3169
  * read would wait on the keyboard without a word.
3133
3170
  */
3171
+ var JOINS = {
3172
+ "group.join": {
3173
+ param: "link",
3174
+ arg: "<link>",
3175
+ command: "group join",
3176
+ what: "A group's link"
3177
+ },
3178
+ "invite.join": {
3179
+ param: "invite",
3180
+ arg: "<invite-or-link>",
3181
+ command: "invite join",
3182
+ what: "An invite"
3183
+ }
3184
+ };
3134
3185
  async function secretsFromStdin(method, params, read, tty = false) {
3135
3186
  const warning = (what) => `${what} on the command line shows in ps and the shell's history: give it on stdin instead`;
3136
3187
  if (method === "wallet.redeem") {
@@ -3142,6 +3193,16 @@ async function secretsFromStdin(method, params, read, tty = false) {
3142
3193
  params.token = token;
3143
3194
  return;
3144
3195
  }
3196
+ const join = JOINS[method];
3197
+ if (join) {
3198
+ if (params[join.param] !== void 0 && params[join.param] !== "-") return warning(join.what);
3199
+ const missing = () => new CliError("usage", `Missing ${join.arg}: give it on stdin (… | ghostly ${join.command}) or as an argument`);
3200
+ if (tty) throw missing();
3201
+ const value = (await read()).trim();
3202
+ if (!value) throw missing();
3203
+ params[join.param] = value;
3204
+ return;
3205
+ }
3145
3206
  if (method === "wallet.create") {
3146
3207
  const onLine = params.apiKey !== void 0;
3147
3208
  if (params.stdin === true) {
@@ -3160,6 +3221,18 @@ async function secretsFromStdin(method, params, read, tty = false) {
3160
3221
  return onLine ? warning("An API key") : void 0;
3161
3222
  }
3162
3223
  }
3224
+ /**
3225
+ * `wallet create --value`: the names given on the command line that the chosen source's form marks `kind: "secret"`
3226
+ * (an NWC URI, a macaroon, a rune, an RPC password), as a warning. `list`: a `wallet.list` answer, where the forms are.
3227
+ */
3228
+ function secretValuesWarning(list, type, provider, names) {
3229
+ if (!names.length) return void 0;
3230
+ const id = provider ?? (type === "bitcoin" ? "bdk" : void 0);
3231
+ const fields = (list?.offers ?? []).find((o) => o.type === type && o.providers)?.providers?.find((p) => p.id === id)?.fields ?? [];
3232
+ const secret = names.filter((name) => fields.some((f) => f.name === name && f.kind === "secret"));
3233
+ if (!secret.length) return void 0;
3234
+ return `${secret.map((name) => `--value ${name}`).join(", ")} on the command line shows in ps and the shell's history: give ${secret.length > 1 ? "them" : "it"} on stdin instead (--stdin, name=value lines)`;
3235
+ }
3163
3236
  //#endregion
3164
3237
  //#region src/main.ts
3165
3238
  var VERSION = package_default.version;
@@ -3168,8 +3241,8 @@ var VERSION = package_default.version;
3168
3241
  * command a running daemon answers loads none of them, and starts about as fast as Node does. Statically imported, they
3169
3242
  * cost every command ~100 ms before its socket was opened.
3170
3243
  */
3171
- var engineApi = () => import("./api-SYPJgFRU.js");
3172
- var engineHost = () => import("./host-C7hA-Za7.js");
3244
+ var engineApi = () => import("./api-BCwaCbKh.js");
3245
+ var engineHost = () => import("./host-C74Sy5jT.js");
3173
3246
  var pretty = false;
3174
3247
  function print(value) {
3175
3248
  process.stdout.write(JSON.stringify(value, null, pretty ? 2 : void 0) + "\n");
@@ -3384,11 +3457,12 @@ var SPECIAL = [
3384
3457
  ],
3385
3458
  ["settings set <key> <json-value>", "Change one: relays, irohRelays, hyperdhtRelay, readRelays, iceServers, publicProfiles, online, shareProfile, sendTyping, autoDownloads, serveFiles, nick"],
3386
3459
  [
3387
- "engine <method> [json-params | -] [--confirm-real] [--show-secret]",
3460
+ "engine <method> [json-params | -] [--confirm-real] [--show-secret] [--force]",
3388
3461
  "Any call of the app's engine, with its own parameters",
3389
3462
  {
3390
3463
  "confirm-real": o("boolean", "Confirm a Mainnet (real money) spend"),
3391
- "show-secret": o("boolean", "Print a call's secrets")
3464
+ "show-secret": o("boolean", "Print a call's secrets"),
3465
+ force: o("boolean", "Send a text that looks like a secret")
3392
3466
  }
3393
3467
  ],
3394
3468
  ["engine --list", "The engine's calls"],
@@ -3467,11 +3541,13 @@ function commandHelp(words) {
3467
3541
  const own = commandWords(row.usage);
3468
3542
  return list.every((word, i) => own[i] === word);
3469
3543
  });
3544
+ const given = words;
3470
3545
  let rows = named(words);
3471
3546
  while (!rows.length && words.length > 1) rows = named(words = words.slice(0, -1));
3472
3547
  const asked = words.join(" ");
3473
3548
  if (!rows.length) throw new CliError("usage", `Unknown command: ${asked} (ghostly help)`);
3474
3549
  const exact = rows.filter((row) => commandWords(row.usage).join(" ") === asked);
3550
+ if (!exact.length && words.length < given.length) throw unknownCommand(given[0], given[1]);
3475
3551
  const lines = [];
3476
3552
  for (const row of exact) {
3477
3553
  lines.push(`Usage: ghostly ${row.usage}`, "", ` ${row.summary}`);
@@ -3611,12 +3687,13 @@ async function profileCommand(sub, argv) {
3611
3687
  if (parsed.options.passphrase === true) throw new CliError("usage", "The passphrase comes from --passphrase-file or GHOSTLY_BACKUP_PASSPHRASE, never the command line");
3612
3688
  if (unsealed && (parsed.options["passphrase-file"] !== void 0 || process.env.GHOSTLY_BACKUP_PASSPHRASE)) throw new CliError("usage", "A passphrase is set (--passphrase-file or GHOSTLY_BACKUP_PASSPHRASE) and --no-passphrase was given: choose one");
3613
3689
  const light = parsed.options.light === true ? { light: true } : {};
3690
+ const path = here(out);
3614
3691
  const params = unsealed ? {
3615
- path: resolve(out),
3692
+ path,
3616
3693
  noPassphrase: true,
3617
3694
  ...light
3618
3695
  } : {
3619
- path: resolve(out),
3696
+ path,
3620
3697
  passphrase: backupPassphrase(parsed.options["passphrase-file"]),
3621
3698
  ...light
3622
3699
  };
@@ -3629,7 +3706,7 @@ async function profileCommand(sub, argv) {
3629
3706
  const [file, name] = parsed.positionals;
3630
3707
  if (!file || !name) throw new CliError("usage", "ghostly profile restore <file> <new profile> [--passphrase-file f] [--use]");
3631
3708
  noMoreThan(2, parsed.positionals, "profile restore <file> <new profile> [--passphrase-file f] [--use]");
3632
- const { backupFileProtection, restoreProfile } = await import("./backup-CSMw2GHv.js");
3709
+ const { backupFileProtection, restoreProfile } = await import("./backup-Cm8ptQQA.js");
3633
3710
  const protection = await backupFileProtection(file);
3634
3711
  const paths = await restoreProfile(g.home, checkProfileName(name), file, protection === "none" ? void 0 : backupPassphrase(parsed.options["passphrase-file"], RESTORE_PASSPHRASE));
3635
3712
  if (parsed.options.use) selectProfile(g.home, name);
@@ -3640,7 +3717,7 @@ async function profileCommand(sub, argv) {
3640
3717
  });
3641
3718
  return;
3642
3719
  }
3643
- default: throw new CliError("usage", "ghostly profile create|list|use|show|set|picture|backup|restore");
3720
+ default: throw unknownCommand("profile", sub);
3644
3721
  }
3645
3722
  }
3646
3723
  /**
@@ -3670,10 +3747,24 @@ function onStopSignals(stop) {
3670
3747
  var BACKUP_PASSPHRASE = "The backup passphrase comes from --passphrase-file or GHOSTLY_BACKUP_PASSPHRASE (at least 12 characters). For a backup that is not encrypted, say so with --no-passphrase";
3671
3748
  /** Restore takes no --no-passphrase: a backup made without one says so itself. */
3672
3749
  var RESTORE_PASSPHRASE = "This backup is encrypted: its passphrase comes from --passphrase-file or GHOSTLY_BACKUP_PASSPHRASE";
3673
- /** The backup passphrase: from a file or the environment, never the command line (it would sit in shell history). */
3750
+ /**
3751
+ * The backup passphrase: from a file or the environment, never the command line (it would sit in shell history). The
3752
+ * file is said as every other file the CLI reads: "" names none, a folder is named, and an empty one is called that,
3753
+ * not "comes from --passphrase-file" to someone who gave it.
3754
+ */
3674
3755
  function backupPassphrase(file, missing = BACKUP_PASSPHRASE) {
3675
- const value = typeof file === "string" ? readFileSync(file, "utf8").replace(/\r?\n$/, "") : process.env.GHOSTLY_BACKUP_PASSPHRASE;
3676
- if (!value) throw new CliError("usage", missing);
3756
+ if (typeof file !== "string") {
3757
+ const value = process.env.GHOSTLY_BACKUP_PASSPHRASE;
3758
+ if (!value) throw new CliError("usage", missing);
3759
+ return value;
3760
+ }
3761
+ let value;
3762
+ try {
3763
+ value = readFileSync(here(file), "utf8").replace(/\r?\n$/, "");
3764
+ } catch (error) {
3765
+ throw localFileError(error, file) ?? error;
3766
+ }
3767
+ if (!value) throw new CliError("bad_request", `${file} is empty: it holds the backup's passphrase`);
3677
3768
  return value;
3678
3769
  }
3679
3770
  async function runDaemon(g) {
@@ -3772,7 +3863,7 @@ async function daemonCommand(argv) {
3772
3863
  });
3773
3864
  return;
3774
3865
  }
3775
- if (sub) throw new CliError("usage", "ghostly daemon [--detach] [--timeout s] | daemon status | daemon stop | daemon restart");
3866
+ if (sub) throw unknownCommand("daemon", sub);
3776
3867
  const startFor = seconds(parsed, 60);
3777
3868
  if (!parsed.options.detach) {
3778
3869
  await runDaemon(g);
@@ -3900,9 +3991,15 @@ async function listenCommand(argv) {
3900
3991
  }
3901
3992
  });
3902
3993
  noMoreThan(0, parsed.positionals, SPECIAL.find(([usage]) => usage.startsWith("listen "))[0]);
3994
+ const blank = (value) => typeof value === "string" && value.trim() === "";
3995
+ if (blank(parsed.options.webhook)) throw new CliError("usage", "--webhook needs a URL");
3996
+ if (blank(parsed.options.exec)) throw new CliError("usage", "--exec needs a command");
3997
+ if (parsed.options.cursor === "") throw new CliError("usage", "--cursor needs a file");
3998
+ if (parsed.options.from?.some(blank)) throw new CliError("usage", "--from needs a chat or a contact key");
3999
+ if (parsed.options.group?.some(blank)) throw new CliError("usage", "--group needs a group");
3903
4000
  const g = globals(parsed);
3904
4001
  pretty = false;
3905
- if (parsed.options.webhook) checkWebhook(String(parsed.options.webhook));
4002
+ if (parsed.options.webhook !== void 0) checkWebhook(String(parsed.options.webhook));
3906
4003
  const types = parsed.options.type ?? [];
3907
4004
  const turns = parsed.options.turns === true;
3908
4005
  if (turns && types.length) throw new CliError("usage", "--turns takes the place of --type: a turn is a message received, or a group message that mentions you");
@@ -4042,6 +4139,10 @@ async function engineCommand(argv) {
4042
4139
  type: "boolean",
4043
4140
  description: ""
4044
4141
  },
4142
+ force: {
4143
+ type: "boolean",
4144
+ description: ""
4145
+ },
4045
4146
  list: {
4046
4147
  type: "boolean",
4047
4148
  description: ""
@@ -4071,7 +4172,8 @@ async function engineCommand(argv) {
4071
4172
  let result = await withSession(g, (s) => s.call("engine.call", {
4072
4173
  method,
4073
4174
  params,
4074
- confirmReal
4175
+ confirmReal,
4176
+ force: parsed.options.force === true
4075
4177
  }), { offline: ENGINE_READS.includes(method) });
4076
4178
  if (method === "getState" && !showSecret && result && typeof result === "object") {
4077
4179
  const { hideStateSecrets } = await engineApi();
@@ -4104,7 +4206,7 @@ async function settingsCommand(sub, argv) {
4104
4206
  printAnswer(await withSession(g, (s) => s.call("settings.set", { [key]: parsedValue })));
4105
4207
  return;
4106
4208
  }
4107
- throw new CliError("usage", "ghostly settings get | settings set <key> <json-value>");
4209
+ throw unknownCommand("settings", sub);
4108
4210
  }
4109
4211
  async function textCommand(name, argv) {
4110
4212
  const spec = TEXT_COMMANDS[name];
@@ -4118,7 +4220,7 @@ async function textCommand(name, argv) {
4118
4220
  if (parsed.options.text !== void 0) {
4119
4221
  if (text || parsed.options.stdin) throw new CliError("usage", "Give the text once: as arguments, --text or stdin");
4120
4222
  text = String(parsed.options.text);
4121
- } else if (parsed.options.stdin || !text && !process.stdin.isTTY) {
4223
+ } else if (parsed.options.stdin || words.length === 0 && !process.stdin.isTTY) {
4122
4224
  if (text) throw new CliError("usage", "Give the text as arguments or on stdin, not both");
4123
4225
  text = (await readStdin()).replace(/\n$/, "");
4124
4226
  }
@@ -4131,7 +4233,7 @@ async function textCommand(name, argv) {
4131
4233
  };
4132
4234
  if (spec.target === "group") params.mentions = parsed.options.mention ?? [];
4133
4235
  if (parsed.options.reply !== void 0) params.reply = parsed.options.reply;
4134
- if (!spec.message) Object.assign(params, (await import("./buttons-CGRqZ7z0.js")).buttonsOf(parsed.options));
4236
+ if (!spec.message) Object.assign(params, (await import("./buttons-PCvHRlkS.js")).buttonsOf(parsed.options));
4135
4237
  print(await withSession(g, (s) => {
4136
4238
  if (spec.target === "chat") {
4137
4239
  params.wait = parsed.options.wait ?? (s.mode === "one-shot" ? spec.message ? "confirmed" : "sent" : "none");
@@ -4230,14 +4332,23 @@ async function tableCommand(name, argv) {
4230
4332
  const params = command.params?.(parsed, args) ?? {};
4231
4333
  for (const key of Object.keys(params)) if (params[key] === void 0) delete params[key];
4232
4334
  if (command.method === "identity.complete") {
4233
- if (params.evidenceFile) params.evidence = readFileSync(params.evidenceFile, "utf8");
4335
+ if (params.evidenceFile) try {
4336
+ params.evidence = readFileSync(params.evidenceFile, "utf8");
4337
+ } catch (error) {
4338
+ throw localFileError(error, params.evidenceFile) ?? error;
4339
+ }
4234
4340
  else if (params.stdin) params.evidence = await readStdin();
4235
4341
  delete params.evidenceFile;
4236
4342
  delete params.stdin;
4237
4343
  }
4344
+ const lineValues = command.method === "wallet.create" ? Object.keys(params.values ?? {}) : [];
4238
4345
  const secretWarning = await secretsFromStdin(command.method, params, readStdin, process.stdin.isTTY === true);
4239
4346
  if (secretWarning) process.stderr.write(`ghostly: ${secretWarning}\n`);
4240
4347
  printAnswer(await withSession(g, async (s) => {
4348
+ if (lineValues.length) {
4349
+ const warning = secretValuesWarning(await s.call("wallet.list", { network: params.network ?? "testnet" }).catch(() => void 0), params.type, params.provider, lineValues);
4350
+ if (warning) process.stderr.write(`ghostly: ${warning}\n`);
4351
+ }
4241
4352
  const result = await s.call(command.method, params);
4242
4353
  if (command.method === "invite.join" && s.mode === "one-shot") await s.call("chat.wait", {
4243
4354
  chat: result.chat,
@@ -4406,4 +4517,4 @@ async function run(argv) {
4406
4517
  //#endregion
4407
4518
  export { run };
4408
4519
 
4409
- //# sourceMappingURL=main-DC8cUkDx.js.map
4520
+ //# sourceMappingURL=main-DNzQOdNG.js.map