@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
@@ -1,6 +1,8 @@
1
- // `nmts put <file>` — one file in, sealed on this machine, paid for with credits.
1
+ // `nmts put <file>` — one file in, sealed on this machine, paid for with credits — or, with
2
+ // `--pay wallet`, paid for by the person's own wallet (`put-wallet.ts`, which this file hands off
3
+ // to before anything is read or priced).
2
4
  //
3
- // ⛔ THE ONLY COMMAND IN THIS TOOL THAT SPENDS. Everything about it is arranged so that is never a
5
+ // ⛔ THE COMMAND THAT SPENDS CREDITS. Everything about it is arranged so that is never a
4
6
  // surprise: `--dry-run` says the price without paying it, the price is printed before the work
5
7
  // starts, and a failure says whether the money already moved. The machinery that keeps a half
6
8
  // finished upload from becoming money that bought nothing is in `upload.ts`.
@@ -13,14 +15,14 @@ import { identityOf } from "../account.js";
13
15
  import { requireAccountCode } from "../code-access.js";
14
16
  import { API_KEY_ENV_VAR, CODE_ENV_VAR, readCredentialsFile, resolveApiKey } from "../credentials.js";
15
17
  import { parseAsked } from "../collision.js";
16
- import { requireConsent } from "../consent.js";
18
+ import { depositDefaultOf, depositLines, parseDeposit, refuseDepositWithWallet } from "../deposit.js";
17
19
  import { DERIVED, loadCrypto } from "../crypto.js";
18
20
  import { buildIndex, fullPathOf, isLive, KIND_FOLDER, normalisePath } from "../drive-paths.js";
19
21
  import { NmtsError } from "../errors.js";
20
22
  import { Progress, silentSink, stderrSink } from "../progress.js";
21
23
  import { setTrashed } from "../item-trash.js";
22
24
  import { addEntry } from "../manifest-write.js";
23
- import { readFileList } from "../manifest.js";
25
+ import { paddingRuleOf, readFileList } from "../manifest.js";
24
26
  import { resolveNetwork } from "../network.js";
25
27
  import { BINARY_NAME } from "../product.js";
26
28
  import { resolveServer } from "../server.js";
@@ -29,6 +31,32 @@ import { createUploadApi } from "../upload-api.js";
29
31
  import { fileSource, partKeysOf, uploadFile } from "../upload-file.js";
30
32
  import { CREDIT_BYTES, creditsFor, measureLocal, partSizeFor, planAndPrice, UPLOAD_EPOCHS, } from "../upload-price.js";
31
33
  import { createBlobProtocol, readCurrentEpoch } from "../walrus-write.js";
