@needmoretruth/nmts-cli 0.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/AGENTS.md +591 -0
  2. package/LICENSE +202 -0
  3. package/LICENSING.md +49 -0
  4. package/README.ko.md +641 -0
  5. package/README.md +657 -0
  6. package/dist/account-proof.js +78 -0
  7. package/dist/account.js +75 -0
  8. package/dist/api-key.js +310 -0
  9. package/dist/api.js +271 -0
  10. package/dist/args.js +137 -0
  11. package/dist/artifact-about.js +70 -0
  12. package/dist/autonomy.js +98 -0
  13. package/dist/code-access.js +76 -0
  14. package/dist/code-vault.js +233 -0
  15. package/dist/collision.js +133 -0
  16. package/dist/commands/balance.js +93 -0
  17. package/dist/commands/consent.js +77 -0
  18. package/dist/commands/create.js +356 -0
  19. package/dist/commands/env.js +136 -0
  20. package/dist/commands/expiring.js +167 -0
  21. package/dist/commands/extend.js +236 -0
  22. package/dist/commands/get.js +130 -0
  23. package/dist/commands/kit.js +136 -0
  24. package/dist/commands/listfile.js +105 -0
  25. package/dist/commands/login.js +335 -0
  26. package/dist/commands/logout.js +33 -0
  27. package/dist/commands/ls.js +202 -0
  28. package/dist/commands/marks.js +176 -0
  29. package/dist/commands/mcp.js +139 -0
  30. package/dist/commands/mode.js +54 -0
  31. package/dist/commands/on-collision.js +45 -0
  32. package/dist/commands/organise.js +296 -0
  33. package/dist/commands/public-code.js +89 -0
  34. package/dist/commands/pull.js +185 -0
  35. package/dist/commands/push.js +278 -0
  36. package/dist/commands/put.js +282 -0
  37. package/dist/commands/rebuild.js +160 -0
  38. package/dist/commands/receive.js +125 -0
  39. package/dist/commands/recovery-list.js +104 -0
  40. package/dist/commands/recovery.js +291 -0
  41. package/dist/commands/s3.js +241 -0
  42. package/dist/commands/settings.js +28 -0
  43. package/dist/commands/share.js +276 -0
  44. package/dist/commands/sweep.js +240 -0
  45. package/dist/commands/trash.js +288 -0
  46. package/dist/commands/trial.js +264 -0
  47. package/dist/commands/update.js +169 -0
  48. package/dist/commands/usage.js +84 -0
  49. package/dist/commands/verify.js +331 -0
  50. package/dist/commands/wallet.js +122 -0
  51. package/dist/commands/whoami.js +33 -0
  52. package/dist/consent.js +200 -0
  53. package/dist/credentials.js +311 -0
  54. package/dist/crypto-surface.js +54 -0
  55. package/dist/crypto.js +137 -0
  56. package/dist/download-part.js +144 -0
  57. package/dist/download-sink.js +213 -0
  58. package/dist/download.js +163 -0
  59. package/dist/drive-paths.js +170 -0
  60. package/dist/environment.js +190 -0
  61. package/dist/errors.js +71 -0
  62. package/dist/exit.js +107 -0
  63. package/dist/expiry.js +115 -0
  64. package/dist/extend-chain.js +137 -0
  65. package/dist/extend-plan.js +149 -0
  66. package/dist/extend-sign.js +130 -0
  67. package/dist/guards.js +15 -0
  68. package/dist/help.js +192 -0
  69. package/dist/human-check.js +54 -0
  70. package/dist/item-trash.js +25 -0
  71. package/dist/kit-file.js +133 -0
  72. package/dist/list-file.js +71 -0
  73. package/dist/list-view-find.js +71 -0
  74. package/dist/list-view-order.js +96 -0
  75. package/dist/main.js +394 -0
  76. package/dist/manifest-create.js +81 -0
  77. package/dist/manifest-write.js +244 -0
  78. package/dist/manifest.js +213 -0
  79. package/dist/mark-render.js +45 -0
  80. package/dist/mcp-args.js +154 -0
  81. package/dist/mcp-tools/context.js +50 -0
  82. package/dist/mcp-tools/files.js +133 -0
  83. package/dist/mcp-tools/organise.js +128 -0
  84. package/dist/mcp-tools/reads.js +89 -0
  85. package/dist/mcp-tools/share.js +61 -0
  86. package/dist/mcp.js +142 -0
  87. package/dist/net-retry.js +84 -0
  88. package/dist/network.js +43 -0
  89. package/dist/notice.js +45 -0
  90. package/dist/product.js +38 -0
  91. package/dist/progress.js +110 -0
  92. package/dist/prompt.js +145 -0
  93. package/dist/rebuild.js +255 -0
  94. package/dist/recovery-assemble.js +148 -0
  95. package/dist/recovery-build.js +260 -0
  96. package/dist/recovery-map-file.js +114 -0
  97. package/dist/recovery-map.js +191 -0
  98. package/dist/recovery-release.js +229 -0
  99. package/dist/recovery-seq.js +63 -0
  100. package/dist/recovery-source.js +143 -0
  101. package/dist/registration.js +83 -0
  102. package/dist/s3/listing.js +134 -0
  103. package/dist/s3/multipart.js +104 -0
  104. package/dist/s3/response-sink.js +41 -0
  105. package/dist/s3/same-file.js +117 -0
  106. package/dist/s3/server.js +252 -0
  107. package/dist/s3/sigv4.js +168 -0
  108. package/dist/s3/staging.js +87 -0
  109. package/dist/s3/xml.js +80 -0
  110. package/dist/safe-path.js +89 -0
  111. package/dist/seal.js +206 -0
  112. package/dist/secret-reader.js +112 -0
  113. package/dist/server.js +33 -0
  114. package/dist/session.js +49 -0
  115. package/dist/setup-questions.js +31 -0
  116. package/dist/share.js +195 -0
  117. package/dist/shared/lib/crypto/size-padding.js +126 -0
  118. package/dist/shared/lib/drive/manifest-codec.js +287 -0
  119. package/dist/shared/lib/drive/manifest-index.js +235 -0
  120. package/dist/shared/lib/drive/manifest-ops.js +267 -0
  121. package/dist/shared/lib/drive/manifest-settings.js +65 -0
  122. package/dist/shared/lib/drive/name-conflict.js +121 -0
  123. package/dist/shared/lib/drive/unique-name.js +48 -0
  124. package/dist/shared/lib/extend/epochs.js +256 -0
  125. package/dist/shared/lib/net/retry-budget.js +95 -0
  126. package/dist/shared/lib/share/shared-file-info.js +65 -0
  127. package/dist/shared/lib/storage-network.js +64 -0
  128. package/dist/shared/lib/upload/part-plan.js +28 -0
  129. package/dist/stdout.js +109 -0
  130. package/dist/trash-sweep.js +123 -0
  131. package/dist/units.js +19 -0
  132. package/dist/update-check.js +164 -0
  133. package/dist/update-source.js +143 -0
  134. package/dist/upload-api.js +98 -0
  135. package/dist/upload-file.js +242 -0
  136. package/dist/upload-price.js +117 -0
  137. package/dist/upload-steps.js +111 -0
  138. package/dist/upload-store.js +252 -0
  139. package/dist/upload-wire.js +25 -0
  140. package/dist/upload.js +294 -0
  141. package/dist/usage-report.js +54 -0
  142. package/dist/wallet-chain.js +76 -0
  143. package/dist/wallet.js +209 -0
  144. package/dist/walrus-write.js +188 -0
  145. package/dist/walrus.js +156 -0
  146. package/package.json +56 -0
  147. package/vendor/nmts-crypto/nmts_crypto_wasm.d.ts +469 -0
  148. package/vendor/nmts-crypto/nmts_crypto_wasm.js +1300 -0
  149. package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm +0 -0
  150. package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm.d.ts +55 -0
  151. package/vendor/nmts-crypto/package.json +17 -0
