@ghostlytools/cli 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +6 -6
  2. package/dist/assets/{api-Dq2QXQDk.js → api-B1fADtqd.js} +65 -20
  3. package/dist/assets/api-B1fADtqd.js.map +1 -0
  4. package/dist/assets/{api-TiB5QSG9.js → api-Bg7bh_6E.js} +2 -2
  5. package/dist/assets/{apiKit-CA2x5GJF.js → apiKit-BsUZPHLn.js} +44 -33
  6. package/dist/assets/apiKit-BsUZPHLn.js.map +1 -0
  7. package/dist/assets/{apps-BxV9i_Y4.js → apps-BaUNt-5c.js} +91 -24
  8. package/dist/assets/apps-BaUNt-5c.js.map +1 -0
  9. package/dist/assets/{apps-C-T8jeji.js → apps-DepomCiX.js} +1 -1
  10. package/dist/assets/{arkade-C_qDmHnm.js → arkade-CNnMaoEQ.js} +4 -4
  11. package/dist/assets/arkade-CNnMaoEQ.js.map +1 -0
  12. package/dist/assets/{backup-Bugnn3Ue.js → backup-B5Q1W-Ey.js} +1 -1
  13. package/dist/assets/{backup-CnMN2oUK.js → backup-Bbz3lyC5.js} +6 -6
  14. package/dist/assets/{backup-CnMN2oUK.js.map → backup-Bbz3lyC5.js.map} +1 -1
  15. package/dist/assets/buttons-CUd7j0ZA.js +2 -0
  16. package/dist/assets/{buttons-CvNBzh2S.js → buttons-DS1iURp8.js} +4 -4
  17. package/dist/assets/{buttons-CvNBzh2S.js.map → buttons-DS1iURp8.js.map} +1 -1
  18. package/dist/assets/{envelope-WjingYSc.js → envelope-fNC941z4.js} +2 -2
  19. package/dist/assets/{envelope-WjingYSc.js.map → envelope-fNC941z4.js.map} +1 -1
  20. package/dist/assets/{errors-DO5z6h_l.js → errors-B-zMxbcd.js} +6 -5
  21. package/dist/assets/{errors-DO5z6h_l.js.map → errors-B-zMxbcd.js.map} +1 -1
  22. package/dist/assets/{ghostly_iroh_web-Dvzh3Y6E.js → ghostly_iroh_web-BsAEFIjl.js} +30 -36
  23. package/dist/assets/ghostly_iroh_web-BsAEFIjl.js.map +1 -0
  24. package/dist/assets/ghostly_iroh_web_bg.wasm +0 -0
  25. package/dist/assets/{groupFiles-IEUW3DQz.js → groupFiles-6qnLCnOm.js} +82 -29
  26. package/dist/assets/groupFiles-6qnLCnOm.js.map +1 -0
  27. package/dist/assets/{host-CWt-jtqz.js → host-Dj3C1fn3.js} +305 -68
  28. package/dist/assets/host-Dj3C1fn3.js.map +1 -0
  29. package/dist/assets/{hyperdhtRelay-CPYwSLLS.js → hyperdhtRelay-CrgYQN-g.js} +2 -2
  30. package/dist/assets/{hyperdhtRelay-CPYwSLLS.js.map → hyperdhtRelay-CrgYQN-g.js.map} +1 -1
  31. package/dist/assets/{irohWeb-4oChIOIp.js → irohWeb-DSyLp5Re.js} +3 -3
  32. package/dist/assets/{irohWeb-4oChIOIp.js.map → irohWeb-DSyLp5Re.js.map} +1 -1
  33. package/dist/assets/{main-DbqcdShN.js → main-BDd11Yd1.js} +159 -54
  34. package/dist/assets/main-BDd11Yd1.js.map +1 -0
  35. package/dist/assets/{mainline-ZkiOnevs.js → mainline-CLTo0nGa.js} +2 -2
  36. package/dist/assets/{mainline-ZkiOnevs.js.map → mainline-CLTo0nGa.js.map} +1 -1
  37. package/dist/assets/{networkReason-CetKkTPo.js → networkReason-DwcadZz5.js} +2 -2
  38. package/dist/assets/{networkReason-CetKkTPo.js.map → networkReason-DwcadZz5.js.map} +1 -1
  39. package/dist/assets/{nwc-CgkqyvsE.js → nwc-BNFyyECH.js} +3 -3
  40. package/dist/assets/{nwc-CgkqyvsE.js.map → nwc-BNFyyECH.js.map} +1 -1
  41. package/dist/assets/{nwcProvider-4eVz_8HP.js → nwcProvider-BbjJOfLD.js} +2 -2
  42. package/dist/assets/{nwcProvider-4eVz_8HP.js.map → nwcProvider-BbjJOfLD.js.map} +1 -1
  43. package/dist/assets/{profiles-BHc9pEYg.js → profiles-gPCjLOQq.js} +2 -2
  44. package/dist/assets/{profiles-BHc9pEYg.js.map → profiles-gPCjLOQq.js.map} +1 -1
  45. package/dist/assets/{registry-DlAKO0Fy.js → registry-CoLHpxkb.js} +17 -32
  46. package/dist/assets/registry-CoLHpxkb.js.map +1 -0
  47. package/dist/assets/{registry-OBJBnHVQ.js → registry-DoTb6uEr.js} +1 -1
  48. package/dist/assets/{resolve-CouHhCpJ.js → resolve-BIDJ-UUl.js} +18 -3
  49. package/dist/assets/resolve-BIDJ-UUl.js.map +1 -0
  50. package/dist/assets/{server-Ss0gJ-nw.js → server-Bx3RGkBC.js} +380 -116
  51. package/dist/assets/server-Bx3RGkBC.js.map +1 -0
  52. package/dist/assets/{src-DDzSERyc.js → src-D7gcJElT.js} +470 -242
  53. package/dist/assets/src-D7gcJElT.js.map +1 -0
  54. package/dist/assets/{turnRefs-B87dtrHM.js → turnRefs-PN644M9o.js} +2 -2
  55. package/dist/assets/{turnRefs-B87dtrHM.js.map → turnRefs-PN644M9o.js.map} +1 -1
  56. package/dist/assets/{usdt-Fj8CQO2z.js → usdt-B2TLRlYa.js} +3 -3
  57. package/dist/assets/{usdt-Fj8CQO2z.js.map → usdt-B2TLRlYa.js.map} +1 -1
  58. package/dist/ghostly.mjs +1 -1
  59. package/package.json +1 -1
  60. package/dist/assets/api-Dq2QXQDk.js.map +0 -1
  61. package/dist/assets/apiKit-CA2x5GJF.js.map +0 -1
  62. package/dist/assets/apps-BxV9i_Y4.js.map +0 -1
  63. package/dist/assets/arkade-C_qDmHnm.js.map +0 -1
  64. package/dist/assets/buttons-Bj7Vf7Pn.js +0 -2
  65. package/dist/assets/ghostly_iroh_web-Dvzh3Y6E.js.map +0 -1
  66. package/dist/assets/groupFiles-IEUW3DQz.js.map +0 -1
  67. package/dist/assets/host-CWt-jtqz.js.map +0 -1
  68. package/dist/assets/main-DbqcdShN.js.map +0 -1
  69. package/dist/assets/registry-DlAKO0Fy.js.map +0 -1
  70. package/dist/assets/resolve-CouHhCpJ.js.map +0 -1
  71. package/dist/assets/server-Ss0gJ-nw.js.map +0 -1
  72. package/dist/assets/src-DDzSERyc.js.map +0 -1