34
+ /** Who pays, or a refusal for a payer this tool does not know. */
35
+ export function payerOf(pay) {
36
+ if (pay === undefined || pay === "credits")
37
+ return "credits";
38
+ if (pay === "wallet")
39
+ return "wallet";
40
+ throw new NmtsError(`--pay takes credits or wallet, not "${pay}".`, {
41
+ exitCode: 2,
42
+ nextStep: `Nothing was sent. --pay wallet buys the storage from the wallet this NMTS key derives; without it credits pay.`,
43
+ });
44
+ }
45
+ /** The two options that only mean something when the wallet pays, refused when it does not. */
46
+ export function refuseWalletOnlyOptions(options) {
47
+ if (options.epochs !== undefined) {
48
+ throw new NmtsError("--epochs only applies with --pay wallet: one credit buys a fixed term.", {
49
+ exitCode: 2,
50
+ nextStep: `Nothing was sent. Add --pay wallet to choose the term, or leave --epochs off to pay with credits.`,
51
+ });
52
+ }
53
+ if (options.storage !== undefined) {
54
+ throw new NmtsError("--storage only applies with --pay wallet: credits buy storage from the treasury.", {
55
+ exitCode: 2,
56
+ nextStep: `Nothing was sent. Add --pay wallet to use a storage resource this wallet holds.`,
57
+ });
58
+ }
59
+ }
32
60
  /**
33
61
  * The folder id `--to` names, or null for the root. Refuses rather than guessing.
34
62
  *
@@ -61,6 +89,16 @@ export function folderIdFor(wanted, entries) {
61
89
  return folder.id;
62
90
  }
63
91
  export async function put(target, options = {}) {
92
+ // ⛔ DECIDED BEFORE ANYTHING IS READ. The wallet path prices in WAL and signs; nothing below this
93
+ // line knows how to do either, and it must not learn.
94
+ if (payerOf(options.pay) === "wallet") {
95
+ refuseDepositWithWallet(options.deposit);
96
+ return (await import("./put-wallet.js")).putWithWallet(target, options);
97
+ }
98
+ refuseWalletOnlyOptions(options);
99
+ // ⛔ BEFORE THE FILE IS EVEN MEASURED. A deposit outside the range is a command line to fix, and
100
+ // a typo that surfaced after the upload would have cost real money to produce.
101
+ const askedDeposit = parseDeposit(options.deposit);
64
102
  const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
65
103
  if (target === undefined || target === "") {
66
104
  throw new NmtsError("Say which file to put.", {
@@ -101,7 +139,10 @@ export async function put(target, options = {}) {
101
139
  // would have cost real money to produce a message about a typo.
102
140
  const asked = parseAsked(options.onCollision);
103
141
  const list = await readFileList(server, key.key, resolved.code, identity.accountId);
104
- const rule = list.manifest?.settings?.paddingMode === "pow2" ? "pow2" : "padme";
142
+ const rule = paddingRuleOf(list.manifest?.settings);
143
+ // The flag if it was given, otherwise the account's own default. Always a number, never left to
144
+ // the server to pick: the price printed below names it, and a server default could differ.
145
+ const deposit = askedDeposit ?? depositDefaultOf(list.manifest?.settings);
105
146
  // ⛔ THE PRICE IS ARITHMETIC, NOT A MEASUREMENT: quoting it by sealing would mean reading and
106
147
  // encrypting a very large file to answer `--dry-run`. Every part rounds up to a whole credit
107
148
  // on its own, exactly as the server charges each reservation, so a file in several parts is
@@ -122,11 +163,14 @@ export async function put(target, options = {}) {
122
163
  parts: plan.length,
123
164
  partSize,
124
165
  credits,
166
+ deposit,
125
167
  epochs: UPLOAD_EPOCHS,
126
168
  }));
127
169
  return 0;
128
170
  }
129
171
  say(`${name} ${size} bytes → ${credits} credit${credits === 1 ? "" : "s"}`);
172
+ for (const line of depositLines(deposit))
173
+ say(line);
130
174
  if (plan.length > 1)
131
175
  say(` in ${plan.length} parts of up to ${partSize} bytes`);
132
176
  say(``);
@@ -137,7 +181,6 @@ export async function put(target, options = {}) {
137
181
  // ⛔ ASKED AFTER THE PRICE IS KNOWN AND BEFORE ANYTHING LEAVES. Working the price out is local
138
182
  // and free, so doing it first costs nothing and lets the refusal name a real number instead
139
183
  // of a warning about spending in general. `--dry-run` returns above this line and never asks.
140
- requireConsent("spend");
141
184
  // ⛔ Resolved from the list read above — which happened BEFORE the money moves, so a rolled-back
142
185
  // or forked list stops the upload rather than being discovered after it is paid for.
143
186
  const parentId = folderIdFor(options.to, list.manifest?.entries ?? []);
@@ -148,6 +191,8 @@ export async function put(target, options = {}) {
148
191
  const currentEpoch = await readCurrentEpoch(network);
149
192
  if (!options.json) {
150
193
  say(`${name} ${size} bytes → ${credits} credit${credits === 1 ? "" : "s"}`);
194
+ for (const line of depositLines(deposit))
195
+ say(line);
151
196
  if (plan.length > 1) {
152
197
  say(` in ${plan.length} parts — each one is bought separately and can be finished later`);
153
198
  }
@@ -199,6 +244,7 @@ export async function put(target, options = {}) {
199
244
  currentEpoch,
200
245
  partSize,
201
246
  padding: { rule, unitBytes: CREDIT_BYTES },
247
+ depositCredits: deposit,
202
248
  onStep,
203
249
  });
204
250
  }
@@ -256,6 +302,7 @@ export async function put(target, options = {}) {
256
302
  sealedBytes,
257
303
  parts: plan.length,
258
304
  credits: result.resumed ? 0 : credits,
305
+ deposit,
259
306
  resumed: result.resumed,
260
307
  renamed: added.name !== name,
261
308
  ...(added.replaced ? { replacedIntoTrash: added.replaced.id } : {}),
@@ -0,0 +1,18 @@
1
+ import type { PairVerdict } from "../shared/lib/drive/rebuild-verify.ts";
2
+ import type { SourceItem } from "../rebuild.ts";
3
+ export interface RebuildOptions {
4
+ server?: string | undefined;
5
+ network?: string | undefined;
6
+ json?: boolean;
7
+ /** Write the rebuilt list. Without this the run reports and writes nothing. */
8
+ yes?: boolean;
9
+ /** Rebuild even though this machine has seen a file list for this account before. */
10
+ force?: boolean;
11
+ write?: (line: string) => void;
12
+ /**
13
+ * The key check, injected. Absent = the real one, which reads 72 bytes of each file's first
14
+ * stored part from the storage network (`rebuild-key-check.ts`).
15
+ */
16
+ verify?: (item: SourceItem) => Promise<PairVerdict>;
17
+ }
18
+ export declare function rebuild(options?: RebuildOptions): Promise<number>;
@@ -19,12 +19,21 @@
19
19
  // exists — so a list that appears while this runs comes back as a refusal rather than as a
