@ghostlytools/cli 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +9 -3
  2. package/dist/assets/{api-CrxVxtY1.js → api-Dq2QXQDk.js} +141 -40
  3. package/dist/assets/api-Dq2QXQDk.js.map +1 -0
  4. package/dist/assets/{api-BiaX6l6f.js → api-TiB5QSG9.js} +2 -2
  5. package/dist/assets/{apiKit-CajiDxdK.js → apiKit-CA2x5GJF.js} +7 -3
  6. package/dist/assets/apiKit-CA2x5GJF.js.map +1 -0
  7. package/dist/assets/{apps-CbfiCSIw.js → apps-BxV9i_Y4.js} +3 -4
  8. package/dist/assets/{apps-CbfiCSIw.js.map → apps-BxV9i_Y4.js.map} +1 -1
  9. package/dist/assets/{apps-t23gkc28.js → apps-C-T8jeji.js} +1 -1
  10. package/dist/assets/{arkade-D4BVJFEQ.js → arkade-C_qDmHnm.js} +2 -2
  11. package/dist/assets/{arkade-D4BVJFEQ.js.map → arkade-C_qDmHnm.js.map} +1 -1
  12. package/dist/assets/{backup-Cg52WeQi.js → backup-Bugnn3Ue.js} +1 -1
  13. package/dist/assets/{backup-BAMi2lAq.js → backup-CnMN2oUK.js} +8 -8
  14. package/dist/assets/backup-CnMN2oUK.js.map +1 -0
  15. package/dist/assets/bark_ffi_wasm_bg-EY2uc0Bx.js.map +1 -1
  16. package/dist/assets/bitcoind-CZchoh43.js +349 -0
  17. package/dist/assets/bitcoind-CZchoh43.js.map +1 -0
  18. package/dist/assets/bitcoindProvider-DGw3xBSa.js +97 -0
  19. package/dist/assets/bitcoindProvider-DGw3xBSa.js.map +1 -0
  20. package/dist/assets/bitcoindevkit_bg-B-iLWrmX.js.map +1 -1
  21. package/dist/assets/buttons-Bj7Vf7Pn.js +2 -0
  22. package/dist/assets/{buttons-CX1pKB0j.js → buttons-CvNBzh2S.js} +4 -5
  23. package/dist/assets/{buttons-CX1pKB0j.js.map → buttons-CvNBzh2S.js.map} +1 -1
  24. package/dist/assets/{endpoint-Dn7H8qE1.js → endpoint-BOTFMpfS.js} +44 -12
  25. package/dist/assets/endpoint-BOTFMpfS.js.map +1 -0
  26. package/dist/assets/{envelope-BvU0fLEt.js → envelope-WjingYSc.js} +2 -2
  27. package/dist/assets/{envelope-BvU0fLEt.js.map → envelope-WjingYSc.js.map} +1 -1
  28. package/dist/assets/{errors-my6VJR_0.js → errors-DO5z6h_l.js} +29 -1
  29. package/dist/assets/errors-DO5z6h_l.js.map +1 -0
  30. package/dist/assets/groupFiles-IEUW3DQz.js +3202 -0
  31. package/dist/assets/groupFiles-IEUW3DQz.js.map +1 -0
  32. package/dist/assets/{host-Cmd205Dc.js → host-CWt-jtqz.js} +41 -16
  33. package/dist/assets/host-CWt-jtqz.js.map +1 -0
  34. package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js → hyperdhtRelay-CPYwSLLS.js} +2 -3
  35. package/dist/assets/{hyperdhtRelay-CEmBpT0Q.js.map → hyperdhtRelay-CPYwSLLS.js.map} +1 -1
  36. package/dist/assets/{irohWeb--YyEA-hX.js → irohWeb-4oChIOIp.js} +2 -3
  37. package/dist/assets/{irohWeb--YyEA-hX.js.map → irohWeb-4oChIOIp.js.map} +1 -1
  38. package/dist/assets/{main-BzNN6bkx.js → main-DbqcdShN.js} +142 -35
  39. package/dist/assets/main-DbqcdShN.js.map +1 -0
  40. package/dist/assets/{mainline-D5D5C-jT.js → mainline-ZkiOnevs.js} +2 -2
  41. package/dist/assets/{mainline-D5D5C-jT.js.map → mainline-ZkiOnevs.js.map} +1 -1
  42. package/dist/assets/{networkReason-DxZWHw6v.js → networkReason-CetKkTPo.js} +2 -2
  43. package/dist/assets/{networkReason-DxZWHw6v.js.map → networkReason-CetKkTPo.js.map} +1 -1
  44. package/dist/assets/nwc-CgkqyvsE.js +612 -0
  45. package/dist/assets/nwc-CgkqyvsE.js.map +1 -0
  46. package/dist/assets/nwcProvider-4eVz_8HP.js +77 -0
  47. package/dist/assets/nwcProvider-4eVz_8HP.js.map +1 -0
  48. package/dist/assets/{profiles-B6F4oa9K.js → profiles-BHc9pEYg.js} +2 -2
  49. package/dist/assets/{profiles-B6F4oa9K.js.map → profiles-BHc9pEYg.js.map} +1 -1
  50. package/dist/assets/registry-DlAKO0Fy.js +3793 -0
  51. package/dist/assets/registry-DlAKO0Fy.js.map +1 -0
  52. package/dist/assets/registry-OBJBnHVQ.js +2 -0
  53. package/dist/assets/resolve-CouHhCpJ.js +591 -0
  54. package/dist/assets/resolve-CouHhCpJ.js.map +1 -0
  55. package/dist/assets/{server-CBCEYKMN.js → server-Ss0gJ-nw.js} +184 -1181
  56. package/dist/assets/server-Ss0gJ-nw.js.map +1 -0
  57. package/dist/assets/{src-DhfGgAUu.js → src-DDzSERyc.js} +2249 -72
  58. package/dist/assets/src-DDzSERyc.js.map +1 -0
  59. package/dist/assets/{turnRefs-ChEfgbSX.js → turnRefs-B87dtrHM.js} +2 -2
  60. package/dist/assets/{turnRefs-ChEfgbSX.js.map → turnRefs-B87dtrHM.js.map} +1 -1
  61. package/dist/assets/types-C4_KafL_.js +122 -0
  62. package/dist/assets/types-C4_KafL_.js.map +1 -0
  63. package/dist/assets/{usdt-C1Xao2xH.js → usdt-Fj8CQO2z.js} +3 -3
  64. package/dist/assets/{usdt-C1Xao2xH.js.map → usdt-Fj8CQO2z.js.map} +1 -1
  65. package/dist/ghostly.mjs +1 -1
  66. package/package.json +1 -1
  67. package/dist/assets/api-CrxVxtY1.js.map +0 -1
  68. package/dist/assets/apiKit-CajiDxdK.js.map +0 -1
  69. package/dist/assets/backup-BAMi2lAq.js.map +0 -1
  70. package/dist/assets/buttons-CWl2yNJu.js +0 -2
  71. package/dist/assets/endpoint-Dn7H8qE1.js.map +0 -1
  72. package/dist/assets/errors-my6VJR_0.js.map +0 -1
  73. package/dist/assets/groupFiles-BNTSDaoq.js +0 -7685
  74. package/dist/assets/groupFiles-BNTSDaoq.js.map +0 -1
  75. package/dist/assets/groupFiles-DBbjOLuD.js +0 -2088
  76. package/dist/assets/groupFiles-DBbjOLuD.js.map +0 -1
  77. package/dist/assets/host-Cmd205Dc.js.map +0 -1
  78. package/dist/assets/main-BzNN6bkx.js.map +0 -1
  79. package/dist/assets/server-CBCEYKMN.js.map +0 -1
  80. package/dist/assets/src-DhfGgAUu.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nwc-CgkqyvsE.js","names":[],"sources":["../../../browser/src/engine/paymentAdapters/providers/nwc.ts"],"sourcesContent":["import { decodeBolt11 } from \"@ghostly/core\";\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport * as nip04 from \"nostr-tools/nip04\";\nimport * as nip44 from \"nostr-tools/nip44\";\nimport { finalizeEvent, getPublicKey, verifyEvent, type Event } from \"nostr-tools/pure\";\nimport type { InvoiceStatus, LightningCapabilities, LightningInfo, LightningInvoice, LightningPayResult, LightningPaymentRef, LightningPaymentStatus, LightningProvider } from \"./lightning\";\nimport { parseNwcUri, type NwcConnection } from \"./nwcProvider\";\nimport { NothingSpentError, type ProviderNetwork } from \"./types\";\n\n/**\n * Lightning through a wallet reached over Nostr Wallet Connect (NIP-47): Alby Hub, a node's NWC service, a\n * hosted wallet. The person pastes the wallet's `nostr+walletconnect://` URI; requests are signed with the\n * key it carries, encrypted to the wallet (NIP-44, or NIP-04 when its info event offers nothing else) and\n * sent through its relay; the answers come back the same way. That wallet holds the sats, applies its own\n * fee limit, and is custodial or not depending on what it is.\n *\n * Money safety (PROVIDERS.md): a request that never reached the relay, or an error code the wallet answers\n * before it tries to pay, is `NothingSpentError`. A `pay_invoice` whose answer does not come back in time,\n * or any other error, is an unknown outcome, and `paymentStatus` asks `lookup_invoice` until it ends.\n */\n\nconst KIND = { info: 13194, request: 23194, response: 23195 } as const;\n/** A relay message, an encrypted answer, an info event, an invoice: nothing bigger is read. */\nconst MAX_MESSAGE = 256 * 1024;\nconst MAX_CONTENT = 64 * 1024;\nconst MAX_INFO = 4096;\nconst MAX_INVOICE = 7089;\nconst CONNECT_MS = 10_000;\nconst ACK_MS = 10_000;\nconst REQUEST_MS = 20_000;\n/** A payment may take a while to route; an answer after this is an unknown outcome, looked up later. */\nconst PAY_MS = 90_000;\n/** The wallet must not start a payment it receives after this (NIP-47 `expiration`). */\nconst PAY_EXPIRY_S = 60;\n/** A payment the wallet never heard of is failed once its invoice can no longer be paid, with room for clocks. */\nconst EXPIRED_MARGIN_MS = 10 * 60_000;\nconst INVOICE_EXPIRY_S = 3600;\n\n/** Error codes a wallet answers before it tries to pay: nothing can have left it. (`BAD_REQUEST`: Alby Hub, an invoice it cannot read.) */\nconst REFUSED_BEFORE_PAYING = new Set([\"RATE_LIMITED\", \"NOT_IMPLEMENTED\", \"INSUFFICIENT_BALANCE\", \"QUOTA_EXCEEDED\", \"RESTRICTED\", \"UNAUTHORIZED\", \"UNSUPPORTED_ENCRYPTION\", \"BAD_REQUEST\"]);\nconst NETWORKS: Record<string, ProviderNetwork> = { mainnet: \"bitcoin\", bitcoin: \"bitcoin\", testnet: \"testnet\", testnet3: \"testnet\", testnet4: \"testnet\", signet: \"signet\", mutinynet: \"mutinynet\", regtest: \"regtest\" };\n\nconst HEX64 = /^[0-9a-f]{64}$/;\nconst hex = (bytes: Uint8Array) => Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\nconst bytes = (text: string) => Uint8Array.from(text.match(/../g)!, (b) => parseInt(b, 16));\nconst now = () => Math.floor(Date.now() / 1000);\nconst isMsat = (value: unknown): value is number => Number.isSafeInteger(value) && (value as number) >= 0;\n\n// -- one relay -----------------------------------------------------------------------------------------\n\n/** The request never reached the relay (not connected, refused by it): the wallet cannot have seen it. */\nexport class NotDelivered extends Error {\n constructor(message: string) { super(message); this.name = \"NotDelivered\"; }\n}\n/** An error the wallet answered, with its NIP-47 code. */\nexport class NwcError extends Error {\n constructor(readonly code: string, message: string) { super(message); this.name = \"NwcError\"; }\n}\n/** No answer in time. The request may or may not have been acted on. */\nexport class NoAnswer extends Error {\n constructor(method: string) { super(`The wallet did not answer ${method} in time`); this.name = \"NoAnswer\"; }\n}\n\ninterface Filter { kinds: number[]; authors: string[]; \"#p\"?: string[]; since?: number; limit?: number }\ninterface Subscription { filter: Filter; event(event: unknown): void; eose?(): void }\n\n/**\n * A minimal NIP-01 client for one relay: it knows exactly whether an event was written to an open socket\n * and whether the relay acknowledged it, which is what tells \"nothing sent\" from \"unknown\" apart.\n * It reconnects when asked to send again, and subscriptions follow it.\n */\nexport class RelayConnection {\n private socket?: WebSocket;\n private opening?: Promise<WebSocket>;\n private readonly subs = new Map<string, Subscription>();\n private readonly acks = new Map<string, { ok(): void; fail(error: Error): void }>();\n private closed = false;\n\n constructor(readonly url: string, private readonly WebSocketImpl: typeof WebSocket = globalThis.WebSocket) {}\n\n open(): Promise<WebSocket> {\n if (this.closed) return Promise.reject(new Error(\"The wallet connection is closed\"));\n if (this.socket?.readyState === 1) return Promise.resolve(this.socket);\n if (!this.opening) {\n const opening = this.connectSocket();\n this.opening = opening;\n opening.catch(() => {}).finally(() => { if (this.opening === opening) this.opening = undefined; });\n }\n return this.opening;\n }\n\n private connectSocket(): Promise<WebSocket> {\n return new Promise<WebSocket>((resolve, reject) => {\n let settled = false;\n const socket = new this.WebSocketImpl(this.url);\n const done = (error?: Error) => {\n if (settled) return;\n settled = true; clearTimeout(timer);\n if (error) { try { socket.close(); } catch { /* already closed */ } reject(error); return; }\n if (this.closed) { socket.close(); reject(new Error(\"The wallet connection is closed\")); return; }\n this.socket = socket;\n for (const [id, sub] of this.subs) this.write([\"REQ\", id, sub.filter]);\n resolve(socket);\n };\n const timer = setTimeout(() => done(new Error(\"The wallet's relay did not answer\")), CONNECT_MS);\n socket.onopen = () => done();\n socket.onerror = () => done(new Error(\"Could not reach the wallet's relay\"));\n socket.onclose = () => {\n done(new Error(\"The wallet's relay closed the connection\"));\n if (this.socket !== socket) return;\n this.socket = undefined;\n // Written, not acknowledged: whether the relay kept it is unknown.\n for (const [id, ack] of this.acks) { this.acks.delete(id); ack.fail(new Error(\"The relay connection dropped before it confirmed the request\")); }\n };\n socket.onmessage = (message) => { if (this.socket === socket) this.receive(message.data); };\n });\n }\n\n subscribe(id: string, filter: Filter, sub: Omit<Subscription, \"filter\">) {\n this.subs.set(id, { ...sub, filter });\n this.write([\"REQ\", id, filter]);\n }\n\n unsubscribe(id: string) {\n if (this.subs.delete(id)) this.write([\"CLOSE\", id]);\n }\n\n /**\n * Events matching `filter` the relay has stored, until it says that is all (or `ms` pass). With\n * `waitForOne`, a relay that has none yet is listened to until one arrives live, within `ms`.\n */\n async fetch(filter: Filter, { ms = CONNECT_MS, waitForOne = false }: { ms?: number; waitForOne?: boolean } = {}): Promise<unknown[]> {\n await this.open();\n const id = `q${hex(crypto.getRandomValues(new Uint8Array(6)))}`, events: unknown[] = [];\n try {\n await new Promise<void>((resolve) => {\n const timer = setTimeout(resolve, ms);\n let stored = false;\n const finish = () => { clearTimeout(timer); resolve(); };\n this.subscribe(id, filter, {\n event: (e) => { if (events.length < 20) events.push(e); if (stored) finish(); },\n eose: () => { stored = true; if (events.length || !waitForOne) finish(); },\n });\n });\n } finally { this.unsubscribe(id); }\n return events;\n }\n\n /**\n * Resolves once the relay accepted the event. `NotDelivered` when it certainly did not reach it (no open\n * socket, refused); any other rejection means it may have.\n */\n async publish(event: Event): Promise<void> {\n let socket: WebSocket;\n try { socket = await this.open(); } catch (error) { throw new NotDelivered(error instanceof Error ? error.message : \"Could not reach the wallet's relay\"); }\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => { this.acks.delete(event.id); reject(new Error(\"The relay did not confirm the request\")); }, ACK_MS);\n this.acks.set(event.id, { ok: () => { clearTimeout(timer); resolve(); }, fail: (error) => { clearTimeout(timer); reject(error); } });\n // A socket that is not open anymore drops what it is given without a word: nothing was sent.\n if (socket.readyState !== 1 || socket !== this.socket) { this.acks.delete(event.id); clearTimeout(timer); reject(new NotDelivered(\"The relay connection closed\")); return; }\n socket.send(JSON.stringify([\"EVENT\", event]));\n });\n }\n\n close() {\n this.closed = true;\n for (const [id, ack] of this.acks) { this.acks.delete(id); ack.fail(new Error(\"The wallet connection is closed\")); }\n this.subs.clear();\n try { this.socket?.close(); } catch { /* already closed */ }\n this.socket = undefined;\n }\n\n private write(message: unknown[]) {\n if (this.socket?.readyState === 1) this.socket.send(JSON.stringify(message));\n }\n\n private receive(data: unknown) {\n if (typeof data !== \"string\" || data.length > MAX_MESSAGE) return;\n let message: unknown;\n try { message = JSON.parse(data); } catch { return; }\n if (!Array.isArray(message) || typeof message[1] !== \"string\") return;\n const [type, id] = message as [string, string];\n if (type === \"EVENT\") this.subs.get(id)?.event(message[2]);\n else if (type === \"EOSE\") this.subs.get(id)?.eose?.();\n else if (type === \"OK\") {\n const ack = this.acks.get(id);\n if (!ack) return;\n this.acks.delete(id);\n const reason = typeof message[3] === \"string\" ? message[3].slice(0, 120) : \"\";\n // \"duplicate:\" is a relay saying it already has it: delivered.\n if (message[2] === true || reason.startsWith(\"duplicate:\")) ack.ok();\n else ack.fail(new NotDelivered(`The relay refused the request${reason ? ` (${reason})` : \"\"}`));\n }\n }\n}\n\n// -- the wallet ----------------------------------------------------------------------------------------\n\n/** What `lookup_invoice` (and `make_invoice`) answer, as far as it is read. Amounts in millisats. */\ninterface NwcTransaction {\n type?: string;\n state?: string;\n invoice?: string;\n payment_hash?: string;\n preimage?: string;\n amount?: number;\n fees_paid?: number;\n settled_at?: number | null;\n expires_at?: number | null;\n}\n\nexport interface NwcOptions {\n signal?: AbortSignal;\n /** For tests: time limits, and a WebSocket other than the global one. */\n requestMs?: number;\n /** How long to wait for the wallet's info event (a new app connection publishes it a moment later). */\n infoMs?: number;\n payMs?: number;\n WebSocket?: typeof WebSocket;\n}\n\ninterface Pending { method: string; resolve(result: Record<string, unknown>): void; reject(error: Error): void; timer: ReturnType<typeof setTimeout> }\n\nexport class NwcLightning implements LightningProvider {\n /** From the info event, then narrowed to what this connection may do (`get_info`). */\n capabilities: LightningCapabilities;\n private readonly clientPubkey: string;\n private readonly subscription = `nwc${hex(crypto.getRandomValues(new Uint8Array(6)))}`;\n private readonly pending = new Map<string, Pending>();\n private conversationKey?: Uint8Array;\n private network: ProviderNetwork = \"bitcoin\";\n private alias?: string;\n private closed = false;\n\n private constructor(private readonly connection: NwcConnection, private readonly relay: RelayConnection, readonly encryption: \"nip44_v2\" | \"nip04\", methods: ReadonlySet<string>, private readonly options: NwcOptions) {\n this.clientPubkey = getPublicKey(connection.secret);\n if (encryption === \"nip44_v2\") this.conversationKey = nip44.v2.utils.getConversationKey(connection.secret, connection.walletPubkey);\n this.capabilities = this.capabilitiesOf(methods);\n }\n\n /**\n * Reaches the wallet through the first relay that answers, reads its info event (the methods and the\n * encryption it offers), listens for its answers, and asks `get_info` for its network.\n */\n static async connect(uri: string, options: NwcOptions = {}): Promise<NwcLightning> {\n const connection = parseNwcUri(uri);\n options.signal?.throwIfAborted();\n let relay: RelayConnection | undefined, lastError: unknown;\n for (const url of connection.relays) {\n const candidate = new RelayConnection(url, options.WebSocket);\n try { await candidate.open(); relay = candidate; break; } catch (error) { candidate.close(); lastError = error; }\n }\n if (!relay) throw lastError instanceof Error ? lastError : new Error(\"Could not reach the wallet's relay\");\n let wallet: NwcLightning | undefined;\n try {\n const info = NwcLightning.newestInfo(await relay.fetch({ kinds: [KIND.info], authors: [connection.walletPubkey], limit: 5 }, { waitForOne: true, ms: options.infoMs }), connection.walletPubkey);\n if (!info) throw new Error(`The wallet has not published its NWC info on ${new URL(relay.url).host}: check the URI, or that the wallet is running`);\n const offered = (info.tags.find((t) => t[0] === \"encryption\")?.slice(1).join(\" \") ?? \"\").split(/\\s+/).filter(Boolean);\n // NIP-47: no `encryption` tag means a wallet that only speaks NIP-04.\n const encryption = offered.includes(\"nip44_v2\") ? \"nip44_v2\" : !offered.length || offered.includes(\"nip04\") ? \"nip04\" : undefined;\n if (!encryption) throw new Error(\"The wallet offers no encryption Ghostly speaks (NIP-44 or NIP-04)\");\n const methods = new Set(info.content.split(/\\s+/).filter(Boolean));\n wallet = new NwcLightning(connection, relay, encryption, methods, options);\n wallet.listen();\n if (options.signal) {\n const opened = wallet;\n if (options.signal.aborted) throw options.signal.reason;\n options.signal.addEventListener(\"abort\", () => void opened.close(), { once: true });\n }\n await wallet.readInfo(methods);\n return wallet;\n } catch (error) {\n if (wallet) await wallet.close(); else relay.close();\n throw error;\n }\n }\n\n private static newestInfo(events: unknown[], walletPubkey: string): Event | undefined {\n return (events as Event[])\n .filter((e) => e && e.kind === KIND.info && e.pubkey === walletPubkey && typeof e.content === \"string\" && e.content.length <= MAX_INFO && Array.isArray(e.tags) && verifyEvent(e))\n .sort((a, b) => b.created_at - a.created_at)[0];\n }\n\n private capabilitiesOf(methods: ReadonlySet<string>): LightningCapabilities {\n const lookup = methods.has(\"lookup_invoice\");\n // Paying needs looking up: a payment whose answer is lost could never be settled, and would hold the source.\n return { receive: methods.has(\"make_invoice\"), send: methods.has(\"pay_invoice\") && lookup, balance: methods.has(\"get_balance\"), lookup };\n }\n\n /**\n * `get_info` says the network and the methods this connection may use (the info event lists the\n * service's). Without it the wallet is taken to be on Bitcoin: a test wallet is then refused in the\n * Testnet mode and its invoices in the Mainnet one, while a real-money wallet never passes for a test one.\n */\n private async readInfo(offered: ReadonlySet<string>) {\n if (!offered.has(\"get_info\")) return;\n let info: Record<string, unknown>;\n try { info = await this.request(\"get_info\", {}); }\n catch (error) {\n // The service has it, this connection may not use it: the same as a wallet without it.\n if (error instanceof NwcError && [\"RESTRICTED\", \"UNAUTHORIZED\", \"NOT_IMPLEMENTED\"].includes(error.code)) return;\n throw error;\n }\n if (info.network !== undefined) {\n const network = typeof info.network === \"string\" ? NETWORKS[info.network.toLowerCase()] : undefined;\n if (!network) throw new Error(\"The wallet is on a network Ghostly does not know\");\n this.network = network;\n }\n if (typeof info.alias === \"string\" && info.alias.trim()) this.alias = info.alias.trim().slice(0, 40);\n if (Array.isArray(info.methods) && info.methods.every((m) => typeof m === \"string\")) {\n this.capabilities = this.capabilitiesOf(new Set(info.methods as string[]));\n }\n }\n\n private listen() {\n this.relay.subscribe(this.subscription, { kinds: [KIND.response], authors: [this.connection.walletPubkey], \"#p\": [this.clientPubkey], since: now() - 60 }, { event: (e) => this.answer(e) });\n }\n\n // -- requests and answers --\n\n private encrypt(text: string) {\n return this.conversationKey ? nip44.v2.encrypt(text, this.conversationKey) : nip04.encrypt(this.connection.secret, this.connection.walletPubkey, text);\n }\n\n private decrypt(content: string) {\n if (this.conversationKey && !content.includes(\"?iv=\")) return nip44.v2.decrypt(content, this.conversationKey);\n return nip04.decrypt(this.connection.secret, this.connection.walletPubkey, content);\n }\n\n /**\n * Sends one request and waits for its answer. `NotDelivered` when it never reached the relay; `NwcError`\n * for an error the wallet answered; `NoAnswer` (or a relay error) when the outcome is unknown.\n */\n async request(method: string, params: Record<string, unknown>, { ms = this.options.requestMs ?? REQUEST_MS, expiresIn }: { ms?: number; expiresIn?: number } = {}): Promise<Record<string, unknown>> {\n if (this.closed) throw new NotDelivered(\"The wallet connection is closed\");\n const tags = [[\"p\", this.connection.walletPubkey]];\n if (this.conversationKey) tags.push([\"encryption\", \"nip44_v2\"]);\n if (expiresIn) tags.push([\"expiration\", String(now() + expiresIn)]);\n const event = finalizeEvent({ kind: KIND.request, created_at: now(), tags, content: this.encrypt(JSON.stringify({ method, params })) }, this.connection.secret);\n const answer = new Promise<Record<string, unknown>>((resolve, reject) => {\n const timer = setTimeout(() => { this.pending.delete(event.id); reject(new NoAnswer(method)); }, ms);\n this.pending.set(event.id, { method, resolve, reject, timer });\n });\n // The answer can come before the relay's acknowledgement (or without one): whichever decides first wins.\n const delivered = this.relay.publish(event).then(() => undefined, (error: unknown) => {\n const waiting = this.pending.get(event.id);\n if (!(error instanceof NotDelivered) || !waiting) return undefined;\n clearTimeout(waiting.timer); this.pending.delete(event.id);\n return error;\n });\n return Promise.race([answer, delivered.then((error) => (error ? Promise.reject(error) : answer))]);\n }\n\n /** Reads an answer: from the wallet, signed, about a request of ours, of the method asked. Anything else is dropped. */\n private answer(raw: unknown) {\n const event = raw as Event;\n if (!event || typeof event !== \"object\" || event.kind !== KIND.response || event.pubkey !== this.connection.walletPubkey) return;\n if (typeof event.content !== \"string\" || event.content.length > MAX_CONTENT || !Array.isArray(event.tags)) return;\n const ref = event.tags.find((t) => Array.isArray(t) && t[0] === \"e\")?.[1];\n const pending = typeof ref === \"string\" ? this.pending.get(ref) : undefined;\n if (!pending || !event.tags.some((t) => Array.isArray(t) && t[0] === \"p\" && t[1] === this.clientPubkey)) return;\n if (!verifyEvent(event)) return;\n let body: { result_type?: unknown; error?: { code?: unknown; message?: unknown } | null; result?: unknown };\n try { body = JSON.parse(this.decrypt(event.content)); } catch { return; }\n if (!body || typeof body !== \"object\" || body.result_type !== pending.method) return;\n clearTimeout(pending.timer);\n this.pending.delete(ref!);\n if (body.error) {\n const code = typeof body.error.code === \"string\" ? body.error.code.slice(0, 40) : \"OTHER\";\n const message = typeof body.error.message === \"string\" && body.error.message.trim() ? body.error.message.trim().slice(0, 200) : code;\n pending.reject(new NwcError(code, message));\n } else if (body.result && typeof body.result === \"object\" && !Array.isArray(body.result)) pending.resolve(body.result as Record<string, unknown>);\n else pending.reject(new NwcError(\"OTHER\", \"The wallet sent an empty answer\"));\n }\n\n private async lookup(paymentHash: string): Promise<NwcTransaction> {\n const tx = await this.request(\"lookup_invoice\", { payment_hash: paymentHash }) as NwcTransaction;\n if (tx.payment_hash !== undefined && String(tx.payment_hash).toLowerCase() !== paymentHash) throw new Error(\"The wallet answered about another invoice\");\n return tx;\n }\n\n // -- LightningProvider --\n\n async info(): Promise<LightningInfo> {\n let balance: number | undefined;\n if (this.capabilities.balance) {\n const result = await this.request(\"get_balance\", {});\n if (!isMsat(result.balance)) throw new Error(\"The wallet returned an invalid balance\");\n balance = Math.floor(result.balance / 1000);\n }\n return { network: this.network, alias: `${this.alias ?? \"NWC wallet\"} via ${new URL(this.relay.url).host}`, balance };\n }\n\n async createInvoice(amount: number, memo?: string): Promise<LightningInvoice> {\n if (!Number.isSafeInteger(amount) || amount <= 0 || !Number.isSafeInteger(amount * 1000)) throw new Error(\"Invalid amount\");\n const result = await this.request(\"make_invoice\", { amount: amount * 1000, ...(memo ? { description: memo.slice(0, 200) } : {}), expiry: INVOICE_EXPIRY_S }) as NwcTransaction;\n const text = typeof result.invoice === \"string\" && result.invoice.length <= MAX_INVOICE ? result.invoice : \"\";\n const decoded = decodeBolt11(text);\n if (!decoded?.paymentHash || decoded.amountMsat !== BigInt(amount) * 1000n) throw new Error(\"The wallet returned an invoice that does not match the request\");\n if (result.payment_hash !== undefined && String(result.payment_hash).toLowerCase() !== decoded.paymentHash) throw new Error(\"The wallet returned an invoice that does not match its payment hash\");\n return { invoice: decoded.invoice, paymentHash: decoded.paymentHash, amount, expiresAt: decoded.expiresAt * 1000 };\n }\n\n async invoiceStatus(invoice: LightningInvoice): Promise<InvoiceStatus> {\n let tx: NwcTransaction;\n try { tx = await this.lookup(invoice.paymentHash); }\n catch (error) {\n if (error instanceof NwcError && error.code === \"NOT_FOUND\" && invoice.expiresAt < Date.now()) return { state: \"expired\" };\n throw error;\n }\n const expired = invoice.expiresAt < Date.now();\n if (tx.type === \"outgoing\") return { state: expired ? \"expired\" : \"open\" };\n // An incoming invoice's preimage is known before it is paid: only `state` or `settled_at` say it was.\n const state = tx.state ?? (tx.settled_at ? \"settled\" : \"pending\");\n if (state === \"settled\") return { state: \"paid\", amount: isMsat(tx.amount) ? Math.floor(tx.amount / 1000) : invoice.amount };\n if (state === \"expired\" || state === \"failed\") return { state: \"expired\" };\n return { state: expired ? \"expired\" : \"open\" };\n }\n\n /**\n * Pays through the wallet. NIP-47 carries no fee limit: the wallet applies its own, and the fee it\n * reports is passed on as it is.\n */\n async payInvoice(invoice: string, _maxFee: number): Promise<LightningPayResult> {\n const decoded = decodeBolt11(invoice);\n if (!decoded?.paymentHash || decoded.amountSat === null) throw new NothingSpentError(\"That invoice cannot be paid through NWC\");\n if (!this.capabilities.send) throw new NothingSpentError(\"This wallet connection may not pay and look up invoices\");\n let result: Record<string, unknown>;\n try {\n result = await this.request(\"pay_invoice\", { invoice: decoded.invoice }, { ms: this.options.payMs ?? PAY_MS, expiresIn: PAY_EXPIRY_S });\n } catch (error) {\n if (error instanceof NotDelivered) throw new NothingSpentError(`The payment did not reach the wallet: ${error.message}`);\n if (error instanceof NwcError && REFUSED_BEFORE_PAYING.has(error.code)) throw new NothingSpentError(`The wallet refused the payment: ${error.message}`);\n // Any other code may come after the wallet tried (Alby Hub answers INTERNAL with LND's failure reason).\n if (error instanceof NwcError) return this.confirmFailure(decoded.invoice, decoded.paymentHash, error);\n throw error;\n }\n const preimage = typeof result.preimage === \"string\" ? result.preimage.toLowerCase() : \"\";\n // \"Paid\" without a proof that pays this invoice is not taken at its word: it is looked up instead.\n if (!HEX64.test(preimage) || hex(sha256(bytes(preimage))) !== decoded.paymentHash) throw new Error(\"The wallet said it paid, without a proof that matches the invoice\");\n return { state: \"paid\", preimage, fee: isMsat(result.fees_paid) ? Math.ceil(result.fees_paid / 1000) : undefined };\n }\n\n /**\n * An error after the wallet may have tried (`PAYMENT_FAILED` \"may be due to a timeout\", `INTERNAL`,\n * `OTHER`…): its own record decides. Only a payment it records as failed is `NothingSpentError`; one it\n * records as paid is paid; anything else is pending, or unknown when it cannot be asked.\n */\n private async confirmFailure(invoice: string, paymentHash: string, failure: NwcError): Promise<LightningPayResult> {\n let status: LightningPaymentStatus;\n try { status = await this.paymentStatus({ invoice, paymentHash }); }\n catch { throw new Error(`The wallet said the payment failed (${failure.message}), and cannot confirm it yet`); }\n if (status.state === \"failed\") throw new NothingSpentError(`The payment failed: ${failure.message}`);\n return status.state === \"paid\" ? { state: \"paid\", fee: status.fee, preimage: status.preimage } : { state: \"pending\" };\n }\n\n async paymentStatus(payment: LightningPaymentRef): Promise<LightningPaymentStatus> {\n let tx: NwcTransaction;\n try { tx = await this.lookup(payment.paymentHash); }\n catch (error) {\n if (!(error instanceof NwcError && error.code === \"NOT_FOUND\")) throw error;\n // The wallet never heard of it: failed once nobody can pay that invoice anymore, until then it may still start.\n const expiresAt = (decodeBolt11(payment.invoice)?.expiresAt ?? Infinity) * 1000;\n return { state: expiresAt + EXPIRED_MARGIN_MS < Date.now() ? \"failed\" : \"pending\" };\n }\n // Our own invoice (a wallet paying itself): not an answer about the payment.\n if (tx.type === \"incoming\") return { state: \"pending\" };\n const preimage = typeof tx.preimage === \"string\" && HEX64.test(tx.preimage.toLowerCase()) ? tx.preimage.toLowerCase() : undefined;\n const proven = !!preimage && hex(sha256(bytes(preimage))) === payment.paymentHash;\n const state = tx.state ?? (tx.settled_at || proven ? \"settled\" : \"pending\");\n const fee = isMsat(tx.fees_paid) ? Math.ceil(tx.fees_paid / 1000) : undefined;\n if (state === \"settled\") {\n if (preimage && !proven) throw new Error(\"The wallet's proof of payment does not match the invoice\");\n return { state: \"paid\", fee, preimage };\n }\n if (state === \"failed\" || state === \"expired\") return { state: \"failed\" };\n return { state: \"pending\" };\n }\n\n async close() {\n if (this.closed) return;\n this.closed = true;\n for (const [id, pending] of this.pending) { clearTimeout(pending.timer); this.pending.delete(id); pending.reject(new Error(\"The wallet connection is closed\")); }\n this.relay.close();\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ import { n as PROVIDER_PLATFORMS } from "./types-C4_KafL_.js";