@@ -1,8 +1,8 @@
1
- import { $a as cardUrl, Ga as ITEM_STATES, Ja as RUN_RESULTS, Ka as PR_CHECKS, Ya as STATUS_CARD_LIMITS, Zn as GROUP_FILE_LIMITS, qa as PR_STATES } from "./src-DDzSERyc.js";
2
- import { a as SECRET_RESULTS, i as ENGINE_READS, r as ENGINE_METHODS, t as turnRef } from "./turnRefs-B87dtrHM.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-CA2x5GJF.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-D7gcJElT.js";
2
+ import { a as SECRET_RESULTS, i as ENGINE_READS, r as ENGINE_METHODS, t as turnRef } from "./turnRefs-PN644M9o.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-BsUZPHLn.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.1",
15
+ version: "1.3.3",
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-C-T8jeji.js")).initApp(a.dir, {
885
+ run: async ({ options }, a) => (await import("./apps-DepomCiX.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-C-T8jeji.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-DepomCiX.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-C-T8jeji.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-DepomCiX.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-C-T8jeji.js")).verifyApp(a.bundle)
1019
+ run: async (_, a) => (await import("./apps-DepomCiX.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-C-T8jeji.js")).signStore(a.index, keyFile(options.key, "store sign <index> --key <file> [--out <dir>]"), options.out)
1036
+ run: async ({ options }, a) => (await import("./apps-DepomCiX.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-TiB5QSG9.js");
3172
- var engineHost = () => import("./host-CWt-jtqz.js");
3244
+ var engineApi = () => import("./api-Bg7bh_6E.js");
3245
+ var engineHost = () => import("./host-Dj3C1fn3.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");
@@ -3467,11 +3540,13 @@ function commandHelp(words) {
3467
3540
  const own = commandWords(row.usage);
3468
3541
  return list.every((word, i) => own[i] === word);
3469
3542
  });