20
20
  // replacement.
21
21
  //
22
+ // ⛔ AND IT DOES NOT SEAL A PAIRING IT HAS NOT CHECKED. Every key the server hands back is tried
23
+ // against its own row's first sealed part before the list is written; a key that does not open
24
+ // its file is left off the entry and reported by id. Only an account where NOT ONE key opened
25
+ // is a failure — that is a wrong key set, and going ahead would seal a list that opens nothing.
26
+ // One file whose aggregator was down is not that, and refusing the whole rebuild over it would
27
+ // leave somebody with no list at all.
28
+ //
22
29
  // ⚠ A THIRD CASE IS NOT A MISSING LIST AT ALL: this machine has a record of a list for this
23
30
  // account and the server now says there is none. That is a list that WENT missing — the shape a
24
31
  // server would take to make a device throw away its real names — so it stops, and `--force` is
25
32
  // how somebody who knows their list was genuinely lost goes ahead anyway.
33
+ import { loadCrypto } from "../crypto.js";
26
34
  import { NmtsError } from "../errors.js";
27
35
  import { createFirstList } from "../manifest-create.js";
36
+ import { accountKeyCheck } from "../rebuild-key-check.js";
28
37
  import { readFileList } from "../manifest.js";
29
38
  import { BINARY_NAME } from "../product.js";
30
39
  import { rebuildFromServer } from "../rebuild.js";
