@needmoretruth/nmts-cli 0.17.2 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/AGENTS.md +124 -516
  2. package/CHANGELOG.md +98 -0
  3. package/README.ko.md +374 -467
  4. package/README.md +384 -465
  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,78 @@
1
+ /** One request this run made, and what came back. */
2
+ export interface HttpEvent {
3
+ readonly kind: "http";
4
+ readonly method: string;
5
+ readonly path: string;
6
+ readonly status: number;
7
+ /** The server's own sentence when it refused. Redacted, like everything else here. */
8
+ readonly error?: string;
9
+ }
10
+ /** One failure this run reported. */
11
+ export interface ErrorEvent {
12
+ readonly kind: "error";
13
+ readonly message: string;
14
+ }
15
+ export type RunEvent = HttpEvent | ErrorEvent;
16
+ /** One line of the log. Short field names: the file has a size cap and this is most of it. */
17
+ export interface RunRecord {
18
+ /** When the run finished, ISO 8601 in UTC. */
19
+ readonly t: string;
20
+ /** The version that ran. A report about a fixed bug is answered by this field alone. */
21
+ readonly v: string;
22
+ readonly cmd: string;
23
+ readonly args: readonly string[];
24
+ readonly exit: number;
25
+ readonly ms: number;
26
+ readonly events: readonly RunEvent[];
27
+ }
28
+ /** Set this to `1` and nothing is written. */
29
+ export declare const NO_RUN_LOG_ENV_VAR = "NMTS_NO_RUN_LOG";
30
+ /**
31
+ * How much of the log is kept.
32
+ *
33
+ * ⛔ A CAP AND NOT A ROTATION SCHEDULE. This file grows by a few hundred bytes per command and is
34
+ * read by one command that sends at most 32 KiB of it, so "the newest quarter of a megabyte" is
35
+ * both more than anybody attaches and small enough that nobody notices it.
36
+ */
37
+ export declare const MAX_LOG_BYTES: number;
38
+ /** The most runs `--attach-log` will take. */
39
+ export declare const MAX_ATTACHED_RUNS = 20;
40
+ /** Where the log lives. Inside the tool's own directory, which is already 0700. */
41
+ export declare function runLogPath(): string;
42
+ /** Has the person turned it off? */
43
+ export declare function runLogIsOff(): boolean;
44
+ /**
45
+ * One request came back. Called from `api.ts`, which is the only place that knows.
46
+ *
47
+ * ⚠ The path is recorded as it was requested, query string and all. Anything secret in it is a
48
+ * label by the time it lands: `redact` runs over the whole record before it is written.
49
+ */
50
+ export declare function noteRequest(method: string, path: string, status: number, error?: string): void;
51
+ /** This run failed, with this sentence. Called from the entry point's own catch. */
52
+ export declare function noteFailure(message: string): void;
53
+ /** Throw away what has been collected. For tests, and for a second run inside one process. */
54
+ export declare function forgetRun(): void;
55
+ /**
56
+ * Which word was the command.
57
+ *
58
+ * ⚠ A HEURISTIC, AND IT IS ALLOWED TO BE. This is a record, not a second parser: the real one runs
59
+ * inside `run()` and has already thrown by the time a failed command line reaches here. What it
60
+ * must not do is pick up the VALUE of an option as the command, which is why it skips them.
61
+ */
62
+ export declare function commandOf(argv: readonly string[]): string;
63
+ /**
64
+ * The argument tokens, with the values that must not be written replaced by their labels.
65
+ *
66
+ * ⛔ BOTH SPELLINGS. `--message x` and `--message=x` are the same option, and a table that covered
67
+ * one of them would write the other into the file.
68
+ */
69
+ export declare function safeArgs(argv: readonly string[]): string[];
70
+ /**
71
+ * Write this run down, and forget what was collected.
72
+ *
73
+ * Returns nothing and throws nothing: see the header. A run that could not be written is a run
74
+ * that is missing from a report, which is a smaller problem than a command that failed for it.
75
+ */
76
+ export declare function recordRun(argv: readonly string[], exit: number, ms: number, now?: Date): void;
77
+ /** The newest `count` runs, oldest first. An unreadable or missing file is no runs. */
78
+ export declare function readRuns(count: number): RunRecord[];
@@ -0,0 +1,301 @@
1
+ // What this tool did, written down so that a report can carry it.
2
+ //
3
+ // ⛔ WHY IT EXISTS. The most useful thing in any report is the sequence: the command that was run,
4
+ // what the server answered, and the sentence it failed with. Asking a person — or an agent — to
5
+ // reconstruct that from memory produces "it did not work", and asking them to paste a terminal
6
+ // produces whatever else was on the screen. So the tool keeps its own record, and `nmts support
7
+ // send --attach-log` is the only thing that reads it.
8
+ //
9
+ // ⛔ IT IS REDACTED ON THE WAY TO THE DISK, NOT ON THE WAY OUT OF IT. A file that held the real
10
+ // values and was cleaned when it was attached would still be a file full of secrets sitting in
11
+ // a home directory, and a home directory ends up in a backup, a synced folder and a container
12
+ // image. `redact.ts` runs before every write here. The attachment runs it again, which costs
13
+ // nothing and covers a line written by an older version of this tool.
14
+ //
15
+ // ⛔ WHAT IS NEVER IN IT: file contents, the NMTS key, the API key, a passphrase. Not because
16
+ // they are filtered — because nothing here is ever handed them. What is written is the command
17
+ // name, the argument tokens, the addresses called, the status numbers, and error sentences.
18
+ //
19
+ // ⛔ WRITING IT MUST NEVER MAKE A COMMAND FAIL. A full disk, a read-only home directory and a
20
+ // config directory owned by somebody else are all ordinary, and none of them is a reason for
21
+ // `nmts get` to report a failure it did not have. Every write is inside a `try` that swallows.
22
+ // ⚠ There is no debug switch in this tool to say so through, so it is silent — which is why
23
+ // this paragraph is here rather than a line of output.
24
+ import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
25
+ import { join } from "node:path";
26
+ import { OPTIONS_TAKING_A_VALUE } from "./args.js";
27
+ import { configDir, modesAreEnforced } from "./credentials.js";
28
+ import { redact } from "./redact.js";
29
+ import { VERSION } from "./product.js";
30
+ /** Set this to `1` and nothing is written. */
31
+ export const NO_RUN_LOG_ENV_VAR = "NMTS_NO_RUN_LOG";
32
+ /**
33
+ * How much of the log is kept.
34
+ *
35
+ * ⛔ A CAP AND NOT A ROTATION SCHEDULE. This file grows by a few hundred bytes per command and is
36
+ * read by one command that sends at most 32 KiB of it, so "the newest quarter of a megabyte" is
37
+ * both more than anybody attaches and small enough that nobody notices it.
38
+ */
39
+ export const MAX_LOG_BYTES = 256 * 1024;
40
+ /**
41
+ * How many events one run records.
42
+ *
43
+ * ⚠ THE NEWEST ARE KEPT. A run that made two thousand requests failed at the end of them, and the
44
+ * last twenty are the ones that say why.
45
+ */
46
+ const MAX_EVENTS = 100;
47
+ /** The most runs `--attach-log` will take. */
48
+ export const MAX_ATTACHED_RUNS = 20;
49
+ /**
50
+ * Options whose VALUE must never be written down, and what stands in for it.
51
+ *
52
+ * ⛔ `--code`, `--passphrase` AND `--api-key` DO NOT EXIST TODAY, and they are here anyway. The
53
+ * rule in `args.ts` is that no option carries a secret; this table is what makes the log safe
54
+ * on the day somebody argues for an exception to it.
55
+ */
56
+ const HIDDEN_VALUES = {
57
+ "--message": "[message]",
58
+ "--omit": "[omitted]",
59
+ "--code": "[account-code]",
60
+ "--api-key": "[api-key]",
61
+ "--passphrase": "[secret]",
62
+ "--token": "[secret]",
63
+ };
64
+ /**
65
+ * What this run has seen so far.
66
+ *
67
+ * ⛔ MODULE STATE, AND IT HAS TO BE. What is being collected is one process's own history, and the
68
+ * two places that report into it — the request path and the entry point — have no object in
69
+ * common and should not be given one just for this.
70
+ */
71
+ let collected = [];
72
+ /** Where the log lives. Inside the tool's own directory, which is already 0700. */
73
+ export function runLogPath() {
74
+ return join(configDir(), "runs.jsonl");
75
+ }
76
+ /** Has the person turned it off? */
77
+ export function runLogIsOff() {
78
+ return process.env[NO_RUN_LOG_ENV_VAR] === "1";
79
+ }
80
+ function remember(event) {
81
+ if (runLogIsOff())
82
+ return;
83
+ collected.push(event);
84
+ if (collected.length > MAX_EVENTS)
85
+ collected = collected.slice(-MAX_EVENTS);
86
+ }
87
+ /**
88
+ * One request came back. Called from `api.ts`, which is the only place that knows.
89
+ *
90
+ * ⚠ The path is recorded as it was requested, query string and all. Anything secret in it is a
91
+ * label by the time it lands: `redact` runs over the whole record before it is written.
92
+ */
93
+ export function noteRequest(method, path, status, error) {
94
+ remember(error === undefined ? { kind: "http", method, path, status } : { kind: "http", method, path, status, error });
95
+ }
96
+ /** This run failed, with this sentence. Called from the entry point's own catch. */
97
+ export function noteFailure(message) {
98
+ remember({ kind: "error", message });
99
+ }
100
+ /** Throw away what has been collected. For tests, and for a second run inside one process. */
101
+ export function forgetRun() {
102
+ collected = [];
103
+ }
104
+ /**
105
+ * Which word was the command.
106
+ *
107
+ * ⚠ A HEURISTIC, AND IT IS ALLOWED TO BE. This is a record, not a second parser: the real one runs
108
+ * inside `run()` and has already thrown by the time a failed command line reaches here. What it
109
+ * must not do is pick up the VALUE of an option as the command, which is why it skips them.
110
+ */
111
+ export function commandOf(argv) {
112
+ const takesValue = new Set(OPTIONS_TAKING_A_VALUE);
113
+ for (let i = 0; i < argv.length; i += 1) {
114
+ const token = argv[i];
115
+ if (token === undefined)
116
+ continue;
117
+ if (takesValue.has(token)) {
118
+ i += 1;
119
+ continue;
120
+ }
121
+ if (token.startsWith("-"))
122
+ continue;
123
+ return token;
124
+ }
125
+ return "";
126
+ }
127
+ /**
128
+ * The argument tokens, with the values that must not be written replaced by their labels.
129
+ *
130
+ * ⛔ BOTH SPELLINGS. `--message x` and `--message=x` are the same option, and a table that covered
131
+ * one of them would write the other into the file.
132
+ */
133
+ export function safeArgs(argv) {
134
+ const out = [];
135
+ for (let i = 0; i < argv.length; i += 1) {
136
+ const token = argv[i];
137
+ if (token === undefined)
138
+ continue;
139
+ const hidden = HIDDEN_VALUES[token];
140
+ if (hidden !== undefined) {
141
+ out.push(token);
142
+ if (i + 1 < argv.length) {
143
+ out.push(hidden);
144
+ i += 1;
145
+ }
146
+ continue;
147
+ }
148
+ const equals = token.indexOf("=");
149
+ if (equals > 0) {
150
+ const head = token.slice(0, equals);
151
+ const label = HIDDEN_VALUES[head];
152
+ if (label !== undefined) {
153
+ out.push(`${head}=${label}`);
154
+ continue;
155
+ }
156
+ }
157
+ out.push(redact(token));
158
+ }
159
+ return out;
160
+ }
161
+ /** Write one line, creating the file 0600 if it is not there yet. */
162
+ function append(line) {
163
+ const dir = configDir();
164
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
165
+ const path = runLogPath();
166
+ if (!existsSync(path)) {
167
+ writeFileSync(path, "", { mode: 0o600 });
168
+ if (modesAreEnforced())
169
+ chmodSync(path, 0o600);
170
+ }
171
+ appendFileSync(path, line);
172
+ if (statSync(path).size > MAX_LOG_BYTES)
173
+ trim(path);
174
+ }
175
+ /**
176
+ * Drop the oldest whole lines until what is left fits.
177
+ *
178
+ * ⛔ WHOLE LINES. Cutting the file at a byte offset leaves a half-written JSON object at the top,
179
+ * and a reader that meets one has to decide whether the file is corrupt or merely trimmed.
180
+ */
181
+ function trim(path) {
182
+ const lines = readFileSync(path, "utf8").split("\n");
183
+ const kept = [];
184
+ let bytes = 0;
185
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
186
+ const line = lines[i];
187
+ if (line === undefined || line === "")
188
+ continue;
189
+ const size = Buffer.byteLength(line, "utf8") + 1;
190
+ if (bytes + size > MAX_LOG_BYTES)
191
+ break;
192
+ kept.unshift(line);
193
+ bytes += size;
194
+ }
195
+ writeFileSync(path, kept.length === 0 ? "" : `${kept.join("\n")}\n`, { mode: 0o600 });
196
+ }
197
+ /**
198
+ * Write this run down, and forget what was collected.
199
+ *
200
+ * Returns nothing and throws nothing: see the header. A run that could not be written is a run
201
+ * that is missing from a report, which is a smaller problem than a command that failed for it.
202
+ */
203
+ export function recordRun(argv, exit, ms, now = new Date()) {
204
+ if (runLogIsOff()) {
205
+ forgetRun();
206
+ return;
207
+ }
208
+ const cmd = commandOf(argv);
209
+ // Everything after the command word is the arguments. A run with no command at all (`--help`,
210
+ // a bare `nmts`) has no such word, and then every token is an argument.
211
+ const from = cmd === "" ? 0 : argv.indexOf(cmd) + 1;
212
+ const record = {
213
+ t: now.toISOString(),
214
+ v: VERSION,
215
+ cmd,
216
+ args: safeArgs(argv.slice(from)),
217
+ exit,
218
+ ms,
219
+ events: collected.map(redactEvent),
220
+ };
221
+ forgetRun();
222
+ try {
223
+ append(`${JSON.stringify(record)}\n`);
224
+ }
225
+ catch {
226
+ // Swallowed on purpose. See the header: the log is a convenience and the command is the work.
227
+ }
228
+ }
229
+ function redactEvent(event) {
230
+ if (event.kind === "error")
231
+ return { kind: "error", message: redact(event.message) };
232
+ const path = redact(event.path);
233
+ if (event.error === undefined)
234
+ return { kind: "http", method: event.method, path, status: event.status };
235
+ return { kind: "http", method: event.method, path, status: event.status, error: redact(event.error) };
236
+ }
237
+ function isEvent(value) {
238
+ if (typeof value !== "object" || value === null)
239
+ return false;
240
+ const kind = Reflect.get(value, "kind");
241
+ if (kind === "error")
242
+ return typeof Reflect.get(value, "message") === "string";
243
+ if (kind !== "http")
244
+ return false;
245
+ return (typeof Reflect.get(value, "method") === "string" &&
246
+ typeof Reflect.get(value, "path") === "string" &&
247
+ typeof Reflect.get(value, "status") === "number");
248
+ }
249
+ /**
250
+ * Read a line back, refusing what this version cannot understand.
251
+ *
252
+ * ⛔ NARROWED, NOT ASSERTED. The file is written by whichever version of this tool ran last, and
253
+ * the one after it may write a field this one has never heard of. A line that does not parse is
254
+ * dropped rather than half-read into an attachment.
255
+ */
256
+ function asRecord(value) {
257
+ if (typeof value !== "object" || value === null)
258
+ return null;
259
+ const at = (name) => Reflect.get(value, name);
260
+ const args = at("args");
261
+ const events = at("events");
262
+ if (typeof at("t") !== "string" || typeof at("cmd") !== "string")
263
+ return null;
264
+ if (typeof at("exit") !== "number" || !Array.isArray(args) || !Array.isArray(events))
265
+ return null;
266
+ return {
267
+ t: String(at("t")),
268
+ v: typeof at("v") === "string" ? String(at("v")) : "",
269
+ cmd: String(at("cmd")),
270
+ args: args.filter((a) => typeof a === "string"),
271
+ exit: Number(at("exit")),
272
+ ms: typeof at("ms") === "number" ? Number(at("ms")) : 0,
273
+ events: events.filter(isEvent),
274
+ };
275
+ }
276
+ /** The newest `count` runs, oldest first. An unreadable or missing file is no runs. */
277
+ export function readRuns(count) {
278
+ let text;
279
+ try {
280
+ text = readFileSync(runLogPath(), "utf8");
281
+ }
282
+ catch {
283
+ return [];
284
+ }
285
+ const records = [];
286
+ for (const line of text.split("\n")) {
287
+ if (line === "")
288
+ continue;
289
+ let parsed;
290
+ try {
291
+ parsed = JSON.parse(line);
292
+ }
293
+ catch {
294
+ continue;
295
+ }
296
+ const record = asRecord(parsed);
297
+ if (record !== null)
298
+ records.push(record);
299
+ }
300
+ return count >= records.length ? records : records.slice(records.length - count);
301
+ }
@@ -0,0 +1,47 @@
1
+ import type { ManifestEntry } from "../shared/lib/drive/manifest-codec.ts";
2
+ import type { ObjectRow } from "./xml.ts";
3
+ /** The one bucket. Named for what it is, and not configurable: two names for one drive is worse. */
4
+ export declare const BUCKET = "drive";
5
+ /** S3's own ceiling, and the default when a client does not ask for one. */
6
+ export declare const MAX_KEYS_LIMIT = 1000;
7
+ export interface DriveObject extends ObjectRow {
8
+ /** The entry this key came from, for the reader that follows a HEAD or GET. */
9
+ readonly entry: ManifestEntry;
10
+ }
11
+ export interface Listing {
12
+ readonly contents: readonly ObjectRow[];
13
+ readonly commonPrefixes: readonly string[];
14
+ readonly truncated: boolean;
15
+ readonly next: string | null;
16
+ }
17
+ export interface ListQuery {
18
+ readonly prefix: string;
19
+ /** Only `/` means anything to S3 clients, but any string is legal and is honoured here. */
20
+ readonly delimiter: string;
21
+ readonly maxKeys: number;
22
+ /** Where to resume: a continuation token, a marker, or start-after. All three are just a key. */
23
+ readonly after: string | null;
24
+ }
25
+ /**
26
+ * An ETag that is stable for a file and changes when the file does.
27
+ *
28
+ * ⛔ IT ENDS IN `-1` FOR A REASON. S3 clients treat an ETag that looks like a hex digest as the
29
+ * MD5 of the object and check downloads against it; this drive has no MD5 of anything -- the
30
+ * bytes are encrypted before they leave the machine and the digest it does keep is a different
31
+ * function. The `-N` suffix is S3's own mark for "assembled from parts, not an MD5", and every
32
+ * client already knows to skip the check when it sees one. Without it a correct download is
33
+ * reported as corrupt.
34
+ */
35
+ export declare function etagOf(entry: ManifestEntry): string;
36
+ /** Every live file in the account, as keys, in the order S3 promises: ascending by key. */
37
+ export declare function objectsOf(entries: readonly ManifestEntry[]): DriveObject[];
38
+ /** Every live folder, as a key ending in the delimiter — see the note at the top of this file. */
39
+ export declare function folderPrefixesOf(entries: readonly ManifestEntry[]): string[];
40
+ /**
41
+ * Apply prefix, delimiter and paging the way `ListObjects` does.
42
+ *
43
+ * The rules are S3's: a key is returned whole unless it holds the delimiter after the prefix, in
44
+ * which case everything up to and including that delimiter becomes a common prefix and the key
45
+ * itself is not listed. Common prefixes and keys share one page budget and one cursor.
46
+ */
47
+ export declare function listObjects(objects: readonly DriveObject[], folders: readonly string[], query: ListQuery): Listing;
@@ -0,0 +1,21 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { DriveWriter } from "./server.ts";
3
+ /** S3's own ceiling, and a bound on what one client can stage on this machine. */
4
+ export declare const MAX_PARTS = 10000;
5
+ export interface MultipartContext {
6
+ readonly req: IncomingMessage;
7
+ readonly res: ServerResponse;
8
+ readonly bucket: string;
9
+ readonly key: string;
10
+ readonly method: string;
11
+ readonly query: URLSearchParams;
12
+ readonly writer: DriveWriter;
13
+ /** The declared payload hash from the signature, when it was a real digest. */
14
+ readonly payloadHash: string | null;
15
+ readonly fail: (status: number, code: string, message: string) => void;
16
+ readonly log?: ((line: string) => void) | undefined;
17
+ }
18
+ /** True when this request belongs to a multipart upload rather than a plain object call. */
19
+ export declare function isMultipartRequest(method: string, query: URLSearchParams): boolean;
20
+ /** Answer one multipart request. Returns false when the shape is not one this gateway knows. */
21
+ export declare function handleMultipart(context: MultipartContext): Promise<boolean>;
@@ -0,0 +1,8 @@
1
+ import type { ServerResponse } from "node:http";
2
+ import type { PlaintextSink } from "../download-sink.ts";
3
+ export interface ResponseSinkOptions {
4
+ /** Headers to send with the 200, once the size is known. */
5
+ readonly headers: Readonly<Record<string, string>>;
6
+ }
7
+ /** A sink that writes one file into an HTTP response and never leaves a short body looking whole. */
8
+ export declare function responseSink(res: ServerResponse, options: ResponseSinkOptions): PlaintextSink;
@@ -0,0 +1,54 @@
1
+ import type { ManifestEntry } from "../shared/lib/drive/manifest-codec.ts";
2
+ import { NmtsError } from "../errors.ts";
3
+ /** What this drive holds at a key, compared with what is arriving. */
4
+ export type SameFile =
5
+ /** Nothing is at that key. */
6
+ "free"
7
+ /** Byte-for-byte what is already there. Nothing to upload. */
8
+ | "same"
9
+ /** Something else is there. */
10
+ | "differs"
11
+ /** Something is there and this drive has no recorded hash for it, so the question cannot be put. */
12
+ | "unknown";
13
+ /**
14
+ * Thrown when a key holds a DIFFERENT file. The gateway answers 409 rather than 500: the request
15
+ * was well formed and the drive declined it, which is what that status is for.
16
+ */
17
+ export declare class KeyConflict extends NmtsError {
18
+ constructor(message: string);
19
+ }
20
+ /** True when a thrown value is that refusal, without importing the class into the protocol layer. */
21
+ export declare function isKeyConflict(error: unknown): boolean;
22
+ /** The SHA-256 of a file on this machine, read in pieces so a large one costs no memory. */
23
+ export declare function hashOfFile(path: string): Promise<Uint8Array>;
24
+ /**
25
+ * Open the hash this drive recorded for a file, with the account's own key.
26
+ *
27
+ * `null` when the entry carries none — files stored before the field existed do not have one, and
28
+ * that is a real state rather than an error. A hash that is present and will not open IS an error:
29
+ * it means the file list was written by another account or altered, and answering "no hash" there
30
+ * would quietly turn a tampered list into an upload.
31
+ */
32
+ export declare function recordedHash(accountCode: string, contentHashCt: string | undefined): Promise<Uint8Array | null>;
33
+ /**
34
+ * The whole question, for one key: what does this drive hold there, and is the file on disk it?
35
+ *
36
+ * ⛔ IT IS ONE FUNCTION SO THERE IS ONE ANSWER. Both ways of uploading — a single PUT, and pieces
37
+ * staged and joined — reach the drive through the same store, and this is what that store asks.
38
+ * Written at the two call sites instead, the two would differ the first time one of them
39
+ * changed, and the difference would show up only above whatever size the client switches at.
40
+ *
41
+ * ⚠ THE FILE IS HASHED ONLY WHEN THE KEY IS TAKEN. An upload onto a free key is the common case
42
+ * and pays nothing for this.
43
+ */
44
+ export declare function verdictForKey(entries: readonly ManifestEntry[], key: string, accountCode: string, path: string): Promise<SameFile>;
45
+ /** The verdict, given what is on record and what arrived. */
46
+ export declare function compare(recorded: Uint8Array | null | undefined, arriving: Uint8Array): SameFile;
47
+ /**
48
+ * What to tell a client whose upload was declined.
49
+ *
50
+ * ⛔ IT NAMES WHICH OF THE TWO HAPPENED. "There is already a file there" is the same sentence for a
51
+ * file that changed and for a file this drive cannot compare, and the two need different things
52
+ * from the person reading the log.
53
+ */
54
+ export declare function refusalFor(verdict: "differs" | "unknown", key: string): KeyConflict;
@@ -0,0 +1,58 @@
1
+ import { type Server } from "node:http";
2
+ import type { Readable } from "node:stream";
3
+ import type { PlaintextSink } from "../download-sink.ts";
4
+ import type { ManifestEntry } from "../shared/lib/drive/manifest-codec.ts";
5
+ import { type DriveObject } from "./listing.ts";
6
+ import { type GatewayCredential } from "./sigv4.ts";
7
+ /** Where the drive is served. Loopback, always — see the note above. */
8
+ export declare const BIND_ADDRESS = "127.0.0.1";
9
+ export interface DriveSource {
10
+ /** The account's live file list. Called per request; the caller decides what to cache. */
11
+ entries(): Promise<readonly ManifestEntry[]>;
12
+ /**
13
+ * Fetch, decrypt and deliver one file into the sink.
14
+ *
15
+ * ⛔ INJECTED RATHER THAN IMPORTED so this server can be driven by a real S3 client in a test
16
+ * without an account, a network and somebody's credits. A gateway whose only test is an
17
+ * end-to-end one is a gateway whose refusals are never tested at all.
18
+ */
19
+ fetch(object: DriveObject, sink: PlaintextSink): Promise<void>;
20
+ /**
21
+ * How to change the drive, when this machine has agreed to spending.
22
+ *
23
+ * ⛔ ABSENT MEANS READ ONLY, AND THAT IS A REFUSAL RATHER THAN A GAP. Uploading spends credits,
24
+ * which is one of the three things this tool asks a person about once per machine, and a
25
+ * gateway cannot ask: its stdin is not a terminal and the caller is a program. So the
26
+ * agreement has to exist beforehand, and where it does not, every write says so.
27
+ */
28
+ readonly write?: DriveWriter;
29
+ }
30
+ export interface DriveWriter {
31
+ /** Store `body` at this key. `size` is the byte count the client declared. */
32
+ put(key: string, body: Readable, size: number): Promise<void>;
33
+ /** Send one file to the trash, where it stays recoverable for thirty days. */
34
+ trash(object: DriveObject): Promise<void>;
35
+ /**
36
+ * Staging for uploads that arrive in pieces. Absent means this gateway refuses them.
37
+ *
38
+ * ⚠ Separate from `put` because the pieces have to land somewhere before they are one file, and
39
+ * where that is belongs to whoever is running this rather than to the protocol.
40
+ */
41
+ readonly multipart?: {
42
+ begin(key: string): Promise<string>;
43
+ part(uploadId: string, partNumber: number, body: Readable, size: number, expectedSha256: string | null): Promise<string>;
44
+ complete(uploadId: string): Promise<string>;
45
+ abort(uploadId: string): Promise<void>;
46
+ };
47
+ }
48
+ export interface GatewayOptions {
49
+ readonly credential: GatewayCredential;
50
+ readonly source: DriveSource;
51
+ /** Called with one line whenever a request is answered, so a person can watch what a tool does. */
52
+ readonly log?: (line: string) => void;
53
+ /** Passed in so a test can hold the clock still. */
54
+ readonly now?: () => number;
55
+ }
56
+ /** A random pair, made fresh every time the gateway starts and stored nowhere. */
57
+ export declare function newCredential(): GatewayCredential;
58
+ export declare function createGateway(options: GatewayOptions): Server;
package/dist/s3/server.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // The gateway itself: an S3 request in, this account's drive out.
2
2
  //