2
+ //#region ../browser/src/engine/paymentAdapters/providers/nwcProvider.ts
3
+ /**
4
+ * Nostr Wallet Connect as the source picker lists it, and its connection URI. The client (`./nwc`) and its NIP-04 /
5
+ * NIP-44 encryption load when a wallet connects, not with the app.
6
+ */
7
+ var MAX_URI = 2048;
8
+ var MAX_RELAYS = 5;
9
+ var HEX64 = /^[0-9a-f]{64}$/;
10
+ var bytes = (text) => Uint8Array.from(text.match(/../g), (b) => parseInt(b, 16));
11
+ /** Only secure relays, except on this machine (a local test relay). */
12
+ function checkRelay(address) {
13
+ let relay;
14
+ try {
15
+ relay = new URL(address);
16
+ } catch {
17
+ throw new Error("The connection URI has a relay that is not a URL");
18
+ }
19
+ if (relay.username || relay.password || relay.hash) throw new Error("The connection URI has a relay with credentials in it");
20
+ const loopback = [
21
+ "127.0.0.1",
22
+ "localhost",
23
+ "[::1]"
24
+ ].includes(relay.hostname);
25
+ if (!(relay.protocol === "wss:" || relay.protocol === "ws:" && loopback)) throw new Error("Use a wallet whose relay is secure (wss://)");
26
+ return relay.href;
27
+ }
28
+ /** Reads a `nostr+walletconnect://` URI. Its errors never repeat it: it holds a secret. */
29
+ function parseNwcUri(input) {
30
+ const text = (input ?? "").trim();
31
+ if (text.length > MAX_URI) throw new Error("That connection URI is too long");
32
+ const match = /^(?:nostr\+walletconnect|nostrwalletconnect):(?:\/\/)?([0-9a-fA-F]{64})\/?\?([^#]*)$/.exec(text);
33
+ if (!match) throw new Error("That is not a Nostr Wallet Connect URI (nostr+walletconnect://…)");
34
+ const params = new URLSearchParams(match[2]);
35
+ const secret = (params.get("secret") ?? "").toLowerCase();
36
+ if (!HEX64.test(secret) || /^0+$/.test(secret)) throw new Error("The connection URI has no valid secret");
37
+ const relays = [...new Set(params.getAll("relay").map(checkRelay))];
38
+ if (!relays.length) throw new Error("The connection URI names no relay");
39
+ if (relays.length > MAX_RELAYS) throw new Error("The connection URI names too many relays");
40
+ return {
41
+ walletPubkey: match[1].toLowerCase(),
42
+ relays,
43
+ secret: bytes(secret)
44
+ };
45
+ }
46
+ var nwc = {
47
+ id: "nwc",
48
+ label: "Nostr Wallet Connect",
49
+ kind: "lightning",
50
+ description: "A wallet you connect with its NWC URI (Alby Hub, a node's NWC service, a hosted wallet). That wallet holds the sats and sets its own fee limit: custodial or not depends on the wallet.",
51
+ networks: [
52
+ "bitcoin",
53
+ "testnet",
54
+ "signet",
55
+ "mutinynet",
56
+ "regtest"
57
+ ],
58
+ platforms: PROVIDER_PLATFORMS,
59
+ fields: [{
60
+ name: "uri",
61
+ label: "Connection URI",
62
+ kind: "secret",
63
+ placeholder: "nostr+walletconnect://…",
64
+ help: "From your wallet's app connections. Allow making, paying and looking up invoices, and reading the balance."
65
+ }],
66
+ validate({ secrets }) {
67
+ parseNwcUri(secrets.uri ?? "");
68
+ },
69
+ async create({ secrets }, host) {
70
+ const { NwcLightning } = await import("./nwc-CgkqyvsE.js");
71
+ return NwcLightning.connect(secrets.uri, { signal: host.signal });
72
+ }
73
+ };
74
+ //#endregion
75
+ export { parseNwcUri as n, nwc as t };
76
+
77
+ //# sourceMappingURL=nwcProvider-4eVz_8HP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nwcProvider-4eVz_8HP.js","names":[],"sources":["../../../browser/src/engine/paymentAdapters/providers/nwcProvider.ts"],"sourcesContent":["import type { LightningProviderDescriptor } from \"./lightning\";\nimport { PROVIDER_PLATFORMS } from \"./types\";\n\n/**\n * Nostr Wallet Connect as the source picker lists it, and its connection URI. The client (`./nwc`) and its NIP-04 /\n * NIP-44 encryption load when a wallet connects, not with the app.\n */\n\nconst MAX_URI = 2048;\nconst MAX_RELAYS = 5;\nconst HEX64 = /^[0-9a-f]{64}$/;\nconst bytes = (text: string) => Uint8Array.from(text.match(/../g)!, (b) => parseInt(b, 16));\n\nexport interface NwcConnection {\n walletPubkey: string;\n relays: string[];\n /** The client key the wallet granted: a secret. */\n secret: Uint8Array;\n}\n\n/** Only secure relays, except on this machine (a local test relay). */\nfunction checkRelay(address: string): string {\n let relay: URL;\n try { relay = new URL(address); } catch { throw new Error(\"The connection URI has a relay that is not a URL\"); }\n if (relay.username || relay.password || relay.hash) throw new Error(\"The connection URI has a relay with credentials in it\");\n const loopback = [\"127.0.0.1\", \"localhost\", \"[::1]\"].includes(relay.hostname);\n if (!(relay.protocol === \"wss:\" || (relay.protocol === \"ws:\" && loopback))) throw new Error(\"Use a wallet whose relay is secure (wss://)\");\n return relay.href;\n}\n\n/** Reads a `nostr+walletconnect://` URI. Its errors never repeat it: it holds a secret. */\nexport function parseNwcUri(input: string): NwcConnection {\n const text = (input ?? \"\").trim();\n if (text.length > MAX_URI) throw new Error(\"That connection URI is too long\");\n const match = /^(?:nostr\\+walletconnect|nostrwalletconnect):(?:\\/\\/)?([0-9a-fA-F]{64})\\/?\\?([^#]*)$/.exec(text);\n if (!match) throw new Error(\"That is not a Nostr Wallet Connect URI (nostr+walletconnect://…)\");\n const params = new URLSearchParams(match[2]);\n const secret = (params.get(\"secret\") ?? \"\").toLowerCase();\n if (!HEX64.test(secret) || /^0+$/.test(secret)) throw new Error(\"The connection URI has no valid secret\");\n const relays = [...new Set(params.getAll(\"relay\").map(checkRelay))];\n if (!relays.length) throw new Error(\"The connection URI names no relay\");\n if (relays.length > MAX_RELAYS) throw new Error(\"The connection URI names too many relays\");\n return { walletPubkey: match[1].toLowerCase(), relays, secret: bytes(secret) };\n}\n\nexport const nwc: LightningProviderDescriptor = {\n id: \"nwc\",\n label: \"Nostr Wallet Connect\",\n kind: \"lightning\",\n description: \"A wallet you connect with its NWC URI (Alby Hub, a node's NWC service, a hosted wallet). That wallet holds the sats and sets its own fee limit: custodial or not depends on the wallet.\",\n networks: [\"bitcoin\", \"testnet\", \"signet\", \"mutinynet\", \"regtest\"],\n platforms: PROVIDER_PLATFORMS,\n fields: [{\n name: \"uri\", label: \"Connection URI\", kind: \"secret\", placeholder: \"nostr+walletconnect://…\",\n help: \"From your wallet's app connections. Allow making, paying and looking up invoices, and reading the balance.\",\n }],\n validate({ secrets }) { parseNwcUri(secrets.uri ?? \"\"); },\n async create({ secrets }, host) {\n const { NwcLightning } = await import(\"./nwc\");\n return NwcLightning.connect(secrets.uri, { signal: host.signal });\n },\n};\n"],"mappings":";;;;;;AAQA,IAAM,UAAU;AAChB,IAAM,aAAa;AACnB,IAAM,QAAQ;AACd,IAAM,SAAS,SAAiB,WAAW,KAAK,KAAK,MAAM,KAAK,IAAK,MAAM,SAAS,GAAG,EAAE,CAAC;;AAU1F,SAAS,WAAW,SAAyB;CAC3C,IAAI;CACJ,IAAI;EAAE,QAAQ,IAAI,IAAI,OAAO;CAAG,QAAQ;EAAE,MAAM,IAAI,MAAM,kDAAkD;CAAG;CAC/G,IAAI,MAAM,YAAY,MAAM,YAAY,MAAM,MAAM,MAAM,IAAI,MAAM,uDAAuD;CAC3H,MAAM,WAAW;EAAC;EAAa;EAAa;CAAO,CAAC,CAAC,SAAS,MAAM,QAAQ;CAC5E,IAAI,EAAE,MAAM,aAAa,UAAW,MAAM,aAAa,SAAS,WAAY,MAAM,IAAI,MAAM,6CAA6C;CACzI,OAAO,MAAM;AACf;;AAGA,SAAgB,YAAY,OAA8B;CACxD,MAAM,QAAQ,SAAS,GAAA,CAAI,KAAK;CAChC,IAAI,KAAK,SAAS,SAAS,MAAM,IAAI,MAAM,iCAAiC;CAC5E,MAAM,QAAQ,uFAAuF,KAAK,IAAI;CAC9G,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,kEAAkE;CAC9F,MAAM,SAAS,IAAI,gBAAgB,MAAM,EAAE;CAC3C,MAAM,UAAU,OAAO,IAAI,QAAQ,KAAK,GAAA,CAAI,YAAY;CACxD,IAAI,CAAC,MAAM,KAAK,MAAM,KAAK,OAAO,KAAK,MAAM,GAAG,MAAM,IAAI,MAAM,wCAAwC;CACxG,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,OAAO,OAAO,OAAO,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC;CAClE,IAAI,CAAC,OAAO,QAAQ,MAAM,IAAI,MAAM,mCAAmC;CACvE,IAAI,OAAO,SAAS,YAAY,MAAM,IAAI,MAAM,0CAA0C;CAC1F,OAAO;EAAE,cAAc,MAAM,EAAE,CAAC,YAAY;EAAG;EAAQ,QAAQ,MAAM,MAAM;CAAE;AAC/E;AAEA,IAAa,MAAmC;CAC9C,IAAI;CACJ,OAAO;CACP,MAAM;CACN,aAAa;CACb,UAAU;EAAC;EAAW;EAAW;EAAU;EAAa;CAAS;CACjE,WAAW;CACX,QAAQ,CAAC;EACP,MAAM;EAAO,OAAO;EAAkB,MAAM;EAAU,aAAa;EACnE,MAAM;CACR,CAAC;CACD,SAAS,EAAE,WAAW;EAAE,YAAY,QAAQ,OAAO,EAAE;CAAG;CACxD,MAAM,OAAO,EAAE,WAAW,MAAM;EAC9B,MAAM,EAAE,iBAAiB,MAAM,OAAO;EACtC,OAAO,aAAa,QAAQ,QAAQ,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAC;CAClE;AACF"}
@@ -1,4 +1,4 @@
1
- import { t as CliError } from "./errors-my6VJR_0.js";
1
+ import { t as CliError } from "./errors-DO5z6h_l.js";
2
2
  import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { createHash } from "node:crypto";
@@ -136,4 +136,4 @@ function lockOwner(paths) {
136
136
  //#endregion
137
137
  export { createProfile as a, listProfiles as c, profilePaths as d, selectProfile as f, checkProfileName as i, lockOwner as l, RESTORED_MARK as n, currentProfile as o, acquireLock as r, ghostlyHome as s, DEFAULT_PROFILE as t, profileExists as u };
138
138
 
139
- //# sourceMappingURL=profiles-B6F4oa9K.js.map
139
+ //# sourceMappingURL=profiles-BHc9pEYg.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"profiles-B6F4oa9K.js","names":[],"sources":["../../src/profiles.ts"],"sourcesContent":["import { closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, writeSync } from \"node:fs\";\nimport { createHash } from \"node:crypto\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { CliError } from \"./errors\";\n\n/**\n * Profiles on disk (WISP 1100 § Profiles and files): `<home>/profiles/<name>/`, 0700, one engine store each.\n * `<home>/current` names the profile commands use when none is given.\n */\nexport const DEFAULT_PROFILE = \"default\";\nconst NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/i;\n\n/**\n * An empty `--home` or `--profile` (an unset variable in a script) is a usage error: taken as not given, it ran the\n * command in the current folder, or as the current profile. An empty variable of the environment counts as unset.\n */\nexport function ghostlyHome(option?: string): string {\n if (option === \"\") throw new CliError(\"usage\", \"--home needs a folder\");\n return resolve(option ?? (process.env.GHOSTLY_HOME || join(homedir(), \".ghostly\")));\n}\n\nexport function checkProfileName(name: string): string {\n if (!NAME.test(name) || name.includes(\"..\")) throw new CliError(\"usage\", `Not a profile name: ${JSON.stringify(name)} (letters, digits, \".\", \"_\" and \"-\", up to 64)`);\n return name;\n}\n\nexport function currentProfile(home: string, option?: string): string {\n if (option === \"\") throw new CliError(\"usage\", \"--profile needs a profile name\");\n if (option !== undefined) return checkProfileName(option);\n if (process.env.GHOSTLY_PROFILE) return checkProfileName(process.env.GHOSTLY_PROFILE);\n const file = join(home, \"current\");\n if (existsSync(file)) {\n const name = readFileSync(file, \"utf8\").trim();\n if (name) return checkProfileName(name);\n }\n return DEFAULT_PROFILE;\n}\n\nexport function profileDir(home: string, name: string): string {\n return join(home, \"profiles\", checkProfileName(name));\n}\n\nexport interface ProfilePaths { name: string; dir: string; db: string; socket: string; lock: string; events: string; files: string; log: string }\nexport function profilePaths(home: string, name: string): ProfilePaths {\n const dir = profileDir(home, name);\n return {\n name, dir, db: join(dir, \"db\"), socket: socketPath(dir), lock: join(dir, \"daemon.lock\"),\n events: join(dir, \"events.jsonl\"), files: join(dir, \"files\"), log: join(dir, \"daemon.log\"),\n };\n}\n\n/**\n * The daemon's socket: in the profile's folder, unless that path is longer than a Unix socket may be (104 bytes on\n * macOS); then in a folder of /tmp named after the profile's, which the daemon makes owner-only and checks before\n * use (`privateFolder`), and whose socket a client connects to only when it is this user's (`ownSocket`).\n */\nfunction socketPath(dir: string): string {\n const inside = join(dir, \"daemon.sock\");\n if (process.platform === \"win32\") return `\\\\\\\\.\\\\pipe\\\\ghostly-${createHash(\"sha256\").update(dir).digest(\"hex\").slice(0, 24)}`;\n if (Buffer.byteLength(inside) <= 100) return inside;\n return join(\"/tmp\", `ghostly-${createHash(\"sha256\").update(dir).digest(\"hex\").slice(0, 24)}`, \"daemon.sock\");\n}\n\nfunction ensureFolder(path: string): void {\n mkdirSync(path, { recursive: true, mode: 0o700 });\n}\n\n/** Makes the profile's folder; refused when it exists and `existing` is not allowed. */\nexport function createProfile(home: string, name: string, { existing = false } = {}): ProfilePaths {\n const paths = profilePaths(home, name);\n if (existsSync(paths.dir) && !existing) throw new CliError(\"refused\", `Profile ${name} exists`);\n ensureFolder(join(home, \"profiles\"));\n ensureFolder(paths.dir);\n return paths;\n}\n\n/**\n * A file in the folder of a profile a restore made, until its engine starts for the first time: the store came back\n * whole, as the backup held it, and the money in it is then treated as every restore treats it (WISP 05 § Restoring).\n */\nexport const RESTORED_MARK = \"restored\";\n\nexport function profileExists(home: string, name: string): boolean {\n return existsSync(profileDir(home, name));\n}\n\nexport function listProfiles(home: string): string[] {\n const root = join(home, \"profiles\");\n if (!existsSync(root)) return [];\n return readdirSync(root).filter((name) => NAME.test(name) && statSync(join(root, name)).isDirectory()).sort();\n}\n\nexport function selectProfile(home: string, name: string): void {\n if (!profileExists(home, name)) throw new CliError(\"not_found\", `No profile ${name}`);\n ensureFolder(home);\n writeFileSync(join(home, \"current\"), name + \"\\n\", { mode: 0o600 });\n}\n\n/**\n * One process per profile: the daemon or a one-shot command. The lock holds the owner's pid; a lock whose\n * process is gone is taken over.\n */\nexport function acquireLock(paths: ProfilePaths): () => void {\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n const fd = openSync(paths.lock, \"wx\", 0o600);\n writeSync(fd, String(process.pid));\n closeSync(fd);\n let released = false;\n return () => {\n if (released) return;\n released = true;\n try { if (readFileSync(paths.lock, \"utf8\").trim() === String(process.pid)) rmSync(paths.lock, { force: true }); } catch { /* gone already */ }\n };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n const owner = lockOwner(paths);\n if (owner !== null) throw new CliError(\"busy\", `Profile ${paths.name} is in use by process ${owner}`, { pid: owner });\n rmSync(paths.lock, { force: true });\n }\n }\n throw new CliError(\"busy\", `Profile ${paths.name} is in use`);\n}\n\n/** The pid holding the profile, or null when nobody (or a process that is gone) does. */\nexport function lockOwner(paths: ProfilePaths): number | null {\n let pid: number;\n try { pid = Number(readFileSync(paths.lock, \"utf8\").trim()); } catch { return null; }\n if (!Number.isInteger(pid) || pid <= 0) return null;\n try { process.kill(pid, 0); return pid; } catch (error) { return (error as NodeJS.ErrnoException).code === \"EPERM\" ? pid : null; }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"profiles-BHc9pEYg.js","names":[],"sources":["../../src/profiles.ts"],"sourcesContent":["import { closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, writeSync } from \"node:fs\";\nimport { createHash } from \"node:crypto\";\nimport { homedir } from \"node:os\";\nimport { join, resolve } from \"node:path\";\nimport { CliError } from \"./errors\";\n\n/**\n * Profiles on disk (WISP 1100 § Profiles and files): `<home>/profiles/<name>/`, 0700, one engine store each.\n * `<home>/current` names the profile commands use when none is given.\n */\nexport const DEFAULT_PROFILE = \"default\";\nconst NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/i;\n\n/**\n * An empty `--home` or `--profile` (an unset variable in a script) is a usage error: taken as not given, it ran the\n * command in the current folder, or as the current profile. An empty variable of the environment counts as unset.\n */\nexport function ghostlyHome(option?: string): string {\n if (option === \"\") throw new CliError(\"usage\", \"--home needs a folder\");\n return resolve(option ?? (process.env.GHOSTLY_HOME || join(homedir(), \".ghostly\")));\n}\n\nexport function checkProfileName(name: string): string {\n if (!NAME.test(name) || name.includes(\"..\")) throw new CliError(\"usage\", `Not a profile name: ${JSON.stringify(name)} (letters, digits, \".\", \"_\" and \"-\", up to 64)`);\n return name;\n}\n\nexport function currentProfile(home: string, option?: string): string {\n if (option === \"\") throw new CliError(\"usage\", \"--profile needs a profile name\");\n if (option !== undefined) return checkProfileName(option);\n if (process.env.GHOSTLY_PROFILE) return checkProfileName(process.env.GHOSTLY_PROFILE);\n const file = join(home, \"current\");\n if (existsSync(file)) {\n const name = readFileSync(file, \"utf8\").trim();\n if (name) return checkProfileName(name);\n }\n return DEFAULT_PROFILE;\n}\n\nexport function profileDir(home: string, name: string): string {\n return join(home, \"profiles\", checkProfileName(name));\n}\n\nexport interface ProfilePaths { name: string; dir: string; db: string; socket: string; lock: string; events: string; files: string; log: string }\nexport function profilePaths(home: string, name: string): ProfilePaths {\n const dir = profileDir(home, name);\n return {\n name, dir, db: join(dir, \"db\"), socket: socketPath(dir), lock: join(dir, \"daemon.lock\"),\n events: join(dir, \"events.jsonl\"), files: join(dir, \"files\"), log: join(dir, \"daemon.log\"),\n };\n}\n\n/**\n * The daemon's socket: in the profile's folder, unless that path is longer than a Unix socket may be (104 bytes on\n * macOS); then in a folder of /tmp named after the profile's, which the daemon makes owner-only and checks before\n * use (`privateFolder`), and whose socket a client connects to only when it is this user's (`ownSocket`).\n */\nfunction socketPath(dir: string): string {\n const inside = join(dir, \"daemon.sock\");\n if (process.platform === \"win32\") return `\\\\\\\\.\\\\pipe\\\\ghostly-${createHash(\"sha256\").update(dir).digest(\"hex\").slice(0, 24)}`;\n if (Buffer.byteLength(inside) <= 100) return inside;\n return join(\"/tmp\", `ghostly-${createHash(\"sha256\").update(dir).digest(\"hex\").slice(0, 24)}`, \"daemon.sock\");\n}\n\nfunction ensureFolder(path: string): void {\n mkdirSync(path, { recursive: true, mode: 0o700 });\n}\n\n/** Makes the profile's folder; refused when it exists and `existing` is not allowed. */\nexport function createProfile(home: string, name: string, { existing = false } = {}): ProfilePaths {\n const paths = profilePaths(home, name);\n if (existsSync(paths.dir) && !existing) throw new CliError(\"refused\", `Profile ${name} exists`);\n ensureFolder(join(home, \"profiles\"));\n ensureFolder(paths.dir);\n return paths;\n}\n\n/**\n * A file in the folder of a profile a restore made, until its engine starts for the first time: the store came back\n * whole, as the backup held it, and the money in it is then treated as every restore treats it (WISP 05 § Restoring).\n */\nexport const RESTORED_MARK = \"restored\";\n\nexport function profileExists(home: string, name: string): boolean {\n return existsSync(profileDir(home, name));\n}\n\nexport function listProfiles(home: string): string[] {\n const root = join(home, \"profiles\");\n if (!existsSync(root)) return [];\n return readdirSync(root).filter((name) => NAME.test(name) && statSync(join(root, name)).isDirectory()).sort();\n}\n\nexport function selectProfile(home: string, name: string): void {\n if (!profileExists(home, name)) throw new CliError(\"not_found\", `No profile ${name}`);\n ensureFolder(home);\n writeFileSync(join(home, \"current\"), name + \"\\n\", { mode: 0o600 });\n}\n\n/**\n * One process per profile: the daemon or a one-shot command. The lock holds the owner's pid; a lock whose\n * process is gone is taken over.\n */\nexport function acquireLock(paths: ProfilePaths): () => void {\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n const fd = openSync(paths.lock, \"wx\", 0o600);\n writeSync(fd, String(process.pid));\n closeSync(fd);\n let released = false;\n return () => {\n if (released) return;\n released = true;\n try { if (readFileSync(paths.lock, \"utf8\").trim() === String(process.pid)) rmSync(paths.lock, { force: true }); } catch { /* gone already */ }\n };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n const owner = lockOwner(paths);\n if (owner !== null) throw new CliError(\"busy\", `Profile ${paths.name} is in use by process ${owner}`, { pid: owner });\n rmSync(paths.lock, { force: true });\n }\n }\n throw new CliError(\"busy\", `Profile ${paths.name} is in use`);\n}\n\n/** The pid holding the profile, or null when nobody (or a process that is gone) does. */\nexport function lockOwner(paths: ProfilePaths): number | null {\n let pid: number;\n try { pid = Number(readFileSync(paths.lock, \"utf8\").trim()); } catch { return null; }\n if (!Number.isInteger(pid) || pid <= 0) return null;\n try { process.kill(pid, 0); return pid; } catch (error) { return (error as NodeJS.ErrnoException).code === \"EPERM\" ? pid : null; }\n}\n"],"mappings":"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"}