@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,161 @@
1
+ import type { ManifestEntry } from "./shared/lib/drive/manifest-codec.ts";
2
+ import { type FindOptions } from "./drive-paths.ts";
3
+ import { type OnCollision } from "./collision.ts";
4
+ import { type ManifestIntent, type SettingsPatch } from "./shared/lib/drive/manifest-ops.ts";
5
+ /**
6
+ * ⛔ THE SAME FIELD NAMES `Session` USES, so a session IS a valid input and nothing has to be
7
+ * translated between the two. One thing with two names is how a caller ends up passing the
8
+ * server where the account id goes on the day a field moves.
9
+ */
10
+ export interface ListEditInput {
11
+ server: string;
12
+ apiKey: string;
13
+ code: string;
14
+ accountId: string;
15
+ }
16
+ export interface ListEditResult {
17
+ /** The version now current. */
18
+ seq: number;
19
+ /** True when the list was rebuilt because another device wrote first. */
20
+ reappliedAfterConflict: boolean;
21
+ /** False when the intent was already true of the list, so nothing was written. */
22
+ changed: boolean;
23
+ /** The list as it now stands — after the edit, or as found when nothing changed. */
24
+ entries: readonly ManifestEntry[];
25
+ }
26
+ /**
27
+ * Apply one intent to the account's sealed file list.
28
+ *
29
+ * ⛔ THE INTENT IS WHAT IS RETRIED, NEVER FINISHED BYTES. On a lost compare-and-swap the list is
30
+ * read again and the intent is applied to the NEW one, so both edits survive. That is why the
31
+ * caller passes a function rather than an intent: an intent computed once against the old list
32
+ * could name a folder id, or a free name, that the new list no longer has.
33
+ *
34
+ * ⛔ AND THE INTENTS COME FROM THE BROWSER'S OWN MODULE, copied here byte-for-byte by
35
+ * `deploy/gen-cli-shared.mjs`. Re-implementing "send to the trash" would look trivial and be
36
+ * wrong in the small places: a re-trashed item must keep its ORIGINAL instant (it is the start
37
+ * of the 30-day window the product promises), and trashing a folder must not stamp its
38
+ * children (that would reset each child's own clock).
39
+ *
40
+ * `make` returning null means there is nothing to do; nothing is written and `changed` is false.
41
+ */
42
+ export declare function applyToList(input: ListEditInput, make: (entries: readonly ManifestEntry[]) => ManifestIntent | null): Promise<ListEditResult>;
43
+ /**
44
+ * Apply a RUN of intents to the account's sealed file list — as ONE write.
45
+ *
46
+ * ⛔ ONE WRITE, NOT ONE PER TARGET. A command naming five things and writing five times is five
47
+ * chances to lose the compare-and-swap, and losing it half way leaves a drive nobody asked
48
+ * for: three things moved, two not, and one error that names neither half. The whole list is
49
+ * rewritten on every save anyway (see the header), so five edits cost exactly what one costs.
50
+ *
51
+ * ⛔ AND `make` DECIDES THE WHOLE RUN AGAIN ON EVERY ATTEMPT. A free name, an existing folder and
52
+ * a live target are all facts about the version that was READ, and a retry happens against a
53
+ * version somebody else has just written. A `make` that folds its own intents onto a working
54
+ * copy as it goes must start that fold from the list it is handed each time — never from the
55
+ * working copy it built on the attempt before.
56
+ *
57
+ * An empty run means there is nothing to do; nothing is written and `changed` is false.
58
+ *
59
+ * ⛔ AND THE ACCOUNT'S SETTINGS RIDE IN THE SAME WRITE. They live in this blob or nowhere (see the
60
+ * header), so a caller that wanted to change one and did it in a second write would spend two
61
+ * version bumps and two chances to lose the compare-and-swap on one edit. `patch` is DESIRED
62
+ * STATE per field, like every intent above, so replaying it after a lost swap lands the same
63
+ * answer. Absent means "carry the settings forward untouched", which is what every caller but
64
+ * one wants.
65
+ */
66
+ export declare function applyManyToList(input: ListEditInput, make: (entries: readonly ManifestEntry[]) => readonly ManifestIntent[], patch?: SettingsPatch): Promise<ListEditResult>;
67
+ /**
68
+ * The entries a run of typed paths names, in the order they were typed.
69
+ *
70
+ * ⛔ HERE, BESIDE THE BATCH WRITE, so every command that takes many paths answers "the same thing
71
+ * named twice" the same way: once. `nmts rm a.txt a.txt` is not two deletions, and a repeated
72
+ * id inside one intent would make the count in the message disagree with the list written.
73
+ *
74
+ * ⛔ AND A PATH THAT DOES NOT RESOLVE REFUSES THE WHOLE RUN, because it throws from here before
75
+ * anything is composed. That is the decision every batch command in this tool makes: nothing
76
+ * is half-done. Moving the four paths that resolved and skipping the fifth would exit 0 on a
77
+ * command that did not do what it was told, and the caller would have to diff the drive to
78
+ * find out which one. A path already IN the state being asked for is not this case — that is
79
+ * a no-op, and each command names it in its own words.
80
+ *
81
+ * ⚠ Call it INSIDE `make`. A path is a question about the list, and the answer changes when
82
+ * another device writes first.
83
+ */
84
+ export declare function batchTargets(entries: readonly ManifestEntry[], paths: readonly string[], options?: FindOptions): ManifestEntry[];
85
+ export interface AddEntryInput extends ListEditInput {
86
+ /** The entry to add. Its `name` may be changed to avoid a collision — see the result. */
87
+ entry: ManifestEntry;
88
+ /**
89
+ * What THIS run asked for when the name is already in use. Absent = whatever this machine is
90
+ * set to (`collision.ts`), which is what an ordinary upload wants.
91
+ */
92
+ onCollision?: OnCollision;
93
+ }
94
+ export interface AddEntryResult {
95
+ /** The version now current. */
96
+ seq: number;
97
+ /** The name the entry actually got, which is not the requested one if that was taken. */
98
+ name: string;
99
+ /** True when the list was rebuilt because another device wrote first. */
100
+ reappliedAfterConflict: boolean;
101
+ /**
102
+ * The file this one displaced, when the name was taken and the answer was to overwrite.
103
+ *
104
+ * ⛔ IT IS IN THE TRASH, NOT GONE. This tool cannot destroy a stored row: the endpoint that does
105
+ * is closed to an API key on purpose (`item-trash.ts`). So the caller's remaining job is to
106
+ * tell the SERVER to trash it too — and what the tool prints must say "trash", never "gone".
107
+ */
108
+ replaced?: {
109
+ id: string;
110
+ name: string;
111
+ };
112
+ }
113
+ /** What one addition turns into, worked out against the list as it stands on THIS attempt. */
114
+ export interface AdditionPlan {
115
+ /** The name it will be stored under. */
116
+ name: string;
117
+ /** Set when this id is ALREADY in the list — then `intents` is empty and nothing is written. */
118
+ alreadyThere?: string;
119
+ /** The live file this displaces, when the name was taken and the answer was to overwrite. */
120
+ replaced?: {
121
+ id: string;
122
+ name: string;
123
+ };
124
+ intents: ManifestIntent[];
125
+ }
126
+ /**
127
+ * Decide what adding this entry does — the whole of the collision rule, with no server in it.
128
+ *
129
+ * ⛔ IT IS RE-RUN ON EVERY COMPARE-AND-SWAP ATTEMPT, so everything it looks at has to come from
130
+ * the `entries` it is handed. A free name, a live holder and a folder id are all facts about
131
+ * the version that was READ, and a retry happens against a version somebody else just wrote.
132
+ *
133
+ * ⛔ ONLY A LIVE FILE IS DISPLACED. A folder can hold the name, and replacing one would mean
134
+ * deleting it and everything under it in order to store a single file. A trashed file holds its
135
+ * name too, and displacing THAT would destroy something already on its way out for a name the
136
+ * person can no longer see. Both are renamed around, with no answer consulted — which is what
137
+ * happened to every collision before anything could be answered at all.
138
+ */
139
+ export declare function planAddition(entries: readonly ManifestEntry[], entry: ManifestEntry,
140
+ /**
141
+ * The ANSWER, already settled — not what a run asked for.
142
+ *
143
+ * ⛔ WHO IS ALLOWED TO SAY "OVERWRITE" IS `collision.ts`'s JOB, not this one's. It weighs the
144
+ * machine's stored answer, what the run asked for, and whether a mode lets an agent decide
145
+ * for itself. Re-deriving any of that here would be a second place for the owner's rule to
146
+ * live, and the copy nobody re-reads is the one that quietly disagrees.
147
+ */
148
+ choice: OnCollision, now?: number): AdditionPlan;
149
+ /**
150
+ * Add one entry to the account's sealed file list.
151
+ *
152
+ * ⛔ THE NAME IS CHOSEN AGAINST THE LIST AS IT IS ON THIS ATTEMPT. That is the reason this passes
153
+ * a function to `applyManyToList`: after a lost compare-and-swap the free names have changed,
154
+ * and a name picked against the old list could land on top of what the other device just added.
155
+ * The collision is judged again on every attempt for the same reason.
156
+ *
157
+ * ⛔ ONLY A LIVE FILE IS DISPLACED. A folder can hold the name, and replacing one would mean
158
+ * deleting it and everything under it in order to store a single file. Those are renamed, with
159
+ * no answer consulted, exactly as every collision was before anything could be answered at all.
160
+ */
161
+ export declare function addEntry(input: AddEntryInput): Promise<AddEntryResult>;
@@ -5,8 +5,10 @@
5
5
  // sealed under a key the server does not have. So this is the last step of an upload and the