3
- // ⛔ LOOPBACK ONLY, AND NO OPTION TO CHANGE IT. The machine running this already holds the account
4
- // code, and one signature is all that stands between a request and every file in the account.
3
+ // ⛔ LOOPBACK ONLY, AND NO OPTION TO CHANGE IT. The machine running this already holds the NMTS
4
+ // key, and one signature is all that stands between a request and every file in the account.
5
5
  // Bound to an address other people can reach, that one signature becomes the whole lock on the
6
6
  // account -- and the key it checks was printed on somebody's terminal. This is the same call the
7
7
  // rest of the system made on 2026-08-20 when every container port was pulled back to loopback.
@@ -0,0 +1,46 @@
1
+ /** How far a request's own timestamp may sit from ours before it is refused. AWS uses the same. */
2
+ export declare const MAX_CLOCK_SKEW_MS: number;
3
+ /** The literals a client may put in `x-amz-content-sha256` instead of a hex digest. */
4
+ export declare const UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
5
+ export declare const STREAMING_PAYLOAD = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
6
+ export declare const STREAMING_PAYLOAD_TRAILER = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER";
7
+ export interface IncomingRequest {
8
+ readonly method: string;
9
+ /** Raw request target, path and query together, exactly as it came off the wire. */
10
+ readonly url: string;
11
+ readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>;
12
+ }
13
+ export interface GatewayCredential {
14
+ readonly accessKeyId: string;
15
+ readonly secretAccessKey: string;
16
+ }
17
+ export type Verified = {
18
+ readonly ok: true;
19
+ readonly payloadHash: string;
20
+ } | {
21
+ readonly ok: false;
22
+ readonly code: string;
23
+ readonly message: string;
24
+ };
25
+ /** `k=v&k2=v2` in the order AWS wants: encoded, sorted by key and then by value. */
26
+ export declare function canonicalQuery(rawQuery: string): string;
27
+ interface AuthorizationParts {
28
+ readonly accessKeyId: string;
29
+ readonly date: string;
30
+ readonly region: string;
31
+ readonly service: string;
32
+ readonly signedHeaders: readonly string[];
33
+ readonly signature: string;
34
+ }
35
+ /** Pull apart `AWS4-HMAC-SHA256 Credential=…, SignedHeaders=…, Signature=…`. */
36
+ export declare function parseAuthorization(header: string | undefined): AuthorizationParts | null;
37
+ /** `20260824T232759Z` → epoch milliseconds, or null if it is not that shape. */
38
+ export declare function amzDateToMs(stamp: string | undefined): number | null;
39
+ /**
40
+ * Rebuild the string the client signed and check that the signature matches.
41
+ *
42
+ * The clock is passed in rather than read here: a test that cannot choose "now" cannot check the
43
+ * skew rule at all, and that rule is the one that stops a captured request being replayed tomorrow.
44
+ */
45
+ export declare function verifySignature(request: IncomingRequest, credential: GatewayCredential, now: number): Verified;
46
+ export {};
package/dist/s3/sigv4.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // AWS Signature Version 4, the verifying half.
2
2
  //
3
3
  // ⛔ WHY A LOCAL SERVER CHECKS SIGNATURES AT ALL. This gateway listens on the loopback address of a
4
- // machine that already holds an account code, and a request that reaches it can upload, read and
4
+ // machine that already holds an NMTS key, and a request that reaches it can upload, read and
5
5
  // delete that account's files. "It is only local" is not an argument: every other program on the
6
6
  // machine, and every page a browser on it loads, can also reach 127.0.0.1. The signature is the
7
7
  // one thing that separates the tool the person started from everything else running as them.
@@ -0,0 +1,10 @@
1
+ import { type Readable } from "node:stream";
2
+ /** What the staging does with a finished file: store it in the drive at that key. */
3
+ export type StoreFile = (key: string, path: string) => Promise<void>;
4
+ export interface Staging {
5
+ begin(key: string): Promise<string>;
6
+ part(uploadId: string, partNumber: number, body: Readable, size: number, expectedSha256: string | null): Promise<string>;
7
+ complete(uploadId: string): Promise<string>;
8
+ abort(uploadId: string): Promise<void>;
9
+ }
10
+ export declare function createStaging(root: string, store: StoreFile): Staging;