@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
@@ -0,0 +1,79 @@
1
+ /** The fixed, greppable prefix. `ak1` is the format version. ⚠ Must match what the server issues. */
2
+ export declare const KEY_PREFIX = "nmts_ak1_";
3
+ /** base64url over 9 random bytes. PUBLIC — this is the handle the account screen lists. */
4
+ export declare const KEY_HANDLE_LEN = 12;
5
+ /** One line, exactly this long: 9 + 12 + 1 + 43. */
6
+ export declare const KEY_LEN: number;
7
+ /**
8
+ * What a string turns out to be.
9
+ *
10
+ * ⛔ NO VARIANT CARRIES THE VALUE. A shape that held the offending string would be a credential in
11
+ * every error message built from it, which is the hole `errors.ts` spends a paragraph on.
12
+ */
13
+ export type KeyShape = {
14
+ readonly kind: "key";
15
+ readonly handle: string;
16
+ } | {
17
+ readonly kind: "malformed";
18
+ } | {
19
+ readonly kind: "not-a-key";
20
+ };
21
+ /**
22
+ * Decide what a string is, without touching the network.
23
+ *
24
+ * ⛔ PARSED BY FIXED OFFSETS, NEVER BY SPLITTING ON `_`. The base64url alphabet contains `_`, so
25
+ * splitting cuts the string in a place that depends on its random bytes: the same code would
26
+ * accept one key and mangle the next. The Rust side says the same thing in the same words.
27
+ */
28
+ export declare function wellFormed(value: string): KeyShape;
29
+ /** Where an offered key came from. Reported, never guessed. */
30
+ export type KeyOffer = "secret-file" | "env" | "terminal";
31
+ /**
32
+ * What `login` decided about the API key.
33
+ *
34
+ * ⛔ FOUR CASES AND NOT A NULLABLE STRING. "No key at all" and "the key that was already here" are
35
+ * different things to a person setting this tool up for the first time: one of them means every
36
+ * command that talks to the server is about to refuse, and that is the sentence they need. A
37
+ * single `string | undefined` would compile everywhere and say nothing.
38
+ */
39
+ export type KeyOutcome = {
40
+ readonly kind: "none";
41
+ } | {
42
+ readonly kind: "unchanged";
43
+ readonly apiKey: string;
44
+ } | {
45
+ readonly kind: "stored";
46
+ readonly apiKey: string;
47
+ readonly handle: string;
48
+ /** Whether a person's periodic check is live for this account right now. */
49
+ readonly verified: boolean;
50
+ readonly from: KeyOffer;
51
+ } | {
52
+ readonly kind: "kept";
53
+ readonly apiKey: string;
54
+ readonly from: KeyOffer;
55
+ };
56
+ export interface KeyIntake {
57
+ /** The server the key will be checked against, and the one it belongs to. */
58
+ server: string;
59
+ /** What is already stored on this machine, if anything. */
60
+ stored: string | undefined;
61
+ /** Injected in tests so the terminal is not involved. An empty answer means "not now". */
62
+ readKey?: (() => Promise<string>) | undefined;
63
+ /** Injected in tests. Answers the question that replaces a stored key. */
64
+ confirmReplace?: (() => Promise<string>) | undefined;
65
+ }
66
+ /** The key to write down, or nothing. */
67
+ export declare function keyToStore(outcome: KeyOutcome): string | undefined;
68
+ /** The name of the place a key was offered from, for a message that has to say which one. */
69
+ export declare function keySourceName(from: KeyOffer): string;
70
+ /**
71
+ * Work out which key this machine should end up with, checking any new one before it is written.
72
+ *
73
+ * ⛔ A KEY ALREADY HERE IS NEVER REPLACED BY A RUN THAT DID NOT SAY SO. `login` is a command about
74
+ * the NMTS key; a person re-sealing their code with a new passphrase, on a machine where an
75
+ * old variable is still set in some shell profile, has not asked for their working key to be
76
+ * swapped for whatever that variable holds. Silently overwriting it would break every agent on
77
+ * the machine at a moment nobody would connect to the command they ran.
78
+ */
79
+ export declare function settleApiKey(intake: KeyIntake): Promise<KeyOutcome>;
package/dist/api-key.js CHANGED
@@ -10,11 +10,11 @@
10
10
  // ⛔ NEVER FROM THE COMMAND LINE, for the reason written at the top of `credentials.ts`: on Linux
