@needmoretruth/nmts-cli 0.17.2 → 0.34.2

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 (419) hide show
  1. package/AGENTS.md +125 -517
  2. package/CHANGELOG.md +104 -0
  3. package/README.ko.md +377 -469
  4. package/README.md +394 -474
  5. package/dist/account-proof.d.ts +29 -0
  6. package/dist/account-proof.js +6 -6
  7. package/dist/account.d.ts +18 -0
  8. package/dist/account.js +5 -5
  9. package/dist/agent-host.d.ts +71 -0
  10. package/dist/agent-host.js +169 -0
  11. package/dist/api-advice.d.ts +2 -0
  12. package/dist/api-advice.js +210 -0
  13. package/dist/api-key.d.ts +79 -0
  14. package/dist/api-key.js +7 -7
  15. package/dist/api-refusal.d.ts +14 -0
  16. package/dist/api-refusal.js +8 -0
  17. package/dist/api.d.ts +114 -0
  18. package/dist/api.js +59 -162
  19. package/dist/args.d.ts +160 -0
  20. package/dist/args.js +118 -30
  21. package/dist/artifact-about.d.ts +49 -0
  22. package/dist/autonomy.d.ts +39 -0
  23. package/dist/autonomy.js +110 -32
  24. package/dist/code-access.d.ts +29 -0
  25. package/dist/code-access.js +3 -3
  26. package/dist/code-vault.d.ts +38 -0
  27. package/dist/code-vault.js +8 -8
  28. package/dist/collision.d.ts +67 -0
  29. package/dist/collision.js +1 -1
  30. package/dist/commands/accept-terms.d.ts +11 -0
  31. package/dist/commands/accept-terms.js +106 -0
  32. package/dist/commands/balance.d.ts +7 -0
  33. package/dist/commands/balance.js +54 -1
  34. package/dist/commands/create-code-file.d.ts +24 -0
  35. package/dist/commands/create-code-file.js +93 -0
  36. package/dist/commands/create-link.d.ts +10 -0
  37. package/dist/commands/create-link.js +232 -0
  38. package/dist/commands/create.d.ts +20 -0
  39. package/dist/commands/create.js +43 -102
  40. package/dist/commands/credits.d.ts +9 -0
  41. package/dist/commands/credits.js +110 -0
  42. package/dist/commands/delete-account.d.ts +11 -0
  43. package/dist/commands/delete-account.js +63 -0
  44. package/dist/commands/deposit.d.ts +7 -0
  45. package/dist/commands/deposit.js +85 -0
  46. package/dist/commands/devices.d.ts +12 -0
  47. package/dist/commands/devices.js +144 -0
  48. package/dist/commands/documents.d.ts +35 -0
  49. package/dist/commands/documents.js +212 -0
  50. package/dist/commands/env.d.ts +5 -0
  51. package/dist/commands/env.js +10 -2
  52. package/dist/commands/erase.d.ts +13 -0
  53. package/dist/commands/erase.js +193 -0
  54. package/dist/commands/expiring.d.ts +19 -0
  55. package/dist/commands/extend.d.ts +33 -0
  56. package/dist/commands/extend.js +54 -15
  57. package/dist/commands/get.d.ts +18 -0
  58. package/dist/commands/key-manage.d.ts +11 -0
  59. package/dist/commands/key-manage.js +162 -0
  60. package/dist/commands/key.d.ts +28 -0
  61. package/dist/commands/key.js +249 -0
  62. package/dist/commands/kit.d.ts +11 -0
  63. package/dist/commands/kit.js +7 -7
  64. package/dist/commands/listfile.d.ts +13 -0
  65. package/dist/commands/listfile.js +12 -5
  66. package/dist/commands/login.d.ts +18 -0
  67. package/dist/commands/login.js +19 -19
  68. package/dist/commands/logout.d.ts +1 -0
  69. package/dist/commands/logout.js +1 -1
  70. package/dist/commands/losses.d.ts +12 -0
  71. package/dist/commands/losses.js +180 -0
  72. package/dist/commands/ls.d.ts +23 -0
  73. package/dist/commands/marks-dispatch.d.ts +2 -0
  74. package/dist/commands/marks-dispatch.js +51 -0
  75. package/dist/commands/marks.d.ts +46 -0
  76. package/dist/commands/marks.js +80 -0
  77. package/dist/commands/mcp.d.ts +24 -0
  78. package/dist/commands/mcp.js +70 -5
  79. package/dist/commands/mode.d.ts +8 -0
  80. package/dist/commands/mode.js +60 -24
  81. package/dist/commands/on-collision.d.ts +6 -0
  82. package/dist/commands/organise.d.ts +46 -0
  83. package/dist/commands/padding.d.ts +7 -0
  84. package/dist/commands/padding.js +114 -0
  85. package/dist/commands/public-code.d.ts +9 -0
  86. package/dist/commands/public-code.js +8 -8
  87. package/dist/commands/pull.d.ts +27 -0
  88. package/dist/commands/push-wallet.d.ts +19 -0
  89. package/dist/commands/push-wallet.js +69 -0
  90. package/dist/commands/push.d.ts +47 -0
  91. package/dist/commands/push.js +37 -7
  92. package/dist/commands/put-wallet.d.ts +77 -0
  93. package/dist/commands/put-wallet.js +316 -0
  94. package/dist/commands/put.d.ts +56 -0
  95. package/dist/commands/put.js +53 -6
  96. package/dist/commands/rebuild.d.ts +18 -0
  97. package/dist/commands/rebuild.js +108 -10
  98. package/dist/commands/receive.d.ts +24 -0
  99. package/dist/commands/recovery-dispatch.d.ts +2 -0
  100. package/dist/commands/recovery-dispatch.js +34 -0
  101. package/dist/commands/recovery-list.d.ts +11 -0
  102. package/dist/commands/recovery-list.js +6 -6
  103. package/dist/commands/recovery.d.ts +25 -0
  104. package/dist/commands/recovery.js +2 -2
  105. package/dist/commands/rollback.d.ts +9 -0
  106. package/dist/commands/rollback.js +118 -0
  107. package/dist/commands/s3.d.ts +23 -0
  108. package/dist/commands/s3.js +6 -6
  109. package/dist/commands/settings.d.ts +5 -0
  110. package/dist/commands/settings.js +21 -6
  111. package/dist/commands/share-dispatch.d.ts +2 -0
  112. package/dist/commands/share-dispatch.js +46 -0
  113. package/dist/commands/share.d.ts +14 -0
  114. package/dist/commands/share.js +13 -3
  115. package/dist/commands/shares-sent.d.ts +18 -0
  116. package/dist/commands/shares-sent.js +95 -0
  117. package/dist/commands/support.d.ts +24 -0
  118. package/dist/commands/support.js +321 -0
  119. package/dist/commands/sweep.d.ts +11 -0
  120. package/dist/commands/tip.d.ts +11 -0
  121. package/dist/commands/tip.js +86 -0
  122. package/dist/commands/trash.d.ts +17 -0
  123. package/dist/commands/trash.js +1 -1
  124. package/dist/commands/trial.d.ts +7 -0
  125. package/dist/commands/trial.js +16 -18
  126. package/dist/commands/unlock.d.ts +16 -0
  127. package/dist/commands/unlock.js +159 -0
  128. package/dist/commands/update.d.ts +42 -0
  129. package/dist/commands/update.js +1 -1
  130. package/dist/commands/usage.d.ts +8 -0
  131. package/dist/commands/verify.d.ts +18 -0
  132. package/dist/commands/verify.js +10 -6
  133. package/dist/commands/wallet-activity.d.ts +26 -0
  134. package/dist/commands/wallet-activity.js +117 -0
  135. package/dist/commands/wallet-donate.d.ts +26 -0
  136. package/dist/commands/wallet-donate.js +163 -0
  137. package/dist/commands/wallet-hall.d.ts +35 -0
  138. package/dist/commands/wallet-hall.js +214 -0
  139. package/dist/commands/wallet-send.d.ts +22 -0
  140. package/dist/commands/wallet-send.js +153 -0
  141. package/dist/commands/wallet-storage-ops.d.ts +24 -0
  142. package/dist/commands/wallet-storage-ops.js +215 -0
  143. package/dist/commands/wallet-storage.d.ts +18 -0
  144. package/dist/commands/wallet-storage.js +95 -0
  145. package/dist/commands/wallet-swap.d.ts +32 -0
  146. package/dist/commands/wallet-swap.js +287 -0
  147. package/dist/commands/wallet.d.ts +36 -0
  148. package/dist/commands/wallet.js +57 -15
  149. package/dist/commands/whoami.d.ts +10 -0
  150. package/dist/commands/whoami.js +24 -6
  151. package/dist/consent.d.ts +140 -0
  152. package/dist/consent.js +89 -30
  153. package/dist/credentials.d.ts +146 -0
  154. package/dist/credentials.js +10 -10
  155. package/dist/crypto-surface.d.ts +197 -0
  156. package/dist/crypto.d.ts +81 -0
  157. package/dist/crypto.js +13 -0
  158. package/dist/deposit.d.ts +25 -0
  159. package/dist/deposit.js +78 -0
  160. package/dist/download-part.d.ts +40 -0
  161. package/dist/download-sink.d.ts +76 -0
  162. package/dist/download.d.ts +79 -0
  163. package/dist/download.js +3 -3
  164. package/dist/drive-paths.d.ts +73 -0
  165. package/dist/environment.d.ts +62 -0
  166. package/dist/environment.js +29 -9
  167. package/dist/errors.d.ts +22 -0
  168. package/dist/errors.js +3 -3
  169. package/dist/exit.d.ts +44 -0
  170. package/dist/expiry.d.ts +58 -0
  171. package/dist/extend-budget.d.ts +35 -0
  172. package/dist/extend-budget.js +81 -0
  173. package/dist/extend-chain.d.ts +43 -0
  174. package/dist/extend-chain.js +40 -3
  175. package/dist/extend-plan.d.ts +148 -0
  176. package/dist/extend-plan.js +1 -1
  177. package/dist/gate.d.ts +18 -0
  178. package/dist/gate.js +100 -0
  179. package/dist/guards.d.ts +2 -0
  180. package/dist/help.d.ts +20 -0
  181. package/dist/help.js +199 -33
  182. package/dist/human-check.d.ts +23 -0
  183. package/dist/index.d.ts +42 -0
  184. package/dist/index.js +49 -0
  185. package/dist/item-trash.d.ts +1 -0
  186. package/dist/kit-file.d.ts +56 -0
  187. package/dist/kit-file.js +10 -10
  188. package/dist/list-file.d.ts +88 -0
  189. package/dist/list-file.js +90 -7
  190. package/dist/list-view-find.d.ts +15 -0
  191. package/dist/list-view-order.d.ts +32 -0
  192. package/dist/main.d.ts +12 -0
  193. package/dist/main.js +112 -113
  194. package/dist/manifest-chunk-cache.d.ts +14 -0
  195. package/dist/manifest-chunk-cache.js +103 -0
  196. package/dist/manifest-chunk-flow.d.ts +81 -0
  197. package/dist/manifest-chunk-flow.js +307 -0
  198. package/dist/manifest-create.d.ts +23 -0
  199. package/dist/manifest-create.js +26 -30
  200. package/dist/manifest-write.d.ts +161 -0
  201. package/dist/manifest-write.js +42 -33
  202. package/dist/manifest.d.ts +82 -0
  203. package/dist/manifest.js +69 -21
  204. package/dist/mark-render.d.ts +26 -0
  205. package/dist/mcp-args.d.ts +17 -0
  206. package/dist/mcp-ask.d.ts +43 -0
  207. package/dist/mcp-ask.js +100 -0
  208. package/dist/mcp-tools/context.d.ts +46 -0
  209. package/dist/mcp-tools/credits.d.ts +3 -0
  210. package/dist/mcp-tools/credits.js +61 -0
  211. package/dist/mcp-tools/files.d.ts +3 -0
  212. package/dist/mcp-tools/files.js +34 -0
  213. package/dist/mcp-tools/organise.d.ts +3 -0
  214. package/dist/mcp-tools/organise.js +82 -2
  215. package/dist/mcp-tools/reads.d.ts +3 -0
  216. package/dist/mcp-tools/reads.js +132 -4
  217. package/dist/mcp-tools/share.d.ts +3 -0
  218. package/dist/mcp-tools/share.js +26 -7
  219. package/dist/mcp-tools/support.d.ts +3 -0
  220. package/dist/mcp-tools/support.js +140 -0
  221. package/dist/mcp-tools/tiers.d.ts +31 -0
  222. package/dist/mcp-tools/tiers.js +147 -0
  223. package/dist/mcp.d.ts +80 -0
  224. package/dist/mcp.js +110 -9
  225. package/dist/net-retry.d.ts +38 -0
  226. package/dist/network.d.ts +10 -0
  227. package/dist/notice.d.ts +7 -0
  228. package/dist/notice.js +7 -7
  229. package/dist/product.d.ts +29 -0
  230. package/dist/product.js +1 -1
  231. package/dist/progress.d.ts +37 -0
  232. package/dist/prompt.d.ts +31 -0
  233. package/dist/prompt.js +1 -1
  234. package/dist/rebuild-key-check.d.ts +26 -0
  235. package/dist/rebuild-key-check.js +90 -0
  236. package/dist/rebuild.d.ts +87 -0
  237. package/dist/rebuild.js +26 -4
  238. package/dist/recovery-assemble.d.ts +40 -0
  239. package/dist/recovery-assemble.js +1 -1
  240. package/dist/recovery-build.d.ts +40 -0
  241. package/dist/recovery-map-file.d.ts +53 -0
  242. package/dist/recovery-map-file.js +4 -4
  243. package/dist/recovery-map.d.ts +197 -0
  244. package/dist/recovery-release.d.ts +104 -0
  245. package/dist/recovery-seq.d.ts +10 -0
  246. package/dist/recovery-source.d.ts +50 -0
  247. package/dist/redact.d.ts +70 -0
  248. package/dist/redact.js +212 -0
  249. package/dist/registration.d.ts +30 -0
  250. package/dist/registration.js +5 -5
  251. package/dist/risk.d.ts +380 -0
  252. package/dist/risk.js +206 -0
  253. package/dist/run-log.d.ts +78 -0
  254. package/dist/run-log.js +301 -0
  255. package/dist/s3/listing.d.ts +47 -0
  256. package/dist/s3/multipart.d.ts +21 -0
  257. package/dist/s3/response-sink.d.ts +8 -0
  258. package/dist/s3/same-file.d.ts +54 -0
  259. package/dist/s3/server.d.ts +58 -0
  260. package/dist/s3/server.js +2 -2
  261. package/dist/s3/sigv4.d.ts +46 -0
  262. package/dist/s3/sigv4.js +1 -1
  263. package/dist/s3/staging.d.ts +10 -0
  264. package/dist/s3/xml.d.ts +28 -0
  265. package/dist/safe-path.d.ts +41 -0
  266. package/dist/seal.d.ts +120 -0
  267. package/dist/secret-reader.d.ts +28 -0
  268. package/dist/server.d.ts +9 -0
  269. package/dist/session.d.ts +32 -0
  270. package/dist/session.js +1 -1
  271. package/dist/setup-questions.d.ts +7 -0
  272. package/dist/share.d.ts +108 -0
  273. package/dist/share.js +1 -1
  274. package/dist/shared/lib/crypto/size-padding.d.ts +55 -0
  275. package/dist/shared/lib/crypto/size-padding.js +5 -0
  276. package/dist/shared/lib/drive/manifest-chunks.d.ts +95 -0
  277. package/dist/shared/lib/drive/manifest-chunks.js +252 -0
  278. package/dist/shared/lib/drive/manifest-codec.d.ts +214 -0
  279. package/dist/shared/lib/drive/manifest-codec.js +7 -7
  280. package/dist/shared/lib/drive/manifest-index.d.ts +98 -0
  281. package/dist/shared/lib/drive/manifest-ops.d.ts +167 -0
  282. package/dist/shared/lib/drive/manifest-ops.js +9 -4
  283. package/dist/shared/lib/drive/manifest-pack.d.ts +65 -0
  284. package/dist/shared/lib/drive/manifest-pack.js +287 -0
  285. package/dist/shared/lib/drive/manifest-settings.d.ts +113 -0
  286. package/dist/shared/lib/drive/manifest-settings.js +87 -6
  287. package/dist/shared/lib/drive/name-conflict.d.ts +76 -0
  288. package/dist/shared/lib/drive/rebuild-verify.d.ts +73 -0
  289. package/dist/shared/lib/drive/rebuild-verify.js +112 -0
  290. package/dist/shared/lib/drive/unique-name.d.ts +9 -0
  291. package/dist/shared/lib/drive/zstd.d.ts +27 -0
  292. package/dist/shared/lib/drive/zstd.js +74 -0
  293. package/dist/shared/lib/extend/epochs.d.ts +205 -0
  294. package/dist/shared/lib/net/retry-budget.d.ts +79 -0
  295. package/dist/shared/lib/share/shared-file-info.d.ts +21 -0
  296. package/dist/shared/lib/storage-control/chain.d.ts +57 -0
  297. package/dist/shared/lib/storage-control/chain.js +82 -0
  298. package/dist/shared/lib/storage-control/plan.d.ts +40 -0
  299. package/dist/shared/lib/storage-control/plan.js +72 -0
  300. package/dist/shared/lib/storage-control/reuse.d.ts +72 -0
  301. package/dist/shared/lib/storage-control/reuse.js +95 -0
  302. package/dist/shared/lib/storage-network.d.ts +25 -0
  303. package/dist/shared/lib/upload/part-plan.d.ts +24 -0
  304. package/dist/shared/lib/wallet/activity.d.ts +138 -0
  305. package/dist/shared/lib/wallet/activity.js +208 -0
  306. package/dist/shared/lib/wallet/send-rules.d.ts +68 -0
  307. package/dist/shared/lib/wallet/send-rules.js +116 -0
  308. package/dist/shared/lib/wallet/swap-rules.d.ts +115 -0
  309. package/dist/shared/lib/wallet/swap-rules.js +199 -0
  310. package/dist/shared/lib/wallet/tip.d.ts +14 -0
  311. package/dist/shared/lib/wallet/tip.js +34 -0
  312. package/dist/shared/lib/wallet/venue-ids.d.ts +23 -0
  313. package/dist/shared/lib/wallet/venue-ids.js +53 -0
  314. package/dist/standing-tip.d.ts +24 -0
  315. package/dist/standing-tip.js +52 -0
  316. package/dist/stdout.d.ts +38 -0
  317. package/dist/storage-control-chain.d.ts +38 -0
  318. package/dist/storage-control-chain.js +78 -0
  319. package/dist/sui-rpc.d.ts +15 -0
  320. package/dist/sui-rpc.js +59 -0
  321. package/dist/support-attach.d.ts +24 -0
  322. package/dist/support-attach.js +72 -0
  323. package/dist/support-copy.d.ts +42 -0
  324. package/dist/support-copy.js +109 -0
  325. package/dist/support-wire.d.ts +49 -0
  326. package/dist/support-wire.js +194 -0
  327. package/dist/trash-sweep.d.ts +55 -0
  328. package/dist/units.d.ts +2 -0
  329. package/dist/update-check.d.ts +50 -0
  330. package/dist/update-source.d.ts +63 -0
  331. package/dist/upload-api.d.ts +3 -0
  332. package/dist/upload-file.d.ts +97 -0
  333. package/dist/upload-file.js +3 -2
  334. package/dist/upload-price.d.ts +50 -0
  335. package/dist/upload-steps.d.ts +26 -0
  336. package/dist/upload-steps.js +29 -9
  337. package/dist/upload-store.d.ts +182 -0
  338. package/dist/upload-wallet-chain.d.ts +54 -0
  339. package/dist/upload-wallet-chain.js +203 -0
  340. package/dist/upload-wallet-plan.d.ts +128 -0
  341. package/dist/upload-wallet-plan.js +184 -0
  342. package/dist/upload-wallet.d.ts +24 -0
  343. package/dist/upload-wallet.js +208 -0
  344. package/dist/upload-wire.d.ts +244 -0
  345. package/dist/upload.d.ts +18 -0
  346. package/dist/upload.js +10 -0
  347. package/dist/usage-report.d.ts +35 -0
  348. package/dist/wallet-activity-chain.d.ts +5 -0
  349. package/dist/wallet-activity-chain.js +31 -0
  350. package/dist/wallet-chain.d.ts +19 -0
  351. package/dist/wallet-chain.js +2 -2
  352. package/dist/wallet-grant.d.ts +50 -0
  353. package/dist/wallet-grant.js +218 -0
  354. package/dist/wallet-send-chain.d.ts +24 -0
  355. package/dist/wallet-send-chain.js +49 -0
  356. package/dist/wallet-sign.d.ts +133 -0
  357. package/dist/wallet-sign.js +317 -0
  358. package/dist/wallet-storage-chain.d.ts +3 -0
  359. package/dist/wallet-storage-chain.js +20 -0
  360. package/dist/wallet-swap-chain.d.ts +52 -0
  361. package/dist/wallet-swap-chain.js +186 -0
  362. package/dist/wallet-swap-quote.d.ts +22 -0
  363. package/dist/wallet-swap-quote.js +126 -0
  364. package/dist/wallet.d.ts +135 -0
  365. package/dist/wallet.js +5 -5
  366. package/dist/walrus-write.d.ts +56 -0
  367. package/dist/walrus-write.js +5 -4
  368. package/dist/walrus.d.ts +86 -0
  369. package/dist/walrus.js +48 -10
  370. package/dist/zstd-node.d.ts +11 -0
  371. package/dist/zstd-node.js +71 -0
  372. package/docs/commands/accept-terms.md +15 -0
  373. package/docs/commands/create.md +17 -0
  374. package/docs/commands/credits.md +34 -0
  375. package/docs/commands/delete-account.md +14 -0
  376. package/docs/commands/deposit.md +27 -0
  377. package/docs/commands/devices.md +9 -0
  378. package/docs/commands/documents.md +10 -0
  379. package/docs/commands/env.md +21 -0
  380. package/docs/commands/extend.md +27 -0
  381. package/docs/commands/folders.md +10 -0
  382. package/docs/commands/get.md +14 -0
  383. package/docs/commands/key.md +14 -0
  384. package/docs/commands/login.md +23 -0
  385. package/docs/commands/logout.md +11 -0
  386. package/docs/commands/losses.md +13 -0
  387. package/docs/commands/ls.md +16 -0
  388. package/docs/commands/marks.md +9 -0
  389. package/docs/commands/mcp.md +33 -0
  390. package/docs/commands/mode.md +29 -0
  391. package/docs/commands/on-collision.md +13 -0
  392. package/docs/commands/padding.md +13 -0
  393. package/docs/commands/public-code.md +9 -0
  394. package/docs/commands/pull.md +9 -0
  395. package/docs/commands/push.md +12 -0
  396. package/docs/commands/put.md +36 -0
  397. package/docs/commands/rebuild.md +13 -0
  398. package/docs/commands/recovery.md +18 -0
  399. package/docs/commands/s3.md +17 -0
  400. package/docs/commands/share.md +15 -0
  401. package/docs/commands/support.md +16 -0
  402. package/docs/commands/tip.md +21 -0
  403. package/docs/commands/trash.md +25 -0
  404. package/docs/commands/trial.md +12 -0
  405. package/docs/commands/unlock.md +25 -0
  406. package/docs/commands/update.md +14 -0
  407. package/docs/commands/usage.md +14 -0
  408. package/docs/commands/verify.md +18 -0
  409. package/docs/commands/wallet.md +67 -0
  410. package/docs/commands/whoami.md +12 -0
  411. package/package.json +67 -8
  412. package/server.json +28 -0
  413. package/vendor/nmts-crypto/nmts_crypto_wasm.d.ts +23 -2
  414. package/vendor/nmts-crypto/nmts_crypto_wasm.js +44 -2
  415. package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm +0 -0
  416. package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm.d.ts +1 -0
  417. package/vendor/nmts-crypto/package.json +1 -1
  418. package/dist/commands/consent.js +0 -77
  419. package/dist/extend-sign.js +0 -130
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // ⛔ WHY THIS EXISTS. Backup programs, sync tools and agent frameworks already know how to talk to
4
4
  // S3. They do not know how to talk to this. Rather than ask every one of them to learn, the tool