6
6
  // one that must not be skipped after the money moved.
7
7
  //
8
- // ⛔ THE WHOLE LIST IS REWRITTEN EVERY TIME. There is no "append" on the wire the blob is sealed
9
- // as one piece. That is why this re-reads immediately before writing: the version it builds on
8
+ // ⛔ THE WHOLE LIST IS DECIDED EVERY TIME, EVEN THOUGH ONLY PART OF IT IS SENT. There is no
9
+ // "append" on the wire: the entries this save means to leave behind are worked out in full and
10
+ // then packed into chunks, and only the chunks whose contents actually changed are written
11
+ // (NCF-3 §6.3). That is why this re-reads immediately before writing — the version it builds on
10
12
  // has to be the current one, and anything another device added since must be carried forward,
11
13
  // not overwritten.
12
14
  //
@@ -17,14 +19,14 @@
17
19
  // · the `prev` link inside the blob makes a fork visible to the NEXT reader on any device.
18
20
  // The account's own settings ride along for the same reason: they live in this blob or nowhere,
19
21
  // so rewriting the list without them would silently clear them.
20
- import { request, ServerError } from "./api.js";
21
- import { AAD, DERIVED, loadCrypto } from "./crypto.js";
22
+ import { ServerError } from "./api.js";
23
+ import { DERIVED, loadCrypto } from "./crypto.js";
22
24
  import { NmtsError } from "./errors.js";