11
11
  // any process can read another's /proc/<pid>/cmdline for as long as it lives, and the shell
12
12
  // writes it to a history file. So there is no --api-key flag here and there never should be.
13
- // The ways in are the three the account code already has — a file an environment variable
13
+ // The ways in are the three the NMTS key already has — a file an environment variable
14
14
  // NAMES, the variable itself, and a prompt that echoes nothing.
15
15
  //
16
16
  // ⛔ THE SHAPE IS JUDGED HERE, BEFORE ANYTHING LEAVES THIS MACHINE, and that is not tidiness. The
17
- // likeliest wrong thing to paste where a key is asked for is the ACCOUNT CODE, and sending it
17
+ // likeliest wrong thing to paste where a key is asked for is the NMTS KEY, and sending it
18
18
  // as a bearer token would put the one secret this product promises never travels onto the wire,
19
19
  // into whatever sits in front of the server, and into its logs. The server does classify it and
20
20
  // refuse (`ACCOUNT_CODE_NOT_A_CREDENTIAL`) rather than store it — but that answer arrives after
@@ -28,7 +28,7 @@
28
28
  // half-accepting.
29
29
  //
30
30
  // ⛔ AND NO FIFTH CONSENT KEY. `consent.ts` allows four and says why: the bar is that the thing
31
- // cannot be undone, costs money, or puts the ACCOUNT CODE somewhere that is not this tool's
31
+ // cannot be undone, costs money, or puts the NMTS KEY somewhere that is not this tool's
32
32
  // sealed file. A key is none of the three — it opens no ciphertext, the account screen revokes
33
33
  // it, it expires by itself, and every command in this tool already reads it out of the
34
34
  // environment without asking. A fifth question here is a fifth chance to teach somebody to
@@ -94,7 +94,7 @@ export function keySourceName(from) {
94
94
  * Work out which key this machine should end up with, checking any new one before it is written.
95
95
  *
96
96
  * ⛔ A KEY ALREADY HERE IS NEVER REPLACED BY A RUN THAT DID NOT SAY SO. `login` is a command about
97
- * the account code; a person re-sealing their code with a new passphrase, on a machine where an
97
+ * the NMTS key; a person re-sealing their code with a new passphrase, on a machine where an
98
98
  * old variable is still set in some shell profile, has not asked for their working key to be
99
99
  * swapped for whatever that variable holds. Silently overwriting it would break every agent on
100
100
  * the machine at a moment nobody would connect to the command they ran.
@@ -270,9 +270,9 @@ async function refuse(shape, value) {
270
270
  });
271
271
  }