3543
+ const given = words;
3470
3544
  let rows = named(words);
3471
3545
  while (!rows.length && words.length > 1) rows = named(words = words.slice(0, -1));
3472
3546
  const asked = words.join(" ");
3473
3547
  if (!rows.length) throw new CliError("usage", `Unknown command: ${asked} (ghostly help)`);
3474
3548
  const exact = rows.filter((row) => commandWords(row.usage).join(" ") === asked);
3549
+ if (!exact.length && words.length < given.length) throw unknownCommand(given[0], given[1]);
3475
3550
  const lines = [];
3476
3551
  for (const row of exact) {
3477
3552
  lines.push(`Usage: ghostly ${row.usage}`, "", ` ${row.summary}`);
@@ -3611,12 +3686,13 @@ async function profileCommand(sub, argv) {
3611
3686
  if (parsed.options.passphrase === true) throw new CliError("usage", "The passphrase comes from --passphrase-file or GHOSTLY_BACKUP_PASSPHRASE, never the command line");
3612
3687
  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
3688
  const light = parsed.options.light === true ? { light: true } : {};
3689
+ const path = here(out);
3614
3690
  const params = unsealed ? {
3615
- path: resolve(out),
3691
+ path,
3616
3692
  noPassphrase: true,
3617
3693
  ...light
3618
3694
  } : {
3619
- path: resolve(out),
3695
+ path,
3620
3696
  passphrase: backupPassphrase(parsed.options["passphrase-file"]),
3621
3697
  ...light
3622
3698
  };
@@ -3629,7 +3705,7 @@ async function profileCommand(sub, argv) {
3629
3705
  const [file, name] = parsed.positionals;
3630
3706
  if (!file || !name) throw new CliError("usage", "ghostly profile restore <file> <new profile> [--passphrase-file f] [--use]");
3631
3707
  noMoreThan(2, parsed.positionals, "profile restore <file> <new profile> [--passphrase-file f] [--use]");
3632
- const { backupFileProtection, restoreProfile } = await import("./backup-Bugnn3Ue.js");
3708
+ const { backupFileProtection, restoreProfile } = await import("./backup-B5Q1W-Ey.js");
3633
3709
  const protection = await backupFileProtection(file);
3634
3710
  const paths = await restoreProfile(g.home, checkProfileName(name), file, protection === "none" ? void 0 : backupPassphrase(parsed.options["passphrase-file"], RESTORE_PASSPHRASE));
3635
3711
  if (parsed.options.use) selectProfile(g.home, name);
@@ -3640,7 +3716,7 @@ async function profileCommand(sub, argv) {
3640
3716
  });
3641
3717
  return;
3642
3718
  }
3643
- default: throw new CliError("usage", "ghostly profile create|list|use|show|set|picture|backup|restore");
3719
+ default: throw unknownCommand("profile", sub);
3644
3720
  }