package/README.md ADDED
@@ -0,0 +1,657 @@
1
+ # nmts
2
+
3
+ Command-line access to [NMTS](https://nmts.me) — end-to-end encrypted storage on the Walrus
4
+ network. For people at a terminal, and for the agents they run.
5
+
6
+ > **[한국어 문서](README.ko.md)**
7
+ >
8
+ > **Talk about NMTS — [Discord](https://discord.gg/pcmRkVmVZk).** Questions, ideas, and
9
+ > what people are building with it. English or Korean; both are read.
10
+
11
+ > **If you are an AI agent, read [AGENTS.md](AGENTS.md) instead.** It says the same things in the
12
+ > order a program needs them, and it is the file to follow when a person points you at this tool.
13
+
14
+ > **Status: early.** Built in the open, and the interface may still change before 1.0. Nothing here is a promise about a shipped feature — `nmts --help` is the current truth
15
+ > about what exists.
16
+
17
+ ## What NMTS is
18
+
19
+ Storage where **the encryption happens on your machine and the keys never leave it.** The server
20
+ receives sealed bytes and has no way to open them; the file contents, the names and the folder
21
+ structure are all inside a sealed list only your account code opens.
22
+
23
+ The bytes themselves live on **Walrus**, a public storage network, paid for on the **Sui** chain.
24
+ Two consequences worth knowing before you start:
25
+
26
+ - **Storage is bought for a period, not forever.** A file has a lease. It can be extended, and
27
+ NMTS warns before one runs out.
28
+ - **There is no password reset.** Your account code *is* the account. It is not recoverable and it
29
+ cannot be changed while keeping the files — that is the same property that stops anyone,
30
+ including NMTS, from opening them.
31
+
32
+ There are two ways to pay: **credits**, where NMTS's treasury buys the storage and your account
33
+ spends credits it already holds, or **your own Sui wallet**, which signs the purchase itself on a
34
+ public chain. Uploading here always uses credits. One command uses the wallet — `nmts extend`,
35
+ which buys more time for a file that is already stored — and it asks for a separate agreement
36
+ before it signs, because a signed purchase is not something NMTS can reverse.
37
+
38
+ ## Install
39
+
40
+ Node 22 or newer. One line, straight from this repository:
41
+
42
+ ```sh
43
+ npm install -g github:needmoretruth/nmts-cli
44
+ nmts --help
45
+ ```
46
+
47
+ That takes the default branch. To pin a version, name a tag:
48
+
49
+ ```sh
50
+ npm install -g github:needmoretruth/nmts-cli#v0.17.2
51
+ ```
52
+
53
+ Or from the tarball on the [latest release](https://github.com/needmoretruth/nmts-cli/releases),
54
+ which is the same package and needs no clone:
55
+
56
+ ```sh
57
+ npm install -g https://github.com/needmoretruth/nmts-cli/releases/latest/download/nmts.tgz
58
+ ```
59
+
60
+ **It is not on a package registry, and `npm install -g nmts` will not find it.** That short name
61
+ is not available either: the registry refuses it as too close to names already published there.
62
+ Nothing else about installing changes: the repository stays the source either way.
63
+
64
+ **Nothing is compiled at install time**, and `dist/` is in this repository for that reason:
65
+ installing straight from a repository cannot build, because npm prepares it in a staging clone
66
+ where the compiler is not there. A committed build can drift from what produced it, so this
67
+ repository's own checks rebuild it on every push and refuse if one byte differs.
68
+
69
+ To work on it rather than install it:
70
+
71
+ ```sh
72
+ git clone https://github.com/needmoretruth/nmts-cli
73
+ cd nmts-cli
74
+ npm install
75
+ node src/main.ts --help
76
+ ```
77
+
78
+ `npm run compile` refreshes `dist/`, and `node dist/main.js` runs it — the same file the installed
79
+ command runs. It is called `compile` and not `build` for a reason worth knowing before renaming
80
+ it: on npm 11, a package with a script named `build` installs from a git URL by linking npm's own
81
+ temporary clone and then deleting it, which leaves a broken command and **reports success**.
82
+
83
+ **There is no native build step and no C compiler anywhere in this**: the encryption engine is a
84
+ WebAssembly module carried in the repository. It runs wherever Node runs — Linux, macOS,
85
+ Windows, and inside a rootless container. Starting it costs about 80 milliseconds, and commands
86
+ load only what they need.
87
+
88
+ ## Staying up to date
89
+
90
+ ```sh
91
+ nmts update
92
+ ```
93
+
94
+ It reads which release is newest, prints the version running and the version published, and
95
+ installs the newer one with `npm install --global` from that release's own address. `--dry-run`
96
+ prints the command and stops. Run from a source checkout rather than an installed copy, it
97
+ refuses: installing would leave two copies and the PATH would decide which one runs.
98
+
99
+ Separately, **once a day, after a command has finished**, it asks
100
+ `https://github.com/needmoretruth/nmts-cli/releases/latest` which release is newest and writes the
101
+ answer down. When that is newer than the version running, the next run prints one line on stderr
102
+ saying so — stderr, so it cannot land in the output of `--json`.
103
+
104
+ That request carries no account code, no API key and no command name: it asks for a page address,
105
+ and what the site can see is that somebody asked for it. It is the only request this tool makes
106
+ that no command asked for; everything else goes to the NMTS server or to the storage network
107
+ because something needed it.
108
+
109
+ Set `NMTS_NO_UPDATE_CHECK` to anything and both halves stop — the lookup and the notice.
110
+ `nmts env` shows what the check last found, or why it did not answer.
111
+
112
+ ## First run
113
+
114
+ ```sh
115
+ nmts env
116
+ ```
117
+
118
+ It contacts nothing and needs no credential. It reports what this machine is, whether a file
119
+ written here can actually be kept private, whether a browser could be opened, and whether it can
120
+ already see your credentials. On anything unfamiliar — a container, a CI runner, someone else's
121
+ laptop — run this first.
122
+
123
+ ## The two credentials
124
+
125
+ They do different jobs and they are not interchangeable.
126
+
127
+ **The account code opens your files.** Every key in the account derives from it: the file keys, the
128
+ sealed list, the wallet. It never goes to the server.
129
+
130
+ ```sh
131
+ export NMTS_ACCOUNT_CODE_FILE=/path # name a file holding it — the recommended way
132
+ nmts login # …or keep it here, sealed under a passphrase
133
+ export NMTS_ACCOUNT_CODE="..." # …or hand it over directly (asks once, see below)
134
+ ```
135
+
136
+ **The API key makes the server answer.** Signing in normally needs a human check that no
137
+ command-line tool can pass; a key, made on the account screen at [nmts.me](https://nmts.me),
138
+ waives that and nothing else. **It opens no file.**
139
+
140
+ ```sh
141
+ export NMTS_API_KEY_FILE=/path # name a file holding it — the recommended way
142
+ export NMTS_API_KEY="..." # …or hand it over directly
143
+ nmts login # …and this writes down whichever it finds, or asks
144
+ ```
145
+
146
+ `nmts ls` needs both: the key so the server answers, the code so the answer can be opened.
147
+
148
+ `nmts login` checks a key with the server before it writes it down, so a wrong one is wrong at the
149
+ moment it is pasted rather than at the next command. It prints the key's public handle and never
150
+ the key itself. A key already stored is not replaced by a run that did not say so: at a terminal
151
+ `login` asks, and where there is no terminal `nmts logout` clears what is there first.
152
+
153
+ **Neither credential is ever accepted as a command-line argument.** On Linux any process can read another
154
+ process's command line, and shells record it in history. There is no flag for either, deliberately.
155
+
156
+ ## Where the code can live
157
+
158
+ Four places, and the tool has an opinion about each. Nothing is unreachable: what changes is
159
+ whether it happens by accident.
160
+
161
+ | | What it does | Asks |
162
+ |---|---|---|
163
+ | `NMTS_ACCOUNT_CODE_FILE=/path` | Reads the code from a file it never copies | nothing |
164
+ | `nmts login` | Seals it under a passphrase at `~/.nmts/credentials.json` | nothing |
165
+ | `nmts login --plain` | Writes it in the clear, mode 600 | once, `unsafe-code-storage` |
166
+ | `NMTS_ACCOUNT_CODE`, holding the code | Uses it straight from the environment | once, `plain-env` |
167
+
168
+ **`nmts login` seals by default.** What lands on disk is not the code: opening it needs a
169
+ passphrase, so a copy of that file in a backup, a synced folder, a container image or a stolen
170
+ laptop is worth nothing on its own. Every command that needs the code asks for the passphrase, or
171
+ reads it from `NMTS_PASSPHRASE`. That costs a fraction of a second and 64 MiB of memory each
172
+ time — which is the point, because it is what makes guessing the passphrase expensive.
173
+
174
+ ⚠ **A passphrase does not protect the code from anything running as you.** Whatever supplies the
175
+ passphrase can be read the same way. On a machine where an agent runs unattended, `NMTS_PASSPHRASE`
176
+ has to be somewhere the agent can reach, and at that point the lock has its key taped beside it.
177
+ That is why the file form — `NMTS_ACCOUNT_CODE_FILE` — is the recommendation for agents rather
178
+ than a lesser option: the code is never copied anywhere, and the permissions are the host's to set.
179
+
180
+ **An environment variable is not private, which is why using one asks.** `docker inspect` prints
181
+ the whole environment of a container. Anything running as you can read `/proc/<pid>/environ` for as
182
+ long as the process lives. Every child process inherits it, and continuous-integration systems
183
+ routinely write it into a log. A variable naming a *file* has none of those, and asks nothing.
184
+
185
+ **`nmts login --env` prints the line to set and writes nothing** — the one command that puts the
186
+ code on your screen, and the reason it is behind the same agreement.
187
+
188
+ **What you do with the code outside this tool is yours.** Putting it in a note, a password manager
189
+ or a repository is not something the tool can see, and it is not something it tries to stop. What
190
+ it can do is make the shape *it* writes a decision somebody took on purpose.
191
+
192
+ ## Before you hand this to an agent
193
+
194
+ Your account code is everything at once. A program that has it can read every file, upload,
195
+ delete, and sign with the wallet — and requests made with it cannot be told apart from your own.
196
+ It cannot be rotated while keeping the account.
197
+
198
+ **Use an account you would be willing to lose.**
199
+
200
+ ## Commands
201
+
202
+ | Command | What it does |
203
+ |---|---|
204
+ | `nmts env` | Where this is running, and what that means. Needs nothing. |
205
+ | `nmts login` / `logout` | Keep or remove an account code on this machine |
206
+ | `nmts whoami` | Which account the stored code belongs to — offline, no server call |
207
+ | `nmts expiring` | Which files run out of bought storage soon, and when |
208
+ | `nmts extend <path>` | Buy more storage time for one file — **signs and spends from the wallet** |
209
+ | `nmts create` | Make a NEW account and print its code once. Nothing can print it again |
210
+ | `nmts trial` | What is left of this week's free credits. `trial apply` asks for some |
211
+ | `nmts recovery-list` | Write the file that finds this account's bytes without NMTS |
212
+ | `nmts kit` | Recovery kit: that list **and the account code**, together in one file |
213
+ | `nmts sweep` | Drop trash entries past their 30 days. **Cannot be undone** — asks every run |
214
+ | `nmts consent` | What this machine has agreed to |
215
+ | `nmts update` | Install the newest published release of this tool |
216
+ | `nmts ls` | List the files |
217
+ | `nmts usage` | What the account holds: counts, bytes, the largest files, the trash |
218
+ | `nmts get <path>` | Download one file, decrypt it, check it |
219
+ | `nmts pull [folder]` | Download a whole folder, or the whole account, keeping its shape |
220
+ | `nmts push <directory>` | Upload a whole directory, keeping its shape — **spends credits** |
221
+ | `nmts put <file>` | Encrypt one file and upload it — **spends credits** |
222
+ | `nmts rm <paths>` | Move things to the trash — restorable for 30 days |
223
+ | `nmts restore <paths>` | Bring things back out of the trash |
224
+ | `nmts mkdir <path>` | Make a folder, and any folder above it that is missing |
225
+ | `nmts mv <paths> <folder>` | Move things into a folder. `/` is the top of the drive |
226
+ | `nmts rename <path> <name>` | Give one thing a new name |
227
+ | `nmts star` / `unstar` | Star files, or take the star off |
228
+ | `nmts pin` / `unpin` | Hold files at the top of their folder, or let them fall back |
229
+ | `nmts label <name> <files>` | Put one label on files. `unlabel` takes it off |
230
+ | `nmts rebuild` | Build a file list from the server's rows, for an account with none |
231
+ | `nmts listfile` | Write this machine's copy of the sealed file list out as a file |
232
+ | `nmts share <path> <address>` | Give one file to another account — **withdrawing does not recall it** |
233
+ | `nmts shares` | What was shared with this account |
234
+ | `nmts receive <id>` | Download one file somebody shared with this account |
235
+ | `nmts unshare <id>` | Withdraw a share you sent, or remove one you were sent |
236
+ | `nmts wallet` | The account's wallet address, and its SUI and WAL balances |
237
+ | `nmts verify` | Ask a person to pass the check that opens this account's limits |
238
+ | `nmts mcp` | Serve a subset of the above as tools over the Model Context Protocol |
239
+ | `nmts s3` | Serve the drive to any S3 program, on this machine only |
240
+
241
+ `ls` takes `--json` and `--all` (include the trash). Trashed entries are hidden by default and the
242
+ count always says how many were hidden. `--find <text>` keeps only files whose name contains the
243
+ text; folders appear only where they hold a match, and the listing says out loud what the query
244
+ left out. `--sort name|size|date` and `--desc` change the order.
245
+
246
+ `share` needs an address, which the other account reads off their own account screen — there is no
247
+ directory and no name lookup, so a mistyped address is caught here by the check symbol built into
248
+ it rather than by asking the server about somebody. **Withdrawing a share stops further downloads
249
+ and cannot reach a copy they have already taken.** That is what handing somebody a file means, and
250
+ it is why sharing asks for an agreement the first time.
251
+
252
+ `pull` fetches each file on its own: one that will not come back is named at the end and the rest
253
+ are still on disk, because refusing the whole thing over one file is how somebody runs it twenty
254
+ times and loses the same nineteen files each run. Files already in the destination are **skipped
255
+ and counted**, never replaced — `--force` replaces, and that cannot be undone.
256
+
257
+ `push` is the other direction, and it behaves differently on purpose. `pull` costs nothing, so it
258
+ carries on past a file that will not come back; `push` **spends**, so it stops at the first failure
259
+ and says what is already uploaded — those files are real and paid for, and running the same command
260
+ again sends only the rest. Files whose name is already in the destination are **skipped**, which is
261
+ what makes running it again safe: this tool never replaces a file, so without that a second run
262
+ would pay for numbered copies of everything. Names beginning with a dot are left alone unless
263
+ `--hidden` is given, because a directory of source code carries its credentials in exactly those
264
+ files and an upload goes to a public storage network. Symbolic links are not followed.
265
+
266
+ `rm`, `restore` and `mv` take several paths and make **one** list write. A path that names nothing
267
+ stops the whole run before any server row is touched: doing four of five and exiting 0 reads as
268
+ "finished", and which one was missed can only be found by comparing the drive.
269
+
270
+ `rebuild` is for an account whose sealed list is gone but whose files are still stored. It rebuilds
271
+ from the server's own rows — the file keys, the hashes, the dates, the sizes and what was in the
272
+ trash all come back; the names, the folders and the arrangement do not, and it says so. It writes
273
+ nothing without `--yes`, and it refuses outright if a list already exists.
274
+
275
+ `wallet` reads; it never signs, sends or spends. The address is derived on this machine from the
276
+ account code, so `nmts wallet address` needs no network at all. A balance that could not be read is
277
+ reported as unread — not as zero.
278
+
279
+ `get` takes `--out` and `--force`. It refuses rather than writing a half-right file: a part that
280
+ will not decrypt, parts that do not add up, or a whole-file hash that does not match all leave
281
+ nothing at the name you asked for. A file on disk is a claim that it is the file. The bytes are
282
+ written as they arrive, under a temporary name in the same directory, and that file is renamed
283
+ into place only once the whole-file hash matches. The file is never held in memory; one part
284
+ at a time is, so what a machine needs is the part size the uploader chose rather than the size
285
+ of the file. A download that fails takes its temporary file with it.
286
+
287
+ `--out -` sends the file to whatever is reading this program instead of writing it, so reading one
288
+ stored file need not leave a copy on the disk. Everything a person reads goes to stderr in that
289
+ mode. It refuses to send bytes a terminal would act on — redirect or pipe it. A pipe has no
290
+ rename, so that mode proves the whole file before it sends a byte, which means holding it: above
291
+ 64 MiB it refuses and says to use `--out <name>` instead.
292
+
293
+ `put` takes `--dry-run`, `--name` and `--to`:
294
+
295
+ ```sh
296
+ nmts put report.pdf --dry-run # what it would cost. Sends nothing, charges nothing.
297
+ nmts put report.pdf --to notes # into an existing folder
298
+ nmts put film.mov --part-size 256MiB # bigger parts: fewer purchases, more memory
299
+ ```
300
+
301
+ One credit per started mebibyte, printed before anything is spent. A name already taken in that
302
+ folder is numbered (`report (2).pdf`) rather than replacing what is there — NMTS keeps no previous
303
+ versions, so replacing would be permanent.
304
+
305
+ A file larger than one part is split, and **each part is bought separately**. The file is read a
306
+ slice at a time, so its size is not bounded by memory; the part size is (64 MiB by default). Each
307
+ part is written down before its own purchase, so a run that stops partway is finished by running
308
+ the same command again — it buys only the parts that were never bought.
309
+
310
+ ### `nmts balance` and `nmts public-code`
311
+
312
+ `balance` asks the server what this account can still pay for: credits left, the same number said
313
+ as bytes so it means something, how much is already held, and the ceilings on spending. It is not
314
+ the same question as `usage` — that one adds up the sealed file list and answers "what do I have",
315
+ this one reads the ledger and answers "what can I still buy". It does not read the storage
316
+ network's clock, so for *when* stored files expire it points at `nmts expiring` rather than
317
+ printing a second deadline from a different source.
318
+
319
+ `public-code` prints the value other accounts send files to — the same **public code** the browser
320
+ shows on the account screen, in the same grouped form — and says whether it has been published.
321
+ Until it is published nobody can send to you: a sender needs the key behind it, and the server is
322
+ where they look. `nmts public-code --publish` writes it.
323
+
324
+ That write is permanent: it cannot be withdrawn or changed. It is also not a choice — it comes
325
+ from your account code, so the same account code produces the same public code on any machine, and
326
+ the server refuses one that is not the fingerprint of its own key. If the server already holds a
327
+ *different* public code for this account, the command stops and says what that means: the account
328
+ code on this machine is not the one the account was made with.
329
+
330
+ ⛔ It is not your account code. That one opens every file you have and is never given to anybody;
331
+ this one is meant to be given away and opens nothing on its own.
332
+
333
+ Sending a file publishes it as a side effect, because a share cannot exist without one. Receiving
334
+ is the case this command is for.
335
+
336
+ ### `nmts recovery` — fetch the recovery program
337
+
338
+ The separate recovery program restores files from the storage network with your account code, a
339
+ recovery list and nothing else — no NMTS server involved. Until now getting it meant installing a
340
+ Rust toolchain and building it, which is a fair ask of somebody auditing it and an unfair one of
341
+ somebody who has just lost access to their files.
342
+
343
+ ```sh
344
+ nmts recovery --out ~/tools
345
+ ```
346
+
347
+ It works out which executable this machine needs, fetches the checksum file for the release first
348
+ and takes the release's own name out of that request, then fetches the executable **from that same
349
+ release** so the two can never come from different ones. The bytes are hashed and compared before
350
+ anything is made runnable; a mismatch deletes the file and refuses. It never replaces a file
351
+ already at that name without `--force`, and it never puts anything on your PATH.
352
+
353
+ It prints the release, the address it came from and the hash, and says plainly what that check
354
+ does and does not prove: it proves the bytes are the bytes that release published; it proves
355
+ nothing about who published the release. The source is in the open and so is the workflow that
356
+ built it — that is the part worth checking.
357
+
358
+ ## The check a person has to pass
359
+
360
+ An API key makes the server answer; it does not stand in for somebody being there. The server
361
+ keeps that as a separate question — has anybody checked lately that a person is behind this
362
+ account — and while the answer is no, the account still works under tighter limits, with some
363
+ requests refused outright.
364
+
365
+ ```sh
366
+ nmts verify --status # is the check live, and until when?
367
+ nmts verify # prints a short code for a person to type at nmts.me, then waits
368
+ ```
369
+
370
+ `nmts verify` cannot pass the check for you, and neither can an agent running it: that is what is
371
+ being checked. It prints a code and an address, and waits until the code is used or stops working.
372
+ Ctrl-C ends the waiting and not the code.
373
+
374
+ It prints the moment the check **ends** rather than a number of days, because the window ends on a
375
+ boundary of the server's own weeks — one passed shortly before a boundary is a short one, and the
376
+ absolute moment is the only honest way to say that.
377
+
378
+ ## Names, folders and the trash
379
+
380
+ ```sh
381
+ nmts mkdir photos/2026/august # makes all three if they are missing
382
+ nmts mv report.pdf photos # `/` moves it back to the top of the drive
383
+ nmts rename report.pdf "q3 report.pdf"
384
+ nmts rm photos/2026 # to the trash, with every file under it
385
+ nmts restore photos/2026
386
+ ```
387
+
388
+ **None of these costs anything or asks anything.** A name, a folder and a parent live only in your
389
+ sealed file list — the server holds an id, a size and a time, and has no place to put a name. So
390
+ renaming and moving are invisible to NMTS, instant, and free.
391
+
392
+ `rm` never destroys: it moves one thing to the trash, where it stays restorable for thirty days.
393
+ The command that erases for good is deliberately not in this tool — a verb with no undo belongs to
394
+ a person at a browser. Trashing a folder trashes what is under it, and each file keeps its own
395
+ thirty-day clock, so restoring the folder does not resurrect something you threw away last week.
396
+
397
+ A path is matched **whole**: `photos/a.jpg` and `a.jpg` are different things, and a path that
398
+ matches two entries is refused rather than resolved to whichever came first.
399
+
400
+ If an upload is interrupted after the credits move, **running the same command again finishes it**
401
+ and costs nothing more. The sealed bytes and the reservation are written down before the money
402
+ moves, so a retry pushes exactly the blob that was bought rather than buying a second one.
403
+
404
+ ## Containers
405
+
406
+ It runs unchanged in Docker and Podman, rootless.
407
+
408
+ There is no image published anywhere, but this repository has a `Dockerfile`, so building one is a
409
+ single command. Both container tools are built and run on every push to this repository, so this is
410
+ a claim you can check rather than one you have to take.
411
+
412
+ ```sh
413
+ docker build -t nmts . # or: podman build -t nmts .
414
+ docker run --rm nmts --version
415
+ ```
416
+
417
+ The image runs as an ordinary user, and it writes what it stores to `/config` — a directory it
418
+ creates for that user, so mounting a volume there works. Mounting a volume on a path an image does
419
+ not have gets you a root-owned volume and a tool that cannot write one byte into it.
420
+
421
+ ```sh
422
+ printf '%s' "$CODE" > /tmp/nmts-code && chmod 600 /tmp/nmts-code
423
+ printf '%s' "$KEY" > /tmp/nmts-key && chmod 600 /tmp/nmts-key
424
+ docker run --rm \
425
+ -v /tmp/nmts-code:/run/secrets/nmts:ro \
426
+ -v /tmp/nmts-key:/run/secrets/api-key:ro \
427
+ -e NMTS_ACCOUNT_CODE_FILE=/run/secrets/nmts \
428
+ -e NMTS_API_KEY_FILE=/run/secrets/api-key \
429
+ nmts ls
430
+ ```
431
+
432
+ ⚠ **Naming a credential file the container does not have is a hard stop, not a fall-through.**
433
+ `NMTS_API_KEY_FILE` pointing at nothing exits 3 before any request — which is the right behaviour
434
+ and the reason both mounts are above.
435
+
436
+ ### Uploading from a container needs one more thing
437
+
438
+ The agreements this tool asks for once are a file in its config directory, and a container that is
439
+ removed takes that file with it — so a fresh container can list and download and will refuse to
440
+ upload, every time. Two ways round it, and both are ordinary:
441
+
442
+ ```sh
443
+ # either bake the agreement into the image
444
+ RUN nmts consent grant spend
445
+
446
+ # or keep the config directory outside the container
447
+ docker run --rm -v nmts-config:/config … nmts put file
448
+ ```
449
+
450
+ The image already points this tool at `/config`, so a volume mounted there is all the second one
451
+ needs. On an image of your own, `NMTS_CONFIG_DIR` moves everything this tool writes — the
452
+ agreements, the stored credentials, the once-a-day update check — to a directory you choose.
453
+ `nmts env` reports where it landed and whether that directory survives the container being removed.
454
+
455
+ **Do not put the account code in an environment variable inside a container.** The whole
456
+ environment is visible to anyone who can inspect it — `docker inspect` prints it. A variable
457
+ holding a *path* gives that reader a filename and nothing else. `NMTS_ACCOUNT_CODE_FILE` works
458
+ with `--secret` mounts, tmpfs, and ordinary bind mounts.
459
+
460
+ `nmts env` tells you which container runtime it is in, whether root here is root on the host, and
461
+ whether the directory it would write to survives the container being removed.
462
+
463
+ ## What it stops to ask about
464
+
465
+ Five things, once per machine: **spending credits**, **storing the account code unsealed**, **using
466
+ it from a plain environment variable**, **giving another account one of your files**, and
467
+ **signing with the wallet**. Each prints what would happen, what could go wrong, and the one
468
+ command that agrees.
469
+
470
+ The last of those belongs to one command: `nmts extend`, which buys more storage time for a file
471
+ that is already stored. Everything else here is paid for with credits, which NMTS issues and can
472
+ put right; that one signs a purchase on a public chain, and nobody — NMTS included — can reverse
473
+ it. That is why it has an agreement of its own rather than sharing the one for spending.
474
+
475
+ Nothing else asks *once per machine*. One command asks *every run* instead: `nmts sweep`, which
476
+ drops trash entries whose thirty days have run out. That destroys this account's copy of the key
477
+ for those files, so a grant given once would make every later sweep silent. Listing, downloading,
478
+ renaming and moving never stop for anyone.
479
+
480
+ `nmts consent` shows what has been agreed to and can take it back.
481
+
482
+ ## Serving the drive to S3 tools
483
+
484
+ `nmts s3` starts a server on this machine that speaks the S3 protocol. Point rclone, the AWS CLI, or
485
+ any backup program that already knows S3 at it, and it lists and downloads this account's files.
486
+
487
+ ```
488
+ $ nmts s3
489
+ This account's drive is being served at http://127.0.0.1:9000, to this machine only.
490
+
491
+ endpoint http://127.0.0.1:9000
492
+ bucket drive
493
+ access key id NMTS…
494
+ secret key …
495
+ ```
496
+
497
+ - **One bucket, named `drive`.** A key is the file's path without the leading slash, so
498
+ `photos/a.jpg` in the drive is `photos/a.jpg` here. Folders come back as common prefixes,
499
+ including empty ones — this drive has real folders and hiding them would describe a different
500
+ drive from the one in the browser.
501
+ - **The credentials are made when the command starts and are stored nowhere.** They stop working
502
+ when it stops.
503
+ - **It listens on 127.0.0.1, and there is no option to change that.** One signature stands between
504
+ a request and every file in the account, and the key it checks was printed on a terminal.
505
+ - **Uploading and deleting work, and both need the spending agreement.** Uploading spends credits,
506
+ so a machine that has not run `nmts consent grant spend` serves the drive read only and says so
507
+ — every write is refused with that sentence rather than answered.
508
+ - **Deleting puts a file in the trash**, where it stays recoverable for thirty days.
509
+ - **A key that already holds the SAME file is answered `200`, and nothing is sent.** What is
510
+ compared is the file's content, not its name: every upload records a hash of the plaintext,
511
+ sealed so only this account can read it, and the gateway compares the arriving bytes against it.
512
+ So a backup that runs every night pays for the files that changed and nothing for the rest.
513
+ - ⛔ **A key that holds a DIFFERENT file is refused with `409`.** This drive does not replace files:
514
+ the same name arrives as a numbered copy, so answering 200 would tell a sync tool it had updated
515
+ a file it had duplicated. Delete it first, or upload under another key. A file stored before
516
+ hashes were recorded has none to compare with, and is refused the same way with its own sentence.
517
+ - **Large files go up in pieces**, the way S3 clients send them: the pieces arrive out of order and
518
+ at the same time, and each one is checked against the hash the client signed for before it
519
+ becomes part of the file. Nothing is stored until every piece is in. ⚠ The comparison above
520
+ happens once the pieces are one file — until then there is nothing to compare — so a large file
521
+ that turns out to be unchanged is sent across the loopback and then not uploaded.
522
+ - ⚠ **The modification time is not carried across.** A file arrives with the time it was uploaded,
523
+ so a tool comparing timestamps decides an unchanged file has changed and offers it again. That
524
+ now costs nothing: the content is compared and the upload is skipped.
525
+ - **A file uploaded from another device can take five seconds to appear**, which is how long a
526
+ file list is reused before it is fetched again.
527
+
528
+ With rclone:
529
+
530
+ ```
531
+ $ rclone config create drive s3 provider=Other region=us-east-1 \
532
+ endpoint=http://127.0.0.1:9000 \
533
+ access_key_id=<the id printed above> secret_access_key=<the secret printed above>
534
+ $ rclone lsf -R drive:drive
535
+ $ rclone copy drive:drive ./somewhere
536
+ $ rclone copy --size-only ./somewhere drive:drive
537
+ ```
538
+
539
+ ## For an agent that speaks MCP
540
+
541
+ `nmts mcp` is a local MCP server: it runs on this machine, over stdin and stdout, and speaks to
542
+ whatever started it. Sign in first (`nmts login`) — it reads the account code this machine already
543
+ keeps and never takes one on a command line.
544
+
545
+ **Claude Code** and **Codex** both add it in one line, and the line is the same:
546
+
547
+ ```
548
+ $ claude mcp add nmts -- nmts mcp --out /where/files/should/land
549
+ $ codex mcp add nmts -- nmts mcp --out /where/files/should/land
550
+ ```
551
+
552
+ **opencode** has no command for it; put this in `opencode.json`:
553
+
554
+ ```json
555
+ { "mcp": { "nmts": { "type": "local", "command": ["nmts", "mcp", "--out", "/where/files/should/land"] } } }
556
+ ```
557
+
558
+ Any other client that runs a local MCP server takes the same two things — the command `nmts` and
559
+ the arguments `mcp --out <directory>`. Where it wants them written is that client's business.
560
+
561
+ It offers twenty tools — reading the account (`nmts_whoami`, `nmts_list`, `nmts_usage`,
562
+ `nmts_expiring`, `nmts_balance`, `nmts_shares`), fetching (`nmts_get`, `nmts_pull`,
563
+ `nmts_receive`), uploading (`nmts_put`, `nmts_push`), rearranging (`nmts_mkdir`, `nmts_move`,
564
+ `nmts_rename`, `nmts_mark`, `nmts_trash`, `nmts_restore`) and sharing (`nmts_public_code`,
565
+ `nmts_share`, `nmts_unshare`).
566
+
567
+ What it deliberately does **not** offer, and why each one is out:
568
+
569
+ - **Credentials and agreements** — signing in or out, making or revoking a key, granting the
570
+ agreements below. Those are yours. A surface that can grant its own permissions has none.
571
+ - **The check a person has to pass.** A machine cannot; that is what the check is for.
572
+ - **Destroying anything for good** — emptying the trash, erasing a file permanently. Putting
573
+ something in the trash *is* there, because it can be taken back.
574
+ - **Rebuilding a lost file list.** It works, but every name it recovers is a placeholder, and you
575
+ should watch that happen rather than read about it afterwards.
576
+ - **Writing your recovery files, and fetching the recovery program.** Those exist for the day this
577
+ service is not there, and they are yours to make and to keep.
578
+
579
+ Nothing it does offer can write outside the directory you name — a model asking for a path that
580
+ climbs out of it gets the file's own name inside it, or a refusal. There is nowhere in a tool
581
+ declaration to put a path on your disk, which is what keeps that true as tools are added.
582
+
583
+ The arguments a tool declares are checked before it runs, and a wrong one is refused rather than
584
+ guessed at: a `dry_run` sent as the string `"true"` is an error, not a paid upload.
585
+
586
+ Implemented directly rather than with an SDK, so it adds no dependency.
587
+
588
+ ## Letting an agent decide for itself
589
+
590
+ By default the tool asks you before anything that has not been agreed to, and an agent driving it
591
+ is told not to answer for you. Two settings change that, and both take a flag that says what it is:
592
+
593
+ ```
594
+ $ nmts mode # what is set now
595
+ $ nmts mode auto --i-accept-the-risk # the agent judges, and goes ahead
596
+ $ nmts mode skip-permissions --i-accept-the-risk # the agent goes ahead
597
+ $ nmts mode off # back to asking
598
+ ```
599
+
600
+ While one is on, **every command says so** on stderr. That is deliberate: this is the setting that
601
+ decides whether anybody is asked before credits are spent, and a setting that stops announcing
602
+ itself is one people forget they turned on.
603
+
604
+ It does not remove the agreements. Spending, wallets, sharing and where the account code may go are
605
+ still recorded one at a time, with dates, and `nmts consent` still lists them. What changes is who
606
+ may record them — with `skip-permissions` on, an agent doing it on your behalf is what you asked
607
+ for; with it off, the instructions it reads say it must not.
608
+
609
+ ## When the connection blinks
610
+
611
+ A request that could not be made — the connection refused, reset, or never established, which is
612
+ what moving between a phone's data and a wifi network looks like — is tried again, with a wait that
613
+ grows between attempts, for about twenty seconds before the failure is reported. Nothing is retried
614
+ silently: the wait is announced.
615
+
616
+ What is **not** retried, and why each one is out:
617
+
618
+ - **A refusal.** The server saying no — wrong key, no credits, not found — is an answer. Asking
619
+ again spends the wait to hear the same thing later. Most refusals also print one line naming what
620
+ to do instead; where they do not, it is because there is nothing to add.
621
+ - **A request that ran out of time.** It already had its thirty seconds, and that deadline exists so
622
+ that an agent running this in a loop is not left waiting.
623
+ - ⛔ **A write with no idempotency key.** A request that reached the server and died on the way back
624
+ looks exactly like one that never arrived, and sending it twice can spend money twice. The two
625
+ calls that pay carry a key — the server's promise that a second copy is the same request — and
626
+ those are repeated. Nothing else that writes is.
627
+
628
+ ## Networks
629
+
630
+ `--network mainnet` or `--network testnet`, or `NMTS_NETWORK`. It is never guessed: the wrong
631
+ network looks in a place your files were never stored and answers "not found" rather than "wrong
632
+ network". Against the live server the network is already known and the flag is optional; against
633
+ any other server it is required.
634
+
635
+ ## Something wrong?
636
+
637
+ **Write to `nmts@nmts.me`** — a fault, a confusing message, a missing feature, anything that got in
638
+ the way. The smallest annoyance is worth an email; most are cheap to fix and invisible from this
639
+ end. Say what you ran and what it said.
640
+
641
+ That address is for **this tool** being wrong. Questions about the service itself, and reports
642
+ about content, go through the contact desk on [nmts.me](https://nmts.me).
643
+
644
+ ## Licence
645
+
646
+ Apache-2.0. The full text is in [LICENSE](LICENSE), verbatim. It moved here from AGPL-3.0-only on
647
+ 2026-08-30; copies already held under the AGPL stay under it.
648
+
649
+ **Build on it, ship it, sell what you build with it.** Nothing is asked of you for using the
650
+ program; redistributing it carries the licence and copyright notices with it. If you still need
651
+ different terms, write to **nmts@nmts.me** and say why — see [LICENSING.md](LICENSING.md).
652
+
653
+ Code is welcome — [CONTRIBUTING.md](CONTRIBUTING.md) says how it reaches here, and the
654
+ [Contributor License Agreement](CLA.md) is what keeps the offer above true for the whole program.
655
+ Bug reports, questions and ideas are welcome too.
656
+
657
+ Copyright © 2026 needmoretruth.