272
272
  if (await looksLikeAnAccountCode(value)) {
273
- return new NmtsError(`That is an account code, not an API key.`, {
273
+ return new NmtsError(`That is an NMTS key, not an API key.`, {
274
274
  exitCode: 2,
275
- nextStep: `It was NOT sent anywhere: the account code stays on this machine, and it is what opens ` +
275
+ nextStep: `It was NOT sent anywhere: the NMTS key stays on this machine, and it is what opens ` +
276
276
  `your files. An API key is a different thing — it makes the server answer a program, and ` +
277
277
  `it opens nothing. Make one on the account screen at ${HOME_URL}; it begins ` +
278
278
  `\`${KEY_PREFIX}\`. ${NOTHING_WAS_WRITTEN}`,
@@ -285,7 +285,7 @@ async function refuse(shape, value) {
285
285
  });
286
286
  }
287
287
  /**
288
- * Is this the account code, pasted where the key goes?
288
+ * Is this the NMTS key, pasted where the key goes?
289
289
  *
290
290
  * ⛔ ASKED OF THE ENGINE'S OWN PARSER, which verifies the code's trailing check symbol. Copying the
291
291
  * alphabet here would be a second implementation of a format this repo keeps in exactly one
@@ -0,0 +1,14 @@
1
+ export interface ServerRefusal {
2
+ code: string;
3
+ message: string;
4
+ /**
5
+ * Whatever the refusal carries beside its words — a limit that was hit, an address to go to.
6
+ *
7
+ * ⚠ Values are not all numbers: `AGENT_VERIFY_REQUIRED` names the page a person opens. Nothing
8
+ * here validates the shape, so a narrower type than the wire's would be a claim, not a check.
9
+ */
10
+ details?: Record<string, string | number>;
11
+ }
12
+ export declare function isRefusal(value: unknown): value is {
13
+ error: ServerRefusal;
14
+ };
@@ -0,0 +1,8 @@
1
+ export function isRefusal(value) {
2
+ if (typeof value !== "object" || value === null || !("error" in value))
3
+ return false;
4
+ const error = Reflect.get(value, "error");
5
+ if (typeof error !== "object" || error === null)
6
+ return false;
7
+ return typeof Reflect.get(error, "code") === "string" && typeof Reflect.get(error, "message") === "string";
8
+ }
package/dist/api.d.ts ADDED
@@ -0,0 +1,114 @@
1
+ import { NmtsError } from "./errors.ts";
2
+ import { type WaitReporter } from "./net-retry.ts";
3
+ /** Default deadline for a request that is not moving file bytes. */
4
+ export declare const DEFAULT_TIMEOUT_MS = 30000;
5
+ export type { ServerRefusal } from "./api-refusal.ts";
6
+ import type { ServerRefusal } from "./api-refusal.ts";
7
+ /** A refusal the server explained. Carries its code so a caller can branch without string matching. */
8
+ export declare class ServerError extends NmtsError {
9
+ readonly status: number;
10
+ readonly code: string;
11
+ /** Seconds to wait. ⛔ `Retry-After` is the only place the server sends it — never the body. */
12
+ readonly retryAfter: number | null;
13
+ /**
14
+ * Whatever the refusal carried beside its words — the two credit amounts a doubled release fee
15
+ * is refused with, a limit that was hit, an address to go to.
16
+ *
17
+ * ⚠ NOT VALIDATED HERE. It arrives from the network and every reader checks the one field it
18
+ * wants before printing it; a narrower type would be a claim rather than a check.
19
+ */
20
+ readonly details: Readonly<Record<string, string | number>>;
21
+ constructor(status: number, refusal: ServerRefusal, nextStep: string | null, retryAfter?: number | null);
22
+ }
23
+ /**
24
+ * A failure the server did NOT explain: a status with no refusal body behind it.
25
+ *
26
+ * ⛔ IT CARRIES THE STATUS SO A CALLER NEED NOT MATCH ON THE MESSAGE. The routes that serve
27
+ * documents answer 404 with an empty body for an id nothing has — there is no code to branch
28
+ * on, and "read the message and look for 404 in it" is how a refusal ends up being decided by
29
+ * a sentence somebody later rewrote. It extends `NmtsError` and changes nothing about how this
30
+ * failure is retried, reported or exited: only that a caller can now ask what the status was.
31
+ */
32
+ export declare class HttpError extends NmtsError {
33
+ readonly status: number;
34
+ constructor(status: number, message: string);
35
+ }
36
+ /**
37
+ * A body the server sent as text, and the name its `Content-Disposition` gives that text.
38
+ *
39
+ * ⛔ THE NAME IS THE SERVER'S, NOT THIS TOOL'S. A notice kept from a terminal and the same notice
40
+ * kept from the browser's download button should be the same bytes under the same name, and
41
+ * the only way to be sure of that is to take the name from the one place both clients read it.
42
+ * ⚠ It is still a name that arrived over the network: whatever writes a file with it puts it
43
+ * through `safe-path.ts` first.
44
+ */
45
+ export interface TextAnswer {
46
+ readonly text: string;
47
+ /** Null when the answer named no file. */
48
+ readonly filename: string | null;
49
+ }
50
+ export interface RequestOptions {
51
+ method?: "GET" | "POST" | "PUT" | "DELETE";
52
+ body?: unknown;
53
+ /** Session bearer token. Sent in the Authorization header and nowhere else. */
54
+ token?: string | undefined;
55
+ timeoutMs?: number;
56
+ signal?: AbortSignal | undefined;
57
+ /**
58
+ * Told before each wait between attempts, so a terminal can say the tool is waiting.
59
+ *
60
+ * ⛔ NOTHING IS RETRIED SILENTLY. A person watching and an agent reading the output both need to
61
+ * know the difference between a tool that is waiting and one that is stuck.
62
+ */
63
+ onWait?: WaitReporter;
64
+ /**
65
+ * How long to keep trying a request that is safe to repeat. Omit for the default.
66
+ *
67
+ * ⛔ 0 MEANS ONE ATTEMPT. That is what a caller measuring the shape of a single failure wants,
68
+ * and it is the only way to ask for it — there is no separate "no retry" flag to fall out of
69
+ * step with this one.
70
+ */
71
+ retryBudgetMs?: number;
72
+ /**
73
+ * Make this request safe to repeat.
74
+ *
75
+ * ⛔ A NARROW OPTION RATHER THAN ARBITRARY HEADERS. The two calls that need it are the two that
76
+ * SPEND -- committing a file and reserving storage -- and a general header bag on a client
77
+ * that carries a bearer token is a way to send that token somewhere it was not meant to go.
78
+ */
79
+ idempotencyKey?: string;
80
+ /**
81
+ * Proof that this run holds the NMTS key, for the three routes that ask for one.
82
+ *
83
+ * ⛔ A NAMED OPTION, FOR THE SAME REASON `idempotencyKey` IS ONE. A general header bag on a
84
+ * client that carries a bearer token is a way to send that token somewhere it was not meant
85
+ * to go; this is one field, filled by one module, and it reaches exactly one header.
86
+ *
87
+ * ⛔ ITS VALUE IS NEVER IN A MESSAGE, A URL OR A LOG. `account-proof.ts` says what it is and
88
+ * what it can still do if it leaks. The server refuses to log it either — see
89
+ * `ACCOUNT_PROOF_HEADER` in `api/src/auth/api_key_auth.rs`.
90
+ */
91
+ accountProof?: string;
92
+ /**
93
+ * What the answer is. Absent means JSON, which is what every `/v1` route sends.
94
+ *
95
+ * ⛔ ONE OPTION RATHER THAN A SECOND CLIENT. The site serves three documents as text — the
96
+ * notice board's rows, one notice, one legal document — and a separate fetch for them would
97
+ * be a second place holding the deadline, the retry rule, the run log and the refusal
98
+ * reading. Two of those going out of step is not hypothetical here: `check:cli-routes` exists
99
+ * because a command once called an address the server did not have, and it can only see
100
+ * calls that come through this function.
101
+ */
102
+ as?: "text";
103
+ }
104
+ /**
105
+ * One request to the NMTS server, returning parsed JSON or throwing a named refusal.
106
+ *
107
+ * `path` starts with `/v1/` for the API, or `/api/` for the three documents the site itself
108
+ * serves. It is joined to the base without any normalising, so a caller cannot accidentally send
109
+ * a request to a different host by passing an absolute URL.
110
+ */
111
+ export declare function request(base: string, path: string, options: RequestOptions & {
112
+ as: "text";
113
+ }): Promise<TextAnswer>;
114
+ export declare function request(base: string, path: string, options?: RequestOptions): Promise<unknown>;
package/dist/api.js CHANGED
@@ -10,181 +10,53 @@
10
10
  // that hangs forever is worse than one that fails, because nothing ever reports it.
11
11
  //
12
12
  // ⛔ THE TOKEN IS NEVER IN A MESSAGE, A URL OR A LOG. It goes in one header and nowhere else.
13
+ import { adviseFor } from "./api-advice.js";
13
14
  import { NmtsError } from "./errors.js";
14
15
  import { isTransient, keepTrying } from "./net-retry.js";
16
+ import { noteRequest } from "./run-log.js";
15
17
  /** Default deadline for a request that is not moving file bytes. */
16
18
  export const DEFAULT_TIMEOUT_MS = 30_000;
19
+ import { isRefusal } from "./api-refusal.js";
17
20
  /** A refusal the server explained. Carries its code so a caller can branch without string matching. */
18
21
  export class ServerError extends NmtsError {
19
22
  status;
20
23
  code;
21
- constructor(status, refusal, nextStep) {
24
+ /** Seconds to wait. ⛔ `Retry-After` is the only place the server sends it — never the body. */
25
+ retryAfter;
26
+ /**
27
+ * Whatever the refusal carried beside its words — the two credit amounts a doubled release fee
28
+ * is refused with, a limit that was hit, an address to go to.
29
+ *
30
+ * ⚠ NOT VALIDATED HERE. It arrives from the network and every reader checks the one field it
31
+ * wants before printing it; a narrower type would be a claim rather than a check.
32
+ */
33
+ details;
34
+ constructor(status, refusal, nextStep, retryAfter = null) {
22
35
  super(refusal.message, { exitCode: 1, nextStep });
23
36
  this.name = "ServerError";
24
37
  this.status = status;
25
38
  this.code = refusal.code;
39
+ this.retryAfter = retryAfter;
40
+ this.details = refusal.details ?? {};
26
41
  }
27
42
  }
28
- /** What a caller does next about a refusal, when the tool knows something the message does not. */
29
- function adviseFor(code) {
30
- switch (code) {
31
- case "CLEARANCE_REQUIRED":
32
- case "TURNSTILE_FAILED":
33
- return ("This account needs a human check, which a command-line tool cannot pass. An API key " +
34
- "made on the account screen is what waives it — put it in NMTS_API_KEY. If that screen " +
35
- "has no place to make one, this server does not have API keys switched on.");
36
- case "UNAUTHORIZED":
37
- return "The credential is missing or expired. Check NMTS_API_KEY, or make a new key.";
38
- // ⛔ Each of these says something different on purpose, because the remedies are different
39
- // and a program that cannot tell them apart will retry the one thing that cannot work.
40
- case "SESSION_REVOKED":
41
- return "That is a browser session somebody ended from another device, not an API key. A key is what a program should carry; NMTS_API_KEY is where it goes.";
42
- case "API_KEY_REVOKED":
43
- return "Somebody revoked this key. It will not start working again — make a new one.";
44
- case "API_KEY_EXPIRED":
45
- return "This key reached the end of the lifetime it was given. Make a new one.";
46
- case "API_KEY_SCOPE":
47
- return ("The key is valid and was not given permission for this. Nothing here will succeed with " +
48
- "it — a key with the right permissions has to be made on the account screen.");
49
- case "API_KEY_MALFORMED":
50
- return ("What was sent is not a well-formed key. Check that the whole string was copied, with " +
51
- "no quotes or line break — it is one line of exactly 65 characters.");
52
- case "ACCOUNT_CODE_NOT_A_CREDENTIAL":
53
- return ("That was an account code, not an API key. The code never goes to the server; it stays " +
54
- "on this machine and opens the files. Put the code in NMTS_ACCOUNT_CODE and the key in " +
55
- "NMTS_API_KEY.");
56
- case "AGENT_VERIFY_REQUIRED":
57
- return ("This was refused because nothing has checked lately that a person is behind this " +
58
- "account's key. Ask the person to run `nmts verify` and to follow what it prints — it " +
59
- "gives them a code to type at a browser, and nothing here can pass that check for them.");
60
- // ⛔ THE REFUSAL IS CORRECT AND THERE IS NOTHING HERE TO WORK AROUND. Accepting terms is a
61
- // person reading a document and agreeing to it; a program doing it for them would be
62
- // signing on somebody else's behalf, and this tool holds an API key, not a person. So the
63
- // only thing missing was the advice — without it an agent gets a bare 403 and starts
64
- // trying credentials, which is the one thing that cannot be the cause.
65
- //
66
- // ⚠ It does not say WHICH requests are refused. The server gates some and not others (reading
67
- // and deleting are not gated today), that line has moved twice, and a sentence here naming
68
- // the list would be a copy of it that nothing keeps true.
69
- case "TERMS_ACCEPTANCE_REQUIRED":
70
- return ("This account has not accepted the terms now in force, and the server refuses this " +
71
- "request until it does. Nothing on this machine can accept them. Ask the person to open " +
72
- "the account screen at nmts.me and accept there. Other requests may still work in the " +
73
- "meantime.");
74
- // ⛔ A KEY IS NOT ENOUGH HERE AND NEVER WILL BE. These routes rebuild what makes the account
75
- // recoverable without NMTS, and the owner's rule is that the code is re-entered for them.
76
- // An agent that reads this as "my key is wrong" starts making new keys, which is the one
77
- // remedy that cannot work.
78
- case "ACCOUNT_PROOF_REQUIRED":
79
- return ("This request needs proof of the account code as well as the key, and what was sent was " +
80
- "missing or did not match. Check that the code this machine is holding belongs to the " +
81
- "same account as the key. Wrong attempts are counted, and three of them lock these " +
82
- "routes for a while.");
83
- case "ACCOUNT_BANNED":
84
- return "This account is suspended. Nothing here will succeed until that is lifted.";
85
- // ── Getting to the starting line ──────────────────────────────────────────────────────────
86
- case "ACCOUNT_EXISTS":
87
- return "An account already exists for that. Use the one you have rather than making another.";
88
- case "ALPHA_NOT_OPEN":
89
- return ("This build asks the server for a channel it does not open. This is not something to " +
90
- "retry or to fix with a different credential — use a release build.");
91
- case "API_KEY_CAP":
92
- return ("The account holds as many live keys as it is allowed. Nothing here can raise the limit: " +
93
- "the person has to revoke a key they no longer use, on the account screen at nmts.me.");
94
- case "API_KEY_CHANNEL":
95
- return ("This account is enrolled on a preview build, and keys are not issued while it is. Ask " +
96
- "the person to leave the preview on the account screen, then make the key.");
97
- case "INVALID_CREDENTIALS":
98
- return ("The server did not accept what was sent. Check the key rather than the account code — " +
99
- "the code never goes to the server and cannot be the cause.");
100
- case "LOCKED_OUT":
101
- return ("Too many failed attempts, so this is shut for a while. Retrying now makes it longer, " +
102
- "not shorter. The refusal carries the moment it lifts; wait for it.");
103
- case "RATE_LIMITED":
104
- return ("Too many requests too quickly. Wait and send fewer — the refusal carries how long. This " +
105
- "is not a credential problem, so changing keys will not help.");
106
- case "SURFACE_MISMATCH":
107
- return ("This account acts through a different build than the one calling. The refusal names " +
108
- "which; nothing on this machine can change it, and the person switches it at nmts.me.");
109
- // ── The terms ─────────────────────────────────────────────────────────────────────────────
110
- case "TERMS_VERSION_MISMATCH":
111
- return ("The versions sent are not the ones in force; the refusal carries the ones that are. " +
112
- "This is a stale copy, not a refusal to serve — read the current versions and send those.");
113
- case "TERMS_NOT_IN_FORCE":
114
- return ("There is nothing to accept, so accepting cannot be what is missing. This is a server " +
115
- "condition; report it rather than retrying.");
116
- // ── Credits and the free trial ────────────────────────────────────────────────────────────
117
- case "CREDIT_FILE_CAP":
118
- return ("One file may cost at most the published cap in credits, and this one costs more. The " +
119
- "refusal carries both numbers. Splitting the file is the way through; more credits is not.");
120
- case "CREDIT_DAILY_CAP":
121
- return ("The account has spent its allowance for today. The refusal carries the cap and what is " +
122
- "spent. Waiting for the day to turn is the only remedy — buying credits does not lift it.");
123
- case "TRIAL_CLOSED":
124
- return "The free trial is not open at all right now. Credits have to come from a funded wallet.";
125
- case "TRIAL_FULL":
126
- return "This week's free-trial places are taken. Applying again this week cannot succeed; next week can.";
127
- case "TRIAL_ALREADY":
128
- return "This account already took the free trial this week. It comes round weekly, not once.";
129
- case "TRIAL_HELD":
130
- return "Free-trial applications are paused pending review. Retrying does not move it.";
131
- case "TRIAL_LINE_CAPPED":
132
- return ("This internet connection has taken its share of this week's places today — the limit is " +
133
- "on the connection, not on the account, so another account here hits it too.");
134
- // ── Storage, the chain, and what is safe to retry ─────────────────────────────────────────
135
- // ⛔ THE THREE OUTCOMES ARE DIFFERENT AND AN AGENT MUST NOT COLLAPSE THEM. Refused means it did
136
- // not happen. Failed means it did not finish. Uncertain means nobody knows — and that is the
137
- // one where retrying blindly can spend money twice.
138
- case "CHAIN_REQUEST_REFUSED":
139
- return ("The storage service refused the request itself, so nothing was spent and nothing was " +
140
- "stored. Retrying the same request will be refused the same way.");
141
- case "CHAIN_REGISTER_FAILED":
142
- return "Registering the storage did not go through. Nothing is stored; the upload can be tried again.";
143
- case "CHAIN_CERTIFY_FAILED":
144
- return ("The bytes went out but the storage was never certified, so the file is not safely stored. " +
145
- "Try the upload again.");
146
- case "CHAIN_UNCERTAIN":
147
- return ("⛔ Nobody knows whether the storage was registered. Do NOT simply retry: doing so can pay " +
148
- "twice for the same file. Run `nmts ls` first and see whether the file is there.");
149
- case "CHAIN_SPEND_CAP":
150
- return ("The service has stopped spending on storage for today. This is not about this account " +
151
- "and no credential or credit changes it. Try tomorrow.");
152
- case "CHAIN_DELETE_FAILED":
153
- return ("The storage could not be released. The file's record is gone from this side either way, " +
154
- "so nothing here is stuck — the storage runs out on its own when its time is up.");
155
- case "RELEASE_NOT_SPONSORED":
156
- return ("This file's storage was not paid for with credits, so it is not the server's to release. " +
157
- "Storage bought from a wallet is released by that wallet.");
158
- case "SPONSORED_STATE":
159
- return ("The upload is not at the step that call belongs to — the steps have an order and one was " +
160
- "skipped or already done. Start the upload again rather than repeating this call.");
161
- // ── Two callers, one drive ────────────────────────────────────────────────────────────────
162
- case "VERSION_CONFLICT":
163
- return ("Something else changed the drive since this was read. Nothing is lost and nothing is " +
164
- "wrong with the credential: read the current state and apply the change to that.");
165
- case "ERASE_BLOCKED":
166
- return ("The account cannot be erased while retained records still point at it. This will not " +
167
- "clear by retrying; the records have their own retention and it has to run out.");
168
- case "CREDITS_SHORT":
169
- return "The account does not have enough credits for this upload.";
170
- default:
171
- return null;
172
- }
173
- }
174
- function isRefusal(value) {
175
- if (typeof value !== "object" || value === null || !("error" in value))
176
- return false;
177
- const error = Reflect.get(value, "error");
178
- if (typeof error !== "object" || error === null)
179
- return false;
180
- return typeof Reflect.get(error, "code") === "string" && typeof Reflect.get(error, "message") === "string";
181
- }
182
43
  /**
183
- * One request to the NMTS server, returning parsed JSON or throwing a named refusal.
44
+ * A failure the server did NOT explain: a status with no refusal body behind it.
184
45
  *
185
- * `path` starts with `/v1/`. It is joined to the base without any normalising, so a caller cannot
186
- * accidentally send a request to a different host by passing an absolute URL.
46
+ * IT CARRIES THE STATUS SO A CALLER NEED NOT MATCH ON THE MESSAGE. The routes that serve
47
+ * documents answer 404 with an empty body for an id nothing has there is no code to branch
48
+ * on, and "read the message and look for 404 in it" is how a refusal ends up being decided by
49
+ * a sentence somebody later rewrote. It extends `NmtsError` and changes nothing about how this
50
+ * failure is retried, reported or exited: only that a caller can now ask what the status was.
187
51
  */
52
+ export class HttpError extends NmtsError {
53
+ status;
54
+ constructor(status, message) {
55
+ super(message, { exitCode: 1 });
56
+ this.name = "HttpError";
57
+ this.status = status;
58
+ }
59
+ }
188
60
  export async function request(base, path, options = {}) {
189
61
  if (!path.startsWith("/"))
190
62
  throw new NmtsError(`A request path must start with "/": ${path}`);
@@ -211,7 +83,9 @@ async function once(base, path, options) {
211
83
  const deadline = setTimeout(() => controller.abort(), timeoutMs);
212
84
  if (options.signal)
213
85
  options.signal.addEventListener("abort", () => controller.abort(), { once: true });
214
- const headers = { accept: "application/json" };
86
+ const headers = {
87
+ accept: options.as === "text" ? "text/plain, text/markdown, */*" : "application/json",
88
+ };
215
89
  if (body !== undefined)
216
90
  headers["content-type"] = "application/json";
217
91
  if (token !== undefined && token.length > 0)
@@ -247,8 +121,12 @@ async function once(base, path, options) {
247
121
  clearTimeout(deadline);
248
122
  }
249
123
  const text = await response.text();
124
+ // ⛔ A TEXT ANSWER IS ONLY TEXT WHEN THE SERVER AGREED. A refusal is JSON however the request
125
+ // asked, so a failing document fetch still goes through the reading below and still reaches
126
+ // the caller as a named refusal rather than as a page of HTML pretending to be a notice.
127
+ const asText = options.as === "text" && response.ok;
250
128
  let parsed = null;
251
- if (text.length > 0) {
129
+ if (text.length > 0 && !asText) {
252
130
  try {
253
131
  parsed = JSON.parse(text);
254
132
  }
@@ -261,11 +139,30 @@ async function once(base, path, options) {
261
139
  });
262
140
  }
263
141
  }
142
+ // ⛔ The run log's one hook: here, where the outcome is known and nothing has yet been thrown.
143
+ noteRequest(method, path, response.status, isRefusal(parsed) ? parsed.error.message : undefined);
264
144
  if (!response.ok) {
265
145
  if (isRefusal(parsed)) {
266
- throw new ServerError(response.status, parsed.error, adviseFor(parsed.error.code));
146
+ const wait = response.headers.get("retry-after");
147
+ throw new ServerError(response.status, parsed.error, adviseFor(parsed.error.code), wait !== null && /^\d+$/.test(wait.trim()) ? Number(wait) : null);
267
148
  }
268
- throw new NmtsError(`${base} answered ${response.status}.`, { exitCode: 1 });
149
+ throw new HttpError(response.status, `${base} answered ${response.status}.`);
269
150
  }
151
+ if (asText)
152
+ return { text, filename: filenameFrom(response.headers.get("content-disposition")) };
270
153
  return parsed;
271
154
  }
155
+ /**
156
+ * The file name a `Content-Disposition` header gives a body, or null when it names none.
157
+ *
158
+ * ⛔ THE LAST SEGMENT AND NOTHING ELSE. This value came over the network and is on its way to a
159
+ * name on somebody's disk. `safe-path.ts` contains it as well, and this is the first of the
160
+ * two: a header saying `filename="../../etc/passwd"` must not survive even as far as being
161
+ * joined to a directory. Anything that reduces to nothing, `.` or `..` is treated as no name
162
+ * at all, which the caller answers by refusing rather than by inventing one.
163
+ */
164
+ function filenameFrom(header) {
165
+ const quoted = header === null ? null : /filename="([^"]*)"/u.exec(header);
166
+ const name = (quoted?.[1] ?? "").split(/[/\\]/u).at(-1) ?? "";
167
+ return name === "" || name === "." || name === ".." ? null : name;
168
+ }