5
- // speaks the protocol they already speak, on this machine, where the account code already is.
5
+ // speaks the protocol they already speak, on this machine, where the NMTS key already is.
6
6
  //
7
7
  // ⛔ WHAT IT IS NOT. It is not a bridge to the internet and cannot be made into one: the address it
8
8
  // binds is loopback and there is no option to change that (`s3/server.ts` says why). Nothing is
@@ -19,7 +19,6 @@ import { tmpdir } from "node:os";
19
19
  import { join } from "node:path";
20
20
  import { pipeline } from "node:stream/promises";
21
21
  import { randomUUID } from "node:crypto";
22
- import { isGranted } from "../consent.js";
23
22
  import { createStaging } from "../s3/staging.js";
24
23
  import { refusalFor, verdictForKey } from "../s3/same-file.js";
25
24
  import { fetchFile } from "../download.js";
@@ -118,10 +117,11 @@ export async function s3(options = {}) {
118
117
  });
119
118
  cachedAt = 0;
120
119
  };
121
- // ⛔ WRITING IS OFF UNLESS THIS MACHINE ALREADY AGREED TO SPENDING. A gateway cannot ask: its
122
- // caller is a program and its stdin is not a terminal. So the question is answered before it
123
- // starts, and where the answer is no every write says so and nothing is charged.
124
- const writable = isGranted("spend");
120
+ // ⛔ THE QUESTION WAS ANSWERED BEFORE THIS STARTED. A gateway cannot ask: its caller is a program
121
+ // and its stdin is not a terminal. `s3` is a medium act (`risk.ts`) — uploads through it spend
122
+ // credits so the tier gate asked at the start, or the mode waved it through, and every write
123
+ // from here on is what was agreed to.
124
+ const writable = true;
125
125
  const server = createGateway({
126
126
  credential,
127
127
  source: {
@@ -0,0 +1,5 @@
1
+ import type { ParsedArgs } from "../args.ts";
2
+ /** Is this one of the settings commands? */
3
+ export declare function isSettingsCommand(command: string): boolean;
4
+ /** Run it. Only call this when `isSettingsCommand` said yes. */
5
+ export declare function runSettings(command: string, args: ParsedArgs): Promise<number>;
@@ -6,22 +6,37 @@
6
6
  // the next setting has an obvious home, rather than another case in the middle of the commands
7
7
  // that move money.
8
8
  //
9
- // ⚠ What each setting MEANS is not here — it is beside the setting itself (`consent.ts`,
9
+ // ⚠ What each setting MEANS is not here — it is beside the setting itself (`consent.ts` · `unlock.ts`,
10
10
  // `autonomy.ts`, `collision.ts`), so a caller that needs the rule does not have to load a
11
11
  // command to get it.
12
12
  /** Is this one of the settings commands? */
13
13
  export function isSettingsCommand(command) {
14
- return command === "consent" || command === "mode" || command === "on-collision";
14
+ return (command === "consent" || command === "unlock" || command === "lock" || command === "mode" || command === "on-collision");
15
15
  }
16
16
  /** Run it. Only call this when `isSettingsCommand` said yes. */
17
17
  export async function runSettings(command, args) {
18
- if (command === "consent") {
19
- const { consent } = await import("./consent.js");
20
- return consent(args.operands[0], args.operands[1], { json: args.json });
18
+ if (command === "consent" || command === "unlock" || command === "lock") {
19
+ const { unlock } = await import("./unlock.js");
20
+ // `consent grant` and `consent revoke` are the older spellings of `unlock` and `lock`.
21
+ const first = args.operands[0];
22
+ const action = command === "lock" ? "lock"
23
+ : command === "unlock" ? (first === undefined ? undefined : "unlock")
24
+ : first === "grant" ? "unlock"
25
+ : first === "revoke" ? "lock"
26
+ : first;
27
+ const target = command === "consent" ? args.operands[1] : first;
28
+ return await unlock(action, target, {
29
+ json: args.json,
30
+ days: args.days,
31
+ until: args.until,
32
+ scope: args.scope,
33
+ capWal: args.capWal,
34
+ capSui: args.capSui,
35
+ });
21
36
  }
22
37
  if (command === "mode") {
23
38
  const { mode } = await import("./mode.js");
24
- return mode(args.operands[0], { json: args.json, accepted: args.iAcceptTheRisk });
39
+ return await mode(args.operands[0], args.operands[1], { json: args.json });
25
40
  }
26
41
  const { onCollision } = await import("./on-collision.js");
27
42
  return onCollision(args.operands[0], { json: args.json });
@@ -0,0 +1,2 @@
1
+ import type { ParsedArgs } from "../args.ts";
2
+ export declare function runShare(command: string, args: ParsedArgs): Promise<number>;
@@ -0,0 +1,46 @@
1
+ // The share verbs (share · shares · unshare · receive), dispatched here rather than in `main.ts`.
2
+ //
3
+ // ⛔ WHY A SECOND SWITCH. `main.ts` is measured (`check:size`) and had no room for `nmts support`;
4
+ // this block shares a file and an option shape, so it moved as one piece with its comments.
5
+ export async function runShare(command, args) {
6
+ switch (command) {
7
+ case "share": {
8
+ const { share } = await import("./share.js");
9
+ return await share(args.operands[0], args.operands[1], {
10
+ server: args.server,
11
+ network: args.network,
12
+ json: args.json,
13
+ yes: args.yes,
14
+ });
15
+ }
16
+ case "shares": {
17
+ const options = { server: args.server, network: args.network, json: args.json };
18
+ if (args.sent !== undefined) {
19
+ const { sharesSent } = await import("./shares-sent.js");
20
+ return await sharesSent(args.sent, options);
21
+ }
22
+ const { shares } = await import("./share.js");
23
+ return await shares(options);
24
+ }
25
+ case "unshare": {
26
+ const { unshare } = await import("./share.js");
27
+ return await unshare(args.operands[0], {
28
+ server: args.server,
29
+ network: args.network,
30
+ json: args.json,
31
+ });
32
+ }
33
+ case "receive": {
34
+ const { receive } = await import("./receive.js");
35
+ return await receive(args.operands[0], {
36
+ server: args.server,
37
+ network: args.network,
38
+ out: args.out,
39
+ force: args.force,
40
+ json: args.json,
41
+ });
42
+ }
43
+ default:
44
+ throw new Error(`not a share verb: ${command}`);
45
+ }
46
+ }
@@ -0,0 +1,14 @@
1
+ export interface ShareOptions {
2
+ server?: string | undefined;
3
+ network?: string | undefined;
4
+ json?: boolean;
5
+ /** `--yes`: the per-attempt answer `share` needs in normal mode (see `share`). */
6
+ yes?: boolean;
7
+ write?: (line: string) => void;
8
+ }
9
+ export declare function out(options: ShareOptions): (line: string) => void;
10
+ /** `nmts share <path> <address>` — hand one file to one account. */
11
+ export declare function share(target: string | undefined, typedAddress: string | undefined, options?: ShareOptions): Promise<number>;
12
+ /** `nmts shares` — what was shared with this account, and what it shared. */
13
+ export declare function shares(options?: ShareOptions): Promise<number>;
14
+ export declare function unshare(id: string | undefined, options?: ShareOptions): Promise<number>;
@@ -21,7 +21,7 @@ import { request, ServerError } from "../api.js";
21
21
  import { openSession } from "../session.js";
22
22
  import { addressFromTyped, identityMatches, openReceived, sealShare, shareKeysOf, } from "../share.js";
23
23
  import { AAD, DERIVED } from "../crypto.js";
24
- function out(options) {
24
+ export function out(options) {
25
25
  return options.write ?? ((line) => process.stdout.write(`${line}\n`));
26
26
  }
27
27
  function b64(bytes) {
@@ -30,7 +30,7 @@ function b64(bytes) {
30
30
  /**
31
31
  * Make sure this account has published the identity other people encrypt to.
32
32
  *
33
- * ⛔ IT IS THE SAME BYTES EVERY TIME. The identity is derived from the account code, so a browser
33
+ * ⛔ IT IS THE SAME BYTES EVERY TIME. The identity is derived from the NMTS key, so a browser
34
34
  * and this tool publish something identical for one account — which is why publishing from here
35
35
  * cannot claim a different account's place or overwrite anything meaningful.
36
36
  */
@@ -87,6 +87,17 @@ export async function share(target, typedAddress, options = {}) {
87
87
  }
88
88
  // ⛔ ASKED AFTER THE FILE IS KNOWN AND BEFORE ANYTHING LEAVES, so the question names a real file.
89
89
  requireConsent("share");
90
+ // ⭐ AND ASKED EVERY TIME (2026-09-05): the once-per-machine unlock above says sharing is
91
+ // allowed at all; `--yes` says THIS file to THIS address. A high act, so the tier gate
92
+ // takes the yes in every mode but skip-permissions, where it hands `--yes` in.
93
+ if (options.yes !== true) {
94
+ say(`Would share "${fullPathOf(index, entry)}" with ${typedAddress}.`);
95
+ say(``);
96
+ say(`⛔ Withdrawing later does not recall it — once the recipient has opened it, they have it.`);
97
+ say(`Nothing was shared. To go ahead: ${BINARY_NAME} share ${JSON.stringify(target)} ${typedAddress} --yes`);
98
+ say(`⛔ If a program is reading this on somebody's behalf: show it to them and let them decide.`);
99
+ return 5;
100
+ }
90
101
  const keys = shareKeysOf(crypt, session.code);
91
102
  const derived = crypt.kdf_derive(crypt.account_code_parse(session.code));
92
103
  const dataKey = derived.slice(DERIVED.dataKey[0], DERIVED.dataKey[1]);
@@ -228,7 +239,6 @@ function printRow(say, one) {
228
239
  say(` ${one.id} ${one.name ?? ""}${size}`);
229
240
  say(` from ${one.sender ?? ""}`);
230
241
  }
231
- /** `nmts unshare <id>` — withdraw a share you sent, or remove one you were sent. */
232
242
  export async function unshare(id, options = {}) {
233
243
  const say = out(options);
234
244
  if (id === undefined || id === "") {
@@ -0,0 +1,18 @@
1
+ import { type ShareOptions } from "./share.ts";
2
+ /**
3
+ * `nmts shares --sent <path>` — who ONE file was shared with.
4
+ *
5
+ * ⛔ IT IS THE OTHER DIRECTION OF `shares`, NOT A FILTER ON IT. The inbox answers "what can I
6
+ * download"; this answers "who can download this", which is the only question a person asking
7
+ * whether to withdraw a share is actually asking. The server keeps the two in different tables
8
+ * and gives them different routes, and joining them here would invent a view neither has.
9
+ *
10
+ * ⛔ THE ADDRESS IS PRINTED AS THE SERVER STORED IT. There is no name, no directory and nothing to
11
+ * look one up against — the recipient of a share is an address and nothing else — so a line
12
+ * that dressed it up as anything friendlier would be inventing an identity.
13
+ *
14
+ * ⚠ IT ONLY READS. Nothing here changes a share, and `unshare <id>` is what takes the id printed
15
+ * at the end of a line and withdraws it.
16
+ */
17
+ export declare function sharesSent(target: string | undefined, options?: ShareOptions): Promise<number>;
18
+ /** `nmts unshare <id>` — withdraw a share you sent, or remove one you were sent. */
@@ -0,0 +1,95 @@
1
+ // `nmts shares --sent` — moved out of `share.ts` whole (that file is measured by `check:size`).
2
+ import { request } from "../api.js";
3
+ import { buildIndex, entryAt, fullPathOf, KIND_FILE, normalisePath } from "../drive-paths.js";
4
+ import { NmtsError } from "../errors.js";
5
+ import { readFileList } from "../manifest.js";
6
+ import { BINARY_NAME } from "../product.js";
7
+ import { openSession } from "../session.js";
8
+ import { out } from "./share.js";
9
+ /**
10
+ * `nmts shares --sent <path>` — who ONE file was shared with.
11
+ *
12
+ * ⛔ IT IS THE OTHER DIRECTION OF `shares`, NOT A FILTER ON IT. The inbox answers "what can I
13
+ * download"; this answers "who can download this", which is the only question a person asking
14
+ * whether to withdraw a share is actually asking. The server keeps the two in different tables
15
+ * and gives them different routes, and joining them here would invent a view neither has.
16
+ *
17
+ * ⛔ THE ADDRESS IS PRINTED AS THE SERVER STORED IT. There is no name, no directory and nothing to
18
+ * look one up against — the recipient of a share is an address and nothing else — so a line
19
+ * that dressed it up as anything friendlier would be inventing an identity.
20
+ *
21
+ * ⚠ IT ONLY READS. Nothing here changes a share, and `unshare <id>` is what takes the id printed
22
+ * at the end of a line and withdraws it.
23
+ */
24
+ export async function sharesSent(target, options = {}) {
25
+ const say = out(options);
26
+ if (target === undefined || target === "") {
27
+ throw new NmtsError("Say which file to look up.", {
28
+ exitCode: 2,
29
+ nextStep: `\`${BINARY_NAME} shares --sent <path>\` — the path as \`${BINARY_NAME} ls\` prints it.`,
30
+ });
31
+ }
32
+ const session = await openSession({ server: options.server, network: options.network });
33
+ const list = await readFileList(session.server, session.apiKey, session.code, session.accountId);
34
+ if (list.manifest === null) {
35
+ throw new NmtsError("This account has no file list, so there is nothing to look up.", {
36
+ exitCode: 4,
37
+ });
38
+ }
39
+ const index = buildIndex(list.manifest.entries);
40
+ const entry = entryAt(list.manifest.entries, normalisePath(target), {
41
+ nothingHappened: "Nothing was changed.",
42
+ });
43
+ if (entry.kind !== KIND_FILE) {
44
+ throw new NmtsError(`No file at "${fullPathOf(index, entry)}".`, {
45
+ exitCode: 4,
46
+ nextStep: "That is a folder, and a share is of one file.",
47
+ });
48
+ }
49
+ const path = fullPathOf(index, entry);
50
+ const answer = await request(session.server, `/v1/shares/sent?item_id=${encodeURIComponent(entry.id)}`, { token: session.apiKey });
51
+ const rows = typeof answer === "object" && answer !== null ? Reflect.get(answer, "shares") : null;
52
+ if (!Array.isArray(rows)) {
53
+ throw new NmtsError("The server's answer did not carry a list of shares.", {
54
+ nextStep: "Nothing was changed. Report it rather than retrying — the shape, not the network, is wrong.",
55
+ });
56
+ }
57
+ if (options.json) {
58
+ // ⛔ THE SERVER'S ROWS, UNCHANGED. A reader of this arm is reading the route, and a shape
59
+ // rewritten here would be a second wire to keep in step with the first.
60
+ say(JSON.stringify({ path, item_id: entry.id, shares: rows }));
61
+ return 0;
62
+ }
63
+ if (rows.length === 0) {
64
+ say(`${path} has not been shared with anyone.`);
65
+ return 0;
66
+ }
67
+ say(`${path} is shared with:`);
68
+ for (const row of rows)
69
+ say(sentLine(row));
70
+ return 0;
71
+ }
72
+ /** One row of `GET /v1/shares/sent`, as a person reads it. */
73
+ function sentLine(row) {
74
+ const at = (name) => {
75
+ const value = typeof row === "object" && row !== null ? Reflect.get(row, name) : undefined;
76
+ if (typeof value !== "string") {
77
+ throw new NmtsError("The server listed a share this version cannot read.", {
78
+ nextStep: "Nothing was changed. A newer version of this tool may understand it.",
79
+ });
80
+ }
81
+ return value;
82
+ };
83
+ return `${at("recipient_address")} since ${utcDay(at("created_at"))} share ${at("id")}`;
84
+ }
85
+ /** The UTC day of an RFC 3339 instant, `YYYY-MM-DD`. */
86
+ function utcDay(instant) {
87
+ const at = new Date(instant);
88
+ if (Number.isNaN(at.getTime())) {
89
+ throw new NmtsError("The server dated a share with something that is not a time.", {
90
+ nextStep: "Nothing was changed. Report it rather than retrying.",
91
+ });
92
+ }
93
+ return at.toISOString().slice(0, 10);
94
+ }
95
+ /** `nmts unshare <id>` — withdraw a share you sent, or remove one you were sent. */
@@ -0,0 +1,24 @@
1
+ export interface SupportOptions {
2
+ server?: string | undefined;
3
+ network?: string | undefined;
4
+ json?: boolean;
5
+ write?: (line: string) => void;
6
+ /** What a report is about, and optionally which part of it. */
7
+ category?: string | undefined;
8
+ sub?: string | undefined;
9
+ /** The message itself, or the file holding it. Neither means: read the standard input. */
10
+ message?: string | undefined;
11
+ messageFile?: string | undefined;
12
+ /** `--attach-log`: absent means none, empty means the default count, otherwise a number. */
13
+ attachLog?: string | undefined;
14
+ /** Values the caller says must not travel, whatever the rules make of them. */
15
+ omit?: readonly string[] | undefined;
16
+ /** Send without being asked. */
17
+ yes?: boolean;
18
+ /** Where a message with no `--message` comes from. Injected so a test needs no pipe. */
19
+ readInput?: () => Promise<string>;
20
+ /** How the person is asked. Injected for the same reason. */
21
+ askPerson?: (question: string) => Promise<string>;
22
+ }
23
+ /** `nmts support <action> [operands]`. */
24
+ export declare function support(action: string | undefined, operands: readonly string[], options?: SupportOptions): Promise<number>;
@@ -0,0 +1,321 @@
1
+ // `nmts support` — writing to the person who builds NMTS, from the command line.
2
+ //
3
+ // ⛔ WHY IT IS IN THE TOOL AND NOT ONLY AN ADDRESS IN THE README. The moment somebody wants to
4
+ // report something is the moment something went wrong, and that is exactly when nobody goes
5
+ // looking for a web page. It is also the one thing an agent can do about a defect: it cannot
6
+ // fix the tool, and it can describe what it ran and what came back better than the person it is
7
+ // working for can.
8
+ //
9
+ // ⛔ NOTHING LEAVES WITHOUT BEING SHOWN FIRST. The preview is not a flag and cannot be turned off:
10
+ // the exact text, the exact attachment and the byte count are printed before anything is sent,
11
+ // in every mode. What a mode changes is only whether somebody is ASKED after seeing it —
12
+ // filing a report is an act the owner wants agents to perform, and the preview is in the
13
+ // transcript either way.
14
+ //
15
+ // ⛔ EVERYTHING IS REDACTED HERE, ON THIS MACHINE, BEFORE IT IS SHOWN. The message, the attached
16
+ // log and anything named with `--omit` all go through `redact.ts` first, so the preview shows
17
+ // what will actually travel rather than what was typed. A preview that showed the original and
18
+ // sent something else would be the wrong way round.
19
+ //
20
+ // ⛔ THE NMTS KEY IS NOT NEEDED AND IS NOT OPENED. This command talks to the server with the
21
+ // API key alone, like `verify` and `trial`. A report is not worth asking somebody for a
22
+ // passphrase, and a command that never holds the code cannot leak it.
23
+ import { NmtsError } from "../errors.js";
24
+ import { BINARY_NAME } from "../product.js";
25
+ import { promptLine } from "../prompt.js";
26
+ import { omitLiterals, redact } from "../redact.js";
27
+ import { MAX_ATTACHED_RUNS, readRuns } from "../run-log.js";
28
+ import { buildLogAttachment, MAX_ATTACHMENT_BYTES } from "../support-attach.js";
29
+ import { SUPPORT_SHORT } from "../support-copy.js";
30
+ import { asList, asTicket, call, checkOmit, checkSub, day, MAX_MESSAGE_CHARS, MAX_REPLY_CHARS, open, requireCategory, requireLength, } from "../support-wire.js";
31
+ /** How many runs `--attach-log` takes when it is given with no number. */
32
+ const DEFAULT_ATTACHED_RUNS = 3;
33
+ /** `nmts support <action> [operands]`. */
34
+ export async function support(action, operands, options = {}) {
35
+ switch (action) {
36
+ case "send":
37
+ return await sendReport(options);
38
+ case "list":
39
+ return await listReports(options);
40
+ case "show":
41
+ return await showReport(operands[0], options);
42
+ case "reply":
43
+ return await replyToReport(operands[0], options);
44
+ default:
45
+ throw new NmtsError(action === undefined ? "`support` needs an action." : `Unknown support action: ${action}`, {
46
+ exitCode: 2,
47
+ nextStep: `The actions are send, list, show and reply. Run \`${BINARY_NAME} support --help\`.`,
48
+ });
49
+ }
50
+ }
51
+ // ── Sending ────────────────────────────────────────────────────────────────────────────────────
52
+ async function sendReport(options) {
53
+ const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
54
+ const category = requireCategory(options.category);
55
+ const subcategory = checkSub(category, options.sub);
56
+ const omit = checkOmit(options.omit ?? []);
57
+ const clean = (text) => omitLiterals(redact(text), omit);
58
+ const message = clean(await readMessage(options)).trim();
59
+ requireLength(message, MAX_MESSAGE_CHARS, "message");
60
+ const attachment = packLog(options.attachLog, clean);
61
+ const total = Buffer.byteLength(message, "utf8") + (attachment?.bytes ?? 0);
62
+ // ⛔ THE MACHINE-READABLE PREVIEW IS THE ONLY THING PRINTED ON THAT PATH, and it is printed
63
+ // INSTEAD of sending rather than before it. Two JSON objects on one stream would let a caller
64
+ // read `sent: false` off a run that then went on to send.
65
+ if (options.json === true) {
66
+ if (!answeredAlready(options)) {
67
+ say(JSON.stringify({
68
+ sent: false,
69
+ category,
70
+ ...(subcategory === null ? {} : { subcategory }),
71
+ message,
72
+ ...(attachment === null ? {} : { log: attachment.text, log_runs: attachment.runs }),
73
+ bytes: total,
74
+ }));
75
+ return 5;
76
+ }
77
+ }
78
+ else {
79
+ for (const line of SUPPORT_SHORT)
80
+ say(line);
81
+ say(``);
82
+ say(`Category: ${subcategory === null ? category : `${category} / ${subcategory}`}`);
83
+ say(`Message (${Buffer.byteLength(message, "utf8")} bytes):`);
84
+ for (const line of message.split("\n"))
85
+ say(` ${line}`);
86
+ if (attachment === null) {
87
+ say(`Log: none`);
88
+ }
89
+ else {
90
+ say(`Log (${attachment.bytes} bytes, ${attachment.runs} runs, ${attachment.dropped} dropped):`);
91
+ for (const line of attachment.text.split("\n"))
92
+ say(` ${line}`);
93
+ }
94
+ say(`Total: ${total} bytes.`);
95
+ if (!(await agreed(options, say))) {
96
+ say(``);
97
+ say(`Nothing was sent. To go ahead: ${BINARY_NAME} support send … --yes`);
98
+ return 5;
99
+ }
100
+ }
101
+ const wire = open(options);
102
+ const answer = await call(wire, "/v1/support/cli", {
103
+ method: "POST",
104
+ body: {
105
+ message,
106
+ category,
107
+ ...(subcategory === null ? {} : { subcategory }),
108
+ ...(attachment === null ? {} : { log: attachment.text }),
109
+ },
110
+ });
111
+ const ticket = asTicket(answer);
112
+ if (options.json === true) {
113
+ say(JSON.stringify(answer));
114
+ return 0;
115
+ }
116
+ say(``);
117
+ say(`Sent. ${ticket.code} — replies arrive in \`${BINARY_NAME} support show ${ticket.code}\`.`);
118
+ return 0;
119
+ }
120
+ /**
121
+ * Has the answer already been given, before anybody is asked anything?
122
+ *
123
+ * ⛔ THE TIER GATE HANDS THE ANSWER: a low act runs unasked in the agent modes, and the gate
124
+ * passes that as `--yes`. Filing a report is an act the owner wants an agent to perform, and
125
+ * the preview above it is in the transcript either way.
126
+ */
127
+ function answeredAlready(options) {
128
+ return options.yes === true;
129
+ }
130
+ /**
131
+ * Put the question, having printed the preview.
132
+ *
133
+ * ⚠ No terminal and no `--yes` is a no, not a hang: `promptLine` answers "" where there is nobody.
134
+ */
135
+ async function agreed(options, say) {
136
+ if (answeredAlready(options))
137
+ return true;
138
+ const ask = options.askPerson ?? promptLine;
139
+ say(``);
140
+ const answer = (await ask(`Send this? [y/N] `)).trim().toLowerCase();
141
+ return answer === "y" || answer === "yes";
142
+ }
143
+ /** The message: the option, then the named file, then the standard input. */
144
+ async function readMessage(options) {
145
+ if (options.message !== undefined)
146
+ return options.message;
147
+ if (options.messageFile !== undefined) {
148
+ const { readFileSync } = await import("node:fs");
149
+ try {
150
+ return readFileSync(options.messageFile, "utf8");
151
+ }
152
+ catch {
153
+ // ⛔ The reason is not repeated: a filesystem error's own text quotes the path back, and
154
+ // that is the one thing a caller already knows.
155
+ throw new NmtsError(`Could not read ${options.messageFile}.`, { exitCode: 2 });
156
+ }
157
+ }
158
+ const read = options.readInput ?? standardInput;
159
+ return await read();
160
+ }
161
+ function standardInput() {
162
+ if (process.stdin.isTTY === true)
163
+ return Promise.resolve("");
164
+ const chunks = [];
165
+ return new Promise((resolve, reject) => {
166
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
167
+ process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
168
+ process.stdin.on("error", reject);
169
+ });
170
+ }
171
+ /** Read the newest runs and pack them, or `null` when the option was not given. */
172
+ function packLog(asked, clean) {
173
+ if (asked === undefined)
174
+ return null;
175
+ const count = asked === "" ? DEFAULT_ATTACHED_RUNS : Number(asked);
176
+ if (!Number.isInteger(count) || count < 0 || count > MAX_ATTACHED_RUNS) {
177
+ throw new NmtsError(`--attach-log takes a whole number of runs, 0 to ${MAX_ATTACHED_RUNS}.`, {
178
+ exitCode: 2,
179
+ });
180
+ }
181
+ if (count === 0)
182
+ return null;
183
+ const runs = readRuns(count);
184
+ if (runs.length === 0)
185
+ return null;
186
+ return buildLogAttachment(runs, clean, MAX_ATTACHMENT_BYTES);
187
+ }
188
+ // ── Reading, and answering ─────────────────────────────────────────────────────────────────────
189
+ async function listReports(options) {
190
+ const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
191
+ const answer = await call(open(options), "/v1/support/cli", {});
192
+ if (options.json === true) {
193
+ say(JSON.stringify(answer));
194
+ return 0;
195
+ }
196
+ const { tickets, unread } = asList(answer);
197
+ if (tickets.length === 0) {
198
+ say(`No reports have been filed from this account with \`${BINARY_NAME} support send\`.`);
199
+ return 0;
200
+ }
201
+ for (const t of tickets) {
202
+ const about = t.subcategory === null ? t.category : `${t.category}/${t.subcategory}`;
203
+ say([
204
+ t.code.padEnd(12),
205
+ about.padEnd(18),
206
+ t.status.padEnd(10),
207
+ day(t.createdAt),
208
+ // ⚠ THE MARK IS DERIVED, because the list carries no per-thread unread flag: the server
209
+ // sends one number for the whole mailbox. What a row does carry is whether it has been
210
+ // answered and whether this account has ever stamped it read, and that pair is the
211
+ // honest half of the question.
212
+ t.status === "answered" && t.lastReadAt === null ? " new" : "",
213
+ ]
214
+ .join(" ")
215
+ .trimEnd());
216
+ }
217
+ say(``);
218
+ say(`${unread} unread.`);
219
+ return 0;
220
+ }
221
+ async function showReport(which, options) {
222
+ const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
223
+ const wire = open(options);
224
+ const id = await resolveTicket(wire, which);
225
+ const answer = await call(wire, `/v1/support/cli/${encodeURIComponent(id)}`, {});
226
+ if (options.json === true) {
227
+ say(JSON.stringify(answer));
228
+ return 0;
229
+ }
230
+ const ticket = asTicket(answer);
231
+ const about = ticket.subcategory === null ? ticket.category : `${ticket.category}/${ticket.subcategory}`;
232
+ say(`${ticket.code} ${about} ${ticket.status} ${ticket.createdAt}`);
233
+ say(``);
234
+ said(say, "you", ticket.createdAt, ticket.message);
235
+ for (const m of ticket.messages ?? []) {
236
+ said(say, m.fromOperator ? "NMTS" : "you", m.createdAt, bodyOf(m));
237
+ }
238
+ return 0;
239
+ }
240
+ async function replyToReport(which, options) {
241
+ const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
242
+ const omit = checkOmit(options.omit ?? []);
243
+ const message = omitLiterals(redact(await readMessage(options)), omit).trim();
244
+ requireLength(message, MAX_REPLY_CHARS, "reply");
245
+ const wire = open(options);
246
+ const id = await resolveTicket(wire, which);
247
+ if (options.json === true) {
248
+ if (!answeredAlready(options)) {
249
+ say(JSON.stringify({ sent: false, code: which, message, bytes: Buffer.byteLength(message, "utf8") }));
250
+ return 5;
251
+ }
252
+ }
253
+ else {
254
+ for (const line of SUPPORT_SHORT)
255
+ say(line);
256
+ say(``);
257
+ say(`Reply (${Buffer.byteLength(message, "utf8")} bytes):`);
258
+ for (const line of message.split("\n"))
259
+ say(` ${line}`);
260
+ if (!(await agreed(options, say))) {
261
+ say(``);
262
+ say(`Nothing was sent. To go ahead: ${BINARY_NAME} support reply … --yes`);
263
+ return 5;
264
+ }
265
+ }
266
+ // ⚠ THE FIELD IS `body`, NOT `message`. A follow-up is the same shape the browser's rail takes,
267
+ // and only the FIRST message of a thread is called a message.
268
+ const answer = await call(wire, `/v1/support/cli/${encodeURIComponent(id)}/reply`, {
269
+ method: "POST",
270
+ body: { body: message },
271
+ });
272
+ if (options.json === true)
273
+ say(JSON.stringify(answer));
274
+ else
275
+ say(`Sent.`);
276
+ return 0;
277
+ }
278
+ /** One turn of a thread, printed with the words indented under who said them. */
279
+ function said(say, who, at, body) {
280
+ say(`${who} ${at}`);
281
+ for (const line of body.split("\n"))
282
+ say(` ${line}`);
283
+ say(``);
284
+ }
285
+ /**
286
+ * What a row holds, when it holds no words.
287
+ *
288
+ * The server sends an empty body for a withdrawn message and for every row that is a notice rather
289
+ * than a sentence, and it composes a notice's words on the screen that shows it. There is no such
290
+ * screen here, so the row is named for what it is.
291
+ */
292
+ function bodyOf(m) {
293
+ if (m.body !== "")
294
+ return m.body;
295
+ return m.deletedAt !== null ? `[withdrawn]` : `[a notice from NMTS]`;
296
+ }
297
+ /**
298
+ * A code or an id, turned into the id the route takes.
299
+ *
300
+ * ⛔ A CODE IS WHAT A PERSON HAS. It is the reference the ticket prints and the one they would
301
+ * quote in an email, and it is not the id in the address — so this looks it up rather than
302
+ * refusing a value that is obviously theirs.
303
+ */
304
+ async function resolveTicket(wire, which) {
305
+ if (which === undefined || which === "") {
306
+ throw new NmtsError(`Which report? Give the code \`${BINARY_NAME} support list\` prints.`, {
307
+ exitCode: 2,
308
+ });
309
+ }
310
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(which))
311
+ return which;
312
+ const { tickets } = asList(await call(wire, "/v1/support/cli", {}));
313
+ const found = tickets.find((t) => t.code.toLowerCase() === which.toLowerCase());
314
+ if (found === undefined) {
315
+ throw new NmtsError(`No report of this account's is called ${which}.`, {
316
+ exitCode: 4,
317
+ nextStep: `\`${BINARY_NAME} support list\` prints the codes.`,
318
+ });
319
+ }
320
+ return found.id;
321
+ }