@needmoretruth/nmts-cli 0.34.4 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +11 -12
- package/CHANGELOG.md +43 -0
- package/README.ko.md +33 -35
- package/README.md +21 -22
- package/dist/account.js +2 -1
- package/dist/arg-options.d.ts +169 -0
- package/dist/arg-options.js +10 -0
- package/dist/args.d.ts +2 -157
- package/dist/args.js +2 -0
- package/dist/artifact-about.d.ts +1 -1
- package/dist/autonomy.d.ts +3 -10
- package/dist/autonomy.js +15 -17
- package/dist/bytes.d.ts +15 -0
- package/dist/bytes.js +63 -0
- package/dist/collision.d.ts +5 -5
- package/dist/collision.js +19 -22
- package/dist/commands/delete-account.js +1 -1
- package/dist/commands/erase.js +1 -1
- package/dist/commands/extend.d.ts +2 -0
- package/dist/commands/extend.js +8 -1
- package/dist/commands/get.js +1 -1
- package/dist/commands/listfile.js +1 -1
- package/dist/commands/mcp.js +5 -1
- package/dist/commands/mode.js +2 -2
- package/dist/commands/on-collision.d.ts +1 -1
- package/dist/commands/on-collision.js +3 -3
- package/dist/commands/platform.d.ts +9 -0
- package/dist/commands/platform.js +124 -0
- package/dist/commands/pull.js +1 -1
- package/dist/commands/push-wallet.js +8 -1
- package/dist/commands/push.d.ts +2 -0
- package/dist/commands/push.js +6 -4
- package/dist/commands/put-payer.d.ts +8 -0
- package/dist/commands/put-payer.js +41 -0
- package/dist/commands/put-wallet.d.ts +9 -3
- package/dist/commands/put-wallet.js +108 -186
- package/dist/commands/put.d.ts +5 -7
- package/dist/commands/put.js +11 -31
- package/dist/commands/receive.js +1 -1
- package/dist/commands/settings.js +1 -1
- package/dist/commands/support.js +3 -3
- package/dist/commands/unlock.js +2 -2
- package/dist/commands/wallet-donate.d.ts +3 -0
- package/dist/commands/wallet-donate.js +6 -2
- package/dist/commands/wallet-hall.d.ts +4 -1
- package/dist/commands/wallet-hall.js +16 -8
- package/dist/commands/wallet-list.d.ts +18 -0
- package/dist/commands/wallet-list.js +116 -0
- package/dist/commands/wallet-send.d.ts +4 -0
- package/dist/commands/wallet-send.js +6 -2
- package/dist/commands/wallet-storage-ops.d.ts +4 -1
- package/dist/commands/wallet-storage-ops.js +7 -2
- package/dist/commands/wallet-swap.d.ts +3 -0
- package/dist/commands/wallet-swap.js +8 -4
- package/dist/commands/wallet-use.d.ts +7 -0
- package/dist/commands/wallet-use.js +55 -0
- package/dist/commands/wallet.d.ts +4 -0
- package/dist/commands/wallet.js +20 -8
- package/dist/crypto.d.ts +8 -5
- package/dist/crypto.js +15 -61
- package/dist/download-sink-node.d.ts +49 -0
- package/dist/download-sink-node.js +204 -0
- package/dist/download-sink.d.ts +0 -48
- package/dist/download-sink.js +6 -199
- package/dist/download.js +5 -4
- package/dist/engine-node.d.ts +19 -0
- package/dist/engine-node.js +77 -0
- package/dist/env-vars.d.ts +20 -0
- package/dist/env-vars.js +27 -0
- package/dist/extend-plan.d.ts +2 -0
- package/dist/gate.js +1 -1
- package/dist/help.js +12 -5
- package/dist/host-contract.d.ts +7 -0
- package/dist/host-contract.js +95 -0
- package/dist/host-node.d.ts +27 -0
- package/dist/host-node.js +192 -0
- package/dist/host.d.ts +99 -0
- package/dist/host.js +59 -0
- package/dist/index.d.ts +6 -40
- package/dist/index.js +20 -32
- package/dist/main.js +11 -10
- package/dist/manifest-chunk-cache.d.ts +3 -3
- package/dist/manifest-chunk-cache.js +39 -44
- package/dist/manifest-chunk-flow.js +15 -14
- package/dist/manifest-write.js +1 -1
- package/dist/manifest.d.ts +2 -2
- package/dist/manifest.js +37 -34
- package/dist/mcp-tools/tiers.js +1 -1
- package/dist/network.d.ts +1 -1
- package/dist/network.js +6 -2
- package/dist/platform-sign.d.ts +143 -0
- package/dist/platform-sign.js +220 -0
- package/dist/portable.d.ts +59 -0
- package/dist/portable.js +82 -0
- package/dist/product.d.ts +1 -1
- package/dist/product.js +1 -1
- package/dist/progress-node.d.ts +2 -0
- package/dist/progress-node.js +14 -0
- package/dist/progress.d.ts +0 -1
- package/dist/progress.js +4 -8
- package/dist/redact.js +3 -2
- package/dist/registration.js +5 -2
- package/dist/risk.d.ts +11 -0
- package/dist/risk.js +16 -0
- package/dist/run-log.d.ts +4 -4
- package/dist/run-log.js +29 -36
- package/dist/seal.js +5 -4
- package/dist/server.d.ts +1 -1
- package/dist/server.js +6 -2
- package/dist/setup-questions.js +2 -2
- package/dist/shared/lib/drive/manifest-ops.d.ts +8 -30
- package/dist/shared/lib/drive/manifest-ops.js +7 -39
- package/dist/shared/lib/drive/manifest-settings-patch.d.ts +52 -0
- package/dist/shared/lib/drive/manifest-settings-patch.js +110 -0
- package/dist/shared/lib/drive/manifest-settings.d.ts +55 -4
- package/dist/shared/lib/drive/manifest-settings.js +66 -28
- package/dist/shared/lib/wallet/discover.d.ts +54 -0
- package/dist/shared/lib/wallet/discover.js +66 -0
- package/dist/standing-tip.d.ts +3 -0
- package/dist/standing-tip.js +1 -0
- package/dist/upload-file-node.d.ts +3 -0
- package/dist/upload-file-node.js +38 -0
- package/dist/upload-file.d.ts +2 -2
- package/dist/upload-file.js +15 -45
- package/dist/upload-price-node.d.ts +8 -0
- package/dist/upload-price-node.js +37 -0
- package/dist/upload-price.d.ts +0 -8
- package/dist/upload-price.js +4 -33
- package/dist/upload-steps.js +3 -3
- package/dist/upload-store.d.ts +14 -14
- package/dist/upload-store.js +68 -75
- package/dist/upload-wallet-chain.js +3 -2
- package/dist/upload-wallet-put.d.ts +118 -0
- package/dist/upload-wallet-put.js +221 -0
- package/dist/upload-wallet.d.ts +3 -0
- package/dist/upload-wallet.js +18 -15
- package/dist/upload.js +16 -15
- package/dist/wallet-list-chain.d.ts +4 -0
- package/dist/wallet-list-chain.js +21 -0
- package/dist/wallet-pay-index.d.ts +21 -0
- package/dist/wallet-pay-index.js +68 -0
- package/dist/wallet-sign-seams.d.ts +69 -0
- package/dist/wallet-sign-seams.js +16 -0
- package/dist/wallet-sign.d.ts +3 -53
- package/dist/wallet-sign.js +22 -18
- package/dist/wallet.d.ts +13 -13
- package/dist/wallet.js +15 -15
- package/dist/walrus-write.js +3 -2
- package/dist/walrus.d.ts +1 -16
- package/dist/walrus.js +11 -22
- package/docs/commands/credits.md +1 -1
- package/docs/commands/platform.md +14 -0
- package/docs/commands/support.md +1 -1
- package/docs/commands/trash.md +1 -1
- package/docs/commands/wallet.md +26 -8
- package/package.json +13 -2
package/dist/risk.d.ts
CHANGED
|
@@ -152,6 +152,9 @@ export declare const ACTS: {
|
|
|
152
152
|
readonly mcp: {
|
|
153
153
|
readonly tier: "none";
|
|
154
154
|
};
|
|
155
|
+
readonly platform: {
|
|
156
|
+
readonly tier: "none";
|
|
157
|
+
};
|
|
155
158
|
readonly "accept-terms": {
|
|
156
159
|
readonly tier: "none";
|
|
157
160
|
};
|
|
@@ -226,6 +229,10 @@ export declare const ACTS: {
|
|
|
226
229
|
readonly tier: "medium";
|
|
227
230
|
readonly what: "Revoke an API key. Whatever used it stops working.";
|
|
228
231
|
};
|
|
232
|
+
readonly "platform.keygen": {
|
|
233
|
+
readonly tier: "medium";
|
|
234
|
+
readonly what: "Make a business key pair and write its private half to a file on this disk.";
|
|
235
|
+
};
|
|
229
236
|
readonly "public-code.publish": {
|
|
230
237
|
readonly tier: "medium";
|
|
231
238
|
readonly what: "Publish this account's public code. Publishing cannot be undone.";
|
|
@@ -344,6 +351,10 @@ export declare const ACTS: {
|
|
|
344
351
|
readonly what: "Send a gift to the developer from the wallet.";
|
|
345
352
|
readonly asksItself: true;
|
|
346
353
|
};
|
|
354
|
+
readonly "wallet.use": {
|
|
355
|
+
readonly tier: "low";
|
|
356
|
+
readonly what: "Change which of this key's wallets pays for storage.";
|
|
357
|
+
};
|
|
347
358
|
readonly "wallet.hall": {
|
|
348
359
|
readonly tier: "none";
|
|
349
360
|
};
|
package/dist/risk.js
CHANGED
|
@@ -69,6 +69,9 @@ export const ACTS = {
|
|
|
69
69
|
verify: { tier: "none" },
|
|
70
70
|
support: { tier: "none" },
|
|
71
71
|
mcp: { tier: "none" },
|
|
72
|
+
// The bare word and `platform register` only say where a business registers; what MAKES
|
|
73
|
+
// something is `platform.keygen` below.
|
|
74
|
+
platform: { tier: "none" },
|
|
72
75
|
"accept-terms": { tier: "none" },
|
|
73
76
|
// ── low ──
|
|
74
77
|
rm: { tier: "low", what: "Move these to the trash, restorable for 30 days." },
|
|
@@ -90,6 +93,11 @@ export const ACTS = {
|
|
|
90
93
|
"key.list": { tier: "none" },
|
|
91
94
|
"key.new": { tier: "medium", what: "Make a new API key for this machine." },
|
|
92
95
|
"key.revoke": { tier: "medium", what: "Revoke an API key. Whatever used it stops working." },
|
|
96
|
+
// ⛔ MEDIUM, BESIDE `key.new`, AND NOT `high`. It writes a long-lived secret to this disk, which
|
|
97
|
+
// is more than a read; but the pair opens nothing until a person registers the public half in
|
|
98
|
+
// a browser, nothing is spent, and the file is never replaced — so it is not the tier that
|
|
99
|
+
// waits for an unlock.
|
|
100
|
+
"platform.keygen": { tier: "medium", what: "Make a business key pair and write its private half to a file on this disk." },
|
|
93
101
|
"public-code.publish": { tier: "medium", what: "Publish this account's public code. Publishing cannot be undone." },
|
|
94
102
|
"losses.dismiss": { tier: "medium", what: "Put this loss notice down. It will not be shown again." },
|
|
95
103
|
// ⛔ MEDIUM AND NOT HIGH. It moves value, so it is not `low`; but it moves it INSIDE one person's
|
|
@@ -125,6 +133,10 @@ export const ACTS = {
|
|
|
125
133
|
// ⛔ READING THE HALL IS `none` AND LISTING A NAME IS `medium`. The read signs nothing and asks
|
|
126
134
|
// nobody's wallet a question; setting a name PUBLISHES a name beside an address on a public
|
|
127
135
|
// page, which is undone by one more run of the same command but cannot be unseen.
|
|
136
|
+
// ⛔ `list` READS; `use` WRITES — it changes which address every later payment leaves from, on
|
|
137
|
+
// every device. Low rather than high because it signs nothing and moves nothing, and running
|
|
138
|
+
// it again puts the old number back.
|
|
139
|
+
"wallet.use": { tier: "low", what: "Change which of this key's wallets pays for storage." },
|
|
128
140
|
"wallet.hall": { tier: "none" },
|
|
129
141
|
"wallet.hall.set": { tier: "medium", what: "Publish this name beside your wallet's address in the gift hall of fame." },
|
|
130
142
|
"wallet.storage.reshape": { tier: "high", lock: "wallet", what: "Cut or join a storage resource the wallet holds — a signed transaction.", asksItself: true },
|
|
@@ -156,6 +168,8 @@ export function actOf(args) {
|
|
|
156
168
|
// both land on the act that publishes; the bare command is a read like `wallet` itself.
|
|
157
169
|
if (sub === "hall")
|
|
158
170
|
return args.name !== undefined || args.remove ? "wallet.hall.set" : "wallet.hall";
|
|
171
|
+
if (sub === "use")
|
|
172
|
+
return "wallet.use";
|
|
159
173
|
return sub === "send" ? "wallet.send" : sub === "swap" ? "wallet.swap" : sub === "donate" ? "wallet.donate" : "wallet";
|
|
160
174
|
case "put":
|
|
161
175
|
return args.pay === "wallet" ? "put.wallet" : "put";
|
|
@@ -173,6 +187,8 @@ export function actOf(args) {
|
|
|
173
187
|
return args.publish ? "public-code.publish" : "public-code";
|
|
174
188
|
case "support":
|
|
175
189
|
return sub === "send" || sub === "reply" ? "support.send" : "support";
|
|
190
|
+
case "platform":
|
|
191
|
+
return sub === "keygen" ? "platform.keygen" : "platform";
|
|
176
192
|
case "on-collision":
|
|
177
193
|
return sub === "" ? "on-collision" : "on-collision.set";
|
|
178
194
|
case "padding":
|
package/dist/run-log.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** The one key the record lives under. On this machine that is `runs.jsonl` in the config directory. */
|
|
2
|
+
export declare const RUN_LOG_KEY = "runlog";
|
|
1
3
|
/** One request this run made, and what came back. */
|
|
2
4
|
export interface HttpEvent {
|
|
3
5
|
readonly kind: "http";
|
|
@@ -37,8 +39,6 @@ export declare const NO_RUN_LOG_ENV_VAR = "NMTS_NO_RUN_LOG";
|
|
|
37
39
|
export declare const MAX_LOG_BYTES: number;
|
|
38
40
|
/** The most runs `--attach-log` will take. */
|
|
39
41
|
export declare const MAX_ATTACHED_RUNS = 20;
|
|
40
|
-
/** Where the log lives. Inside the tool's own directory, which is already 0700. */
|
|
41
|
-
export declare function runLogPath(): string;
|
|
42
42
|
/** Has the person turned it off? */
|
|
43
43
|
export declare function runLogIsOff(): boolean;
|
|
44
44
|
/**
|
|
@@ -73,6 +73,6 @@ export declare function safeArgs(argv: readonly string[]): string[];
|
|
|
73
73
|
* Returns nothing and throws nothing: see the header. A run that could not be written is a run
|
|
74
74
|
* that is missing from a report, which is a smaller problem than a command that failed for it.
|
|
75
75
|
*/
|
|
76
|
-
export declare function recordRun(argv: readonly string[], exit: number, ms: number, now?: Date): void
|
|
76
|
+
export declare function recordRun(argv: readonly string[], exit: number, ms: number, now?: Date): Promise<void>;
|
|
77
77
|
/** The newest `count` runs, oldest first. An unreadable or missing file is no runs. */
|
|
78
|
-
export declare function readRuns(count: number): RunRecord[]
|
|
78
|
+
export declare function readRuns(count: number): Promise<RunRecord[]>;
|
package/dist/run-log.js
CHANGED
|
@@ -21,12 +21,13 @@
|
|
|
21
21
|
// `nmts get` to report a failure it did not have. Every write is inside a `try` that swallows.
|
|
22
22
|
// ⚠ There is no debug switch in this tool to say so through, so it is silent — which is why
|
|
23
23
|
// this paragraph is here rather than a line of output.
|
|
24
|
-
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
25
|
-
import { join } from "node:path";
|
|
26
24
|
import { OPTIONS_TAKING_A_VALUE } from "./args.js";
|
|
27
|
-
import {
|
|
25
|
+
import { fromUtf8, utf8 } from "./bytes.js";
|
|
26
|
+
import { host } from "./host.js";
|
|
28
27
|
import { redact } from "./redact.js";
|
|
29
28
|
import { VERSION } from "./product.js";
|
|
29
|
+
/** The one key the record lives under. On this machine that is `runs.jsonl` in the config directory. */
|
|
30
|
+
export const RUN_LOG_KEY = "runlog";
|
|
30
31
|
/** Set this to `1` and nothing is written. */
|
|
31
32
|
export const NO_RUN_LOG_ENV_VAR = "NMTS_NO_RUN_LOG";
|
|
32
33
|
/**
|
|
@@ -69,13 +70,9 @@ const HIDDEN_VALUES = {
|
|
|
69
70
|
* common and should not be given one just for this.
|
|
70
71
|
*/
|
|
71
72
|
let collected = [];
|
|
72
|
-
/** Where the log lives. Inside the tool's own directory, which is already 0700. */
|
|
73
|
-
export function runLogPath() {
|
|
74
|
-
return join(configDir(), "runs.jsonl");
|
|
75
|
-
}
|
|
76
73
|
/** Has the person turned it off? */
|
|
77
74
|
export function runLogIsOff() {
|
|
78
|
-
return
|
|
75
|
+
return host().env(NO_RUN_LOG_ENV_VAR) === "1";
|
|
79
76
|
}
|
|
80
77
|
function remember(event) {
|
|
81
78
|
if (runLogIsOff())
|
|
@@ -158,41 +155,34 @@ export function safeArgs(argv) {
|
|
|
158
155
|
}
|
|
159
156
|
return out;
|
|
160
157
|
}
|
|
161
|
-
/** Write one line, creating the file 0600 if it is not there yet. */
|
|
162
|
-
function append(line) {
|
|
163
|
-
const dir = configDir();
|
|
164
|
-
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
165
|
-
const path = runLogPath();
|
|
166
|
-
if (!existsSync(path)) {
|
|
167
|
-
writeFileSync(path, "", { mode: 0o600 });
|
|
168
|
-
if (modesAreEnforced())
|
|
169
|
-
chmodSync(path, 0o600);
|
|
170
|
-
}
|
|
171
|
-
appendFileSync(path, line);
|
|
172
|
-
if (statSync(path).size > MAX_LOG_BYTES)
|
|
173
|
-
trim(path);
|
|
174
|
-
}
|
|
175
158
|
/**
|
|
176
|
-
*
|
|
159
|
+
* Add one line, trimming the oldest away when the record has grown past its cap.
|
|
160
|
+
*
|
|
161
|
+
* ⛔ WHOLE LINES. Cutting at a byte offset leaves a half-written JSON object at the top, and a
|
|
162
|
+
* reader that meets one has to decide whether the record is corrupt or merely trimmed.
|
|
177
163
|
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
164
|
+
* ⚠ READ, ADD, WRITE, rather than an append. A host's state is bytes under a key — the only shape
|
|
165
|
+
* a browser's store and a file both have — and the record is a quarter of a megabyte at most,
|
|
166
|
+
* which is what makes rewriting it affordable at the end of a run.
|
|
180
167
|
*/
|
|
181
|
-
function
|
|
182
|
-
const
|
|
168
|
+
async function append(line) {
|
|
169
|
+
const state = host().state;
|
|
170
|
+
const held = await state.read(RUN_LOG_KEY);
|
|
171
|
+
const lines = (held === undefined ? "" : fromUtf8(held)).split("\n");
|
|
172
|
+
lines.push(line.replace(/\n$/, ""));
|
|
183
173
|
const kept = [];
|
|
184
174
|
let bytes = 0;
|
|
185
175
|
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
186
|
-
const
|
|
187
|
-
if (
|
|
176
|
+
const one = lines[i];
|
|
177
|
+
if (one === undefined || one === "")
|
|
188
178
|
continue;
|
|
189
|
-
const size =
|
|
179
|
+
const size = utf8(one).length + 1;
|
|
190
180
|
if (bytes + size > MAX_LOG_BYTES)
|
|
191
181
|
break;
|
|
192
|
-
kept.unshift(
|
|
182
|
+
kept.unshift(one);
|
|
193
183
|
bytes += size;
|
|
194
184
|
}
|
|
195
|
-
|
|
185
|
+
await state.write(RUN_LOG_KEY, utf8(kept.length === 0 ? "" : `${kept.join("\n")}\n`));
|
|
196
186
|
}
|
|
197
187
|
/**
|
|
198
188
|
* Write this run down, and forget what was collected.
|
|
@@ -200,7 +190,7 @@ function trim(path) {
|
|
|
200
190
|
* Returns nothing and throws nothing: see the header. A run that could not be written is a run
|
|
201
191
|
* that is missing from a report, which is a smaller problem than a command that failed for it.
|
|
202
192
|
*/
|
|
203
|
-
export function recordRun(argv, exit, ms, now = new Date()) {
|
|
193
|
+
export async function recordRun(argv, exit, ms, now = new Date()) {
|
|
204
194
|
if (runLogIsOff()) {
|
|
205
195
|
forgetRun();
|
|
206
196
|
return;
|
|
@@ -220,7 +210,7 @@ export function recordRun(argv, exit, ms, now = new Date()) {
|
|
|
220
210
|
};
|
|
221
211
|
forgetRun();
|
|
222
212
|
try {
|
|
223
|
-
append(`${JSON.stringify(record)}\n`);
|
|
213
|
+
await append(`${JSON.stringify(record)}\n`);
|
|
224
214
|
}
|
|
225
215
|
catch {
|
|
226
216
|
// Swallowed on purpose. See the header: the log is a convenience and the command is the work.
|
|
@@ -274,10 +264,13 @@ function asRecord(value) {
|
|
|
274
264
|
};
|
|
275
265
|
}
|
|
276
266
|
/** The newest `count` runs, oldest first. An unreadable or missing file is no runs. */
|
|
277
|
-
export function readRuns(count) {
|
|
267
|
+
export async function readRuns(count) {
|
|
278
268
|
let text;
|
|
279
269
|
try {
|
|
280
|
-
|
|
270
|
+
const held = await host().state.read(RUN_LOG_KEY);
|
|
271
|
+
if (held === undefined)
|
|
272
|
+
return [];
|
|
273
|
+
text = fromUtf8(held);
|
|
281
274
|
}
|
|
282
275
|
catch {
|
|
283
276
|
return [];
|
package/dist/seal.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// ⛔ THE PLAINTEXT NEVER LEAVES THIS PROCESS. What goes out is the NCF-3 stream; what the server
|
|
9
9
|
// is told is its LENGTH. The name, the folder and the real size are written into the account's
|
|
10
10
|
// sealed file list, which the server cannot open.
|
|
11
|
-
import {
|
|
11
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
12
|
+
import { toBase64Url } from "./bytes.js";
|
|
12
13
|
import { AAD } from "./crypto.js";
|
|
13
14
|
import { NmtsError } from "./errors.js";
|
|
14
15
|
import { chunkCount, sealedLenFor as sealedLength, } from "./shared/lib/crypto/size-padding.js";
|
|
@@ -108,8 +109,8 @@ export function fileSecrets(crypt, dataKey, contentDigest) {
|
|
|
108
109
|
const contentHashCt = crypt.envelope_seal(dataKey, encoder.encode(AAD.contentHash), contentDigest);
|
|
109
110
|
return {
|
|
110
111
|
dek,
|
|
111
|
-
dekWrapped:
|
|
112
|
-
contentHashCt:
|
|
112
|
+
dekWrapped: toBase64Url(dekWrapped),
|
|
113
|
+
contentHashCt: toBase64Url(contentHashCt),
|
|
113
114
|
};
|
|
114
115
|
}
|
|
115
116
|
/**
|
|
@@ -180,7 +181,7 @@ export async function sealFile(crypt, dataKey, plaintext) {
|
|
|
180
181
|
nextStep: "The storage network has nothing to store and would refuse the reservation.",
|
|
181
182
|
});
|
|
182
183
|
}
|
|
183
|
-
const digest =
|
|
184
|
+
const digest = sha256(plaintext);
|
|
184
185
|
const secrets = fileSecrets(crypt, dataKey, digest);
|
|
185
186
|
digest.fill(0);
|
|
186
187
|
try {
|
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const DEFAULT_SERVER = "https://nmts.me";
|
|
2
|
-
export
|
|
2
|
+
export { SERVER_ENV_VAR } from "./env-vars.ts";
|
|
3
3
|
/**
|
|
4
4
|
* Resolve the server for this run: an explicit argument, then the environment, then the default.
|
|
5
5
|
*
|
package/dist/server.js
CHANGED
|
@@ -4,9 +4,13 @@
|
|
|
4
4
|
// development against a local stack, not as a knob to tune. It is read from an argument or the
|
|
5
5
|
// environment rather than stored, so a credentials file cannot silently point a later run
|
|
6
6
|
// somewhere else than the run that wrote it.
|
|
7
|
+
import { SERVER_ENV_VAR } from "./env-vars.js";
|
|
7
8
|
import { NmtsError } from "./errors.js";
|
|
9
|
+
import { host } from "./host.js";
|
|
8
10
|
export const DEFAULT_SERVER = "https://nmts.me";
|
|
9
|
-
|
|
11
|
+
// Re-exported so every caller still finds it here; the name itself lives in a module with no
|
|
12
|
+
// imports, because `nmts --help` prints it (`env-vars.ts`).
|
|
13
|
+
export { SERVER_ENV_VAR } from "./env-vars.js";
|
|
10
14
|
/**
|
|
11
15
|
* Resolve the server for this run: an explicit argument, then the environment, then the default.
|
|
12
16
|
*
|
|
@@ -14,7 +18,7 @@ export const SERVER_ENV_VAR = "NMTS_SERVER";
|
|
|
14
18
|
* typo that lands on another scheme must stop here rather than somewhere further in.
|
|
15
19
|
*/
|
|
16
20
|
export function resolveServer(explicit) {
|
|
17
|
-
const raw = explicit ??
|
|
21
|
+
const raw = explicit ?? host().env(SERVER_ENV_VAR) ?? DEFAULT_SERVER;
|
|
18
22
|
let url;
|
|
19
23
|
try {
|
|
20
24
|
url = new URL(raw);
|
package/dist/setup-questions.js
CHANGED
|
@@ -19,13 +19,13 @@ import { promptLine, stdinIsATerminal } from "./prompt.js";
|
|
|
19
19
|
* never sees a line.
|
|
20
20
|
*/
|
|
21
21
|
export async function askAboutCollisions(say) {
|
|
22
|
-
if (hasChosen() || !stdinIsATerminal())
|
|
22
|
+
if ((await hasChosen()) || !stdinIsATerminal())
|
|
23
23
|
return;
|
|
24
24
|
say(``);
|
|
25
25
|
say(`When a file with that name is already in the drive:`);
|
|
26
26
|
say(` ${ANSWER_NUMBER.rename} ${COLLISION_MEANS.rename}`);
|
|
27
27
|
say(` ${ANSWER_NUMBER.overwrite} ${COLLISION_MEANS.overwrite}`);
|
|
28
28
|
const choice = readAnswer(await promptLine(`[${ANSWER_NUMBER.rename}] `));
|
|
29
|
-
setChoice(choice, VERSION, new Date());
|
|
29
|
+
await setChoice(choice, VERSION, new Date());
|
|
30
30
|
say(`${choice} — ${COLLISION_MEANS[choice]} Change it: ${BINARY_NAME} on-collision <rename|overwrite>`);
|
|
31
31
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ManifestEntry } from "./manifest-codec.ts";
|
|
2
2
|
/** One drive edit, in a form that can be replayed onto a newer list. */
|
|
3
3
|
export type ManifestIntent =
|
|
4
4
|
/** Insert or replace by id. Upload-commit and folder-create both land here. */
|
|
@@ -115,37 +115,15 @@ export type ManifestIntent =
|
|
|
115
115
|
*/
|
|
116
116
|
export declare function applyIntent(entries: readonly ManifestEntry[], intent: ManifestIntent): readonly ManifestEntry[];
|
|
117
117
|
/**
|
|
118
|
-
*
|
|
118
|
+
* The account settings' own vocabulary — the padding rule, one settings edit, and how an edit
|
|
119
|
+
* lands — comes back out of `manifest-settings-patch.ts`, beside the fields it names.
|
|
119
120
|
*
|
|
120
|
-
* ⛔
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
121
|
+
* ⛔ RE-EXPORTED RATHER THAN MOVED AWAY. Every caller in both packages spells it `manifest-ops`,
|
|
122
|
+
* and a settings patch IS one of this file's intents as far as they are concerned. It moved
|
|
123
|
+
* because the settings write path had grown into the file's ceiling (`check:size`), and
|
|
124
|
+
* splitting it puts each rule beside the field it is about.
|
|
124
125
|
*/
|
|
125
|
-
export type PaddingMode
|
|
126
|
-
/**
|
|
127
|
-
* One account-settings edit, as DESIRED STATE per field — never "toggle", so replaying it onto a
|
|
128
|
-
* list another device wrote lands the same answer. A default value means absence in the codec.
|
|
129
|
-
*/
|
|
130
|
-
export interface SettingsPatch {
|
|
131
|
-
developerMode?: boolean;
|
|
132
|
-
textScalePct?: number;
|
|
133
|
-
/** Which rule seals future uploads: `"padme"` = the default, `"none"` = the file's exact length. */
|
|
134
|
-
paddingMode?: PaddingMode;
|
|
135
|
-
/** Credits held back with each credit-paid upload, 0 to `DEPOSIT_MAX_CREDITS`. */
|
|
136
|
-
depositDefault?: number;
|
|
137
|
-
/** The standing tip in tenths of a percent (0 = none) and the instant its terms were agreed to (0 clears). */
|
|
138
|
-
tipTenths?: number;
|
|
139
|
-
tipConsentAt?: number;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Apply one settings patch, returning new settings. Returns the SAME reference when nothing
|
|
143
|
-
* changed, so callers can skip a save (a version bump every other device must download).
|
|
144
|
-
*
|
|
145
|
-
* A text scale is CLAMPED into the codec's bounds here — this is the one write path, so a value
|
|
146
|
-
* the slider or the typed field lets through never reaches the wire out of range.
|
|
147
|
-
*/
|
|
148
|
-
export declare function applySettingsPatch(settings: AccountSettings, patch: SettingsPatch): AccountSettings;
|
|
126
|
+
export { applySettingsPatch, type PaddingMode, type SettingsPatch, } from "./manifest-settings-patch.ts";
|
|
149
127
|
/** Replay a whole queue in order. Used to rebuild after a version conflict. */
|
|
150
128
|
export declare function applyIntents(entries: readonly ManifestEntry[], intents: readonly ManifestIntent[]): readonly ManifestEntry[];
|
|
151
129
|
/**
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Runtime import (relative + .ts — the header's node --test rule) for the bounds the patch obeys.
|
|
2
|
-
import { TEXT_SCALE_DEFAULT_PCT, TEXT_SCALE_MAX_PCT, TEXT_SCALE_MIN_PCT, } from "./manifest-codec.js";
|
|
3
|
-
import { applyDepositPatch, applyTipPatch } from "./manifest-settings.js";
|
|
4
1
|
/**
|
|
5
2
|
* Apply one intent, returning a new list. The input is never mutated: the store keeps the
|
|
6
3
|
* pre-save snapshot around to rebuild from after a version conflict.
|
|
@@ -160,44 +157,15 @@ function withShares(e, shares, at) {
|
|
|
160
157
|
return { ...e, shares, updatedAt: at };
|
|
161
158
|
}
|
|
162
159
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
160
|
+
* The account settings' own vocabulary — the padding rule, one settings edit, and how an edit
|
|
161
|
+
* lands — comes back out of `manifest-settings-patch.ts`, beside the fields it names.
|
|
165
162
|
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
163
|
+
* ⛔ RE-EXPORTED RATHER THAN MOVED AWAY. Every caller in both packages spells it `manifest-ops`,
|
|
164
|
+
* and a settings patch IS one of this file's intents as far as they are concerned. It moved
|
|
165
|
+
* because the settings write path had grown into the file's ceiling (`check:size`), and
|
|
166
|
+
* splitting it puts each rule beside the field it is about.
|
|
168
167
|
*/
|
|
169
|
-
export
|
|
170
|
-
const next = { ...settings };
|
|
171
|
-
if (patch.developerMode !== undefined) {
|
|
172
|
-
if (patch.developerMode)
|
|
173
|
-
next.developerMode = true;
|
|
174
|
-
else
|
|
175
|
-
delete next.developerMode;
|
|
176
|
-
}
|
|
177
|
-
if (patch.paddingMode !== undefined) {
|
|
178
|
-
// The default is spelled as absence, like every other field here — so two devices that both
|
|
179
|
-
// "choose the default" write the same bytes and neither bumps the list's version.
|
|
180
|
-
if (patch.paddingMode === "pow2" || patch.paddingMode === "none")
|
|
181
|
-
next.paddingMode = patch.paddingMode;
|
|
182
|
-
else
|
|
183
|
-
delete next.paddingMode;
|
|
184
|
-
}
|
|
185
|
-
if (patch.textScalePct !== undefined && Number.isFinite(patch.textScalePct)) {
|
|
186
|
-
const pct = Math.round(Math.min(TEXT_SCALE_MAX_PCT, Math.max(TEXT_SCALE_MIN_PCT, patch.textScalePct)));
|
|
187
|
-
if (pct === TEXT_SCALE_DEFAULT_PCT)
|
|
188
|
-
delete next.textScalePct;
|
|
189
|
-
else
|
|
190
|
-
next.textScalePct = pct;
|
|
191
|
-
}
|
|
192
|
-
applyDepositPatch(next, patch.depositDefault);
|
|
193
|
-
applyTipPatch(next, patch.tipTenths, patch.tipConsentAt);
|
|
194
|
-
const same = (next.developerMode === true) === (settings.developerMode === true) &&
|
|
195
|
-
next.paddingMode === settings.paddingMode && next.depositDefault === settings.depositDefault &&
|
|
196
|
-
next.textScalePct === settings.textScalePct &&
|
|
197
|
-
next.tipTenths === settings.tipTenths &&
|
|
198
|
-
next.tipConsentAt === settings.tipConsentAt;
|
|
199
|
-
return same ? settings : next;
|
|
200
|
-
}
|
|
168
|
+
export { applySettingsPatch, } from "./manifest-settings-patch.js";
|
|
201
169
|
/** Replay a whole queue in order. Used to rebuild after a version conflict. */
|
|
202
170
|
export function applyIntents(entries, intents) {
|
|
203
171
|
return intents.reduce(applyIntent, entries);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type AccountSettings } from "./manifest-settings.ts";
|
|
2
|
+
/** Folds a deposit patch into a settings copy: out-of-range is clamped, the full deposit clears. */
|
|
3
|
+
export declare function applyDepositPatch(next: AccountSettings, depositDefault?: number): void;
|
|
4
|
+
/** Folds a tip patch into a settings copy: 0 clears, above the cap is capped, fractions are rounded. */
|
|
5
|
+
export declare function applyTipPatch(next: AccountSettings, tipTenths?: number, tipConsentAt?: number): void;
|
|
6
|
+
/**
|
|
7
|
+
* Folds a wallet patch into a settings copy: out-of-range is clamped, wallet 0 and a list of one
|
|
8
|
+
* clear, and the count is left holding the paying wallet.
|
|
9
|
+
*
|
|
10
|
+
* ⛔ THE COUNT FOLLOWS THE NUMBER, in this one write path. `wallet use 7` on a list of three
|
|
11
|
+
* wallets means the person is paying from a wallet the screen would not have drawn; raising the
|
|
12
|
+
* count here is what makes the two facts agree on every device at once, rather than in whichever
|
|
13
|
+
* screen happens to notice.
|
|
14
|
+
*/
|
|
15
|
+
export declare function applyWalletPatch(next: AccountSettings, activeWallet?: number, walletCount?: number): void;
|
|
16
|
+
/**
|
|
17
|
+
* Which size-padding rule an account seals its next upload under.
|
|
18
|
+
*
|
|
19
|
+
* ⛔ DECLARED BESIDE THE FIELD THAT CARRIES IT, not in `lib/crypto/padding.ts` where the padding
|
|
20
|
+
* itself lives — `padding.ts` imports it through `manifest-ops.ts`, which re-exports it. The
|
|
21
|
+
* direction matters: this file is copied byte-for-byte into the `nmts` command-line package,
|
|
22
|
+
* and a type reaching out of it into the crypto tree would drag that whole tree along with it
|
|
23
|
+
* for the sake of two string literals.
|
|
24
|
+
*/
|
|
25
|
+
export type PaddingMode = "padme" | "pow2" | "none";
|
|
26
|
+
/**
|
|
27
|
+
* One account-settings edit, as DESIRED STATE per field — never "toggle", so replaying it onto a
|
|
28
|
+
* list another device wrote lands the same answer. A default value means absence in the codec.
|
|
29
|
+
*/
|
|
30
|
+
export interface SettingsPatch {
|
|
31
|
+
developerMode?: boolean;
|
|
32
|
+
textScalePct?: number;
|
|
33
|
+
/** Which rule seals future uploads: `"padme"` = the default, `"none"` = the file's exact length. */
|
|
34
|
+
paddingMode?: PaddingMode;
|
|
35
|
+
/** Credits held back with each credit-paid upload, 0 to `DEPOSIT_MAX_CREDITS`. */
|
|
36
|
+
depositDefault?: number;
|
|
37
|
+
/** The standing tip in tenths of a percent (0 = none) and the instant its terms were agreed to (0 clears). */
|
|
38
|
+
tipTenths?: number;
|
|
39
|
+
tipConsentAt?: number;
|
|
40
|
+
/** Which wallet pays, by index (0 = the wallet every account starts with). */
|
|
41
|
+
activeWallet?: number;
|
|
42
|
+
/** How many wallets this account's list holds. Raised to hold `activeWallet`, never lowered. */
|
|
43
|
+
walletCount?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Apply one settings patch, returning new settings. Returns the SAME reference when nothing
|
|
47
|
+
* changed, so callers can skip a save (a version bump every other device must download).
|
|
48
|
+
*
|
|
49
|
+
* A text scale is CLAMPED into the codec's bounds here — this is the one write path, so a value
|
|
50
|
+
* the slider or the typed field lets through never reaches the wire out of range.
|
|
51
|
+
*/
|
|
52
|
+
export declare function applySettingsPatch(settings: AccountSettings, patch: SettingsPatch): AccountSettings;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// What a DEVICE writes into the account settings — one edit, as desired state per field.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ APART FROM `manifest-settings.ts` BECAUSE THE TWO ANSWER DIFFERENT QUESTIONS. That file says
|
|
4
|
+
// what the bytes carry and what a value some other build wrote is worth (out of range is
|
|
5
|
+
// DROPPED — falling back to a default is always safe to read). This one says what happens when
|
|
6
|
+
// a person moves a control here (out of range is CLAMPED — the slider must not be able to write
|
|
7
|
+
// something the codec would then throw away). Mixing the two is how a rule ends up applied on
|
|
8
|
+
// one side only.
|
|
9
|
+
//
|
|
10
|
+
// ⚠ PUBLISHED — copied byte-for-byte into the `nmts` command-line package, and reached from there
|
|
11
|
+
// through `manifest-ops.ts`, which re-exports everything below.
|
|
12
|
+
import { ACTIVE_WALLET_DEFAULT, DEPOSIT_DEFAULT_CREDITS, DEPOSIT_MAX_CREDITS, TEXT_SCALE_DEFAULT_PCT, TEXT_SCALE_MAX_PCT, TEXT_SCALE_MIN_PCT, TIP_TENTHS_MAX, WALLET_COUNT_DEFAULT, WALLET_COUNT_MAX, WALLET_INDEX_LIMIT, walletCountOf, } from "./manifest-settings.js";
|
|
13
|
+
/** Folds a deposit patch into a settings copy: out-of-range is clamped, the full deposit clears. */
|
|
14
|
+
export function applyDepositPatch(next, depositDefault) {
|
|
15
|
+
if (depositDefault === undefined || !Number.isFinite(depositDefault))
|
|
16
|
+
return;
|
|
17
|
+
const credits = Math.round(Math.min(DEPOSIT_MAX_CREDITS, Math.max(0, depositDefault)));
|
|
18
|
+
if (credits === DEPOSIT_DEFAULT_CREDITS)
|
|
19
|
+
delete next.depositDefault;
|
|
20
|
+
else
|
|
21
|
+
next.depositDefault = credits;
|
|
22
|
+
}
|
|
23
|
+
/** Folds a tip patch into a settings copy: 0 clears, above the cap is capped, fractions are rounded. */
|
|
24
|
+
export function applyTipPatch(next, tipTenths, tipConsentAt) {
|
|
25
|
+
if (tipTenths !== undefined && Number.isFinite(tipTenths)) {
|
|
26
|
+
const t = Math.round(Math.min(TIP_TENTHS_MAX, Math.max(0, tipTenths)));
|
|
27
|
+
if (t === 0)
|
|
28
|
+
delete next.tipTenths;
|
|
29
|
+
else
|
|
30
|
+
next.tipTenths = t;
|
|
31
|
+
}
|
|
32
|
+
if (tipConsentAt !== undefined && Number.isFinite(tipConsentAt)) {
|
|
33
|
+
if (tipConsentAt <= 0)
|
|
34
|
+
delete next.tipConsentAt;
|
|
35
|
+
else
|
|
36
|
+
next.tipConsentAt = Math.round(tipConsentAt);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Folds a wallet patch into a settings copy: out-of-range is clamped, wallet 0 and a list of one
|
|
41
|
+
* clear, and the count is left holding the paying wallet.
|
|
42
|
+
*
|
|
43
|
+
* ⛔ THE COUNT FOLLOWS THE NUMBER, in this one write path. `wallet use 7` on a list of three
|
|
44
|
+
* wallets means the person is paying from a wallet the screen would not have drawn; raising the
|
|
45
|
+
* count here is what makes the two facts agree on every device at once, rather than in whichever
|
|
46
|
+
* screen happens to notice.
|
|
47
|
+
*/
|
|
48
|
+
export function applyWalletPatch(next, activeWallet, walletCount) {
|
|
49
|
+
if (activeWallet !== undefined && Number.isFinite(activeWallet)) {
|
|
50
|
+
const index = Math.round(Math.min(WALLET_INDEX_LIMIT - 1, Math.max(0, activeWallet)));
|
|
51
|
+
if (index === ACTIVE_WALLET_DEFAULT)
|
|
52
|
+
delete next.activeWallet;
|
|
53
|
+
else
|
|
54
|
+
next.activeWallet = index;
|
|
55
|
+
}
|
|
56
|
+
if (walletCount !== undefined && Number.isFinite(walletCount)) {
|
|
57
|
+
const count = Math.round(Math.min(WALLET_COUNT_MAX, Math.max(WALLET_COUNT_DEFAULT, walletCount)));
|
|
58
|
+
if (count === WALLET_COUNT_DEFAULT)
|
|
59
|
+
delete next.walletCount;
|
|
60
|
+
else
|
|
61
|
+
next.walletCount = count;
|
|
62
|
+
}
|
|
63
|
+
const counted = walletCountOf(next);
|
|
64
|
+
if (counted === WALLET_COUNT_DEFAULT)
|
|
65
|
+
delete next.walletCount;
|
|
66
|
+
else
|
|
67
|
+
next.walletCount = counted;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Apply one settings patch, returning new settings. Returns the SAME reference when nothing
|
|
71
|
+
* changed, so callers can skip a save (a version bump every other device must download).
|
|
72
|
+
*
|
|
73
|
+
* A text scale is CLAMPED into the codec's bounds here — this is the one write path, so a value
|
|
74
|
+
* the slider or the typed field lets through never reaches the wire out of range.
|
|
75
|
+
*/
|
|
76
|
+
export function applySettingsPatch(settings, patch) {
|
|
77
|
+
const next = { ...settings };
|
|
78
|
+
if (patch.developerMode !== undefined) {
|
|
79
|
+
if (patch.developerMode)
|
|
80
|
+
next.developerMode = true;
|
|
81
|
+
else
|
|
82
|
+
delete next.developerMode;
|
|
83
|
+
}
|
|
84
|
+
if (patch.paddingMode !== undefined) {
|
|
85
|
+
// The default is spelled as absence, like every other field here — so two devices that both
|
|
86
|
+
// "choose the default" write the same bytes and neither bumps the list's version.
|
|
87
|
+
if (patch.paddingMode === "pow2" || patch.paddingMode === "none")
|
|
88
|
+
next.paddingMode = patch.paddingMode;
|
|
89
|
+
else
|
|
90
|
+
delete next.paddingMode;
|
|
91
|
+
}
|
|
92
|
+
if (patch.textScalePct !== undefined && Number.isFinite(patch.textScalePct)) {
|
|
93
|
+
const pct = Math.round(Math.min(TEXT_SCALE_MAX_PCT, Math.max(TEXT_SCALE_MIN_PCT, patch.textScalePct)));
|
|
94
|
+
if (pct === TEXT_SCALE_DEFAULT_PCT)
|
|
95
|
+
delete next.textScalePct;
|
|
96
|
+
else
|
|
97
|
+
next.textScalePct = pct;
|
|
98
|
+
}
|
|
99
|
+
applyDepositPatch(next, patch.depositDefault);
|
|
100
|
+
applyTipPatch(next, patch.tipTenths, patch.tipConsentAt);
|
|
101
|
+
applyWalletPatch(next, patch.activeWallet, patch.walletCount);
|
|
102
|
+
const same = (next.developerMode === true) === (settings.developerMode === true) &&
|
|
103
|
+
next.paddingMode === settings.paddingMode && next.depositDefault === settings.depositDefault &&
|
|
104
|
+
next.textScalePct === settings.textScalePct &&
|
|
105
|
+
next.tipTenths === settings.tipTenths &&
|
|
106
|
+
next.tipConsentAt === settings.tipConsentAt &&
|
|
107
|
+
next.activeWallet === settings.activeWallet &&
|
|
108
|
+
next.walletCount === settings.walletCount;
|
|
109
|
+
return same ? settings : next;
|
|
110
|
+
}
|