@@ -37,6 +46,8 @@ function summary(built, wrote, extra = {}) {
37
46
  live: built.live,
38
47
  trashed: built.trashed,
39
48
  keyless: built.keyless,
49
+ verified: built.verified,
50
+ unverified: built.unverified.map((u) => ({ id: u.id, reason: u.reason })),
40
51
  unaccounted: built.unaccounted,
41
52
  namesRecovered: false,
42
53
  foldersRecovered: false,
@@ -70,16 +81,45 @@ export async function rebuild(options = {}) {
70
81
  // enough to show movement and rare enough not to bury what comes after it. The machine-readable
71
82
  // run stays silent: its output is one object, and a progress line on that stream would break it.
72
83
  let announced = 0;
73
- const built = await rebuildFromServer({
74
- server: session.server,
75
- apiKey: session.apiKey,
76
- onProgress: (read) => {
77
- if (options.json === true || read < announced + 1000)
78
- return;
79
- announced = read;
80
- say(` read ${read} stored files so far...`);
81
- },
82
- });
84
+ // THE CHECKER HOLDS THIS ACCOUNT'S DATA KEY until `done()`, so every path out of the read —
85
+ // including a refusal from the listing — has to reach it.
86
+ let checker = null;
87
+ let verify = options.verify;
88
+ if (verify === undefined) {
89
+ const real = accountKeyCheck({
90
+ server: session.server,
91
+ apiKey: session.apiKey,
92
+ accountCode: session.code,
93
+ chain: session.network,
94
+ crypt: await loadCrypto(),
95
+ });
96
+ checker = real;
97
+ verify = (item) => real.check(item);
98
+ }
99
+ let built;
100
+ try {
101
+ let checkedAnnounced = 0;
102
+ built = await rebuildFromServer({
103
+ server: session.server,
104
+ apiKey: session.apiKey,
105
+ verify,
106
+ onProgress: (read) => {
107
+ if (options.json === true || read < announced + 1000)
108
+ return;
109
+ announced = read;
110
+ say(` read ${read} stored files so far...`);
111
+ },
112
+ onVerifyProgress: (checked, total) => {
113
+ if (options.json === true || checked < checkedAnnounced + 1000)
114
+ return;
115
+ checkedAnnounced = checked;
116
+ say(` checked ${checked} of ${total} keys against their own files...`);
117
+ },
118
+ });
119
+ }
120
+ finally {
121
+ checker?.done();
122
+ }
83
123
  if (built.entries.length === 0) {
84
124
  if (options.json) {
85
125
  say(summary(built, false));
@@ -89,6 +129,17 @@ export async function rebuild(options = {}) {
89
129
  say(`There is nothing to rebuild from. Nothing was changed.`);
90
130
  return 0;
91
131
  }
132
+ // ⛔ NOT ONE KEY OPENED ITS OWN FILE, and at least one row had a key to try. That is not a bad
133
+ // afternoon on the network — it is a key set that belongs to something else, and sealing it
134
+ // would put a list on the server whose every entry names bytes it cannot open.
135
+ if (built.verified === 0 && built.keyless < built.entries.length) {
136
+ throw new NmtsError(`Not one of this account's keys opened the file it was filed beside.`, {
137
+ exitCode: 4,
138
+ nextStep: `Nothing was changed. Either this is not this account's NMTS key, or the storage network could ` +
139
+ `not be reached at all — ${describeReasons(built)}. A list sealed from these pairs would ` +
140
+ `name every file and open none of them.`,
141
+ });
142
+ }
92
143
  if (options.yes !== true) {
93
144
  if (options.json) {
94
145
  say(summary(built, false));
@@ -111,6 +162,7 @@ export async function rebuild(options = {}) {
111
162
  say(` Every file is at the top of the drive under a placeholder name — the server had no name,`);
112
163
  say(` no folder and no placement to give back.`);
113
164
  say(` \`${BINARY_NAME} ls\` shows them; \`${BINARY_NAME} rename\` and \`${BINARY_NAME} mv\` put them back.`);
165
+ reportKeys(say, built);
114
166
  reportGaps(say, built);
115
167
  return 0;
116
168
  }
@@ -133,8 +185,54 @@ function describe(say, built) {
133
185
  say(``);
134
186
  say(` None of that is a fault: the server was built not to know it. It keeps a row per stored`);
135
187
  say(` file and the key that opens it, and nothing about what the file is called.`);
188
+ reportKeys(say, built);
136
189
  reportGaps(say, built);
137
190
  }
191
+ /**
192
+ * Which keys were shown to open their own file, and which were not.
193
+ *
194
+ * ⛔ THE UNVERIFIED ONES ARE NAMED, one line each. A count alone would say "some of your files came
195
+ * back without a key" and leave nobody able to act on it; the id is what `nmts ls` shows and
196
+ * what a second run can be compared against.
197
+ */
198
+ function reportKeys(say, built) {
199
+ const checked = built.entries.length - built.keyless;
200
+ if (checked === 0)
201
+ return;
202
+ say(``);
203
+ const notVerified = countUnverified(built);
204
+ say(` Keys checked against their own files: ${built.verified} verified, ${notVerified} not.`);
205
+ if (notVerified === 0)
206
+ return;
207
+ say(` A file whose key was not verified keeps its entry and no key — nothing else on this side`);
208
+ say(` can say that key belongs to that file, and a wrong pairing would be sealed for good.`);
209
+ for (const one of built.unverified) {
210
+ if (one.reason === "no-key")
211
+ continue;
212
+ say(` ${one.id} ${WHY[one.reason]}`);
213
+ }
214
+ }
215
+ /** How many pairs were left unverified, not counting rows that never had a key to check. */
216
+ function countUnverified(built) {
217
+ return built.unverified.filter((u) => u.reason !== "no-key").length;
218
+ }
219
+ /** One plain line per reason a pair was not shown to belong together. */
220
+ const WHY = {
221
+ "no-key": "the server holds no key for it",
222
+ "no-parts": "the server names no stored bytes for it",
223
+ unreadable: "its stored bytes could not be read, so the key could not be tried",
224
+ "wrong-key": "this key does not open this file",
225
+ };
226
+ /** The reasons behind a wholly unverified account, most common first, for the refusal's next step. */
227
+ function describeReasons(built) {
228
+ const counts = new Map();
229
+ for (const one of built.unverified)
230
+ counts.set(one.reason, (counts.get(one.reason) ?? 0) + 1);
231
+ return [...counts.entries()]
232
+ .sort((a, b) => b[1] - a[1])
233
+ .map(([reason, n]) => `${n} × ${WHY[reason]}`)
234
+ .join(", ");
235
+ }
138
236
  /** The parts of the account this rebuild could not account for. Printed on both paths. */
139
237
  function reportGaps(say, built) {
140
238
  if (built.keyless > 0) {
@@ -0,0 +1,24 @@
1
+ import { type ByteDestination } from "../stdout.ts";
2
+ export interface ReceiveOptions {
3
+ server?: string | undefined;
4
+ network?: string | undefined;
5
+ /** Where to write it. Defaults to the name the sender gave it, in the working directory. */
6
+ out?: string | undefined;
7
+ /**
8
+ * A DIRECTORY to write into, keeping the sender's name — for callers that must not let a name
9
+ * from somewhere else choose a path.
10
+ *
11
+ * ⛔ IT IS NOT `out` WITH A SLASH. `out` is a full path the caller picked; this one says "the
12
+ * name is theirs, the place is mine", and the name is reduced to its last segment and checked
13
+ * against this directory before anything opens. A sender who calls their file
14
+ * `../../.ssh/authorized_keys` has picked a legal name for a file and must not thereby pick a
15
+ * path on somebody else's disk. Ignored when `out` is given.
16
+ */
17
+ intoDir?: string | undefined;
18
+ force?: boolean;
19
+ /** Where the bytes go when `--out -` was given. Injected so a test can read them. */
20
+ stdout?: ByteDestination;
21
+ json?: boolean;
22
+ write?: (line: string) => void;
23
+ }
24
+ export declare function receive(id: string | undefined, options?: ReceiveOptions): Promise<number>;
@@ -0,0 +1,2 @@
1
+ import type { ParsedArgs } from "../args.ts";
2
+ export declare function runRecovery(command: string, args: ParsedArgs): Promise<number>;
@@ -0,0 +1,34 @@
1
+ // The recovery verbs (recovery · recovery-list · kit), dispatched here rather than in `main.ts`.
2
+ //
3
+ // ⛔ WHY A SECOND SWITCH. `main.ts` is measured (`check:size`) and had no room for `nmts support`;
4
+ // this block shares a file and an option shape, so it moved as one piece with its comments.
5
+ export async function runRecovery(command, args) {
6
+ switch (command) {
7
+ case "recovery": {
8
+ const { recovery } = await import("./recovery.js");
9
+ return await recovery({ out: args.out, force: args.force, json: args.json });
10
+ }
11
+ case "recovery-list": {
12
+ const { recoveryList } = await import("./recovery-list.js");
13
+ return await recoveryList({
14
+ server: args.server,
15
+ network: args.network,
16
+ out: args.out,
17
+ force: args.force,
18
+ json: args.json,
19
+ });
20
+ }
21
+ case "kit": {
22
+ const { kit } = await import("./kit.js");
23
+ return await kit({
24
+ server: args.server,
25
+ network: args.network,
26
+ out: args.out,
27
+ force: args.force,
28
+ json: args.json,
29
+ });
30
+ }
31
+ default:
32
+ throw new Error(`not a recovery verb: ${command}`);
33
+ }
34
+ }
@@ -0,0 +1,11 @@
1
+ export interface RecoveryListOptions {
2
+ server?: string | undefined;
3
+ network?: string | undefined;
4
+ /** Where to put it: a directory, or the file name to write. Default: this directory. */
5
+ out?: string | undefined;
6
+ /** Replace a file that is already there. Off by default, and saying so is the point. */
7
+ force?: boolean;
8
+ json?: boolean;
9
+ write?: (line: string) => void;
10
+ }
11
+ export declare function recoveryList(options?: RecoveryListOptions): Promise<number>;
@@ -1,16 +1,16 @@
1
1
  // `nmts recovery-list` — writing the account's recovery list out as a file.
2
2
  //
3
- // ⛔ WHY IT EXISTS. The recovery list is the answer to "NMTS is gone and I still have my account
4
- // code": it holds, encrypted, where every file's bytes are on the public storage network, and
3
+ // ⛔ WHY IT EXISTS. The recovery list is the answer to "NMTS is gone and I still have my NMTS
4
+ // key": it holds, encrypted, where every file's bytes are on the public storage network, and
5
5
  // the key that opens each of them. The account screen has always been able to write one. An
6
6
  // account used only from a terminal could not, so the artefact that exists for the day this
7
7
  // service is not there did not exist for those accounts at all.
8
8
  //
9
9
  // ⛔ IT IS NOT THE FILE-LIST COPY, AND NEITHER REPLACES THE OTHER. `nmts listfile` writes the names
10
10
  // and keys this machine has seen; this one writes the storage addresses, which that file has
11
- // none of. Keep both, and keep both somewhere other than the account code.
11
+ // none of. Keep both, and keep both somewhere other than the NMTS key.
12
12
  //
13
- // ⛔ THE ACCOUNT CODE IS NOT IN THE FILE. This file plus the code is the account, so keeping them
13
+ // ⛔ THE NMTS KEY IS NOT IN THE FILE. This file plus the code is the account, so keeping them
14
14
  // together turns one theft into a total loss. The artefact that deliberately carries both is
15
15
  // `nmts kit`, and it says so about itself.
16
16
  //
@@ -67,7 +67,7 @@ export async function recoveryList(options = {}) {
67
67
  say(``);
68
68
  say(` Recovery list ${seq} — ${built.fileCount} files, ${built.totalBytes} bytes described.`);
69
69
  say(` It holds where each file's bytes are on the storage network, and the key that opens`);
70
- say(` each one, sealed with the account code.`);
70
+ say(` each one, sealed with the NMTS key.`);
71
71
  say(``);
72
72
  say(` Recorded with the server: version ${seq}, kept on this machine (no storage-network`);
73
73
  say(` copy), read from the account at ${assembled.capturedAt}.`);
@@ -78,7 +78,7 @@ export async function recoveryList(options = {}) {
78
78
  say(` this. Nothing here is covered for them.`);
79
79
  }
80
80
  say(``);
81
- say(` ⛔ It does not contain the account code. Keep it somewhere other than the code: together`);
81
+ say(` ⛔ It does not contain the NMTS key. Keep it somewhere other than your NMTS key: together`);
82
82
  say(` they are the whole account.`);
83
83
  say(` ⚠ A list goes stale. Run this again after uploading: of two files, the one whose filename`);
84
84
  say(` carries the higher number supersedes the other.`);
@@ -0,0 +1,25 @@
1
+ export interface RecoveryOptions {
2
+ /** Where to put it: a directory, or the file name to write. Default: the working directory. */
3
+ out?: string | undefined;
4
+ /** Replace a file that is already there. Off by default, and saying so is the point. */
5
+ force?: boolean;
6
+ json?: boolean;
7
+ write?: (line: string) => void;
8
+ /**
9
+ * Where the program is published.
10
+ *
11
+ * ⚠ Overridable so a test can drive the whole download against a server on this machine. There
12
+ * is no command-line option for it: this is not somewhere a person should be talked into
13
+ * pointing an executable download.
14
+ */
15
+ source?: string | undefined;
16
+ /**
17
+ * What machine to fetch for.
18
+ *
19
+ * ⚠ Overridable so the refusal a machine with no published executable gets can be tested on a
20
+ * machine that has one. Defaults to what this process is running on.
21
+ */
22
+ platform?: string;
23
+ arch?: string;
24
+ }
25
+ export declare function recovery(options?: RecoveryOptions): Promise<number>;
@@ -1,7 +1,7 @@
1
1
  // `nmts recovery` — fetching the standalone recovery program, checked, into a file you name.
2
2
  //
3
3
  // ⛔ WHY IT EXISTS. The recovery program is the answer to "NMTS is gone and I still have my
4
- // account code": it reads files back off the public storage network on its own, talking to no
4
+ // NMTS key": it reads files back off the public storage network on its own, talking to no
5
5
  // NMTS server. Until its release workflow existed, getting it meant installing a Rust toolchain
6
6
  // and building it — a fair ask of somebody auditing it, an unfair one of somebody who has just
7
7
  // lost access to their files, and an impossible one for an agent working in a terminal.
@@ -25,7 +25,7 @@
25
25
  // who runs this has to be the person who decided to have it.
26
26
  //
27
27
  // ⚠ WHAT IT ASKS OF THE NETWORK IS NOT THE NMTS API. It talks to the source-hosting site the
28
- // recovery program is published on, and to nothing else; no account code, no API key and no
28
+ // recovery program is published on, and to nothing else; no NMTS key, no API key and no
29
29
  // session is involved, and the command works signed out.
30
30
  import { createHash } from "node:crypto";
31
31
  import { chmodSync, existsSync, rmSync, statSync, writeFileSync } from "node:fs";
@@ -0,0 +1,9 @@
1
+ export interface RollbackOptions {
2
+ server?: string | undefined;
3
+ network?: string | undefined;
4
+ json?: boolean;
5
+ /** Go ahead and write it back. Without this the run reports and changes nothing. */
6
+ yes?: boolean;
7
+ write?: (line: string) => void;
8
+ }
9
+ export declare function rollback(options?: RollbackOptions): Promise<number>;
@@ -0,0 +1,118 @@
1
+ // `nmts rollback` — putting the version of the file list that came before back as the current one.
2
+ //
3
+ // ⛔ IT EXISTS FOR ONE SITUATION: the current list will not open, or opens as something that is
4
+ // not this account's drive. A list that cannot be opened presents a full account as an empty
5
+ // one, and the natural response to an empty account is to upload everything a second time. The
6
+ // server retains the version each write replaced for exactly that day.
7
+ //
8
+ // ⛔ IT DOES NOT READ THE CURRENT LIST, AND THAT IS THE POINT. Every other command here opens the
9
+ // sealed blob before it does anything; this one moves bytes the server is holding from one
10
+ // place to another. A rollback that refused because the current list would not open would
11
+ // refuse in the only case it is for.
12
+ // ⚠ It does open the OLDER one, far enough to read the names of the chunks it is made of and no
13
+ // further (`namedChunks`). Bytes that will not open answer "no chunks", which is exactly the
14
+ // request this command has always made — so nothing it used to handle is refused now.
15
+ //
16
+ // ⛔ WHAT IT COSTS: the newer version's additions leave the list. The BYTES are untouched — the
17
+ // server still holds every row, the storage is still bought, and `nmts rebuild` finds files no
18
+ // list names — but a name, a folder and the key that opens a file live in the list and nowhere
19
+ // else, so a file added after the version being restored comes back nameless or not at all.
20
+ //
21
+ // ⛔ SO IT IS A PERSON'S ACT, IN EVERY MODE. This is not a decision about spending or about risk
22
+ // that a person can hand to an agent in advance: it is a judgement that the drive somebody can
23
+ // see is wrong, and nothing an unattended program reads can tell it that. There is no MCP tool
24
+ // either, so this is the only door and it is shut from both sides.
25
+ import { request } from "../api.js";
26
+ import { NmtsError } from "../errors.js";
27
+ import { isRecord } from "../guards.js";
28
+ import { namedChunks } from "../manifest-chunk-flow.js";
29
+ import { recordWrittenList } from "../manifest.js";
30
+ import { BINARY_NAME } from "../product.js";
31
+ import { openSession } from "../session.js";
32
+ function asVersion(value) {
33
+ if (!isRecord(value)) {
34
+ throw new NmtsError("The server's answer was not an object.", {
35
+ nextStep: "Nothing was changed.",
36
+ });
37
+ }
38
+ if (value["state"] === "absent")
39
+ return { state: "absent" };
40
+ if (value["state"] === "present" && typeof value["seq"] === "number" && typeof value["ct"] === "string") {
41
+ return { state: "present", seq: value["seq"], ct: value["ct"] };
42
+ }
43
+ throw new NmtsError("The server answered with a file list this version cannot read.", {
44
+ nextStep: "Nothing was changed. A newer version of this tool may understand it.",
45
+ });
46
+ }
47
+ /** The one refusal for "there is nothing to go back to", whichever half of the pair is missing. */
48
+ function nothingRetained() {
49
+ return new NmtsError("The server holds no previous version of the file list.", {
50
+ exitCode: 4,
51
+ nextStep: `Nothing was changed. \`${BINARY_NAME} rebuild\` builds a list from the server's rows when ` +
52
+ `there is no list to go back to.`,
53
+ });
54
+ }
55
+ export async function rollback(options = {}) {
56
+ const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
57
+ const session = await openSession({ server: options.server, network: options.network });
58
+ const previous = asVersion(await request(session.server, "/v1/manifest/previous", { token: session.apiKey }));
59
+ if (previous.state === "absent")
60
+ throw nothingRetained();
61
+ const current = asVersion(await request(session.server, "/v1/manifest", { token: session.apiKey }));
62
+ // A current list with nothing before it is the same answer in different words: there is no
63
+ // version this one replaced, so there is nothing to put back.
64
+ if (current.state === "absent")
65
+ throw nothingRetained();
66
+ if (options.yes !== true) {
67
+ // ⛔ THE UNCONFIRMED ANSWER SAYS WHAT WOULD HAPPEN IN ITS OWN WORDS, never in the words the
68
+ // finished act uses. A caller that read `restored_seq` off a run that changed nothing would
69
+ // report a rollback that never happened.
70
+ if (options.json === true) {
71
+ say(JSON.stringify({ previous_seq: previous.seq, current_seq: current.seq, changed: false }));
72
+ return 5;
73
+ }
74
+ say(`Version ${previous.seq} of the file list would go back as the current one, over version ` +
75
+ `${current.seq}.`);
76
+ say(``);
77
+ say(`⛔ What version ${current.seq} added is out of the list afterwards: a file's name, the`);
78
+ say(` folder it sits in and the key that opens it live in the list and nowhere else. The`);
79
+ say(` bytes are not touched — the storage stays bought — and \`${BINARY_NAME} rebuild\` finds`);
80
+ say(` files the list does not name.`);
81
+ say(``);
82
+ say(`Nothing was changed. To go ahead: ${BINARY_NAME} rollback --yes`);
83
+ say(`⛔ If a program is reading this on somebody's behalf: show it to them and let them decide.`);
84
+ return 5;
85
+ }
86
+ // ⛔ THE OLDER INDEX'S CHUNKS ARE NAMED AGAIN, and nothing is uploaded. At format version 2 the
87
+ // entries live in chunks the index names by hash; the server frees a chunk no current or
88
+ // retained index names, so a write that named none would put back an index whose contents it
89
+ // had just thrown away. The chunks themselves are already there — the server kept what the
90
+ // retained version names — which is why this is a list of names and not an upload.
91
+ const refs = await namedChunks(session.code, previous.ct);
92
+ // ⛔ THE SAME COMPARE-AND-SWAP EVERY OTHER WRITE USES, and it is not retried. A conflict here
93
+ // means somebody wrote the list while this ran, so what would be replaced is no longer the
94
+ // version this run showed the person — and re-applying it would roll back a version they
95
+ // never saw.
96
+ const answer = await request(session.server, "/v1/manifest", {
97
+ method: "PUT",
98
+ token: session.apiKey,
99
+ body: { base_seq: current.seq, ct: previous.ct, refs },
100
+ });
101
+ const seq = isRecord(answer) ? answer["seq"] : undefined;
102
+ if (typeof seq !== "number" || !Number.isSafeInteger(seq) || seq < 1) {
103
+ throw new NmtsError("The file list was written but the server did not say which version it is now.", {
104
+ nextStep: `The older list is back. Run \`${BINARY_NAME} ls\` to see it.`,
105
+ });
106
+ }
107
+ // This machine's own copy follows the server, so the next command does not treat what is now
108
+ // being served as a list going backwards behind its back.
109
+ await recordWrittenList(session.accountId, seq, previous.ct);
110
+ if (options.json === true) {
111
+ say(JSON.stringify({ restored_seq: previous.seq, replaced_seq: current.seq }));
112
+ return 0;
113
+ }
114
+ say(`Put version ${previous.seq} of the file list back as the current one, over version ` +
115
+ `${current.seq}. What version ${current.seq} added is out of the list now; the bytes are ` +
116
+ `still stored, and \`${BINARY_NAME} rebuild\` finds files the list does not name.`);
117
+ return 0;
118
+ }
@@ -0,0 +1,23 @@
1
+ /** MinIO's port, which is what most S3 tools already have in their examples. */
2
+ export declare const DEFAULT_PORT = 9000;
3
+ /**
4
+ * How long a file list may be reused before it is fetched again.
5
+ *
6
+ * ⛔ THERE IS A CACHE BECAUSE A SYNC IS THOUSANDS OF REQUESTS. Reading the list per request would
7
+ * mean a server round trip and a decryption for each one, so a listing of a large drive would
8
+ * take minutes and cost the account's rate budget. ⚠ It also means a file uploaded from another
9
+ * device can be up to this long in appearing here, which is the trade and is written in the
10
+ * tool's own words when it starts.
11
+ */
12
+ export declare const LIST_CACHE_MS = 5000;
13
+ export interface S3Options {
14
+ server?: string | undefined;
15
+ network?: string | undefined;
16
+ /** Which port to listen on. Loopback either way. */
17
+ port?: string | undefined;
18
+ json?: boolean;
19
+ write?: (line: string) => void;
20
+ /** Resolves when the caller wants the gateway to stop. Tests pass one; a person presses Ctrl-C. */
21
+ until?: Promise<void>;
22
+ }
23
+ export declare function s3(options?: S3Options): Promise<number>;