@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/dist/main.js
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// nmts — entry point.
|
|
3
|
+
//
|
|
4
|
+
// Copyright 2026 needmoretruth
|
|
5
|
+
//
|
|
6
|
+
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
|
7
|
+
// except in compliance with the License. You may obtain a copy of the License in the LICENSE
|
|
8
|
+
// file, or at <http://www.apache.org/licenses/LICENSE-2.0>.
|
|
9
|
+
//
|
|
10
|
+
// Unless required by applicable law or agreed to in writing, software distributed under the
|
|
11
|
+
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
|
12
|
+
// either express or implied. See the License for the specific language governing permissions
|
|
13
|
+
// and limitations under the License.
|
|
14
|
+
//
|
|
15
|
+
// Other terms can be asked for — see LICENSING.md.
|
|
16
|
+
//
|
|
17
|
+
// ⛔ EVERY EXIT GOES THROUGH ONE PLACE. A tool an agent drives is read by its exit code far more
|
|
18
|
+
// often than by its output, so the codes are fixed and documented:
|
|
19
|
+
// 0 done · 1 something went wrong · 2 the command line was wrong · 3 not signed in ·
|
|
20
|
+
// 4 the command exists but could not do it · 5 waiting on the person's agreement ·
|
|
21
|
+
// 130 cancelled.
|
|
22
|
+
// ⚠ 4 IS "COULD NOT", NOT "NOT BUILT". This block used to say "the command exists but is not
|
|
23
|
+
// built", which is not what any command means by it — "no such path", "that is a file", "a
|
|
24
|
+
// folder cannot go inside itself" are all 4. `NotBuiltYetError` shares the number and carries
|
|
25
|
+
// "do not retry"; nothing collides today because nothing is announced-and-unfinished, and if
|
|
26
|
+
// that list is ever repopulated the unfinished case needs a number of its own
|
|
27
|
+
// (2026-08-23).
|
|
28
|
+
//
|
|
29
|
+
// ⛔ `run` RETURNS A CODE AND NEVER EXITS. Only the bottom of this file calls process.exit, so the
|
|
30
|
+
// whole command surface can be driven from a test without ending the test runner.
|
|
31
|
+
import { parseArgs } from "./args.js";
|
|
32
|
+
import { NmtsError, NotBuiltYetError, renderError } from "./errors.js";
|
|
33
|
+
import { endQuietlyOnClosedPipe, exitCodeFor, invokedDirectly, noteUpdateAfter } from "./exit.js";
|
|
34
|
+
import { helpText } from "./help.js";
|
|
35
|
+
import { BINARY_NAME, VERSION } from "./product.js";
|
|
36
|
+
/**
|
|
37
|
+
* Commands the help text announces but this version cannot run.
|
|
38
|
+
*
|
|
39
|
+
* ⛔ ANNOUNCED-AND-UNFINISHED IS NOT THE SAME AS UNKNOWN, and an agent needs to tell them apart:
|
|
40
|
+
* unknown means "you guessed the name wrong, try again", unfinished means "stop, this will not
|
|
41
|
+
* work however you spell it". They get different exit codes for exactly that reason.
|
|
42
|
+
*
|
|
43
|
+
* ⛔ EMPTY IS THE GOAL, NOT AN OVERSIGHT. Everything help names, this version runs.
|
|
44
|
+
*/
|
|
45
|
+
export const NOT_BUILT_YET = [];
|
|
46
|
+
export async function run(argv) {
|
|
47
|
+
const args = parseArgs(argv);
|
|
48
|
+
if (args.version) {
|
|
49
|
+
process.stdout.write(`${VERSION}\n`);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
if (args.help || args.command === null || args.command === "help") {
|
|
53
|
+
process.stdout.write(helpText(VERSION));
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
// ⛔ A MODE THAT STOPPED ANNOUNCING ITSELF IS ONE PEOPLE FORGET THEY TURNED ON, and this one
|
|
57
|
+
// decides whether anybody is asked before money is spent. So it is said on EVERY run, to
|
|
58
|
+
// stderr — stdout belongs to whatever is reading this tool's output.
|
|
59
|
+
// ⚠ `mode` itself is exempt: the command that prints the setting does not need it twice.
|
|
60
|
+
if (args.command !== "mode") {
|
|
61
|
+
const { announcement, currentMode } = await import("./autonomy.js");
|
|
62
|
+
const line = announcement(currentMode());
|
|
63
|
+
if (line !== null)
|
|
64
|
+
process.stderr.write(`${line}\n`);
|
|
65
|
+
}
|
|
66
|
+
// ⛔ EVERY COMMAND IS LOADED ONLY WHEN IT IS THE COMMAND. `put` pulls in the storage network's
|
|
67
|
+
// SDK, which is the largest thing this package can load, and importing it at the top of this
|
|
68
|
+
// file made `nmts --help` pay for it: startup went from 0.13s to 0.20s the day `put` landed.
|
|
69
|
+
// An agent runs this tool in a loop, so a fixed cost per invocation is paid thousands of
|
|
70
|
+
// times — and the cost grows with every command added, which is exactly the shape of problem
|
|
71
|
+
// that is never noticed until it is large. `check:cli-startup` measures it.
|
|
72
|
+
switch (args.command) {
|
|
73
|
+
case "login": {
|
|
74
|
+
const { login } = await import("./commands/login.js");
|
|
75
|
+
return await login({
|
|
76
|
+
server: args.server,
|
|
77
|
+
network: args.network,
|
|
78
|
+
plain: args.plain,
|
|
79
|
+
env: args.env,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
case "logout": {
|
|
83
|
+
const { logout } = await import("./commands/logout.js");
|
|
84
|
+
return logout();
|
|
85
|
+
}
|
|
86
|
+
case "whoami": {
|
|
87
|
+
const { whoami } = await import("./commands/whoami.js");
|
|
88
|
+
return await whoami({ server: args.server, network: args.network });
|
|
89
|
+
}
|
|
90
|
+
case "ls": {
|
|
91
|
+
const { ls } = await import("./commands/ls.js");
|
|
92
|
+
return await ls({
|
|
93
|
+
server: args.server,
|
|
94
|
+
network: args.network,
|
|
95
|
+
json: args.json,
|
|
96
|
+
all: args.all,
|
|
97
|
+
find: args.find,
|
|
98
|
+
sort: args.sort,
|
|
99
|
+
desc: args.desc,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
case "usage": {
|
|
103
|
+
const { usage } = await import("./commands/usage.js");
|
|
104
|
+
return await usage({ server: args.server, network: args.network, json: args.json });
|
|
105
|
+
}
|
|
106
|
+
case "balance": {
|
|
107
|
+
const { balance } = await import("./commands/balance.js");
|
|
108
|
+
return await balance({ server: args.server, network: args.network, json: args.json });
|
|
109
|
+
}
|
|
110
|
+
case "public-code": {
|
|
111
|
+
const { publicCode } = await import("./commands/public-code.js");
|
|
112
|
+
return await publicCode({
|
|
113
|
+
server: args.server,
|
|
114
|
+
network: args.network,
|
|
115
|
+
publish: args.publish,
|
|
116
|
+
json: args.json,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
case "wallet": {
|
|
120
|
+
const { wallet } = await import("./commands/wallet.js");
|
|
121
|
+
return await wallet(args.operands[0], {
|
|
122
|
+
server: args.server,
|
|
123
|
+
network: args.network,
|
|
124
|
+
json: args.json,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
case "expiring": {
|
|
128
|
+
const { expiring } = await import("./commands/expiring.js");
|
|
129
|
+
return await expiring({ server: args.server, network: args.network, json: args.json });
|
|
130
|
+
}
|
|
131
|
+
case "extend": {
|
|
132
|
+
const { extend } = await import("./commands/extend.js");
|
|
133
|
+
return await extend(args.operands[0], {
|
|
134
|
+
server: args.server,
|
|
135
|
+
network: args.network,
|
|
136
|
+
epochs: args.epochs,
|
|
137
|
+
dryRun: args.dryRun,
|
|
138
|
+
yes: args.yes,
|
|
139
|
+
json: args.json,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
case "create": {
|
|
143
|
+
const { create } = await import("./commands/create.js");
|
|
144
|
+
return await create({
|
|
145
|
+
server: args.server,
|
|
146
|
+
network: args.network,
|
|
147
|
+
out: args.out,
|
|
148
|
+
acceptTerms: args.acceptTerms,
|
|
149
|
+
acceptPrivacy: args.acceptPrivacy,
|
|
150
|
+
json: args.json,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
case "trial": {
|
|
154
|
+
const { trial } = await import("./commands/trial.js");
|
|
155
|
+
return await trial(args.operands[0], {
|
|
156
|
+
server: args.server,
|
|
157
|
+
network: args.network,
|
|
158
|
+
json: args.json,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
case "get": {
|
|
162
|
+
const { get } = await import("./commands/get.js");
|
|
163
|
+
return await get(args.operands[0], {
|
|
164
|
+
server: args.server,
|
|
165
|
+
network: args.network,
|
|
166
|
+
out: args.out,
|
|
167
|
+
force: args.force,
|
|
168
|
+
json: args.json,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
case "put": {
|
|
172
|
+
const { put } = await import("./commands/put.js");
|
|
173
|
+
return await put(args.operands[0], {
|
|
174
|
+
server: args.server,
|
|
175
|
+
network: args.network,
|
|
176
|
+
name: args.name,
|
|
177
|
+
to: args.to,
|
|
178
|
+
dryRun: args.dryRun,
|
|
179
|
+
partSize: args.partSize,
|
|
180
|
+
onCollision: args.onCollision,
|
|
181
|
+
json: args.json,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
case "push": {
|
|
185
|
+
const { push } = await import("./commands/push.js");
|
|
186
|
+
return await push(args.operands[0], {
|
|
187
|
+
server: args.server,
|
|
188
|
+
network: args.network,
|
|
189
|
+
to: args.to,
|
|
190
|
+
dryRun: args.dryRun,
|
|
191
|
+
hidden: args.hidden,
|
|
192
|
+
partSize: args.partSize,
|
|
193
|
+
onCollision: args.onCollision,
|
|
194
|
+
json: args.json,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
case "rm": {
|
|
198
|
+
const { rm } = await import("./commands/trash.js");
|
|
199
|
+
return await rm(args.operands, { server: args.server, network: args.network, json: args.json });
|
|
200
|
+
}
|
|
201
|
+
case "restore": {
|
|
202
|
+
const { restore } = await import("./commands/trash.js");
|
|
203
|
+
return await restore(args.operands, { server: args.server, network: args.network, json: args.json });
|
|
204
|
+
}
|
|
205
|
+
case "sweep": {
|
|
206
|
+
const { sweep } = await import("./commands/sweep.js");
|
|
207
|
+
return await sweep({
|
|
208
|
+
server: args.server,
|
|
209
|
+
network: args.network,
|
|
210
|
+
json: args.json,
|
|
211
|
+
yes: args.yes,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
case "rebuild": {
|
|
215
|
+
const { rebuild } = await import("./commands/rebuild.js");
|
|
216
|
+
return await rebuild({
|
|
217
|
+
server: args.server,
|
|
218
|
+
network: args.network,
|
|
219
|
+
json: args.json,
|
|
220
|
+
yes: args.yes,
|
|
221
|
+
force: args.force,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
case "listfile": {
|
|
225
|
+
const { listfile } = await import("./commands/listfile.js");
|
|
226
|
+
return await listfile({ out: args.out, force: args.force });
|
|
227
|
+
}
|
|
228
|
+
case "pull": {
|
|
229
|
+
const { pull } = await import("./commands/pull.js");
|
|
230
|
+
return await pull(args.operands[0], {
|
|
231
|
+
server: args.server,
|
|
232
|
+
network: args.network,
|
|
233
|
+
out: args.out,
|
|
234
|
+
force: args.force,
|
|
235
|
+
json: args.json,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
case "mkdir": {
|
|
239
|
+
const { mkdir } = await import("./commands/organise.js");
|
|
240
|
+
return await mkdir(args.operands[0], { server: args.server, network: args.network, json: args.json });
|
|
241
|
+
}
|
|
242
|
+
case "mv": {
|
|
243
|
+
const { mv } = await import("./commands/organise.js");
|
|
244
|
+
return await mv(args.operands, {
|
|
245
|
+
server: args.server,
|
|
246
|
+
network: args.network,
|
|
247
|
+
json: args.json,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
case "rename": {
|
|
251
|
+
const { rename } = await import("./commands/organise.js");
|
|
252
|
+
return await rename(args.operands[0], args.operands[1], {
|
|
253
|
+
server: args.server,
|
|
254
|
+
network: args.network,
|
|
255
|
+
json: args.json,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
case "star": {
|
|
259
|
+
const { star } = await import("./commands/marks.js");
|
|
260
|
+
return await star(args.operands, { server: args.server, network: args.network, json: args.json });
|
|
261
|
+
}
|
|
262
|
+
case "unstar": {
|
|
263
|
+
const { unstar } = await import("./commands/marks.js");
|
|
264
|
+
return await unstar(args.operands, { server: args.server, network: args.network, json: args.json });
|
|
265
|
+
}
|
|
266
|
+
case "pin": {
|
|
267
|
+
const { pin } = await import("./commands/marks.js");
|
|
268
|
+
return await pin(args.operands, { server: args.server, network: args.network, json: args.json });
|
|
269
|
+
}
|
|
270
|
+
case "unpin": {
|
|
271
|
+
const { unpin } = await import("./commands/marks.js");
|
|
272
|
+
return await unpin(args.operands, { server: args.server, network: args.network, json: args.json });
|
|
273
|
+
}
|
|
274
|
+
case "label": {
|
|
275
|
+
const { label } = await import("./commands/marks.js");
|
|
276
|
+
return await label(args.operands[0], args.operands.slice(1), {
|
|
277
|
+
server: args.server,
|
|
278
|
+
network: args.network,
|
|
279
|
+
json: args.json,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
case "unlabel": {
|
|
283
|
+
const { unlabel } = await import("./commands/marks.js");
|
|
284
|
+
return await unlabel(args.operands[0], args.operands.slice(1), {
|
|
285
|
+
server: args.server,
|
|
286
|
+
network: args.network,
|
|
287
|
+
json: args.json,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
case "share": {
|
|
291
|
+
const { share } = await import("./commands/share.js");
|
|
292
|
+
return await share(args.operands[0], args.operands[1], {
|
|
293
|
+
server: args.server,
|
|
294
|
+
network: args.network,
|
|
295
|
+
json: args.json,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
case "shares": {
|
|
299
|
+
const { shares } = await import("./commands/share.js");
|
|
300
|
+
return await shares({ server: args.server, network: args.network, json: args.json });
|
|
301
|
+
}
|
|
302
|
+
case "unshare": {
|
|
303
|
+
const { unshare } = await import("./commands/share.js");
|
|
304
|
+
return await unshare(args.operands[0], {
|
|
305
|
+
server: args.server,
|
|
306
|
+
network: args.network,
|
|
307
|
+
json: args.json,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
case "receive": {
|
|
311
|
+
const { receive } = await import("./commands/receive.js");
|
|
312
|
+
return await receive(args.operands[0], {
|
|
313
|
+
server: args.server,
|
|
314
|
+
network: args.network,
|
|
315
|
+
out: args.out,
|
|
316
|
+
force: args.force,
|
|
317
|
+
json: args.json,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
case "env": {
|
|
321
|
+
const { env } = await import("./commands/env.js");
|
|
322
|
+
return env({ json: args.json });
|
|
323
|
+
}
|
|
324
|
+
case "consent":
|
|
325
|
+
case "mode":
|
|
326
|
+
case "on-collision": {
|
|
327
|
+
const { runSettings } = await import("./commands/settings.js");
|
|
328
|
+
return runSettings(args.command, args);
|
|
329
|
+
}
|
|
330
|
+
case "verify": {
|
|
331
|
+
const { verify } = await import("./commands/verify.js");
|
|
332
|
+
return await verify({ server: args.server, json: args.json, status: args.status });
|
|
333
|
+
}
|
|
334
|
+
case "recovery": {
|
|
335
|
+
const { recovery } = await import("./commands/recovery.js");
|
|
336
|
+
return await recovery({ out: args.out, force: args.force, json: args.json });
|
|
337
|
+
}
|
|
338
|
+
case "recovery-list": {
|
|
339
|
+
const { recoveryList } = await import("./commands/recovery-list.js");
|
|
340
|
+
return await recoveryList({
|
|
341
|
+
server: args.server,
|
|
342
|
+
network: args.network,
|
|
343
|
+
out: args.out,
|
|
344
|
+
force: args.force,
|
|
345
|
+
json: args.json,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
case "kit": {
|
|
349
|
+
const { kit } = await import("./commands/kit.js");
|
|
350
|
+
return await kit({
|
|
351
|
+
server: args.server,
|
|
352
|
+
network: args.network,
|
|
353
|
+
out: args.out,
|
|
354
|
+
force: args.force,
|
|
355
|
+
json: args.json,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
case "update": {
|
|
359
|
+
const { update } = await import("./commands/update.js");
|
|
360
|
+
return await update({ json: args.json, dryRun: args.dryRun });
|
|
361
|
+
}
|
|
362
|
+
case "mcp": {
|
|
363
|
+
const { mcp } = await import("./commands/mcp.js");
|
|
364
|
+
return await mcp({ server: args.server, network: args.network, out: args.out });
|
|
365
|
+
}
|
|
366
|
+
case "s3": {
|
|
367
|
+
const { s3 } = await import("./commands/s3.js");
|
|
368
|
+
return await s3({ server: args.server, network: args.network, port: args.port, json: args.json });
|
|
369
|
+
}
|
|
370
|
+
default:
|
|
371
|
+
if (NOT_BUILT_YET.includes(args.command)) {
|
|
372
|
+
throw new NotBuiltYetError(`\`${args.command}\``);
|
|
373
|
+
}
|
|
374
|
+
throw new NmtsError(`Unknown command: ${args.command}`, {
|
|
375
|
+
exitCode: 2,
|
|
376
|
+
nextStep: `Run \`${BINARY_NAME} --help\` to see what this version does.`,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async function main() {
|
|
381
|
+
endQuietlyOnClosedPipe();
|
|
382
|
+
const argv = process.argv.slice(2);
|
|
383
|
+
try {
|
|
384
|
+
process.exitCode = await run(argv);
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
process.stderr.write(`${renderError(error, BINARY_NAME)}\n`);
|
|
388
|
+
process.exitCode = exitCodeFor(error);
|
|
389
|
+
}
|
|
390
|
+
await noteUpdateAfter(argv, VERSION);
|
|
391
|
+
}
|
|
392
|
+
if (invokedDirectly(import.meta.filename)) {
|
|
393
|
+
await main();
|
|
394
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Writing VERSION 1 of an account's sealed file list — the only write that builds on nothing.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ IT IS A SEPARATE DOOR FROM EVERY OTHER WRITE, AND THAT IS THE POINT. Ordinary edits read the
|
|
4
|
+
// current list, apply an intent to it and hand the server the version they built on; there is
|
|
5
|
+
// no version to build on here, so this one declares `base_seq: null` — "I believe this account
|
|
6
|
+
// has no list". The server accepts that only while none exists. So the guarantee "a rebuild
|
|
7
|
+
// never overwrites a list" is not a check this file performs and could forget: it is the shape
|
|
8
|
+
// of the request. A list that appeared while this ran comes back as a version conflict, and a
|
|
9
|
+
// conflict here is a REFUSAL rather than something to retry — retrying would mean rebuilding on
|
|
10
|
+
// top of somebody's real names.
|
|
11
|
+
//
|
|
12
|
+
// ⛔ AND THE CALLER MUST STILL LOOK FIRST. The server's refusal is the last line, not the first:
|
|
13
|
+
// reading the list before building one is what lets this tool say "this account already has a
|
|
14
|
+
// file list" without spending a listing of the whole account first.
|
|
15
|
+
import { request, ServerError } from "./api.js";
|
|
16
|
+
import { AAD, DERIVED, loadCrypto } from "./crypto.js";
|
|
17
|
+
import { NmtsError } from "./errors.js";
|
|
18
|
+
import { recordWrittenList } from "./manifest.js";
|
|
19
|
+
import { encodeManifest } from "./shared/lib/drive/manifest-codec.js";
|
|
20
|
+
/**
|
|
21
|
+
* Seal these entries as version 1 and write them, or refuse because a list already exists.
|
|
22
|
+
*
|
|
23
|
+
* ⛔ NO `prev` LINK, because there is nothing before this. Version 1 is the one version that is
|
|
24
|
+
* allowed not to name what it continued from; every version after it must, or the fork check
|
|
25
|
+
* has a hole exactly where a fork would be introduced.
|
|
26
|
+
*
|
|
27
|
+
* ⛔ NO SETTINGS EITHER. Account settings live in this blob or nowhere, and a rebuild has none to
|
|
28
|
+
* carry: they were in the list that was lost. Writing an empty set is not a loss caused here.
|
|
29
|
+
*/
|
|
30
|
+
export async function createFirstList(input, entries) {
|
|
31
|
+
const crypt = await loadCrypto();
|
|
32
|
+
const [from, to] = DERIVED.fileListKey;
|
|
33
|
+
const derived = crypt.kdf_derive(crypt.account_code_parse(input.code));
|
|
34
|
+
const key = derived.slice(from, to);
|
|
35
|
+
derived.fill(0);
|
|
36
|
+
try {
|
|
37
|
+
const body = await encodeManifest(entries, 1);
|
|
38
|
+
const sealed = crypt.envelope_seal(key, new TextEncoder().encode(AAD.fileList), body);
|
|
39
|
+
body.fill(0);
|
|
40
|
+
const ct = Buffer.from(sealed).toString("base64url");
|
|
41
|
+
let answer;
|
|
42
|
+
try {
|
|
43
|
+
answer = await request(input.server, "/v1/manifest", {
|
|
44
|
+
method: "PUT",
|
|
45
|
+
token: input.apiKey,
|
|
46
|
+
// ⛔ `null` IS THE WHOLE SAFETY DEVICE. Any number here would mean "replace the version I
|
|
47
|
+
// read", which is exactly what a rebuild must never do.
|
|
48
|
+
body: { base_seq: null, ct },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof ServerError && error.code === "VERSION_CONFLICT") {
|
|
53
|
+
throw new NmtsError("This account already has a file list, so nothing was rebuilt.", {
|
|
54
|
+
exitCode: 4,
|
|
55
|
+
nextStep: "Nothing was changed. A list appeared while this ran — another device wrote one, or " +
|
|
56
|
+
"one was there all along. Run `nmts ls` to see what it holds; a rebuild would have " +
|
|
57
|
+
"replaced its names with placeholders.",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
const seq = seqOf(answer);
|
|
63
|
+
// ⛔ ONLY NOW. Recording a version the server did not accept would leave this machine believing
|
|
64
|
+
// in a list that never existed, and then refusing the real one as a rollback.
|
|
65
|
+
await recordWrittenList(input.accountId, seq, ct);
|
|
66
|
+
return { seq };
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
key.fill(0);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function seqOf(answer) {
|
|
73
|
+
if (typeof answer === "object" && answer !== null) {
|
|
74
|
+
const seq = Reflect.get(answer, "seq");
|
|
75
|
+
if (typeof seq === "number" && Number.isSafeInteger(seq) && seq >= 1)
|
|
76
|
+
return seq;
|
|
77
|
+
}
|
|
78
|
+
throw new NmtsError("The file list was written but the server did not say which version it is now.", {
|
|
79
|
+
nextStep: "The list is saved. Run `nmts ls` to see it.",
|
|
80
|
+
});
|
|
81
|
+
}
|