@ghostlytools/cli 1.3.9 → 1.4.0

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 (98) hide show
  1. package/README.md +13 -4
  2. package/SKILL.md +7 -4
  3. package/dist/assets/{api-Cp9GxjUO.js → api-BqLfti32.js} +2 -2
  4. package/dist/assets/{api-BCrwJ57L.js → api-JMkEO0RR.js} +130 -50
  5. package/dist/assets/api-JMkEO0RR.js.map +1 -0
  6. package/dist/assets/{apiKit-B4qhnLQT.js → apiKit-BpIUCwa-.js} +3 -3
  7. package/dist/assets/{apiKit-B4qhnLQT.js.map → apiKit-BpIUCwa-.js.map} +1 -1
  8. package/dist/assets/{apps-CpxENMgl.js → apps-BdfyGVZ1.js} +1 -1
  9. package/dist/assets/{apps-DOH1P_S0.js → apps-Bettj7WQ.js} +6 -6
  10. package/dist/assets/apps-Bettj7WQ.js.map +1 -0
  11. package/dist/assets/{arkade-Cyf5MGxG.js → arkade-CP4HvKaa.js} +2 -2
  12. package/dist/assets/{arkade-Cyf5MGxG.js.map → arkade-CP4HvKaa.js.map} +1 -1
  13. package/dist/assets/{backup-CqM-0-2v.js → backup-BbO2zLjx.js} +1 -1
  14. package/dist/assets/{backup-CTpFFQ-4.js → backup-BmBtta66.js} +7 -7
  15. package/dist/assets/{backup-CTpFFQ-4.js.map → backup-BmBtta66.js.map} +1 -1
  16. package/dist/assets/blobFiles-B0rNnHlO.js +35 -0
  17. package/dist/assets/blobFiles-B0rNnHlO.js.map +1 -0
  18. package/dist/assets/{buttons-BtxwyM2l.js → buttons-YgijrprJ.js} +4 -4
  19. package/dist/assets/{buttons-BtxwyM2l.js.map → buttons-YgijrprJ.js.map} +1 -1
  20. package/dist/assets/buttons-_GBlxAti.js +2 -0
  21. package/dist/assets/{endpoint-BOTFMpfS.js → endpoint-Cl3cgAYl.js} +3 -2
  22. package/dist/assets/endpoint-Cl3cgAYl.js.map +1 -0
  23. package/dist/assets/{envelope-DW8TWKPD.js → envelope-C7vnmsJN.js} +2 -2
  24. package/dist/assets/{envelope-DW8TWKPD.js.map → envelope-C7vnmsJN.js.map} +1 -1
  25. package/dist/assets/{errors-B-zMxbcd.js → errors-BLSPp0tK.js} +3 -1
  26. package/dist/assets/errors-BLSPp0tK.js.map +1 -0
  27. package/dist/assets/{fileBytes-Di7VZtmU.js → fileBytes-B-fqGdEd.js} +3 -3
  28. package/dist/assets/{fileBytes-Di7VZtmU.js.map → fileBytes-B-fqGdEd.js.map} +1 -1
  29. package/dist/assets/{fileBytes-CgbibpbV.js → fileBytes-C2jycN_7.js} +1 -1
  30. package/dist/assets/{fileBytes-CfSXjTFa.js → fileBytes-D6fqFQEX.js} +6 -3
  31. package/dist/assets/fileBytes-D6fqFQEX.js.map +1 -0
  32. package/dist/assets/fileBytesIdb-DOxm_BKt.js +2 -0
  33. package/dist/assets/{fileBytesIdb-D5u6H8X_.js → fileBytesIdb-_HnxdacI.js} +3 -3
  34. package/dist/assets/{fileBytesIdb-D5u6H8X_.js.map → fileBytesIdb-_HnxdacI.js.map} +1 -1
  35. package/dist/assets/{fileBytesOpfs-Dd1N4ERH.js → fileBytesOpfs-p_CANncD.js} +3 -3
  36. package/dist/assets/{fileBytesOpfs-Dd1N4ERH.js.map → fileBytesOpfs-p_CANncD.js.map} +1 -1
  37. package/dist/assets/{groupFiles-uxa0U9_n.js → groupFiles-sIdGq4AV.js} +5 -5
  38. package/dist/assets/groupFiles-sIdGq4AV.js.map +1 -0
  39. package/dist/assets/{host-DPy0eaH9.js → host-Drd-c4VO.js} +147 -25
  40. package/dist/assets/host-Drd-c4VO.js.map +1 -0
  41. package/dist/assets/{hyperdhtRelay-De5FT7Am.js → hyperdhtRelay-13aQ4KOf.js} +2 -2
  42. package/dist/assets/{hyperdhtRelay-De5FT7Am.js.map → hyperdhtRelay-13aQ4KOf.js.map} +1 -1
  43. package/dist/assets/{idb-B5L8wuOm.js → idb-CMVgtn2k.js} +59 -6
  44. package/dist/assets/idb-CMVgtn2k.js.map +1 -0
  45. package/dist/assets/{irohWeb-z5QDDWa-.js → irohWeb-DHCxSvHF.js} +2 -2
  46. package/dist/assets/{irohWeb-z5QDDWa-.js.map → irohWeb-DHCxSvHF.js.map} +1 -1
  47. package/dist/assets/{main-Dt7xrqtb.js → main-BWAwKY4f.js} +476 -141
  48. package/dist/assets/main-BWAwKY4f.js.map +1 -0
  49. package/dist/assets/{mainline-Cld-bKeC.js → mainline-CsG-DdJ5.js} +2 -2
  50. package/dist/assets/{mainline-Cld-bKeC.js.map → mainline-CsG-DdJ5.js.map} +1 -1
  51. package/dist/assets/{networkReason-vUHo6A_C.js → networkReason-B91upMCb.js} +3 -3
  52. package/dist/assets/{networkReason-vUHo6A_C.js.map → networkReason-B91upMCb.js.map} +1 -1
  53. package/dist/assets/{nwc-zXpmd2FN.js → nwc-BFv67iSz.js} +3 -3
  54. package/dist/assets/{nwc-zXpmd2FN.js.map → nwc-BFv67iSz.js.map} +1 -1
  55. package/dist/assets/{nwcProvider-D482SQ1a.js → nwcProvider-BKNFdH9v.js} +2 -2
  56. package/dist/assets/{nwcProvider-D482SQ1a.js.map → nwcProvider-BKNFdH9v.js.map} +1 -1
  57. package/dist/assets/{profiles-gPCjLOQq.js → profiles-DSxPYdlU.js} +13 -3
  58. package/dist/assets/profiles-DSxPYdlU.js.map +1 -0
  59. package/dist/assets/{registry-CxsDR3c6.js → registry-CHPd_hf7.js} +1 -1
  60. package/dist/assets/{registry-OVx2kY3d.js → registry-qbnWmwCE.js} +3 -3
  61. package/dist/assets/registry-qbnWmwCE.js.map +1 -0
  62. package/dist/assets/{resolve-CPTAnu-9.js → resolve-B63_uSu7.js} +2 -2
  63. package/dist/assets/{resolve-CPTAnu-9.js.map → resolve-B63_uSu7.js.map} +1 -1
  64. package/dist/assets/{restoredRows-Db3oUTko.js → restoredRows-DeBImGRd.js} +2 -2
  65. package/dist/assets/{restoredRows-Db3oUTko.js.map → restoredRows-DeBImGRd.js.map} +1 -1
  66. package/dist/assets/{server-p5DJnN7T.js → server-Cd1dLOB8.js} +1485 -240
  67. package/dist/assets/server-Cd1dLOB8.js.map +1 -0
  68. package/dist/assets/{src-Dg1bvMNv.js → src-DzXL-GYu.js} +7277 -5975
  69. package/dist/assets/src-DzXL-GYu.js.map +1 -0
  70. package/dist/assets/turnRefs-BCtM10d3.js +357 -0
  71. package/dist/assets/turnRefs-BCtM10d3.js.map +1 -0
  72. package/dist/assets/{usdt-BY5OIIvG.js → usdt-B7JWDhQV.js} +3 -3
  73. package/dist/assets/{usdt-BY5OIIvG.js.map → usdt-B7JWDhQV.js.map} +1 -1
  74. package/dist/ghostly.mjs +1 -1
  75. package/package.json +7 -6
  76. package/skills/ghostly/README.md +41 -0
  77. package/skills/ghostly/SKILL.md +79 -0
  78. package/skills/ghostly/scripts/listen +67 -0
  79. package/skills/ghostly/scripts/react +25 -0
  80. package/skills/ghostly/scripts/say +44 -0
  81. package/skills/ghostly/scripts/status +27 -0
  82. package/dist/assets/api-BCrwJ57L.js.map +0 -1
  83. package/dist/assets/apps-DOH1P_S0.js.map +0 -1
  84. package/dist/assets/buttons-CJqNkG-Q.js +0 -2
  85. package/dist/assets/endpoint-BOTFMpfS.js.map +0 -1
  86. package/dist/assets/errors-B-zMxbcd.js.map +0 -1
  87. package/dist/assets/fileBytes-CfSXjTFa.js.map +0 -1
  88. package/dist/assets/fileBytesIdb-CFP-DSkZ.js +0 -2
  89. package/dist/assets/groupFiles-uxa0U9_n.js.map +0 -1
  90. package/dist/assets/host-DPy0eaH9.js.map +0 -1
  91. package/dist/assets/idb-B5L8wuOm.js.map +0 -1
  92. package/dist/assets/main-Dt7xrqtb.js.map +0 -1
  93. package/dist/assets/profiles-gPCjLOQq.js.map +0 -1
  94. package/dist/assets/registry-OVx2kY3d.js.map +0 -1
  95. package/dist/assets/server-p5DJnN7T.js.map +0 -1
  96. package/dist/assets/src-Dg1bvMNv.js.map +0 -1
  97. package/dist/assets/turnRefs-WnggXm2T.js +0 -323
  98. package/dist/assets/turnRefs-WnggXm2T.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"apps-DOH1P_S0.js","names":[],"sources":["../../src/appTemplate.ts","../../src/apps.ts"],"sourcesContent":["/*\n * What `ghostly app init` writes (WISP 1200, Apps · Publishing): the smallest app that does something with a contact.\n * Three files, each whole in this module, so the CLI's one built file carries them: the manifest as a publisher writes\n * it, a page that says hello to the same app on the contact's side, and a README with the way from here to a store.\n */\n\nexport interface AppTemplate { name: string; title: string }\n\nconst html = (text: string) => text.replace(/[&<>\"]/g, (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\" })[c]!);\n\n/** `ghostly-app.json`: the manifest without `publisher`, `sequence` and `files`, which `app publish` writes. */\nexport function templateManifest({ name, title }: AppTemplate): Record<string, unknown> {\n return {\n name,\n version: \"0.1.0\",\n kind: \"mini-app\",\n title,\n tagline: \"Say hello to a contact\",\n description: \"Open it in a chat and your contact gets a card to join. Each side can then say hello to the other.\",\n entry: \"index.html\",\n permissions: [\"chat\"],\n view: \"chat\",\n runtime: { host: \">=1.2\", clients: [\"web\", \"desktop\"] },\n license: \"MIT\",\n };\n}\n\n/**\n * `index.html`: the whole app in one file, as the sandbox needs it (no network, so nothing is loaded from elsewhere).\n * Its script is plain JavaScript typed with JSDoc against `@ghostlytools/sdk/app`, so it runs as written and still type\n * checks (test/appInit.test.ts does). The title is the only thing put into it, as text. Its type imports are in single\n * quotes: they are text of the page, not imports of the CLI's own build (test/packageDeps.test.ts reads those).\n */\nexport function templatePage({ title }: AppTemplate): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${html(title)}</title>\n<style>\n :root { color-scheme: light dark; --bg: #ffffff; --fg: #16181d; --muted: #5b6270; --line: #d9dde3; --accent: #2f6fed; }\n :root[data-theme=\"dark\"] { --bg: #16181d; --fg: #f2f4f7; --muted: #a3abb8; --line: #343a45; --accent: #7aa5ff; }\n body { margin: 0; padding: 20px; background: var(--bg); color: var(--fg); font: 16px/1.4 system-ui, sans-serif; }\n h1 { margin: 0 0 4px; font-size: 20px; }\n #status { margin: 0 0 16px; color: var(--muted); }\n button { font: inherit; padding: 8px 16px; border: 1px solid var(--accent); border-radius: 8px; background: var(--accent); color: var(--bg); }\n button:disabled { opacity: 0.5; }\n ul { margin: 16px 0 0; padding: 0; list-style: none; }\n li { padding: 8px 0; border-top: 1px solid var(--line); }\n</style>\n</head>\n<body>\n<h1>${html(title)}</h1>\n<p id=\"status\" role=\"status\">Starting</p>\n<button id=\"hello\" type=\"button\" disabled>Say hello</button>\n<ul id=\"log\" aria-label=\"Messages\"></ul>\n<script type=\"module\">\n// The types of the API come from the SDK (npm install --save-dev --prefix .. @ghostlytools/sdk); nothing of it runs here.\n/** @typedef {import('@ghostlytools/sdk/app').MiniAppApi} MiniAppApi */\n/** @typedef {import('@ghostlytools/sdk/app').MiniAppJson} MiniAppJson */\n\n/** The longest text shown from a contact's frame. */\nconst MAX_TEXT = 200;\n\nconst status = /** @type {HTMLElement} */ (document.getElementById(\"status\"));\nconst button = /** @type {HTMLButtonElement} */ (document.getElementById(\"hello\"));\nconst log = /** @type {HTMLElement} */ (document.getElementById(\"log\"));\n\n/** @param {string} text */\nfunction show(text) {\n const line = document.createElement(\"li\");\n line.textContent = text;\n log.append(line);\n}\n\n/**\n * What the contact's app sent, when it is a hello. A frame comes from another device: read only what this app sends,\n * and never put it in the page as HTML.\n * @param {MiniAppJson} data\n * @returns {string | null}\n */\nfunction helloOf(data) {\n if (!data || typeof data !== \"object\" || Array.isArray(data) || data.type !== \"hello\") return null;\n return typeof data.text === \"string\" && data.text.length <= MAX_TEXT ? data.text : null;\n}\n\n/**\n * Why a send was refused, in words. The code is the rejection's message (MINI_APP_ERROR_CODES in the SDK); a later\n * client may add one.\n * @param {unknown} error\n */\nfunction refusal(error) {\n switch (error instanceof Error ? error.message : \"\") {\n case \"offline\": return \"Not sent: you are not both online\";\n case \"peer-closed\": case \"not-open\": return \"Not sent: your contact has not opened this app\";\n case \"not-allowed\": return \"Open this app in a chat to say hello\";\n default: return \"Not sent\";\n }\n}\n\n/** @param {MiniAppApi} ghostly */\nasync function start(ghostly) {\n // Listen before anything is awaited, so no frame is missed.\n ghostly.chat.on(\"message\", (data) => {\n const text = helloOf(data);\n if (text !== null) show(\"Your contact: \" + text);\n });\n ghostly.chat.on(\"peer\", (peer) => {\n status.textContent = peer.open ? \"Your contact has this app open\" : \"Waiting for your contact to open this app\";\n });\n\n const context = await ghostly.context();\n const dark = context.theme ? context.theme === \"dark\" : matchMedia(\"(prefers-color-scheme: dark)\").matches;\n document.documentElement.dataset.theme = dark ? \"dark\" : \"light\";\n status.textContent = !context.inChat ? \"Open this app in a chat to say hello\"\n : context.peer ? \"Your contact has this app open\" : \"Waiting for your contact to open this app\";\n\n button.disabled = false;\n button.addEventListener(\"click\", async () => {\n try {\n await ghostly.chat.send({ type: \"hello\", text: \"Hello!\" });\n show(\"You: Hello!\");\n } catch (error) {\n show(refusal(error));\n }\n });\n}\n\n/** Ghostly's runner gives the app \\`window.ghostly\\`; a browser tab has none. */\nconst ghostly = /** @type {{ ghostly?: MiniAppApi }} */ (window).ghostly;\nif (ghostly) void start(ghostly);\nelse status.textContent = \"This page is a Ghostly app: publish it and open it in Ghostly\";\n</script>\n</body>\n</html>\n`;\n}\n\n/** `README.md`: what the folder holds and the way from it to a store. */\nexport function templateReadme({ name, title }: AppTemplate): string {\n return `# ${title}\n\nA [Ghostly](https://github.com/MiguelMedeiros/ghostly) app, started with \\`ghostly app init\\`. Opened in a 1:1 chat, it\ngives your contact a card to join, and each side can say hello to the other.\n\nA Ghostly app ([WISP 1200](https://github.com/MiguelMedeiros/ghostly/blob/dev/docs/wisps/1200-marketplace.md)) is a\npage that runs in the client's sandbox. It has no network, and talks to Ghostly only through \\`window.ghostly\\`:\nframes of at most 32 KiB to the same app on the contact's side (\\`chat.send\\`, \\`chat.on\\`), storage per app and per\nchat, and the files of its own bundle. Apps run in Ghostly from release 1.2.\n\n| File | What it is |\n|---|---|\n| \\`ghostly-app.json\\` | The manifest, without \\`publisher\\`, \\`sequence\\` and \\`files\\`: \\`ghostly app publish\\` writes those |\n| \\`index.html\\` | The app, whole: styles and script inline |\n| \\`README.md\\` | This file |\n\n## Write it\n\n\\`index.html\\` is plain JavaScript, typed with JSDoc against \\`@ghostlytools/sdk/app\\`: \\`MiniAppApi\\` is\n\\`window.ghostly\\`, and the same module has the limits (\\`MINI_APP_LIMITS\\`) and the codes a refused call rejects with\n(\\`MINI_APP_ERROR_CODES\\`). For the types in your editor, install it in the folder above this one, where your editor\nstill finds it: \\`ghostly app publish\\` takes every file of this folder, so \\`node_modules\\` and \\`package.json\\` stay\nout of it.\n\n\\`\\`\\`sh\nnpm install --save-dev --prefix .. @ghostlytools/sdk\n\\`\\`\\`\n\n- What a contact's app sends is untrusted: check its shape, bound its size, and show it as text, never as HTML.\n- \\`\"view\": \"chat\"\\` runs the app inside a chat with one contact. \\`\"full\"\\` is a full-screen app opened from the Apps page.\n- \\`permissions\\` lists what the person is asked for: \\`chat\\` (frames to the contact's app), \\`name\\` (the person's\n display name) and \\`internet\\`. Adding one later makes every install ask before it updates.\n\n## Build\n\nThere is nothing to build yet: the page is the app. Once it grows into several source files, or TypeScript, build it\ninto one self-contained \\`index.html\\` (scripts, styles and images inline) and put that file and \\`ghostly-app.json\\`\nalone in a folder of their own, which is the folder you publish.\n[ghostly-chess](https://github.com/MiguelMedeiros/ghostly-chess) does it with Vite and a small\n\\`scripts/stage.mjs\\`. \\`ghostly app publish\\` takes every file of the folder it is given but dot files, this README\nincluded, so do not run it on a repository with sources, \\`node_modules\\` or anything private in it.\n\n## Publish\n\n\\`\\`\\`sh\nghostly app publish . --key ~/ghostly-keys/${name}-publisher.key\nghostly app verify app.ghostlyapp\n\\`\\`\\`\n\n\\`publish\\` signs the folder into \\`app.ghostlyapp\\`. The first run makes the publisher key at the path you name:\n\n- Keep it **outside this folder** and its repository (\\`publish\\` refuses a key inside it), and never commit it.\n- Back it up. An app is updated only with the key that signed it; a lost key means a new app.\n\n\\`verify\\` checks the bundle as Ghostly will and prints its \\`ref\\` (\\`<publisher key>/${name}\\`), \\`sequence\\` and\n\\`digest\\`. For the next version, raise \\`version\\` in \\`ghostly-app.json\\` and publish again: the sequence goes up\nby one by itself.\n\n## Try it\n\nCommit \\`app.ghostlyapp\\` to a public GitHub repository, then in Ghostly open Apps, choose Add, and paste its URL\n(\\`https://raw.githubusercontent.com/<owner>/<repo>/HEAD/app.ghostlyapp\\`). Set \\`sources\\` in \\`ghostly-app.json\\`\nto that URL, in a list, so installs made from it find your updates. Open the app from a chat's + menu; your contact\ngets a card to install and open it.\n\n## List it in a store\n\nA store is a signed list of apps. The official one is a Git repository,\n[ghostly-store](https://github.com/MiguelMedeiros/ghostly-store), and apps enter it by pull request:\n\n1. Commit \\`app.ghostlyapp\\` to your public repository and note that commit.\n2. Add \\`apps/${name}.<first 16 characters of your publisher key>/listing.json\\` with the \\`ref\\`, \\`sequence\\`\n and \\`digest\\` that \\`ghostly app verify\\` printed. Its \\`urls\\` start with a jsDelivr URL pinned to the full commit\n (\\`https://cdn.jsdelivr.net/gh/<owner>/<repo>@<40-character commit>/app.ghostlyapp\\`).\n3. Open the pull request. Its check verifies the bundle; a maintainer reviews the app and the owner signs the index.\n\nThe store's [CONTRIBUTING](https://github.com/MiguelMedeiros/ghostly-store/blob/main/CONTRIBUTING.md) has the rules.\nAn app installed from a store updates only to the version that store lists, so each new version is a pull request too.\n\n## If something goes wrong\n\nIf a version is harmful, \\`ghostly app revoke . --key <your key> --up-to <sequence>\\` writes \\`ghostly-revoke.json\\`;\npublish it beside \\`app.ghostlyapp\\` and Ghostly stops those versions.\n`;\n}\n","import { createHash, createPrivateKey, generateKeyPairSync } from \"node:crypto\";\nimport { closeSync, existsSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync, writeSync } from \"node:fs\";\nimport { basename, dirname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport {\n APP_BUNDLE_LIMITS, APP_PREFIXES, appFingerprint, appRef, appRevocationFor, appStoreDecision, assembleAppBundle, canonicalJson, canonicalJsonBytes,\n APP_STATEMENT_LIMITS, checkAppManifest, checkAppRevokeStatement, checkAppStoreIndex, fromBase64Url, isAppUrl, readAppBundle,\n readAppRevocations, readAppStore, seedSigner, signAppObject, signAppRevocation, signAppStore, toBase64Url, toZ32, type AppBundle,\n type AppManifest, type AppRevokeStatement, type AppStoreIndex, type AppStoreReading, type SignedAppRevocation, type Signer,\n} from \"@ghostly/core\";\nimport { templateManifest, templatePage, templateReadme } from \"./appTemplate\";\nimport { CliError } from \"./errors\";\n\n/*\n * The publisher tools of WISP 1200 (Apps · Publishing): `app init` writes a folder to start from, `app publish` bundles\n * and signs a folder, `app verify` checks a bundle as a client would, `store sign` signs a store index. They run here, on files: no profile, no daemon, no\n * network but the one read `app verify` makes of a URL.\n *\n * Keys. A publisher key and a store key are Ed25519 keys of their own, never a profile's (WISP 1200 · Publisher\n * identity). Each lives in a file the command names with `--key`: a PKCS #8 PEM block (what `openssl genpkey -algorithm\n * ed25519` writes, so secret scanners know it for a private key), after one line that says which kind it is. The file\n * is made owner-only (0600) when a command makes it, and refused when anyone else may read it. A key is never printed:\n * only its public key and fingerprint are.\n */\n\nexport const APP_SOURCE = \"ghostly-app.json\";\nexport const APP_BUNDLE_FILE = \"app.ghostlyapp\";\nexport const APP_REVOKE_FILE = \"ghostly-revoke.json\";\nexport const STORE_INDEX_FILE = \"ghostly-store.json\";\nexport const STORE_SIG_FILE = \"ghostly-store.sig\";\n\n/** How long `app verify` waits for a URL, and how many redirects it follows (each checked as the first). */\nconst FETCH_TIMEOUT_MS = 60_000;\nconst MAX_REDIRECTS = 5;\n\n// ---------- keys ----------\n\nexport type KeyKind = \"publisher\" | \"store\";\n\nconst KEY_HEADER: Record<KeyKind, string> = {\n publisher: \"Ghostly publisher key (WISP 1200). Secret: never share or commit it. Back it up: an app signed with it is updated only with it.\",\n store: \"Ghostly store key (WISP 1200). Secret: keep it offline, never share or commit it. Back it up: a store is its key.\",\n};\nconst PEM = /-----BEGIN PRIVATE KEY-----\\r?\\n([A-Za-z0-9+/=\\r\\n]+)-----END PRIVATE KEY-----/;\n\nexport interface LoadedKey { signer: Signer; key: string; created: boolean }\n\n/** Refuses a key file that is not a plain file of this user's, or that anyone else may read or write. */\nfunction checkKeyFile(path: string): void {\n const info = statSync(path);\n if (!info.isFile()) throw new CliError(\"bad_request\", `${path} is not a file: --key names a key file`);\n if (process.platform === \"win32\") return;\n if (process.getuid && info.uid !== process.getuid()) throw new CliError(\"refused\", `${path} is not this user's: a key file must be yours alone`);\n if ((info.mode & 0o077) !== 0) {\n throw new CliError(\"refused\", `${path} may be read by others (mode ${(info.mode & 0o777).toString(8)}): run chmod 600 ${path}, and think whether the key was seen`);\n }\n}\n\n/** Reads a key file: the PEM block, an Ed25519 key, and the kind its first line names (when it names one). */\nfunction readKey(path: string, kind: KeyKind): LoadedKey {\n checkKeyFile(path);\n const text = readFileSync(path, \"utf8\");\n const other: KeyKind = kind === \"publisher\" ? \"store\" : \"publisher\";\n if (text.startsWith(`Ghostly ${other} key`)) {\n throw new CliError(\"refused\", `${path} is a ${other} key: ${kind === \"publisher\" ? \"a store key never signs an app\" : \"a publisher key never signs a store index\"} (WISP 1200 · Stores)`);\n }\n const block = PEM.exec(text);\n let seed: Uint8Array | undefined;\n if (block) {\n try {\n const key = createPrivateKey(block[0]);\n const d = key.asymmetricKeyType === \"ed25519\" ? key.export({ format: \"jwk\" }).d : undefined;\n if (d) seed = fromBase64Url(d);\n } catch { /* said below, without the file's text */ }\n }\n if (!seed || seed.length !== 32) throw new CliError(\"bad_request\", `${path} holds no Ed25519 private key (a PEM \"PRIVATE KEY\" block, as openssl genpkey -algorithm ed25519 writes)`);\n const signer = seedSigner(seed);\n return { signer, key: toZ32(signer.publicKey), created: false };\n}\n\n/** Makes a key file: owner-only from the start, never over a file that is there. */\nfunction makeKey(path: string, kind: KeyKind): LoadedKey {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\n const { privateKey } = generateKeyPairSync(\"ed25519\");\n const pem = privateKey.export({ type: \"pkcs8\", format: \"pem\" }) as string;\n const fd = openSync(path, \"wx\", 0o600);\n try { writeSync(fd, `${KEY_HEADER[kind]}\\n${pem}`); } finally { closeSync(fd); }\n return { ...readKey(path, kind), created: true };\n}\n\n/** What the person is told on stderr when a key was made: where, which, and to back it up. Never the key. */\nfunction madeKeyNote(path: string, kind: KeyKind, key: string): string {\n return kind === \"publisher\"\n ? `new publisher key written to ${path}; back it up (${appFingerprint(key)}: an app is updated only with the key that signed it, and phase 1 has no recovery)`\n : `new store key written to ${path}; back it up and keep it offline (${appFingerprint(key)}: a store is its key)`;\n}\n\n// ---------- reading bundles ----------\n\nconst refused = (what: string, reading: { reason: string; detail?: string }, extra: Record<string, unknown> = {}) =>\n new CliError(\"refused\", `${what} (${reading.reason}${reading.detail ? `: ${reading.detail}` : \"\"})`, { reason: reading.reason, ...(reading.detail ? { detail: reading.detail } : {}), ...extra });\n\n/** A bundle file's bytes: its size is checked before it is read, so a huge file is refused unread. */\nfunction readBundleFile(path: string): Uint8Array {\n const info = statSync(path);\n if (info.isDirectory()) throw new CliError(\"bad_request\", `${path} is a folder: name a .ghostlyapp file`);\n if (info.size > APP_BUNDLE_LIMITS.bundleBytes) throw refused(`${path} is not a valid bundle`, { reason: \"too-large\", detail: `${info.size} bytes` });\n return new Uint8Array(readFileSync(path));\n}\n\n/**\n * A bundle at an https URL, as a client fetches one: `https:` only, no user or password, jsDelivr only at a full\n * commit (WISP 1200 · Manifest, \"Every URL\"); every redirect is held to the same rule, and the download stops past\n * 16 MiB.\n */\nasync function fetchBundle(url: string): Promise<Uint8Array> {\n let at = url;\n for (let hop = 0; ; hop++) {\n if (!isAppUrl(at)) {\n throw new CliError(hop ? \"refused\" : \"usage\", hop\n ? `${url} redirects to ${at}, which a client does not read (https only, no user or password, jsDelivr only at a full commit)`\n : `Not a URL a client reads: https only, at most ${APP_BUNDLE_LIMITS.url} characters, no user or password, and jsDelivr only as https://cdn.jsdelivr.net/gh/<owner>/<repo>@<40-hex commit>/<path>`);\n }\n let response: Response;\n try {\n response = await fetch(at, { redirect: \"manual\", signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\n } catch (error) {\n if (error instanceof Error && error.name === \"TimeoutError\") throw new CliError(\"timeout\", `${at} did not answer in ${FETCH_TIMEOUT_MS / 1000} s`);\n throw new CliError(\"unavailable\", `Could not read ${at}: ${error instanceof Error ? error.message : String(error)}`);\n }\n if (response.status >= 300 && response.status < 400 && response.headers.get(\"location\")) {\n await response.body?.cancel();\n if (hop >= MAX_REDIRECTS) throw new CliError(\"refused\", `${url} redirects more than ${MAX_REDIRECTS} times`);\n at = new URL(response.headers.get(\"location\")!, at).href;\n continue;\n }\n if (response.status === 404 || response.status === 410) { await response.body?.cancel(); throw new CliError(\"not_found\", `${at} answered ${response.status}`); }\n if (!response.ok || !response.body) { await response.body?.cancel(); throw new CliError(\"unavailable\", `${at} answered ${response.status}`); }\n const limit = APP_BUNDLE_LIMITS.bundleBytes;\n const tooLarge = (bytes: number | string) => refused(`${url} is not a valid bundle`, { reason: \"too-large\", detail: `${bytes} bytes` });\n const length = Number(response.headers.get(\"content-length\"));\n if (Number.isFinite(length) && length > limit) { await response.body.cancel(); throw tooLarge(length); }\n const chunks: Uint8Array[] = [];\n let total = 0;\n const reader = response.body.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.length;\n if (total > limit) { await reader.cancel(); throw tooLarge(`more than ${limit}`); }\n chunks.push(value);\n }\n } catch (error) {\n if (error instanceof CliError) throw error;\n if (error instanceof Error && error.name === \"TimeoutError\") throw new CliError(\"timeout\", `${at} did not finish in ${FETCH_TIMEOUT_MS / 1000} s`);\n throw new CliError(\"unavailable\", `Could not read ${at}: ${error instanceof Error ? error.message : String(error)}`);\n }\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) { out.set(chunk, offset); offset += chunk.length; }\n return out;\n }\n}\n\n/** What a checked bundle is, as `app verify` and `app publish` print it. */\nfunction described(bundle: AppBundle, bytes: Uint8Array) {\n const m = bundle.manifest;\n return {\n ref: appRef(m.publisher, m.name), publisher: m.publisher, fingerprint: appFingerprint(m.publisher), name: m.name,\n title: m.title, version: m.version, sequence: m.sequence, digest: bundle.digest, bytes: bytes.length,\n sha256: toBase64Url(createHash(\"sha256\").update(bytes).digest()),\n };\n}\n\n/** `app verify <bundle|url>`: every check a client makes before it stores a bundle (WISP 1200 · Reading a bundle). */\nexport async function verifyApp(source: string): Promise<Record<string, unknown>> {\n const { bundle, bytes } = await checkedBundle(source);\n return { valid: true, ...described(bundle, bytes), manifest: bundle.manifest };\n}\n\n/**\n * The `ghostly-revoke.json` beside a bundle, as a client reads it beside each bundle URL: beside the URL (without its\n * query), or beside the file. None there, one that does not answer, or one a client would not read, revokes nothing.\n */\nasync function revocationsBeside(source: string, isUrl: boolean): Promise<{ file: string; revocations: SignedAppRevocation[] }> {\n let file: string;\n let bytes: Uint8Array;\n try {\n if (isUrl) {\n const at = new URL(source);\n at.search = \"\";\n at.hash = \"\";\n file = new URL(APP_REVOKE_FILE, at).href;\n bytes = await fetchBundle(file);\n } else {\n file = join(dirname(resolve(source)), APP_REVOKE_FILE);\n if (!existsSync(file)) return { file, revocations: [] };\n bytes = readBundleFile(file);\n }\n } catch { return { file: source, revocations: [] }; }\n const read = readAppRevocations(bytes);\n return { file, revocations: read.ok ? read.revocations : [] };\n}\n\n/**\n * A bundle from a file or an https URL, with every check of `app verify`; `url` when it came from one. A version its\n * publisher revoked in the `ghostly-revoke.json` beside it is refused: a client stops it, with no \"Run anyway\".\n */\nexport async function checkedBundle(source: string): Promise<{ bundle: AppBundle; bytes: Uint8Array; url?: string }> {\n const isUrl = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(source);\n const bytes = isUrl ? await fetchBundle(source) : readBundleFile(resolve(source));\n const read = readAppBundle(bytes);\n if (!read.ok) throw refused(`${source} is not a valid bundle`, read);\n const m = read.bundle.manifest;\n const { file, revocations } = await revocationsBeside(source, isUrl);\n const revocation = appRevocationFor({ ref: appRef(m.publisher, m.name), sequence: m.sequence, digest: read.bundle.digest }, revocations);\n if (revocation) {\n throw refused(`${source} is a version its publisher revoked in ${file}: a client stops it`, { reason: \"revoked\", ...(revocation.statement.reason !== undefined && { detail: revocation.statement.reason }) }, { file });\n }\n return { bundle: read.bundle, bytes, ...(isUrl && { url: source }) };\n}\n\n// ---------- starting an app ----------\n\n/** A folder's name as an app's: lower case, anything else a hyphen (`My App` is `my-app`). */\nconst nameFrom = (folder: string) => folder.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n/** An app's name as a title: `my-app` is `My app`. */\nconst titleFrom = (name: string) => (name[0]!.toUpperCase() + name.slice(1)).replace(/-/g, \" \");\n\n/**\n * `app init <dir> [--name <name>] [--title <title>] [--force]`: a small working app in `<dir>` (made when missing),\n * which `app publish <dir>` signs as it is: `ghostly-app.json`, an `index.html` that says hello to the contact, and a\n * README with the way to a store (./appTemplate.ts). The name is `--name`, else the folder's; the title `--title`, else\n * the name's. Nothing is written when one of the three files is there, unless `--force`, which replaces those three only.\n */\nexport async function initApp(dirArg: string, what: { name?: string; title?: string; force?: boolean } = {}): Promise<Record<string, unknown>> {\n const usage = \"ghostly app init <dir> [--name <name>] [--title <title>] [--force]\";\n const dir = resolve(dirArg);\n const name = what.name ?? nameFrom(basename(dir));\n const title = what.title ?? (name ? titleFrom(name) : \"\");\n const page = templatePage({ name, title });\n // Checked as publish will check it, under a key of zeros and the page's own entry: what is written always publishes.\n const pageBytes = new TextEncoder().encode(page);\n const checked = checkAppManifest({\n ...templateManifest({ name, title }), ghostlyApp: 1, publisher: toZ32(new Uint8Array(32)), sequence: 1,\n files: [{ path: \"index.html\", size: pageBytes.length, sha256: toBase64Url(createHash(\"sha256\").update(pageBytes).digest()) }],\n }, { strict: true });\n if (!checked.ok) {\n if (checked.detail === \"name is not valid\") {\n throw new CliError(\"usage\", what.name === undefined\n ? `The folder's name gives no app name (${JSON.stringify(basename(dir))}): give --name, a lowercase letter then up to 31 lowercase letters, digits or hyphens: ${usage}`\n : `--name is a lowercase letter then up to 31 lowercase letters, digits or hyphens, not ${JSON.stringify(what.name)}`);\n }\n if (checked.detail === \"title is not valid\") throw new CliError(\"usage\", `--title is one line of 1 to ${APP_BUNDLE_LIMITS.title} characters, not ${JSON.stringify(title)}`);\n throw refused(\"The template makes no valid manifest\", checked);\n }\n\n if (existsSync(dir) && !statSync(dir).isDirectory()) throw new CliError(\"bad_request\", `${dir} is a file: name a folder for the app`);\n const files: [string, string][] = [\n [APP_SOURCE, `${JSON.stringify(templateManifest({ name, title }), null, 2)}\\n`],\n [\"index.html\", page],\n [\"README.md\", templateReadme({ name, title })],\n ];\n const held = files.map(([path]) => path).filter((path) => existsSync(join(dir, path)));\n if (held.length && !what.force) {\n throw refused(`${dir} already holds ${held.join(\", \")}: nothing was written (--force replaces ${held.length > 1 ? \"them\" : \"it\"})`, { reason: \"exists\" }, { files: held });\n }\n mkdirSync(dir, { recursive: true });\n for (const [path, text] of files) writeWhole(join(dir, path), new TextEncoder().encode(text));\n return {\n dir, name, title, files: files.map(([path]) => path), ...(held.length ? { replaced: held } : {}),\n next: `ghostly app publish ${dirArg} --key <a key file outside ${dirArg}>`,\n };\n}\n\n// ---------- publishing ----------\n\n/** Every file of the folder, as the bundle names it; dot files and folders are left out and listed. */\nfunction collectFiles(dir: string, leaveOut: Set<string>): { files: { path: string; full: string; size: number }[]; skipped: string[] } {\n const files: { path: string; full: string; size: number }[] = [];\n const skipped: string[] = [];\n let bytes = 0;\n const walk = (folder: string) => {\n for (const name of readdirSync(folder).sort()) {\n const full = join(folder, name);\n const path = relative(dir, full).split(sep).join(\"/\");\n if (leaveOut.has(full)) continue;\n if (name.startsWith(\".\")) { skipped.push(path); continue; }\n const info = lstatSync(full);\n if (info.isSymbolicLink()) throw new CliError(\"bad_request\", `${path} is a link: a bundle holds the folder's own files (copy it in, or remove it)`);\n if (info.isDirectory()) { walk(full); continue; }\n if (!info.isFile()) throw new CliError(\"bad_request\", `${path} is not a file`);\n files.push({ path, full, size: info.size });\n bytes += info.size;\n if (files.length > APP_BUNDLE_LIMITS.files) throw refused(`${dir} holds more files than a bundle may`, { reason: \"too-many-files\", detail: `more than ${APP_BUNDLE_LIMITS.files}` });\n if (bytes > APP_BUNDLE_LIMITS.bundleBytes) throw refused(`${dir} holds more than a bundle may`, { reason: \"too-large\", detail: `more than ${APP_BUNDLE_LIMITS.bundleBytes} bytes of files` });\n }\n };\n walk(dir);\n // The manifest lists every path in code-unit order, across folders: `lib-extra.js` before `lib/x.js`, which the walk\n // reaches first (`-` and `.` sort before `/`).\n files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n return { files, skipped };\n}\n\n/**\n * A private key in a file: a PEM private key block with its body (PKCS #8, OpenSSH, RSA, EC, encrypted), or a Ghostly\n * key file's first line. A library that only names the block's header in its code does not match.\n */\nconst PRIVATE_KEY = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----\\r?\\n[A-Za-z0-9+/=\\r\\n:,-]{40,}-----END [A-Z0-9 ]*PRIVATE KEY-----|^Ghostly (publisher|store) key \\(WISP 1200\\)/;\n\n/** Whether `path` is `dir` or anything under it. */\nconst isInside = (dir: string, path: string) => { const rel = relative(dir, path); return !rel.startsWith(\"..\") && !isAbsolute(rel); };\n\n/** Fields of `ghostly-app.json` the command writes itself. */\nconst WRITTEN = [\"publisher\", \"files\"] as const;\n\nfunction readSource(path: string): Record<string, unknown> {\n if (!existsSync(path)) throw new CliError(\"not_found\", `No ${APP_SOURCE} in ${dirname(path)}: it holds the manifest's fields (name, version, title, tagline, entry, permissions, runtime, license, ...) but publisher, sequence and files, which publish writes`);\n let value: unknown;\n try { value = JSON.parse(readFileSync(path, \"utf8\")); } catch (error) { throw new CliError(\"bad_request\", `${path} is not JSON: ${error instanceof Error ? error.message : String(error)}`); }\n if (!value || typeof value !== \"object\" || Array.isArray(value)) throw new CliError(\"bad_request\", `${path} is not a JSON object`);\n const draft = value as Record<string, unknown>;\n const written = WRITTEN.filter((k) => k in draft);\n if (written.length) throw new CliError(\"bad_request\", `${path} has ${written.join(\" and \")}: publish writes ${written.length > 1 ? \"them\" : \"it\"} from the key and the folder's files`);\n if (draft.ghostlyApp !== undefined && draft.ghostlyApp !== 1) throw refused(`${path} is not a manifest this release makes`, { reason: \"unsupported-format\", detail: \"ghostlyApp is not 1\" });\n if (draft.sequence !== undefined && !(Number.isSafeInteger(draft.sequence) && (draft.sequence as number) >= 1)) {\n throw new CliError(\"bad_request\", `${path}: sequence is a whole number from 1 (or leave it out: publish raises it by itself)`);\n }\n return draft;\n}\n\n/** Writes a file whole or not at all: a reader never sees half a bundle (and a dot file left by a crash is never bundled). */\nfunction writeWhole(path: string, bytes: Uint8Array): void {\n const tmp = join(dirname(path), `.${basename(path)}.${process.pid}.tmp`);\n writeFileSync(tmp, bytes);\n renameSync(tmp, path);\n}\n\n/**\n * `app publish <dir> --key <file> [--out <file>]`: `<dir>/ghostly-app.json` (the manifest without `publisher`,\n * `sequence` and `files`) and every other file in `<dir>` become one signed bundle, by default `<dir>/app.ghostlyapp`.\n * The sequence is `--sequence` (higher than the bundle already at `--out`'s), else one more than that bundle's (or\n * the source's `sequence`, if higher, else 1). The\n * key file is made when it is missing and no earlier bundle is there to update.\n */\nexport async function publishApp(dirArg: string, keyArg: string, outArg?: string, sequenceArg?: number): Promise<Record<string, unknown>> {\n if (sequenceArg !== undefined && !(Number.isSafeInteger(sequenceArg) && sequenceArg >= 1)) throw new CliError(\"usage\", `--sequence takes a whole number from 1, not ${sequenceArg}`);\n const dir = resolve(dirArg);\n if (!statSync(dir).isDirectory()) throw new CliError(\"bad_request\", `${dir} is not a folder`);\n const keyPath = resolve(keyArg);\n // Every file of the folder is published, and the folder is often the repository the bundle is committed to.\n if (isInside(dir, keyPath)) {\n throw refused(`${keyPath} is inside ${dir}, which is published: keep the publisher key outside the app's folder (and its repository)`, { reason: \"private-key\" });\n }\n const out = resolve(outArg ?? join(dir, APP_BUNDLE_FILE));\n const sourcePath = join(dir, APP_SOURCE);\n const draft = readSource(sourcePath);\n\n // The version this one follows: the bundle already at the output, which only its own key may follow.\n let previous: AppManifest | null = null;\n if (existsSync(out)) {\n const read = readAppBundle(readBundleFile(out));\n if (!read.ok) throw refused(`${out} is there and is not a valid bundle: move it away, or name another --out`, read);\n previous = read.bundle.manifest;\n }\n\n let key: LoadedKey;\n if (existsSync(keyPath)) key = readKey(keyPath, \"publisher\");\n else if (previous) {\n throw new CliError(\"not_found\", `No key file ${keyPath}: ${out} is signed by ${appFingerprint(previous.publisher)}, and only that key publishes its next version`);\n } else key = makeKey(keyPath, \"publisher\");\n\n if (previous && previous.publisher !== key.key) {\n throw refused(`${out} is ${appRef(previous.publisher, previous.name)}, signed by another key (${appFingerprint(previous.publisher)}): a version of an app is signed by its own key`, { reason: \"other-app\" }, { publisher: previous.publisher });\n }\n if (previous && previous.name !== draft.name) {\n throw refused(`${out} is the app ${JSON.stringify(previous.name)}, not ${JSON.stringify(draft.name)}: name another --out for another app`, { reason: \"other-app\" });\n }\n\n if (sequenceArg !== undefined && previous && sequenceArg <= previous.sequence) {\n throw refused(`--sequence ${sequenceArg} is not higher than the ${previous.sequence} of ${out}: every client refuses a lower one, and the same one with other content is equivocation`, { reason: sequenceArg < previous.sequence ? \"rollback\" : \"equivocation\" }, { previous: previous.sequence });\n }\n const sequence = sequenceArg ?? Math.max(previous ? previous.sequence + 1 : 1, (draft.sequence as number | undefined) ?? 1);\n const { files, skipped } = collectFiles(dir, new Set([sourcePath, out, join(dir, APP_REVOKE_FILE)]));\n const contents = files.map((f) => ({ path: f.path, bytes: new Uint8Array(readFileSync(f.full)) }));\n // A bundle is public: a private key found in the folder (an old key, a store key, any PEM) stops the publish.\n const keyFile = contents.find((f) => PRIVATE_KEY.test(Buffer.from(f.bytes).toString(\"latin1\")));\n if (keyFile) throw refused(`${keyFile.path} in ${dir} holds a private key, and a bundle is public: move it out of the app's folder`, { reason: \"private-key\", detail: keyFile.path });\n const manifest = {\n ...draft,\n ghostlyApp: 1,\n publisher: key.key,\n sequence,\n files: contents.map((f) => ({ path: f.path, size: f.bytes.length, sha256: toBase64Url(createHash(\"sha256\").update(f.bytes).digest()) })),\n };\n const checked = checkAppManifest(JSON.parse(JSON.stringify(manifest)), { strict: true });\n if (!checked.ok) throw refused(`${sourcePath} and the folder's files make no valid manifest`, checked);\n\n const { bytes: manifestBytes, signature } = await signAppObject(APP_PREFIXES.app, checked.manifest, key.signer);\n const bundle = assembleAppBundle(manifestBytes, canonicalJsonBytes(signature), contents.map((f) => f.bytes));\n // Read back as a client would (the icon's header, the bundle's size): what is written is never a bundle it refuses.\n const read = readAppBundle(bundle);\n if (!read.ok) throw refused(\"The bundle made is not valid\", read);\n // Nor one the folder's own revocations already stop: every client that reads them would refuse it, for good.\n const ref = appRef(key.key, checked.manifest.name);\n const revocations = existsSync(join(dir, APP_REVOKE_FILE)) ? readAppRevocations(new Uint8Array(readFileSync(join(dir, APP_REVOKE_FILE)))) : null;\n const held = revocations?.ok ? revocations.revocations : [];\n if (appRevocationFor({ ref, sequence, digest: read.bundle.digest }, held)) {\n const clear = Math.max(sequence, ...held.map((r) => r.statement.app === ref && \"upTo\" in r.statement ? r.statement.upTo : 0)) + 1;\n throw refused(`${join(dir, APP_REVOKE_FILE)} already revokes version ${sequence} of ${ref}, and every client that reads it would stop it: give --sequence ${clear}`, { reason: \"revoked\" }, { sequence: clear });\n }\n mkdirSync(dirname(out), { recursive: true });\n writeWhole(out, bundle);\n\n return {\n ...described(read.bundle, bundle),\n previous: previous?.sequence ?? null,\n out,\n files: contents.map((f) => f.path),\n ...(skipped.length ? { skipped } : {}),\n keyCreated: key.created,\n ...(key.created ? { warning: madeKeyNote(keyPath, \"publisher\", key.key) } : {}),\n };\n}\n\n// ---------- revocations ----------\n\n/**\n * `app revoke <dir> --key <file> (--digest <digest>... | --up-to <sequence>) [--reason <text>] [--bundle <file>]`: a\n * `ghostly-revoke/1` statement (WISP 1200 · Publisher keys, Revocation) for the app whose bundle is `<dir>/app.ghostlyapp`\n * (or `--bundle`), signed by that app's publisher key and added to `<dir>/ghostly-revoke.json`, which stays canonical.\n * The key is never made here: only the key that signed the app may revoke its versions.\n */\nexport async function revokeApp(dirArg: string, keyArg: string, what: { digests?: string[]; upTo?: number; reason?: string; bundle?: string; force?: boolean }): Promise<Record<string, unknown>> {\n const dir = resolve(dirArg);\n const bundlePath = resolve(what.bundle ?? join(dir, APP_BUNDLE_FILE));\n const listPath = join(dir, APP_REVOKE_FILE);\n const usage = \"ghostly app revoke <dir> --key <file> (--digest <digest>... | --up-to <sequence>) [--reason <text>]\";\n const digests = what.digests ?? [];\n if ((digests.length > 0) === (what.upTo !== undefined)) throw new CliError(\"usage\", `Give --digest (again for each) or --up-to, one of the two: ${usage}`);\n if (what.upTo !== undefined && !(Number.isSafeInteger(what.upTo) && what.upTo >= 1)) throw new CliError(\"usage\", `--up-to takes a sequence, a whole number from 1, not ${what.upTo}`);\n\n if (!existsSync(bundlePath)) throw new CliError(\"not_found\", `No bundle ${bundlePath}: a revocation names the app of the bundle in <dir> (or --bundle)`);\n const read = readAppBundle(readBundleFile(bundlePath));\n if (!read.ok) throw refused(`${bundlePath} is not a valid bundle`, read);\n const manifest = read.bundle.manifest;\n const ref = appRef(manifest.publisher, manifest.name);\n // Clients keep a revocation for good: past the newest version (a typo, 30 for 3), it stops versions not made yet.\n if (what.upTo !== undefined && what.upTo > manifest.sequence && !what.force) {\n throw new CliError(\"usage\", `--up-to ${what.upTo} is past ${bundlePath}'s version, ${manifest.sequence}: it would also stop versions ${manifest.sequence + 1} to ${what.upTo} before they exist, for good. Give --up-to ${manifest.sequence}, or --force if versions up to ${what.upTo} were published from elsewhere`);\n }\n\n const keyPath = resolve(keyArg);\n if (!existsSync(keyPath)) throw new CliError(\"not_found\", `No key file ${keyPath}: only the key that signed ${ref} (${appFingerprint(manifest.publisher)}) revokes its versions`);\n const key = readKey(keyPath, \"publisher\");\n if (key.key !== manifest.publisher) {\n throw refused(`${keyPath} is not the publisher key of ${ref} (${appFingerprint(manifest.publisher)}): a revocation by any other key is refused by every client`, { reason: \"signature-key\" });\n }\n\n const statement = (digests.length\n ? { ghostlyRevoke: 1, app: ref, digests }\n : { ghostlyRevoke: 1, app: ref, upTo: what.upTo }) as AppRevokeStatement;\n if (what.reason !== undefined) statement.reason = what.reason;\n const problem = checkAppRevokeStatement(statement);\n if (problem) throw refused(\"Not a revocation a client reads\", { reason: \"bad-revocation\", detail: problem });\n\n let list: SignedAppRevocation[] = [];\n if (existsSync(listPath)) {\n const held = readAppRevocations(new Uint8Array(readFileSync(listPath)));\n if (!held.ok) throw refused(`${listPath} is there and is not a valid ghostly-revoke.json: fix or move it first`, held);\n list = held.revocations;\n }\n const signed = await signAppRevocation(statement, key.signer);\n const same = list.some((r) => canonicalJson(r.statement) === canonicalJson(signed.statement));\n if (!same) {\n if (list.length >= APP_STATEMENT_LIMITS.revocations) throw refused(`${listPath} holds ${list.length} revocations already`, { reason: \"bad-revocation\", detail: `at most ${APP_STATEMENT_LIMITS.revocations}` });\n list = [...list, signed];\n const bytes = canonicalJsonBytes(list);\n const check = readAppRevocations(bytes);\n if (!check.ok) throw refused(\"The revocation list made is not valid\", check);\n writeWhole(listPath, bytes);\n }\n const covers = digests.length\n ? { digests, current: digests.includes(read.bundle.digest) }\n : { upTo: what.upTo, current: manifest.sequence <= what.upTo! };\n return { app: ref, ...covers, ...(what.reason !== undefined ? { reason: what.reason } : {}), added: !same, revocations: list.length, file: listPath };\n}\n\n// ---------- store indexes ----------\n\n/**\n * `store sign <index> --key <file> [--out <dir>]`: a store index written by hand (any JSON layout; `key` may be left\n * out and is then the signing key's) becomes `ghostly-store.json`, canonical, and `ghostly-store.sig`, in `--out` or\n * beside the index. It is read back as a client reads it (`expires` at most 90 days ahead), and refused when the index\n * already there has a higher `sequence`, or the same one with other content, or is signed by another key (another\n * store is another `--out`). The key is made only when no signed index is there.\n */\nexport async function signStore(indexArg: string, keyArg: string, outArg?: string, now = Math.floor(Date.now() / 1000)): Promise<Record<string, unknown>> {\n const indexPath = resolve(indexArg);\n const keyPath = resolve(keyArg);\n const outDir = resolve(outArg ?? dirname(indexPath));\n let value: unknown;\n try { value = JSON.parse(readFileSync(indexPath, \"utf8\")); } catch (error) {\n if (error instanceof SyntaxError) throw new CliError(\"bad_request\", `${indexPath} is not JSON: ${error.message}`);\n throw error;\n }\n if (!value || typeof value !== \"object\" || Array.isArray(value)) throw new CliError(\"bad_request\", `${indexPath} is not a JSON object`);\n const index = value as Record<string, unknown>;\n\n // The index this one follows, when the folder holds a signed one: a store is its key, so only that key signs over it.\n const indexOut = join(outDir, STORE_INDEX_FILE), sigOut = join(outDir, STORE_SIG_FILE);\n let held: AppStoreReading | null = null;\n if (existsSync(indexOut) && existsSync(sigOut)) {\n const read = readAppStore(new Uint8Array(readFileSync(indexOut)), new Uint8Array(readFileSync(sigOut)), now);\n if (read.ok) held = read.store;\n }\n\n let key: LoadedKey;\n if (existsSync(keyPath)) key = readKey(keyPath, \"store\");\n else if (index.key !== undefined) throw new CliError(\"not_found\", `No key file ${keyPath}: the index names the store key ${typeof index.key === \"string\" ? appFingerprint(index.key) : JSON.stringify(index.key)}`);\n else if (held) throw new CliError(\"not_found\", `No key file ${keyPath}: ${indexOut} is signed by ${appFingerprint(held.index.key)}, and only that key signs its next index`);\n else key = makeKey(keyPath, \"store\");\n if (index.key !== undefined && index.key !== key.key) {\n throw refused(`${indexPath} names another store key than ${keyPath}'s (${appFingerprint(key.key)})`, { reason: \"store-key\" });\n }\n if (held && held.index.key !== key.key) {\n throw refused(`${indexOut} is signed by another store key (${appFingerprint(held.index.key)}) than ${keyPath}'s (${appFingerprint(key.key)}): a store is its key; sign with that key, or give another --out for another store`, { reason: \"store-key\" }, { held: held.index.key });\n }\n index.key = key.key;\n\n const checked = checkAppStoreIndex(index, { strict: true });\n if (!checked.ok) throw refused(`${indexPath} is not a valid store index`, checked);\n const { indexBytes, sigBytes } = await signAppStore(index as unknown as AppStoreIndex, key.signer);\n const read = readAppStore(indexBytes, sigBytes, now, key.key);\n if (!read.ok) throw refused(`${indexPath} would be refused by a client`, read);\n const store = read.store;\n\n let previous: number | null = null;\n if (held) {\n previous = held.index.sequence;\n const decision = appStoreDecision(\n { key: key.key, sequence: held.index.sequence, digest: held.digest },\n { key: key.key, sequence: store.index.sequence, digest: store.digest },\n );\n if (decision === \"rollback\" || decision === \"equivocation\") {\n throw refused(`${indexOut} already holds sequence ${held.index.sequence} of this store: ${decision === \"rollback\" ? \"a lower one is refused by every client\" : \"the same one with other content is refused as equivocation\"}; raise sequence`, { reason: decision }, { held: held.index.sequence });\n }\n }\n\n mkdirSync(outDir, { recursive: true });\n writeWhole(indexOut, indexBytes);\n writeWhole(sigOut, sigBytes);\n const notes = [\n ...(key.created ? [madeKeyNote(keyPath, \"store\", key.key)] : []),\n ...(store.expired ? [`expires (${new Date(store.index.expires * 1000).toISOString()}) is past: clients still install from it and say the store was not updated since then`] : []),\n ];\n return {\n key: key.key, fingerprint: appFingerprint(key.key), name: store.index.name, kind: store.index.kind,\n sequence: store.index.sequence, previous, expires: store.index.expires, expired: store.expired,\n apps: store.index.apps.length, removed: store.index.removed.length, revoked: store.index.revoked.length,\n digest: store.digest, index: indexOut, sig: sigOut, keyCreated: key.created,\n ...(notes.length ? { warning: notes.join(\"; \") } : {}),\n };\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import { r as buttonsOf } from "./buttons-BtxwyM2l.js";
2
- export { buttonsOf };
@@ -1 +0,0 @@
1
- {"version":3,"file":"endpoint-BOTFMpfS.js","names":[],"sources":["../../../../native/transports/hyperdht/redial.mjs","../../../../native/transports/hyperdht/endpoint.mjs"],"sourcesContent":["/**\n * How long a HyperDHT dial whose stream opened may go without the contact's preface before it is dialled again.\n *\n * The first connection a freshly started process makes sometimes opens (the handshake went through the DHT and the\n * stream is connected to the right address) and then carries nothing: the contact never sees it, and UDX gives up\n * only after 13 s. A connection made again at once goes through in milliseconds. Found as a chat moved to HyperDHT\n * right after the contact's app restarted taking about 28 s in CI (the move's 8 s, then its 20 s retry), 1 run in 6;\n * reproduced with hyperdht alone under load (bug hunt r11h). On loopback the preface comes within milliseconds of\n * the stream opening, one round trip on the internet.\n */\nexport const PREFACE_WAIT_MS = 2000\n\n/**\n * The longest a stream that opened waits for the preface on a slow path: the 8 s a chat's move gives its dial. Past\n * it nobody is waiting for the dial made again, and UDX gives a silent stream up at 13 s anyway.\n */\nexport const PREFACE_MAX_WAIT_MS = 8000\n\n/**\n * How many of the stream's smoothed round trips the preface may take. A contact that waits for our first packet\n * before its side of the stream connects (we are behind a firewall) writes its preface one round trip after our\n * stream opened; a lost packet adds a retransmission timeout (at least one round trip more). Three keep a slow path's\n * preface from being dialled again for nothing (a fixed 2 s on a 3 s path made three dials) and leave every path\n * under 667 ms on the 2 s.\n */\nexport const RTT_FACTOR = 3\n\n/**\n * Before the stream has its first RTT sample, how many times the dial's own opening (from the dial to the stream\n * open) the preface may take. The opening is the DHT lookup plus the handshake through a DHT node, so it took at\n * least two round trips of the path, and the preface comes one round trip after the stream opened: once covers it\n * (on a 3 s path the first sample comes only 3 s after the stream opened, past the 2 s). It is not more because a\n * stalled stream never gets a sample, and its opening is mostly the lookup (1-3 s on the public DHT even for a near\n * contact): twice a 2.5 s opening would dial again only 7.5 s after the dial, past the move's 8 s once the second\n * dial's own opening is added. A dial that opened in milliseconds (loopback, a near contact) stays on the 2 s.\n */\nexport const OPENING_FACTOR = 1\n\n/**\n * The preface wait for a stream whose smoothed RTT is `rtt` ms and whose dial took `opening` ms to open: three round\n * trips once there is a sample, the opening itself before, between `floor` and `ceiling`.\n */\nexport function prefaceWait(rtt, opening = 0, floor = PREFACE_WAIT_MS, ceiling = PREFACE_MAX_WAIT_MS) {\n const wait = rtt > 0 ? RTT_FACTOR * rtt : OPENING_FACTOR * (opening || 0)\n return Math.min(ceiling, Math.max(floor, wait))\n}\n\n/**\n * At most this many dials for one `connect`: the first, and two more if they stalled, 2 s apart, all within the 8 s a\n * chat's move gives its dial. Under load a dial made again stalled too about once in 20 (bug hunt r11h).\n */\nexport const MAX_DIALS = 3\n\n/**\n * Dials with `dialOnce` until one brings the contact's preface. `dialOnce()` starts a connection and returns\n * `{ opened, ready, close }`: `opened` resolves true once the stream is open (the handshake is done), `ready` with the\n * bound channel once the contact's preface came, and `close()` drops it; `rtt()`, if given, is the stream's smoothed\n * RTT in ms so far, 0 before its first sample (UDX's `rawStream.rtt`). A dial that opened and brought no preface\n * within `waitMs`, or longer on a slow path (`prefaceWait`, at most `maxWaitMs`), is closed and made again, up to\n * `maxDials` in all. The RTT is read again whenever the wait would end, so a sample taken after the stream opened\n * counts. A dial that fails before it opened (the contact not found, refused) fails the whole connect at once, as\n * before: there is nothing to wait for.\n */\nexport function redial(dialOnce, { waitMs = PREFACE_WAIT_MS, maxWaitMs = PREFACE_MAX_WAIT_MS, maxDials = MAX_DIALS } = {}) {\n return new Promise((resolve, reject) => {\n let settled = false\n let dials = 0\n const attempt = () => {\n dials++\n const dialledAt = Date.now()\n const dial = dialOnce()\n let timer = null\n let stalled = false\n dial.opened.then(open => {\n if (!open || settled) return\n const openedAt = Date.now()\n const check = () => {\n if (settled) return\n const left = openedAt + prefaceWait(dial.rtt?.(), openedAt - dialledAt, waitMs, maxWaitMs) - Date.now()\n if (left > 0) { timer = setTimeout(check, left); return }\n if (dials >= maxDials) return\n stalled = true\n dial.close()\n attempt()\n }\n timer = setTimeout(check, waitMs)\n }, () => {})\n dial.ready.then(bound => {\n clearTimeout(timer)\n if (settled || stalled) { bound.channel.close(); return }\n settled = true\n resolve(bound)\n }, error => {\n clearTimeout(timer)\n // A dial closed here for stalling ends as closed: the next one carries the connect.\n if (settled || stalled) return\n settled = true\n reject(error)\n })\n }\n attempt()\n })\n}\n","import DHT from 'hyperdht'\nimport { redial } from './redial.mjs'\n\nexport const MAX_FRAME = 60 * 1024\n/** The top bit of a frame's length word: the frame is a binary message, not UTF-8 text (WISP 103). */\nconst BINARY = 0x80000000\nconst PREFACE = 'ghostly/paired-chat/1'\n/** How long a closing channel waits for what it sent last to go out before the stream is destroyed anyway. */\nconst CLOSE_FLUSH_MS = 300\n/**\n * Bytes a channel may hold before `send` throws: above what every sender here lets itself queue (the CLI's 1 MiB high\n * water mark and an app's quarter above it; the Desktop page's send budget over the sidecar's `SEND_ROOM`), so it\n * trips only for one that ignores its own line.\n */\nconst MAX_BUFFERED = 2 * 1024 * 1024\n/**\n * `drained()` resolves once a channel holds no more than this: no more than the sidecar's `SEND_ROOM`, so its wait for\n * room never spins on a `drained()` that resolves at once.\n */\nconst DRAINED_AT = 64 * 1024\n/**\n * How often a wait in `drained()` looks again. Each stream says `drain` only once its own queue is empty, which on a fast\n * link comes first; on a slow one this finds the queue at `DRAINED_AT` before it runs dry.\n */\nconst DRAIN_POLL_MS = 10\n\n/**\n * What a connection's stream still holds: secret-stream's queue and the UDX stream's under it. Both are streamx\n * streams, which count it in `_writableState.buffered` (hyperdht is pinned; hyperdhtEndpointBuffered.test.ts reads it).\n * secret-stream has no `writableLength`: read from there, it was always 0.\n */\nfunction held(socket) {\n return (socket._writableState?.buffered ?? 0) + (socket.rawStream?._writableState?.buffered ?? 0)\n}\n\n/** Native UDP + NoiseSecretStream. The binding comes from the authenticated\n * stream itself, never from an application frame supplied by the other peer. */\nexport async function createHyperEndpoint(seed, options = {}) {\n if (!(seed instanceof Uint8Array) || seed.length !== 32) throw new Error('Invalid HyperDHT seed')\n const keyPair = DHT.keyPair(Buffer.from(seed))\n const node = new DHT({ ...options, keyPair })\n const channels = new Set()\n let stopped = false\n let incomingHandler = null\n const waiting = []\n const descriptor = { publicKey: keyPair.publicKey.toString('hex') }\n\n /** `onChannel`: hears the channel as soon as it counts against the limit (a dial that stalls is dropped from it). */\n async function attach(socket, onChannel) {\n if (stopped || channels.size >= 2) { socket.destroy(); throw new Error('Native connection limit reached') }\n let reader = null\n let closeHandler = null\n let closed = false\n let ending = null\n let pending = []\n let buffer = Buffer.alloc(0)\n let preface = false\n let resolveOpen, rejectOpen\n const ready = new Promise((resolve, reject) => { resolveOpen = resolve; rejectOpen = reject })\n const timer = setTimeout(() => { socket.destroy(new Error('HyperDHT handshake timed out')) }, 20000)\n const channel = {\n // It carries binary messages (WISP 100 § Text and binary messages): a capability that needs them may be offered.\n binary: true,\n get bufferedAmount() { return held(socket) },\n get onMessage() { return reader },\n set onMessage(value) { reader = value; if (value) for (const text of pending.splice(0)) value(text) },\n get onClose() { return closeHandler },\n set onClose(value) { closeHandler = value; if (closed) value?.() },\n send(data) {\n const binary = data instanceof Uint8Array\n if (closed || !(binary || typeof data === 'string') || !data.length || (binary ? data.length : Buffer.byteLength(data)) > MAX_FRAME || this.bufferedAmount > MAX_BUFFERED)\n throw new Error('Native channel is closed or exceeds its frame budget')\n const payload = binary ? Buffer.from(data.buffer, data.byteOffset, data.byteLength) : Buffer.from(data)\n const header = Buffer.alloc(4); header.writeUInt32BE((binary ? BINARY : 0) + payload.length)\n socket.write(Buffer.concat([header, payload]))\n },\n drained() {\n if (this.bufferedAmount <= DRAINED_AT) return Promise.resolve()\n if (closed) return Promise.reject(new Error('Native channel closed'))\n return new Promise((resolve, reject) => {\n const raw = socket.rawStream\n const check = () => { if (this.bufferedAmount <= DRAINED_AT) finish(resolve) }\n const gone = () => finish(() => reject(new Error('Native channel closed')))\n const poll = setInterval(check, DRAIN_POLL_MS)\n poll.unref?.()\n function finish(settle) {\n clearInterval(poll); socket.off('drain', check); raw?.off('drain', check); socket.off('close', gone); settle()\n }\n socket.on('drain', check); raw?.on('drain', check); socket.once('close', gone)\n })\n },\n // What was sent last (a goodbye, as an app stops) goes out before the stream ends: destroyed at once, it was\n // dropped unsent. A contact that never finishes the stream has CLOSE_FLUSH_MS.\n close() {\n if (closed || ending) return ending ?? Promise.resolve()\n ending = new Promise(resolve => { socket.once('close', resolve) })\n const timer = setTimeout(() => socket.destroy(), CLOSE_FLUSH_MS)\n timer.unref?.()\n socket.end()\n return ending\n },\n }\n channels.add(channel)\n onChannel?.(channel)\n socket.on('error', error => rejectOpen(error))\n // The contact ended its side (its channel closed): this one ends too, so the stream closes now, not on its timer.\n socket.on('end', () => socket.end())\n socket.on('close', () => {\n closed = true; pending = []; clearTimeout(timer); channels.delete(channel)\n rejectOpen(new Error('Native channel closed')); closeHandler?.()\n })\n const opened = () => {\n if (socket.handshakeHash?.length !== 64 || socket.publicKey?.length !== 32 || socket.remotePublicKey?.length !== 32) {\n socket.destroy(new Error('Noise session binding unavailable')); return\n }\n channel.send(PREFACE)\n }\n socket.on('data', chunk => {\n // Secretstream chunks are bounded by its record layer. Process all frames\n // before retaining a partial one; never allocate based on an unchecked size.\n buffer = Buffer.concat([buffer, chunk])\n while (buffer.length >= 4) {\n const word = buffer.readUInt32BE(0)\n const binary = word >= BINARY, length = word % BINARY\n // A binary frame before the preface is another protocol.\n if (length === 0 || length > MAX_FRAME || (binary && !preface)) { socket.destroy(new Error('Invalid frame length')); return }\n if (buffer.length < 4 + length) break\n let text\n if (binary) text = new Uint8Array(buffer.subarray(4, 4 + length))\n else {\n try { text = new TextDecoder('utf-8', { fatal: true }).decode(buffer.subarray(4, 4 + length)) }\n catch { socket.destroy(new Error('Invalid UTF-8 frame')); return }\n }\n buffer = buffer.subarray(4 + length)\n if (!preface) {\n if (text !== PREFACE) { socket.destroy(new Error('Unexpected application protocol')); return }\n preface = true; clearTimeout(timer)\n resolveOpen({ channel, binding: { transport: 'hyperdht/1', context: socket.handshakeHash.toString('hex'),\n identities: [socket.publicKey.toString('hex'), socket.remotePublicKey.toString('hex')].sort() } })\n } else if (reader) reader(text)\n else if (pending.length < 64) pending.push(text)\n else { socket.destroy(new Error('Receive queue exceeded')); return }\n }\n })\n void socket.opened.then(ok => { if (ok) opened(); else rejectOpen(new Error('Noise handshake failed')) })\n return ready\n }\n\n const server = node.createServer(socket => {\n void attach(socket).then(bound => {\n if (stopped) bound.channel.close()\n else if (incomingHandler) incomingHandler(bound)\n else waiting.push(bound)\n }).catch(() => {})\n })\n try { await server.listen(keyPair) }\n catch (error) { await node.destroy(); throw error }\n return {\n transport: 'hyperdht/1', descriptor, onDescriptor: null,\n get onConnection() { return incomingHandler },\n set onConnection(value) { incomingHandler = value; if (value) for (const bound of waiting.splice(0)) value(bound) },\n async connect(address) {\n if (stopped || !address || !/^[a-f0-9]{64}$/.test(address.publicKey)) throw new Error('Invalid HyperDHT endpoint')\n const remote = Buffer.from(address.publicKey, 'hex')\n // A dial whose stream opened and carried nothing is made again (`redial.mjs`): it is dropped from the limit at\n // once, so the next one has its place even while another channel is live. The wait follows the stream's RTT.\n return redial(() => {\n const socket = node.connect(remote, { keyPair })\n let counted = null\n const ready = attach(socket, channel => { counted = channel })\n return { opened: socket.opened, ready, rtt: () => socket.rawStream?.rtt ?? 0,\n close() { if (counted) channels.delete(counted); socket.destroy() } }\n })\n },\n // The channels end first (their last frames out, CLOSE_FLUSH_MS at most): the node's socket goes with it.\n async close() { stopped = true; const ended = [...channels].map(channel => channel.close()); waiting.length = 0; await Promise.all(ended); await server.close(); await node.destroy() },\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors-B-zMxbcd.js","names":[],"sources":["../../src/errors.ts"],"sourcesContent":["/**\n * Errors that reach a bot: a stable `code` to branch on, a message for people, and the exit status the CLI\n * leaves with (WISP 1100 § Exit codes). The daemon sends `{code, message}` over the socket; the CLI prints\n * `{\"error\": {code, message}}` on stdout and exits with `exit`.\n */\nexport type ErrorCode =\n | \"bad_request\" | \"usage\" | \"not_found\" | \"refused\" | \"unavailable\" | \"confirm\" | \"timeout\"\n | \"engine\" | \"busy\" | \"internal\";\n\nexport const EXIT: Record<ErrorCode, number> = {\n engine: 1, refused: 1, unavailable: 1, bad_request: 1, busy: 1, internal: 1,\n usage: 2, not_found: 3, timeout: 4, confirm: 5,\n};\n\nexport class CliError extends Error {\n constructor(readonly code: ErrorCode, message: string, readonly details?: Record<string, unknown>) {\n super(message);\n this.name = \"CliError\";\n }\n toJSON(): { code: ErrorCode; message: string; details?: Record<string, unknown> } {\n return { code: this.code, message: this.message, ...(this.details ? { details: this.details } : {}) };\n }\n}\n\n/** Anything thrown, as the error a bot sees: the engine's own refusals keep their words. */\nexport function asCliError(error: unknown): CliError {\n if (error instanceof CliError) return error;\n if (error && typeof error === \"object\" && \"code\" in error && \"message\" in error && typeof (error as { code: unknown }).code === \"string\"\n && Object.hasOwn(EXIT, (error as { code: string }).code)) return new CliError((error as { code: ErrorCode }).code, String((error as { message: unknown }).message));\n // A contact the chat's link could not reach (its `GhostlyHttpError` \"unreachable\"): not there now, not the engine failing.\n if (error instanceof Error && (error as { code?: unknown }).code === \"unreachable\") return new CliError(\"unavailable\", error.message);\n const known = engineKnown(error);\n if (known) return known;\n const local = localFileError(error);\n if (local) return local;\n const unopened = profileOpenError(error);\n if (unopened) return unopened;\n const message = error instanceof Error ? error.message : String(error);\n if (/real money|confirmedReal|Mainnet/i.test(message) && /confirm/i.test(message)) return new CliError(\"confirm\", message);\n if (/other network|cross-network|network does not match/i.test(message)) return new CliError(\"refused\", message);\n return new CliError(\"engine\", message);\n}\n\n/**\n * The engine's known errors (`EngineError`, packages/core engineErrors.ts) a bot branches on, by their `engineCode`: a\n * host or the network not answering is `unavailable` (try again later), one too slow `timeout`, a value the person gave\n * `bad_request`, money that is not there `refused`. Any other keeps `engine`. Read by its shape, as `profileOpenError`.\n */\nconst ENGINE_CODES: Record<string, ErrorCode> = {\n mintUnreachable: \"unavailable\", lnurlUnreachable: \"unavailable\", hostUnreachable: \"unavailable\", networkUnreachable: \"unavailable\", usdtRpcUnavailable: \"unavailable\",\n hostTimedOut: \"timeout\", networkTimedOut: \"timeout\",\n invalidMintUrl: \"bad_request\", mintNotHttps: \"bad_request\", invalidMaxFee: \"bad_request\",\n notEnoughSats: \"refused\", ecashAlreadySpent: \"refused\",\n};\n\nfunction engineKnown(error: unknown): CliError | undefined {\n if (!(error instanceof Error)) return undefined;\n const { engineCode, values } = error as { engineCode?: unknown; values?: { host?: unknown } };\n if (typeof engineCode !== \"string\" || !Object.hasOwn(ENGINE_CODES, engineCode)) return undefined;\n // The app's reason for a Lightning address it could not read (a browser's CORS) is no reason in a terminal.\n if (engineCode === \"lnurlUnreachable\" && typeof values?.host === \"string\") return new CliError(\"unavailable\", `Could not reach ${values.host}`);\n return new CliError(ENGINE_CODES[engineCode], error.message);\n}\n\n/**\n * The profile's database did not open (the engine's `ProfileOpenError`, packages/browser/src/shared/idb.ts): `engine`\n * (exit 1) with the engine's own words, which name the versions when a newer ghostly stored it, and the reason and\n * the versions in `details` for a bot. Read by its shape: this module is also the thin client's, which loads no engine.\n */\nfunction profileOpenError(error: unknown): CliError | undefined {\n if (!(error instanceof Error) || error.name !== \"ProfileOpenError\") return undefined;\n const failure = (error as { failure?: { reason?: unknown; storedVersion?: unknown; supportedVersion?: unknown } }).failure;\n if (!failure || typeof failure.reason !== \"string\") return undefined;\n return new CliError(\"engine\", error.message, {\n reason: failure.reason === \"newer\" ? \"newer_profile\" : `profile_${failure.reason}`,\n ...(typeof failure.storedVersion === \"number\" ? { storedVersion: failure.storedVersion } : {}),\n ...(typeof failure.supportedVersion === \"number\" ? { supportedVersion: failure.supportedVersion } : {}),\n });\n}\n\n/**\n * A file or folder on this machine the command could not open (`file save --dir`, `profile backup --out`): the\n * person's path, not the engine refusing, so it says so with the code a bot branches on. Before, a folder that did\n * not exist was `engine` (exit 1) with Node's `ENOENT: no such file or directory, open '…'`. `file`: the path read, for\n * an error that names none (a folder read as a file).\n */\nexport function localFileError(error: unknown, file?: string): CliError | undefined {\n if (!error || typeof error !== \"object\" || typeof (error as { syscall?: unknown }).syscall !== \"string\") return undefined;\n const { code, path } = error as { code?: unknown; path?: unknown };\n const where = typeof path === \"string\" ? path : file ?? \"the path\";\n if (code === \"ENOENT\" || code === \"ENOTDIR\") return new CliError(\"not_found\", `No such file or folder: ${where}`);\n if (code === \"EACCES\" || code === \"EPERM\" || code === \"EROFS\") return new CliError(\"refused\", `Not allowed to write or read ${where}`);\n if (code === \"EISDIR\") return new CliError(\"bad_request\", `${where} is a folder: name a file`);\n if (code === \"ENAMETOOLONG\") return new CliError(\"bad_request\", `A name in ${where} is too long for this file system (at most 255 bytes)`);\n return undefined;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"fileBytes-CfSXjTFa.js","names":[],"sources":["../../src/runtime/fileBytes.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { mkdir, open, readdir, rm, stat, statfs, type FileHandle } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { FILE_BYTES_STEP, checkFileId, digestText, fileSpace, type FileBytes } from \"@ghostly/browser/shared/fileBytes\";\n\n/**\n * Files as real files in the profile's folder (`files/<space>/<id>`, 0600 in 0700 folders), the Node twin of the\n * Desktop's `native` backend (`shared/fileBytesNative.ts`, which goes through Rust). Registered as `native` and\n * preferred, so every file of any size is a file on disk, read and written a step at a time.\n */\nexport class NodeFileBytes implements FileBytes {\n readonly kind = \"native\" as const;\n private readonly writers = new Map<string, Promise<FileHandle>>();\n\n constructor(private readonly root: string) {}\n\n private folder(space = fileSpace()): string { return join(this.root, space); }\n private path(id: string): string { return join(this.folder(), checkFileId(id)); }\n\n private writer(id: string): Promise<FileHandle> {\n const path = this.path(id);\n let handle = this.writers.get(path);\n if (!handle) {\n handle = mkdir(this.folder(), { recursive: true, mode: 0o700 }).then(() => open(path, \"a\", 0o600));\n this.writers.set(path, handle);\n handle.catch(() => this.writers.delete(path));\n }\n return handle;\n }\n\n async stage(id: string, source: Blob, onProgress?: (copied: number) => void): Promise<string> {\n await this.remove(id);\n try {\n for (let offset = 0; offset < source.size; offset += FILE_BYTES_STEP) {\n const bytes = new Uint8Array(await source.slice(offset, offset + FILE_BYTES_STEP).arrayBuffer());\n await this.append(id, offset, bytes);\n onProgress?.(offset + bytes.length);\n }\n if (source.size === 0) await this.append(id, 0, new Uint8Array());\n await this.close(id);\n return await this.digest(id);\n } catch (error) {\n await this.remove(id).catch(() => {});\n throw error;\n }\n }\n\n async append(id: string, offset: number, bytes: Uint8Array): Promise<void> {\n const size = (await this.size(id)) ?? 0;\n if (offset !== size) throw new Error(`Append at ${offset}, but the file holds ${size} bytes`);\n const handle = await this.writer(id);\n if (bytes.length) await handle.write(bytes);\n }\n\n async flush(id: string): Promise<void> {\n const handle = this.writers.get(this.path(id));\n if (handle) await (await handle).sync();\n }\n\n async close(id: string): Promise<void> {\n const path = this.path(id);\n const handle = this.writers.get(path);\n if (!handle) return;\n this.writers.delete(path);\n const file = await handle;\n await file.sync();\n await file.close();\n }\n\n async size(id: string): Promise<number | null> {\n const path = this.path(id);\n try { return (await stat(path)).size; } catch { return null; }\n }\n\n async truncate(id: string, size: number): Promise<void> {\n await this.close(id);\n const handle = await open(this.path(id), \"r+\");\n try { await handle.truncate(size); await handle.sync(); } finally { await handle.close(); }\n }\n\n async read(id: string, offset: number, length: number): Promise<Uint8Array> {\n const handle = await open(this.path(id), \"r\");\n try {\n const buffer = Buffer.alloc(Math.min(length, 16 * FILE_BYTES_STEP));\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, offset);\n return new Uint8Array(buffer.subarray(0, bytesRead));\n } finally { await handle.close(); }\n }\n\n async digest(id: string): Promise<string> {\n const hash = createHash(\"sha256\");\n const handle = await open(this.path(id), \"r\");\n try {\n for await (const chunk of handle.createReadStream({ highWaterMark: FILE_BYTES_STEP })) hash.update(chunk as Buffer);\n } finally { await handle.close().catch(() => {}); }\n return digestText(new Uint8Array(hash.digest()));\n }\n\n /** The file read into memory, to show; a large one only ever goes to disk (`ghostly file save`). */\n async blob(id: string, type: string): Promise<Blob | null> {\n const size = await this.size(id);\n if (size === null || size > 64 * 1024 * 1024) return null;\n const parts: Uint8Array[] = [];\n for (let offset = 0; offset < size; offset += FILE_BYTES_STEP) parts.push(await this.read(id, offset, FILE_BYTES_STEP));\n return new Blob(parts as BlobPart[], { type });\n }\n\n async remove(id: string): Promise<void> {\n await this.close(id).catch(() => {});\n await rm(this.path(id), { force: true });\n }\n\n async removeWhere(prefix: string): Promise<void> {\n if (prefix && !/^[A-Za-z0-9_-]{1,200}$/.test(prefix)) throw new Error(\"Invalid file id\");\n let names: string[];\n try { names = await readdir(this.folder()); } catch { return; }\n await Promise.all(names.filter((name) => name.startsWith(prefix)).map((name) => this.remove(name)));\n }\n\n async room(): Promise<number | null> {\n try {\n await mkdir(this.root, { recursive: true, mode: 0o700 });\n const fs = await statfs(this.root);\n return fs.bavail * fs.bsize;\n } catch { return null; }\n }\n\n async dropSpace(space: string): Promise<void> {\n await rm(this.folder(space), { recursive: true, force: true });\n }\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import { t as IdbFileBytes } from "./fileBytesIdb-D5u6H8X_.js";
2
- export { IdbFileBytes };