25
+ import { writeChunkedList } from "./manifest-chunk-flow.js";
23
26
  import { readFileList, recordWrittenList } from "./manifest.js";
24
- import { encodeManifest } from "./shared/lib/drive/manifest-codec.js";
25
27
  import { buildIndex, entryAt, isLive, KIND_FILE, namesIn, normaliseName } from "./drive-paths.js";
26
28
  import { decide } from "./collision.js";
27
- import { applyIntents } from "./shared/lib/drive/manifest-ops.js";
29
+ import { applyIntents, applySettingsPatch, } from "./shared/lib/drive/manifest-ops.js";
28
30
  import { uniqueFileName } from "./shared/lib/drive/unique-name.js";
29
31
  /** How many times a lost compare-and-swap is re-applied before giving up. */
30
32
  const CONFLICT_RETRIES = 3;
@@ -68,8 +70,15 @@ export async function applyToList(input, make) {
68
70
  * working copy it built on the attempt before.
69
71
  *
70
72
  * An empty run means there is nothing to do; nothing is written and `changed` is false.
73
+ *
74
+ * ⛔ AND THE ACCOUNT'S SETTINGS RIDE IN THE SAME WRITE. They live in this blob or nowhere (see the
75
+ * header), so a caller that wanted to change one and did it in a second write would spend two
76
+ * version bumps and two chances to lose the compare-and-swap on one edit. `patch` is DESIRED
77
+ * STATE per field, like every intent above, so replaying it after a lost swap lands the same
78
+ * answer. Absent means "carry the settings forward untouched", which is what every caller but
79
+ * one wants.
71
80
  */