3645
3721
  }
3646
3722
  /**
@@ -3670,10 +3746,24 @@ function onStopSignals(stop) {
3670
3746
  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
3747
  /** Restore takes no --no-passphrase: a backup made without one says so itself. */
3672
3748
  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). */
3749
+ /**
3750
+ * The backup passphrase: from a file or the environment, never the command line (it would sit in shell history). The
3751
+ * file is said as every other file the CLI reads: "" names none, a folder is named, and an empty one is called that,
3752
+ * not "comes from --passphrase-file" to someone who gave it.
3753
+ */
3674
3754
  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);
3755
+ if (typeof file !== "string") {
3756
+ const value = process.env.GHOSTLY_BACKUP_PASSPHRASE;
3757
+ if (!value) throw new CliError("usage", missing);
3758
+ return value;
3759
+ }
3760
+ let value;
3761
+ try {
3762
+ value = readFileSync(here(file), "utf8").replace(/\r?\n$/, "");
3763
+ } catch (error) {
3764
+ throw localFileError(error, file) ?? error;
3765
+ }
3766
+ if (!value) throw new CliError("bad_request", `${file} is empty: it holds the backup's passphrase`);
3677
3767
  return value;
3678
3768
  }
3679
3769
  async function runDaemon(g) {
@@ -3772,7 +3862,7 @@ async function daemonCommand(argv) {
3772
3862
  });
3773
3863
  return;
3774
3864
  }
3775
- if (sub) throw new CliError("usage", "ghostly daemon [--detach] [--timeout s] | daemon status | daemon stop | daemon restart");
3865
+ if (sub) throw unknownCommand("daemon", sub);
3776
3866
  const startFor = seconds(parsed, 60);
3777
3867
  if (!parsed.options.detach) {
3778
3868
  await runDaemon(g);
@@ -3900,9 +3990,15 @@ async function listenCommand(argv) {
3900
3990
  }
3901
3991
  });
3902
3992
  noMoreThan(0, parsed.positionals, SPECIAL.find(([usage]) => usage.startsWith("listen "))[0]);
3993
+ const blank = (value) => typeof value === "string" && value.trim() === "";
3994
+ if (blank(parsed.options.webhook)) throw new CliError("usage", "--webhook needs a URL");
3995
+ if (blank(parsed.options.exec)) throw new CliError("usage", "--exec needs a command");
3996
+ if (parsed.options.cursor === "") throw new CliError("usage", "--cursor needs a file");
3997
+ if (parsed.options.from?.some(blank)) throw new CliError("usage", "--from needs a chat or a contact key");
3998
+ if (parsed.options.group?.some(blank)) throw new CliError("usage", "--group needs a group");
3903
3999
  const g = globals(parsed);
3904
4000
  pretty = false;
3905
- if (parsed.options.webhook) checkWebhook(String(parsed.options.webhook));
4001
+ if (parsed.options.webhook !== void 0) checkWebhook(String(parsed.options.webhook));
3906
4002
  const types = parsed.options.type ?? [];
3907
4003
  const turns = parsed.options.turns === true;
3908
4004
  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");
@@ -4104,7 +4200,7 @@ async function settingsCommand(sub, argv) {
4104
4200
  printAnswer(await withSession(g, (s) => s.call("settings.set", { [key]: parsedValue })));
4105
4201
  return;
4106
4202
  }
