@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.
- package/AGENTS.md +591 -0
- package/LICENSE +202 -0
- package/LICENSING.md +49 -0
- package/README.ko.md +641 -0
- package/README.md +657 -0
- package/dist/account-proof.js +78 -0
- package/dist/account.js +75 -0
- package/dist/api-key.js +310 -0
- package/dist/api.js +271 -0
- package/dist/args.js +137 -0
- package/dist/artifact-about.js +70 -0
- package/dist/autonomy.js +98 -0
- package/dist/code-access.js +76 -0
- package/dist/code-vault.js +233 -0
- package/dist/collision.js +133 -0
- package/dist/commands/balance.js +93 -0
- package/dist/commands/consent.js +77 -0
- package/dist/commands/create.js +356 -0
- package/dist/commands/env.js +136 -0
- package/dist/commands/expiring.js +167 -0
- package/dist/commands/extend.js +236 -0
- package/dist/commands/get.js +130 -0
- package/dist/commands/kit.js +136 -0
- package/dist/commands/listfile.js +105 -0
- package/dist/commands/login.js +335 -0
- package/dist/commands/logout.js +33 -0
- package/dist/commands/ls.js +202 -0
- package/dist/commands/marks.js +176 -0
- package/dist/commands/mcp.js +139 -0
- package/dist/commands/mode.js +54 -0
- package/dist/commands/on-collision.js +45 -0
- package/dist/commands/organise.js +296 -0
- package/dist/commands/public-code.js +89 -0
- package/dist/commands/pull.js +185 -0
- package/dist/commands/push.js +278 -0
- package/dist/commands/put.js +282 -0
- package/dist/commands/rebuild.js +160 -0
- package/dist/commands/receive.js +125 -0
- package/dist/commands/recovery-list.js +104 -0
- package/dist/commands/recovery.js +291 -0
- package/dist/commands/s3.js +241 -0
- package/dist/commands/settings.js +28 -0
- package/dist/commands/share.js +276 -0
- package/dist/commands/sweep.js +240 -0
- package/dist/commands/trash.js +288 -0
- package/dist/commands/trial.js +264 -0
- package/dist/commands/update.js +169 -0
- package/dist/commands/usage.js +84 -0
- package/dist/commands/verify.js +331 -0
- package/dist/commands/wallet.js +122 -0
- package/dist/commands/whoami.js +33 -0
- package/dist/consent.js +200 -0
- package/dist/credentials.js +311 -0
- package/dist/crypto-surface.js +54 -0
- package/dist/crypto.js +137 -0
- package/dist/download-part.js +144 -0
- package/dist/download-sink.js +213 -0
- package/dist/download.js +163 -0
- package/dist/drive-paths.js +170 -0
- package/dist/environment.js +190 -0
- package/dist/errors.js +71 -0
- package/dist/exit.js +107 -0
- package/dist/expiry.js +115 -0
- package/dist/extend-chain.js +137 -0
- package/dist/extend-plan.js +149 -0
- package/dist/extend-sign.js +130 -0
- package/dist/guards.js +15 -0
- package/dist/help.js +192 -0
- package/dist/human-check.js +54 -0
- package/dist/item-trash.js +25 -0
- package/dist/kit-file.js +133 -0
- package/dist/list-file.js +71 -0
- package/dist/list-view-find.js +71 -0
- package/dist/list-view-order.js +96 -0
- package/dist/main.js +394 -0
- package/dist/manifest-create.js +81 -0
- package/dist/manifest-write.js +244 -0
- package/dist/manifest.js +213 -0
- package/dist/mark-render.js +45 -0
- package/dist/mcp-args.js +154 -0
- package/dist/mcp-tools/context.js +50 -0
- package/dist/mcp-tools/files.js +133 -0
- package/dist/mcp-tools/organise.js +128 -0
- package/dist/mcp-tools/reads.js +89 -0
- package/dist/mcp-tools/share.js +61 -0
- package/dist/mcp.js +142 -0
- package/dist/net-retry.js +84 -0
- package/dist/network.js +43 -0
- package/dist/notice.js +45 -0
- package/dist/product.js +38 -0
- package/dist/progress.js +110 -0
- package/dist/prompt.js +145 -0
- package/dist/rebuild.js +255 -0
- package/dist/recovery-assemble.js +148 -0
- package/dist/recovery-build.js +260 -0
- package/dist/recovery-map-file.js +114 -0
- package/dist/recovery-map.js +191 -0
- package/dist/recovery-release.js +229 -0
- package/dist/recovery-seq.js +63 -0
- package/dist/recovery-source.js +143 -0
- package/dist/registration.js +83 -0
- package/dist/s3/listing.js +134 -0
- package/dist/s3/multipart.js +104 -0
- package/dist/s3/response-sink.js +41 -0
- package/dist/s3/same-file.js +117 -0
- package/dist/s3/server.js +252 -0
- package/dist/s3/sigv4.js +168 -0
- package/dist/s3/staging.js +87 -0
- package/dist/s3/xml.js +80 -0
- package/dist/safe-path.js +89 -0
- package/dist/seal.js +206 -0
- package/dist/secret-reader.js +112 -0
- package/dist/server.js +33 -0
- package/dist/session.js +49 -0
- package/dist/setup-questions.js +31 -0
- package/dist/share.js +195 -0
- package/dist/shared/lib/crypto/size-padding.js +126 -0
- package/dist/shared/lib/drive/manifest-codec.js +287 -0
- package/dist/shared/lib/drive/manifest-index.js +235 -0
- package/dist/shared/lib/drive/manifest-ops.js +267 -0
- package/dist/shared/lib/drive/manifest-settings.js +65 -0
- package/dist/shared/lib/drive/name-conflict.js +121 -0
- package/dist/shared/lib/drive/unique-name.js +48 -0
- package/dist/shared/lib/extend/epochs.js +256 -0
- package/dist/shared/lib/net/retry-budget.js +95 -0
- package/dist/shared/lib/share/shared-file-info.js +65 -0
- package/dist/shared/lib/storage-network.js +64 -0
- package/dist/shared/lib/upload/part-plan.js +28 -0
- package/dist/stdout.js +109 -0
- package/dist/trash-sweep.js +123 -0
- package/dist/units.js +19 -0
- package/dist/update-check.js +164 -0
- package/dist/update-source.js +143 -0
- package/dist/upload-api.js +98 -0
- package/dist/upload-file.js +242 -0
- package/dist/upload-price.js +117 -0
- package/dist/upload-steps.js +111 -0
- package/dist/upload-store.js +252 -0
- package/dist/upload-wire.js +25 -0
- package/dist/upload.js +294 -0
- package/dist/usage-report.js +54 -0
- package/dist/wallet-chain.js +76 -0
- package/dist/wallet.js +209 -0
- package/dist/walrus-write.js +188 -0
- package/dist/walrus.js +156 -0
- package/package.json +56 -0
- package/vendor/nmts-crypto/nmts_crypto_wasm.d.ts +469 -0
- package/vendor/nmts-crypto/nmts_crypto_wasm.js +1300 -0
- package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm +0 -0
- package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm.d.ts +55 -0
- 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.
|