72
- export async function applyManyToList(input, make) {
81
+ export async function applyManyToList(input, make, patch) {
73
82
  const crypt = await loadCrypto();
74
83
  const [from, to] = DERIVED.fileListKey;
75
84
  const derived = crypt.kdf_derive(crypt.account_code_parse(input.code));
@@ -80,30 +89,40 @@ export async function applyManyToList(input, make) {
80
89
  for (let attempt = 0; attempt <= CONFLICT_RETRIES; attempt += 1) {
81
90
  const current = await readFileList(input.server, input.apiKey, input.code, input.accountId);
82
91
  const entries = current.manifest ? current.manifest.entries : [];
92
+ // ⛔ THE SAME REFERENCE COMES BACK WHEN NOTHING CHANGED, which is what the no-op test below
93
+ // reads. `applySettingsPatch` promises that, and the empty object stands in for an
94
+ // account that has never written a setting so that the comparison has something to hold.
95
+ const held = current.manifest?.settings ?? {};
96
+ const settings = patch === undefined ? held : applySettingsPatch(held, patch);
83
97
  const intents = make(entries);
98
+ const next = intents.length === 0 ? entries : applyIntents(entries, intents);
84
99
  // ⛔ A no-op is a SUCCESS, not a failure. Renaming a file to the name it already has, or
85
100
  // trashing something already in the trash, must not cost a version bump every other
86
101
  // device then has to download.
87
- if (intents.length === 0) {
88
- return { seq: current.seq ?? 0, reappliedAfterConflict: conflicted, changed: false, entries };
89
- }
90
- const next = applyIntents(entries, intents);
91
- if (next === entries) {
102
+ if (next === entries && settings === held) {
92
103
  return { seq: current.seq ?? 0, reappliedAfterConflict: conflicted, changed: false, entries };
93
104
  }
94
- const body = await encodeManifest(next, (current.seq ?? 0) + 1, current.fingerprint, current.manifest?.settings);
95
- const sealed = crypt.envelope_seal(key, new TextEncoder().encode(AAD.fileList), body);
96
- body.fill(0);
97
- const ct = Buffer.from(sealed).toString("base64url");
105
+ const io = {
106
+ server: input.server,
107
+ apiKey: input.apiKey,
108
+ accountId: input.accountId,
109
+ crypt,
110
+ key,
111
+ };
98
112
  try {
99
- const answer = await request(input.server, "/v1/manifest", {
100
- method: "PUT",
101
- token: input.apiKey,
102
- body: { base_seq: current.seq ?? null, ct },
113
+ // ALWAYS VERSION 2 (NCF-3 §6.3.6). A list read as version 1 has no chunks to build on,
114
+ // so it is packed from scratch and the index names the version-1 blob as its parent —
115
+ // the link crosses the version boundary unchanged, and the account is converted.
116
+ const written = await writeChunkedList(io, {
117
+ previous: current.chunks ?? [],
118
+ entries: next,
119
+ seq: (current.seq ?? 0) + 1,
120
+ ...(current.fingerprint !== undefined ? { prev: current.fingerprint } : {}),
121
+ settings,
122
+ baseSeq: current.seq ?? null,
103
123
  });
104
- const seq = seqOf(answer);
105
- await recordWrittenList(input.accountId, seq, ct);
106
- return { seq, reappliedAfterConflict: conflicted, changed: true, entries: next };
124
+ await recordWrittenList(input.accountId, written.seq, written.ct);
125
+ return { seq: written.seq, reappliedAfterConflict: conflicted, changed: true, entries: next };
107
126
  }
108
127
  catch (error) {
109
128
  // ⛔ A version conflict is an ORDINARY outcome, not a failure: another device wrote first.
@@ -232,13 +251,3 @@ export async function addEntry(input) {
232
251
  ...(replaced ? { replaced } : {}),
233
252
  };
234
253
  }
235
- function seqOf(answer) {
236
- if (typeof answer === "object" && answer !== null) {
237
- const seq = Reflect.get(answer, "seq");
238
- if (typeof seq === "number" && Number.isSafeInteger(seq) && seq >= 1)
239
- return seq;
240
- }
241
- throw new NmtsError("The file list was written but the server did not say which version it is now.", {
242
- nextStep: "The entry is saved. Run `nmts ls` to see it.",
243
- });
244
- }
@@ -0,0 +1,82 @@
1
+ import type { Manifest } from "./shared/lib/drive/manifest-codec.ts";
2
+ import type { HeldChunk } from "./shared/lib/drive/manifest-pack.ts";
3
+ import type { AccountSettings } from "./shared/lib/drive/manifest-settings.ts";
4
+ import type { PaddingRule } from "./shared/lib/crypto/size-padding.ts";
5
+ /** This machine's copy of one account's sealed file list. */
6
+ /**
7
+ * Which size-padding rule an account's settings select for what this run seals.
8
+ *
9
+ * ⛔ ONE PLACE, because four commands seal uploads. A rule read four ways is a rule some of them
10
+ * eventually get wrong, and the way it shows up is that an account which asked to store files
11
+ * at their exact size quietly pays for rounded-up bytes from one command and not another.
12
+ *
13
+ * A spelling this build does not know reads as the DEFAULT rather than as "no padding": sealing by
14
+ * a rule this copy cannot reproduce would give a file a size no reader here can account for.
15
+ */
16
+ export declare function paddingRuleOf(settings: AccountSettings | undefined): PaddingRule;
17
+ export interface KeptList {
18
+ /** The version these bytes carry. Higher is newer — the same counter every device syncs by. */
19
+ seq: number;
20
+ /** When THIS MACHINE wrote the copy, RFC3339 on its own clock. */
21
+ savedAt: string;
22
+ /** The sealed blob, base64url: exactly the bytes the server served or this tool wrote. */
23
+ ct: string;
24
+ }
25
+ /**
26
+ * The copy this machine holds for an account, or null when it holds none.
27
+ *
28
+ * ⚠ A COPY THAT CANNOT BE READ IS REPORTED AS NO COPY, on purpose. There is nothing to salvage
29
+ * from a truncated one, the next read of the list replaces it, and a command that refused to
30
+ * write out a good copy because an old one is unreadable would be refusing the very thing it is
31
+ * for.
32
+ */
33
+ export declare function readKeptList(accountId: string): KeptList | null;
34
+ /**
35
+ * Record a version this machine WROTE, so the server cannot serve an older one back afterwards.
36
+ *
37
+ * ⛔ ONLY AFTER THE SERVER ACCEPTED IT. Recording a version that lost the compare-and-swap would
38
+ * leave this machine believing in a list that never existed — and then refusing the real one as
39
+ * a rollback.
40
+ */
41
+ export declare function recordWrittenList(accountId: string, seq: number, ct: string): Promise<void>;
42
+ /** True when this machine has a record for the account — i.e. a rollback would be visible. */
43
+ export declare function hasSeenBefore(accountId: string): boolean;
44
+ export interface FileList {
45
+ /** null when the account has no list yet — a new account, not an error. */
46
+ manifest: Manifest | null;
47
+ /**
48
+ * base64url SHA-256 of the sealed blob this list came out of. Absent with no list.
49
+ *
50
+ * ⛔ A WRITER NEEDS IT. The next version has to name the blob it continued from, or the fork
51
+ * check has a hole exactly where a fork would be introduced.
52
+ */
53
+ fingerprint?: string;
54
+ /** The version the sealed blob itself claims. Absent with no list. */
55
+ seq?: number;
56
+ /** What the server's column said, when it disagreed with the sealed value. */
57
+ serverSeqDisagreed?: number;
58
+ /** True when nothing on this machine could have caught a rollback. */
59
+ firstTimeOnThisMachine: boolean;
60
+ /**
61
+ * The sealed format this list turned out to be: 1 for the single blob, 2 for index plus chunks.
62
+ *
63
+ * ⚠ READERS ACCEPT BOTH; WRITERS WRITE 2 (NCF-3 §6.3). An account converts on its first save by
64
+ * a build that knows version 2, and nothing converts on read.
65
+ */
66
+ version?: number;
67
+ /**
68
+ * Version 2 only: the chunks this list was read out of, in placement order.
69
+ *
70
+ * ⛔ A WRITER NEEDS THEM. Comparing the new entries against these is what lets a save rewrite the
71
+ * one chunk that changed instead of the whole list. Empty means "there are none to build on",
72
+ * which is both a version-1 list and an account with no items — and both pack from scratch.
73
+ */
74
+ chunks?: readonly HeldChunk[];
75
+ }
76
+ /**
77
+ * Fetch and open the account's file list.
78
+ *
79
+ * `accountCode` is used here and not kept: the file-list key is derived, used, and zeroed. The
80
+ * derivation output holds every other key in the account, so it does not outlive this call.
81
+ */
82
+ export declare function readFileList(base: string, apiKey: string, accountCode: string, accountId: string): Promise<FileList>;
package/dist/manifest.js CHANGED
@@ -16,18 +16,25 @@
16
16
  //
17
17
  // ⛔ AND THE SEALED BYTES THEMSELVES ARE KEPT, beside that record. The record alone is a detector:
18
18
  // it can tell that a list went backwards, and it cannot hand anybody a list. The blob can — it
19
- // is the account's names, folders and file keys, sealed with the account code, and a copy of it
19
+ // is the account's names, folders and file keys, sealed with the NMTS key, and a copy of it
20
20
  // on this machine is one of the two things a person needs when the server has nothing to give
21
21
  // them. A tool that read the list on every run and then threw it away left an account used only
22
22
  // from a terminal with neither. It is written with the record, by the one function that writes
23
23
  // either, so the two can never describe different versions.
24
+ //
25
+ // ⛔ AT FORMAT VERSION 2 THOSE BYTES ARE THE INDEX, AND THE ENTRIES ARE BESIDE IT. The list is an
26
+ // index plus immutable chunks named by their own hash (NCF-3 §6.3); the index is kept here
27
+ // exactly as the single blob was, and the chunks are kept by name in the chunk store
28
+ // (`manifest-chunk-cache.ts`), which is pruned to what the list just read names. So the copy is
29
+ // still complete — it is simply in two places, and `nmts listfile` writes them out as one file.
24
30
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
25
31
  import { join } from "node:path";
26
32
  import { request } from "./api.js";
27
33
  import { AAD, DERIVED, loadCrypto } from "./crypto.js";
28
34
  import { configDir } from "./credentials.js";
29
35
  import { NmtsError } from "./errors.js";
30
- import { decodeManifest } from "./shared/lib/drive/manifest-codec.js";
36
+ import { openChunks } from "./manifest-chunk-flow.js";
37
+ import { decodeFileList, FILE_LIST_VERSION_CHUNKED, } from "./shared/lib/drive/manifest-chunks.js";
31
38
  function asResponse(value) {
32
39
  if (typeof value !== "object" || value === null)
33
40
  throw new NmtsError("The server's answer was not an object.");
@@ -67,6 +74,21 @@ function keptListPath(accountId) {
67
74
  }
68
75
  return join(configDir(), `file-list-${accountId}.json`);
69
76
  }
77
+ /** This machine's copy of one account's sealed file list. */
78
+ /**
79
+ * Which size-padding rule an account's settings select for what this run seals.
80
+ *
81
+ * ⛔ ONE PLACE, because four commands seal uploads. A rule read four ways is a rule some of them
82
+ * eventually get wrong, and the way it shows up is that an account which asked to store files
83
+ * at their exact size quietly pays for rounded-up bytes from one command and not another.
84
+ *
85
+ * A spelling this build does not know reads as the DEFAULT rather than as "no padding": sealing by
86
+ * a rule this copy cannot reproduce would give a file a size no reader here can account for.
87
+ */
88
+ export function paddingRuleOf(settings) {
89
+ const mode = settings?.paddingMode;
90
+ return mode === "pow2" || mode === "none" ? mode : "padme";
91
+ }
70
92
  function isKeptList(value) {
71
93
  if (typeof value !== "object" || value === null)
72
94
  return false;
@@ -186,28 +208,54 @@ export async function readFileList(base, apiKey, accountCode, accountId) {
186
208
  const derived = crypt.kdf_derive(crypt.account_code_parse(accountCode));
187
209
  const key = derived.slice(from, to);
188
210
  derived.fill(0);
189
- let body;
211
+ // ⛔ THE KEY LIVES UNTIL THE CHUNKS ARE OPEN. A version-2 list is an index plus sealed chunks
212
+ // under a label of their own, so the same key opens two kinds of blob and the zeroing has to
213
+ // wait for the second kind — which is why one `finally` now wraps the whole read.
190
214
  try {
191
- body = crypt.envelope_open(key, new TextEncoder().encode(AAD.fileList), Buffer.from(answer.ct, "base64url"));
192
- }
193
- catch {
194
- throw new NmtsError("The file list did not open with this account's key.", {
195
- nextStep: "Either the code belongs to a different account, or the stored bytes are not what this " +
196
- "account sealed. Nothing was changed.",
197
- });
215
+ let body;
216
+ try {
217
+ body = crypt.envelope_open(key, new TextEncoder().encode(AAD.fileList), Buffer.from(answer.ct, "base64url"));
218
+ }
219
+ catch {
220
+ throw new NmtsError("The file list did not open with this account's key.", {
221
+ nextStep: "Either the NMTS key belongs to a different account, or the stored bytes are not what this " +
222
+ "account sealed. Nothing was changed.",
223
+ });
224
+ }
225
+ const doc = await decodeFileList(body);
226
+ body.fill(0);
227
+ const io = { server: base, apiKey, accountId, crypt, key };
228
+ // A version-1 blob carries its own entries; a version-2 index names chunks that have to be
229
+ // fetched, checked against the names it gave them, and read end to end (§6.3.2).
230
+ const chunks = doc.v === FILE_LIST_VERSION_CHUNKED ? await openChunks(io, doc.index) : [];
231
+ const manifest = doc.v === FILE_LIST_VERSION_CHUNKED
232
+ ? {
233
+ v: FILE_LIST_VERSION_CHUNKED,
234
+ seq: doc.index.seq,
235
+ ...(doc.index.p !== undefined ? { prev: doc.index.p } : {}),
236
+ entries: chunks.flatMap((c) => [...c.items]),
237
+ ...(doc.index.settings !== undefined ? { settings: doc.index.settings } : {}),
238
+ }
239
+ : doc.manifest;
240
+ const out = {
241
+ manifest,
242
+ seq: manifest.seq,
243
+ fingerprint: fp,
244
+ firstTimeOnThisMachine: first,
245
+ version: doc.v,
246
+ chunks,
247
+ };
248
+ // ⛔ The sealed number is the authenticated one, so it is what gets recorded and what a later
249
+ // run compares against. The column is reported when it differs and otherwise ignored.
250
+ if (manifest.seq !== answer.seq)
251
+ out.serverSeqDisagreed = answer.seq;
252
+ // ⛔ THE VERSION THAT IS KEPT IS THE SEALED ONE, and so are the bytes it came out of. Believing
253
+ // the server's column here would let it decide which copy this machine keeps. The bytes are
254
+ // the INDEX at version 2; its chunks are kept beside it, by name, in the chunk store.
255
+ writeSeen(accountId, manifest.seq, fp, answer.ct);
256
+ return out;
198
257
  }
199
258
  finally {
200
259
  key.fill(0);
201
260
  }
202
- const manifest = await decodeManifest(body);
203
- body.fill(0);
204
- const out = { manifest, seq: manifest.seq, fingerprint: fp, firstTimeOnThisMachine: first };
205
- // ⛔ The sealed number is the authenticated one, so it is what gets recorded and what a later
206
- // run compares against. The column is reported when it differs and otherwise ignored.
207
- if (manifest.seq !== answer.seq)
208
- out.serverSeqDisagreed = answer.seq;
209
- // ⛔ THE VERSION THAT IS KEPT IS THE SEALED ONE, and so are the bytes it came out of. Believing
210
- // the server's column here would let it decide which copy this machine keeps.
211
- writeSeen(accountId, manifest.seq, fp, answer.ct);
212
- return out;
213
261
  }
@@ -0,0 +1,26 @@
1
+ import type { ManifestEntry } from "./shared/lib/drive/manifest-codec.ts";
2
+ /** The three marks, in the shape `ls --json` prints and `markSuffix` draws. */
3
+ export interface EntryMarks {
4
+ /** Starred: shown in the drive's favourites as well as in the folder the file lives in. */
5
+ favorite: boolean;
6
+ /** Held at the top of its own folder's listing, whatever the sort says. */
7
+ pinned: boolean;
8
+ /** The person's own labels for this entry, as text. Empty when it wears none. */
9
+ labels: readonly string[];
10
+ }
11
+ /**
12
+ * The marks on one entry — always all three.
13
+ *
14
+ * ⚠ ABSENT IS `false` HERE AND ABSENCE IN THE LIST, and the difference is deliberate. The format
15
+ * writes a mark only when it is on, because the whole list is re-sealed on every change and
16
+ * re-downloaded on every cold start. A reader parsing this needs the opposite: a field that is
17
+ * always there, so "this file is not starred" cannot be mistaken for "this output does not say".
18
+ */
19
+ export declare function marksOf(entry: ManifestEntry): EntryMarks;
20
+ /**
21
+ * What `ls` puts after a row, or an empty string when the entry wears no mark.
22
+ *
23
+ * ⚠ It carries its own leading spaces, exactly like the trash suffix beside it, so a row with no
24
+ * marks is byte-for-byte the row that was printed before marks existed.
25
+ */
26
+ export declare function markSuffix(marks: EntryMarks): string;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Keywords a schema uses that this checker does not enforce.
3
+ *
4
+ * ⛔ THE POINT IS THAT IT IS NOT EMPTY-BY-ASSUMPTION. A checker that silently skips what it does
5
+ * not understand still returns "no problems", and a schema that grew a `minimum` or a `oneOf`
6
+ * would go on being advertised while nothing held it. A test compares this against the real
7
+ * tool table, so growing a schema past this file turns something red.
8
+ */
9
+ export declare function unsupported(schema: unknown): string[];
10
+ /**
11
+ * Compare one call's arguments against the schema its tool advertised.
12
+ *
13
+ * Returns the problems, most important first: a missing required argument before a wrong type,
14
+ * because a caller that forgot one is usually about to be told about the other for the same reason.
15
+ * An empty array means the call may proceed.
16
+ */
17
+ export declare function checkArgs(schema: unknown, args: unknown): string[];
@@ -0,0 +1,43 @@
1
+ /** What came back, once the three protocol actions are read for what they mean here. */
2
+ export type AskOutcome =
3
+ /** The person said yes. */
4
+ "yes"
5
+ /** The person said no, or dismissed the question without answering. Both mean: do not proceed. */
6
+ | "no"
7
+ /** There was nobody to ask — the client never said it could. */
8
+ | "unreachable";
9
+ /** Sends one JSON-RPC request to the client and resolves with whatever comes back as its result. */
10
+ export type SendRequest = (method: string, params: Record<string, unknown>) => Promise<unknown>;
11
+ /**
12
+ * The question this server asks, in one place so that every caller asks it the same way.
13
+ *
14
+ * ⛔ ONE BOOLEAN, NOT A FREE-TEXT FIELD. A client renders this schema into whatever it renders it
15
+ * into, and a checkbox somebody has to tick is the shape that survives every rendering. A text
16
+ * field would let a client accept "no" as a filled-in answer.
17
+ */
18
+ export declare const CONFIRM_SCHEMA: Readonly<Record<string, unknown>>;
19
+ /**
20
+ * Read an `elicitation/create` result. Pure, so the three actions can be tested without a pipe.
21
+ *
22
+ * ⛔ ONLY `accept` WITH `confirm === true` IS A YES. `decline` and `cancel` are both no — the
23
+ * specification distinguishes "refused" from "dismissed" so that a server can offer something
24
+ * else, and here there is nothing else to offer. An `accept` carrying no content, or content
25
+ * with the box unticked, is a no as well: the person was shown the question and did not agree.
26
+ *
27
+ * ⛔ ANYTHING UNRECOGNISED IS A NO. A malformed answer is not an answer, and the direction to fail
28
+ * in is the one where a file is not handed to somebody.
29
+ */
30
+ export declare function readAnswer(result: unknown): AskOutcome;
31
+ /** Whether the client said, at `initialize`, that it can put a question in front of a person. */
32
+ export declare function declaredElicitation(capabilities: unknown): boolean;
33
+ /** How a session asks, or `null` when this session has no way to ask at all. */
34
+ export type Asker = ((message: string) => Promise<AskOutcome>) | null;
35
+ /**
36
+ * Build the asker for a session.
37
+ *
38
+ * ⛔ THE CAPABILITY IS READ ONCE, FROM `initialize`, AND NEVER GUESSED AFTERWARDS. A client that
39
+ * did not declare elicitation is not sent one: the specification says a server may only use a
40
+ * capability the other side declared, and a request it does not understand is at best an error
41
+ * on the wire and at worst a hung tool call waiting for an answer that is never coming.
42
+ */
43
+ export declare function askerFor(capabilities: unknown, send: SendRequest): Asker;