4107
- throw new CliError("usage", "ghostly settings get | settings set <key> <json-value>");
4203
+ throw unknownCommand("settings", sub);
4108
4204
  }
4109
4205
  async function textCommand(name, argv) {
4110
4206
  const spec = TEXT_COMMANDS[name];
@@ -4118,7 +4214,7 @@ async function textCommand(name, argv) {
4118
4214
  if (parsed.options.text !== void 0) {
4119
4215
  if (text || parsed.options.stdin) throw new CliError("usage", "Give the text once: as arguments, --text or stdin");
4120
4216
  text = String(parsed.options.text);
4121
- } else if (parsed.options.stdin || !text && !process.stdin.isTTY) {
4217
+ } else if (parsed.options.stdin || words.length === 0 && !process.stdin.isTTY) {
4122
4218
  if (text) throw new CliError("usage", "Give the text as arguments or on stdin, not both");
4123
4219
  text = (await readStdin()).replace(/\n$/, "");
4124
4220
  }
@@ -4131,7 +4227,7 @@ async function textCommand(name, argv) {
4131
4227
  };
4132
4228
  if (spec.target === "group") params.mentions = parsed.options.mention ?? [];
4133
4229
  if (parsed.options.reply !== void 0) params.reply = parsed.options.reply;
4134
- if (!spec.message) Object.assign(params, (await import("./buttons-Bj7Vf7Pn.js")).buttonsOf(parsed.options));
4230
+ if (!spec.message) Object.assign(params, (await import("./buttons-CUd7j0ZA.js")).buttonsOf(parsed.options));
4135
4231
  print(await withSession(g, (s) => {
4136
4232
  if (spec.target === "chat") {
4137
4233
  params.wait = parsed.options.wait ?? (s.mode === "one-shot" ? spec.message ? "confirmed" : "sent" : "none");
@@ -4230,14 +4326,23 @@ async function tableCommand(name, argv) {
4230
4326
  const params = command.params?.(parsed, args) ?? {};
4231
4327
  for (const key of Object.keys(params)) if (params[key] === void 0) delete params[key];
4232
4328
  if (command.method === "identity.complete") {
4233
- if (params.evidenceFile) params.evidence = readFileSync(params.evidenceFile, "utf8");
4329
+ if (params.evidenceFile) try {
4330
+ params.evidence = readFileSync(params.evidenceFile, "utf8");
4331
+ } catch (error) {
4332
+ throw localFileError(error, params.evidenceFile) ?? error;
4333
+ }
4234
4334
  else if (params.stdin) params.evidence = await readStdin();
4235
4335
  delete params.evidenceFile;
4236
4336
  delete params.stdin;
4237
4337
  }
4338
+ const lineValues = command.method === "wallet.create" ? Object.keys(params.values ?? {}) : [];
4238
4339
  const secretWarning = await secretsFromStdin(command.method, params, readStdin, process.stdin.isTTY === true);
4239
4340
  if (secretWarning) process.stderr.write(`ghostly: ${secretWarning}\n`);
4240
4341
  printAnswer(await withSession(g, async (s) => {
4342
+ if (lineValues.length) {
4343
+ const warning = secretValuesWarning(await s.call("wallet.list", { network: params.network ?? "testnet" }).catch(() => void 0), params.type, params.provider, lineValues);
4344
+ if (warning) process.stderr.write(`ghostly: ${warning}\n`);
4345
+ }
4241
4346
  const result = await s.call(command.method, params);
4242
4347
  if (command.method === "invite.join" && s.mode === "one-shot") await s.call("chat.wait", {
4243
4348
  chat: result.chat,
@@ -4406,4 +4511,4 @@ async function run(argv) {
4406
4511
  //#endregion
4407
4512
  export { run };
4408
4513
 
4409
- //# sourceMappingURL=main-DbqcdShN.js.map
4514
+ //# sourceMappingURL=main-BDd11Yd1.js.map