@needmoretruth/nmts-cli 0.35.0 → 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 +26 -0
- package/README.ko.md +19 -21
- package/README.md +21 -22
- package/dist/account.js +2 -1
- package/dist/arg-options.d.ts +3 -1
- 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/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 +2 -1
- package/dist/commands/push.js +6 -4
- package/dist/commands/put-wallet.js +5 -3
- package/dist/commands/put.js +8 -5
- 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-hall.d.ts +4 -1
- package/dist/commands/wallet-hall.js +16 -8
- 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.d.ts +1 -1
- 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/gate.js +1 -1
- package/dist/help.js +9 -4
- 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 -49
- package/dist/index.js +20 -45
- package/dist/main.js +7 -6
- 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 +7 -0
- package/dist/risk.js +10 -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/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.js +2 -2
- package/dist/upload-wallet.js +16 -15
- package/dist/upload.js +16 -15
- package/dist/wallet-sign-seams.d.ts +12 -0
- package/dist/wallet-sign.js +10 -13
- 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 +11 -7
- package/package.json +13 -2
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// The two Platform credentials, made here: a business's signature over one request, and the
|
|
2
|
+
// delegation token it mints for one of its users.
|
|
3
|
+
//
|
|
4
|
+
// ⛔ THE SERVER HOLDS ONLY THE VERIFICATION HALF. A business makes this key pair, keeps the secret
|
|
5
|
+
// half, and registers the public half once. Nothing here is ever sent: what travels is a
|
|
6
|
+
// signature, and a signature cannot be turned back into the key that made it.
|
|
7
|
+
//
|
|
8
|
+
// ⛔ NO CURVE IS WRITTEN HERE. `@noble/curves` signs and verifies; this module decides only WHAT
|
|
9
|
+
// is signed. Every byte layout below is the one the server verifies, and each is a fixed
|
|
10
|
+
// sentence with its own context string — a signature made for one purpose must not verify as
|
|
11
|
+
// another, which is the whole reason the contexts exist.
|
|
12
|
+
//
|
|
13
|
+
// ⛔ THE BODY IS INSIDE A BUSINESS SIGNATURE, as a SHA-256 digest in lower-case hex. So a proxy
|
|
14
|
+
// that rewrote one byte of the request invalidates it, and a signature captured off one request
|
|
15
|
+
// cannot be replayed onto a different body, method or path.
|
|
16
|
+
//
|
|
17
|
+
// ⚠ THE FIELDS ARE NEWLINE-SEPARATED and none of them can contain a newline — the timestamp is
|
|
18
|
+
// digits, the method is letters, a request path carries none, and the digest is hex. Joining
|
|
19
|
+
// them without a separator would let two different requests produce the same bytes.
|
|
20
|
+
//
|
|
21
|
+
// ⚠ A TOKEN'S PAYLOAD IS SIGNED AS THE BYTES THAT TRAVEL, never as a value re-encoded on the far
|
|
22
|
+
// side: two JSON writers disagree about spacing and key order, and a receiver that re-serialised
|
|
23
|
+
// would verify some correct tokens and refuse others.
|
|
24
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
25
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
26
|
+
import { bytesToHex, randomBytes } from "@noble/hashes/utils.js";
|
|
27
|
+
import { concat, fromBase64Url, toBase64Url, utf8 } from "./bytes.js";
|
|
28
|
+
import { NmtsError } from "./errors.js";
|
|
29
|
+
/** The fixed prefix of a business signature. `1` is the format version, so a later shape is `bs2_`. */
|
|
30
|
+
export const BUSINESS_PREFIX = "nmts_bs1_";
|
|
31
|
+
/** …and of a delegation token. */
|
|
32
|
+
export const DELEGATION_PREFIX = "nmts_dt1_";
|
|
33
|
+
/** What a business signature covers, before the moment, the nonce, the method, the path and the digest. */
|
|
34
|
+
export const BUSINESS_CONTEXT = "nmts/p1/business/v1";
|
|
35
|
+
/** What a business signs over a delegation payload. */
|
|
36
|
+
export const DELEGATION_CONTEXT = "nmts/p1/delegation/v1";
|
|
37
|
+
/** What the NEW key signs over the old one when a business replaces its key. */
|
|
38
|
+
export const ROTATE_CONTEXT = "nmts/p1/rotate/v1";
|
|
39
|
+
/** Ed25519 key and signature lengths, named because a parser and a refusal both state them. */
|
|
40
|
+
export const PUBKEY_LEN = 32;
|
|
41
|
+
export const PRIVATE_KEY_LEN = 32;
|
|
42
|
+
export const SIGNATURE_LEN = 64;
|
|
43
|
+
/**
|
|
44
|
+
* The random half of a delegation payload, and of a business signature: 16 bytes each, so two
|
|
45
|
+
* credentials made in the same second for the same thing are still different strings.
|
|
46
|
+
*/
|
|
47
|
+
export const NONCE_LEN = 16;
|
|
48
|
+
/** The longest life a delegation token may be minted with — thirty days. */
|
|
49
|
+
export const DELEGATION_MAX_TTL_SECS = 2_592_000;
|
|
50
|
+
/**
|
|
51
|
+
* What a delegation token may open, by name.
|
|
52
|
+
*
|
|
53
|
+
* ⛔ THE FIRST THREE ARE AN API KEY'S OWN SCOPES, value for value. One vocabulary, so that a
|
|
54
|
+
* business asking for "read and write" asks for the number a person's key already means by it.
|
|
55
|
+
* `register` is the fourth and opens exactly one door: making the account the token names.
|
|
56
|
+
*/
|
|
57
|
+
export const SCOPE_BITS = {
|
|
58
|
+
files_read: 1,
|
|
59
|
+
files_write: 2,
|
|
60
|
+
storage_spend: 4,
|
|
61
|
+
register: 8,
|
|
62
|
+
};
|
|
63
|
+
/** Every bit a token may carry. */
|
|
64
|
+
export const SCOPE_ALL = SCOPE_BITS.files_read | SCOPE_BITS.files_write | SCOPE_BITS.storage_spend | SCOPE_BITS.register;
|
|
65
|
+
/**
|
|
66
|
+
* A new key pair, from the runtime's own random source.
|
|
67
|
+
*
|
|
68
|
+
* ⚠ The randomness is the curve library's, which is the platform's `crypto.getRandomValues`. There
|
|
69
|
+
* is no second source here: a key drawn from anything weaker is a business somebody else can
|
|
70
|
+
* speak for.
|
|
71
|
+
*/
|
|
72
|
+
export function generateBusinessKeys() {
|
|
73
|
+
const pair = ed25519.keygen();
|
|
74
|
+
return { publicKey: toBase64Url(pair.publicKey), privateKey: toBase64Url(pair.secretKey) };
|
|
75
|
+
}
|
|
76
|
+
/** The public half of a private key, so a caller never has to keep the two in step by hand. */
|
|
77
|
+
export function businessPublicKey(privateKey) {
|
|
78
|
+
return toBase64Url(ed25519.getPublicKey(secretBytes(privateKey)));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The bytes a business signature covers.
|
|
82
|
+
*
|
|
83
|
+
* `path` is the request path as it is sent, with no query string: no Platform door takes one, and
|
|
84
|
+
* a signature that did not cover a parameter the server then read would be a signature over half
|
|
85
|
+
* the request.
|
|
86
|
+
*
|
|
87
|
+
* ⚠ THE NONCE IS SIGNED AS THE TEXT THAT TRAVELS, base64url, on its own line after the moment —
|
|
88
|
+
* so the sender and the receiver agree about the field without either re-encoding it.
|
|
89
|
+
*/
|
|
90
|
+
export function businessSigningInput(ts, nonce, method, path, body) {
|
|
91
|
+
return utf8(`${BUSINESS_CONTEXT}\n${ts}\n${nonce}\n${method.toUpperCase()}\n${path}\n${bytesToHex(sha256(body))}`);
|
|
92
|
+
}
|
|
93
|
+
/** The bytes a delegation signature covers: the context, then the payload exactly as it travels. */
|
|
94
|
+
export function delegationSigningInput(payload) {
|
|
95
|
+
return concat([utf8(`${DELEGATION_CONTEXT}\n`), payload]);
|
|
96
|
+
}
|
|
97
|
+
/** The bytes the new key signs during a rotation: the context, then the key being replaced. */
|
|
98
|
+
export function rotationSigningInput(oldPublicKey) {
|
|
99
|
+
return concat([utf8(`${ROTATE_CONTEXT}\n`), exactly(oldPublicKey, PUBKEY_LEN, "public key")]);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The whole `Authorization: Bearer` value for one request to a Platform door.
|
|
103
|
+
*
|
|
104
|
+
* ⛔ ONE SIGNATURE, ONE REQUEST. The server remembers each accepted signature for the length of its
|
|
105
|
+
* clock window and refuses a second presentation of the same one, so a value from here is not a
|
|
106
|
+
* credential to keep — it is made for the request it is about and spent on it.
|
|
107
|
+
*
|
|
108
|
+
* ⛔ AND THAT IS WHY THE NONCE IS HERE. Ed25519 is deterministic and the moment is whole seconds,
|
|
109
|
+
* so two identical requests inside one second would otherwise be the same bytes — and the
|
|
110
|
+
* second of them would be refused as a replay of the first.
|
|
111
|
+
*/
|
|
112
|
+
export function signBusinessRequest(request) {
|
|
113
|
+
const ts = request.at ?? nowSeconds();
|
|
114
|
+
const nonce = request.nonce ?? toBase64Url(randomBytes(NONCE_LEN));
|
|
115
|
+
// ⚠ Checked here because the server checks it: a credential it cannot describe is one it
|
|
116
|
+
// refuses, and the refusal would arrive as an authentication failure on a correct request.
|
|
117
|
+
exactly(nonce, NONCE_LEN, "nonce");
|
|
118
|
+
const message = businessSigningInput(ts, nonce, request.method, request.path, request.body ?? new Uint8Array(0));
|
|
119
|
+
const signature = ed25519.sign(message, secretBytes(request.privateKey));
|
|
120
|
+
return `${BUSINESS_PREFIX}${request.accountId}.${ts}.${nonce}.${toBase64Url(signature)}`;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Mint one delegation token.
|
|
124
|
+
*
|
|
125
|
+
* ⛔ THE LIFE IS REFUSED HERE AS WELL AS THERE. A token longer than the ceiling is refused by the
|
|
126
|
+
* server as malformed, which reaches the business as an authentication failure on somebody
|
|
127
|
+
* else's request; refusing it where it is made names the mistake to the program that made it.
|
|
128
|
+
*/
|
|
129
|
+
export function mintDelegation(request) {
|
|
130
|
+
const scope = scopeMask(request.scope);
|
|
131
|
+
if (request.ttlSecs <= 0 || !Number.isFinite(request.ttlSecs)) {
|
|
132
|
+
throw new NmtsError("A delegation token's life must be a positive number of seconds.", {
|
|
133
|
+
exitCode: 2,
|
|
134
|
+
nextStep: "Nothing was minted. Ask for the number of seconds the token should last.",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (request.ttlSecs > DELEGATION_MAX_TTL_SECS) {
|
|
138
|
+
throw new NmtsError(`A delegation token may last at most ${DELEGATION_MAX_TTL_SECS} seconds (30 days); ${request.ttlSecs} was asked for.`, {
|
|
139
|
+
exitCode: 2,
|
|
140
|
+
nextStep: "Nothing was minted. Mint a shorter one and mint it again when it runs out — a token " +
|
|
141
|
+
"cannot be withdrawn, so its life is the only bound on it.",
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const exp = (request.at ?? nowSeconds()) + Math.floor(request.ttlSecs);
|
|
145
|
+
const nonce = request.nonce ?? toBase64Url(randomBytes(NONCE_LEN));
|
|
146
|
+
// ⚠ The nonce's shape is checked here because the server checks it: a payload it cannot describe
|
|
147
|
+
// is one it refuses, and the refusal would arrive on a user's request rather than on this call.
|
|
148
|
+
exactly(nonce, NONCE_LEN, "nonce");
|
|
149
|
+
const payload = utf8(JSON.stringify({ v: 1, b: request.business, u: request.user, exp, s: scope, n: nonce }));
|
|
150
|
+
const signature = ed25519.sign(delegationSigningInput(payload), secretBytes(request.privateKey));
|
|
151
|
+
return `${DELEGATION_PREFIX}${toBase64Url(payload)}.${toBase64Url(signature)}`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The proof the new key makes of itself when a business replaces its key.
|
|
155
|
+
*
|
|
156
|
+
* ⛔ THE OLD KEY IS IN THE MESSAGE, so this proof is about THIS replacement and cannot be lifted
|
|
157
|
+
* onto another. The request carrying it is signed by the old key, so a rotation needs both
|
|
158
|
+
* halves in one hand.
|
|
159
|
+
*/
|
|
160
|
+
export function rotationProof(oldPublicKey, newPrivateKey) {
|
|
161
|
+
return toBase64Url(ed25519.sign(rotationSigningInput(oldPublicKey), secretBytes(newPrivateKey)));
|
|
162
|
+
}
|
|
163
|
+
/** The bitmask these scope names make. */
|
|
164
|
+
export function scopeMask(scopes) {
|
|
165
|
+
let mask = 0;
|
|
166
|
+
for (const name of scopes)
|
|
167
|
+
mask |= SCOPE_BITS[name];
|
|
168
|
+
if (mask === 0) {
|
|
169
|
+
throw new NmtsError("A delegation token with no scope can open nothing.", {
|
|
170
|
+
exitCode: 2,
|
|
171
|
+
nextStep: `Nothing was minted. Name at least one of: ${Object.keys(SCOPE_BITS).join(", ")}.`,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return mask;
|
|
175
|
+
}
|
|
176
|
+
/** Does this signature hold over these bytes? Here so a caller can check its own work offline. */
|
|
177
|
+
export function signatureHolds(publicKey, message, signature) {
|
|
178
|
+
try {
|
|
179
|
+
return ed25519.verify(exactly(signature, SIGNATURE_LEN, "signature"), message, exactly(publicKey, PUBKEY_LEN, "public key"), {
|
|
180
|
+
zip215: false,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// A key or a signature that is not well formed did not verify, which is the answer asked for.
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** Unix seconds. One reading, so a signature and the value inside it cannot come from two clocks. */
|
|
189
|
+
function nowSeconds() {
|
|
190
|
+
return Math.floor(Date.now() / 1000);
|
|
191
|
+
}
|
|
192
|
+
/** The private key as bytes, refusing anything that is not one — before it is handed to the curve. */
|
|
193
|
+
function secretBytes(privateKey) {
|
|
194
|
+
return exactly(privateKey, PRIVATE_KEY_LEN, "private key");
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Base64url of exactly this many bytes, or a refusal that names the field and never the value.
|
|
198
|
+
*
|
|
199
|
+
* ⛔ THE VALUE IS NOT IN THE MESSAGE. One of the things that comes through here is a private key,
|
|
200
|
+
* and a refusal a caller logs is not a place for it.
|
|
201
|
+
*/
|
|
202
|
+
function exactly(text, length, what) {
|
|
203
|
+
let bytes;
|
|
204
|
+
try {
|
|
205
|
+
bytes = fromBase64Url(text);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
throw new NmtsError(`That ${what} is not base64url.`, {
|
|
209
|
+
exitCode: 2,
|
|
210
|
+
nextStep: `Nothing was signed. A ${what} is ${length} bytes written as base64url.`,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (bytes.length !== length) {
|
|
214
|
+
throw new NmtsError(`That ${what} is ${bytes.length} bytes; it must be ${length}.`, {
|
|
215
|
+
exitCode: 2,
|
|
216
|
+
nextStep: `Nothing was signed. Check that the right value was passed as the ${what}.`,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return bytes;
|
|
220
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export { forgetHost, host, hostIsRegistered, registerHost } from "./host.ts";
|
|
2
|
+
export type { EngineHost, Host, HostName, StateHost, ZstdHost } from "./host.ts";
|
|
3
|
+
export { hostContract } from "./host-contract.ts";
|
|
4
|
+
export { concat, fromBase64Url, fromUtf8, toBase64Url, utf8 } from "./bytes.ts";
|
|
5
|
+
export { setZstdCodec, zstdCodec, zstdContentSize } from "./shared/lib/drive/zstd.ts";
|
|
6
|
+
export type { ZstdCodec } from "./shared/lib/drive/zstd.ts";
|
|
7
|
+
export { NmtsError, NotLoggedInError, renderError } from "./errors.ts";
|
|
8
|
+
export { request, ServerError, DEFAULT_TIMEOUT_MS } from "./api.ts";
|
|
9
|
+
export type { RequestOptions, ServerRefusal } from "./api.ts";
|
|
10
|
+
export { DEFAULT_SERVER, SERVER_ENV_VAR, resolveServer } from "./server.ts";
|
|
11
|
+
export { NETWORKS, NETWORK_ENV_VAR, resolveNetwork } from "./network.ts";
|
|
12
|
+
export type { Network } from "./network.ts";
|
|
13
|
+
export { assertUsableCode, identityOf } from "./account.ts";
|
|
14
|
+
export type { AccountIdentity } from "./account.ts";
|
|
15
|
+
export { AAD, DERIVED, loadCrypto } from "./crypto.ts";
|
|
16
|
+
export type { CryptoGlue } from "./crypto.ts";
|
|
17
|
+
export { isCryptoGlue, missingExports } from "./crypto-surface.ts";
|
|
18
|
+
export { readFileList } from "./manifest.ts";
|
|
19
|
+
export type { FileList } from "./manifest.ts";
|
|
20
|
+
export { addEntry, applyManyToList, applyToList, planAddition } from "./manifest-write.ts";
|
|
21
|
+
export type { AddEntryInput, AddEntryResult, ListEditInput, ListEditResult } from "./manifest-write.ts";
|
|
22
|
+
export type { Manifest, ManifestEntry } from "./shared/lib/drive/manifest-codec.ts";
|
|
23
|
+
export { buildIndex, entryAt, folderIdFor, fullPathOf, isLive, KIND_FILE, KIND_FOLDER, namesIn, normalisePath, trashedAt, } from "./drive-paths.ts";
|
|
24
|
+
export type { FindOptions, ManifestIndex } from "./drive-paths.ts";
|
|
25
|
+
export { setTrashed } from "./item-trash.ts";
|
|
26
|
+
export { businessPublicKey, businessSigningInput, BUSINESS_CONTEXT, BUSINESS_PREFIX, delegationSigningInput, DELEGATION_CONTEXT, DELEGATION_MAX_TTL_SECS, DELEGATION_PREFIX, generateBusinessKeys, mintDelegation, NONCE_LEN, PUBKEY_LEN, rotationProof, rotationSigningInput, ROTATE_CONTEXT, SCOPE_ALL, SCOPE_BITS, scopeMask, signatureHolds, signBusinessRequest, SIGNATURE_LEN, } from "./platform-sign.ts";
|
|
27
|
+
export type { BusinessKeyPair, BusinessRequest, DelegationRequest, ScopeName } from "./platform-sign.ts";
|
|
28
|
+
export { newAccountCode, registrationProofOf } from "./registration.ts";
|
|
29
|
+
export type { RegistrationProof } from "./registration.ts";
|
|
30
|
+
export { partKeysOf, uploadFile } from "./upload-file.ts";
|
|
31
|
+
export type { FileUploadInput, FileUploadStep, PlaintextSource } from "./upload-file.ts";
|
|
32
|
+
export { createUploadApi } from "./upload-api.ts";
|
|
33
|
+
export { CREDIT_BYTES, creditsFor, partSizeFor, planAndPrice, UPLOAD_EPOCHS } from "./upload-price.ts";
|
|
34
|
+
export { clearItemRecord, clearReservation } from "./upload-store.ts";
|
|
35
|
+
export { UploadError } from "./upload-wire.ts";
|
|
36
|
+
export type { BlobProtocol, UploadApi, UploadResult, UploadStep } from "./upload-wire.ts";
|
|
37
|
+
export type { PaddingRule } from "./shared/lib/crypto/size-padding.ts";
|
|
38
|
+
export { DEFAULT_PART_BYTES } from "./seal.ts";
|
|
39
|
+
export { createBlobProtocol, readCurrentEpoch } from "./walrus-write.ts";
|
|
40
|
+
export { walletPut } from "./upload-wallet-put.ts";
|
|
41
|
+
export type { WalletPutContext, WalletPutFile, WalletPutOutcome, WalletPutReview, WalletPutSeams, } from "./upload-wallet-put.ts";
|
|
42
|
+
export { DEFAULT_UPLOAD_EPOCHS } from "./upload-wallet-plan.ts";
|
|
43
|
+
export type { PartQuote, StorageChoice, UploadBudget, WalletUploadReads } from "./upload-wallet-plan.ts";
|
|
44
|
+
export type { Spend } from "./wallet-grant.ts";
|
|
45
|
+
export { fetchFile, fetchWithKey } from "./download.ts";
|
|
46
|
+
export type { FetchedFile, FetchInput } from "./download.ts";
|
|
47
|
+
export type { PlaintextSink } from "./download-sink.ts";
|
|
48
|
+
export { AGGREGATOR_ENV_VAR, readBlob, RELAY_ENV_VAR, SUI_RPC_ENV_VAR } from "./walrus.ts";
|
|
49
|
+
export type { ReadOptions } from "./walrus.ts";
|
|
50
|
+
export { coinAmount, readBalances, walCoinType, walletAddress } from "./wallet.ts";
|
|
51
|
+
export type { ChainReader, CoinBalance, WalletBalances } from "./wallet.ts";
|
|
52
|
+
export { chainReader } from "./wallet-chain.ts";
|
|
53
|
+
export { signerAddress, signExtension, signTransfer } from "./wallet-sign.ts";
|
|
54
|
+
export type { SignTransfer } from "./wallet-sign.ts";
|
|
55
|
+
export { activeWalletOf, walletCountOf, WALLET_INDEX_LIMIT } from "./shared/lib/drive/manifest-settings.ts";
|
|
56
|
+
export { discoverWallets, WALLET_SCAN_GAP } from "./shared/lib/wallet/discover.ts";
|
|
57
|
+
export type { WalletProbe, WalletScan } from "./shared/lib/wallet/discover.ts";
|
|
58
|
+
export { hasHistory } from "./wallet-list-chain.ts";
|
|
59
|
+
export { HOME_URL, PRODUCT_NAME, SOURCE_URL, SUPPORT_EMAIL, VERSION } from "./product.ts";
|
package/dist/portable.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// The library surface that runs ANYWHERE — what `import "@needmoretruth/nmts-cli/portable"` hands
|
|
2
|
+
// a program that brought its own host.
|
|
3
|
+
//
|
|
4
|
+
// ⛔ THIS IS `index.ts` MINUS THE THINGS THAT NEED NODE, and it is the file the machine check
|
|
5
|
+
// judges. `test/portable-closure.test.ts` walks everything reachable from the built version of
|
|
6
|
+
// this file and fails on a `node:` import, a `process.`, a `Buffer.` or an `import.meta.url` —
|
|
7
|
+
// by file and by line. So this is not a promise anybody has to remember: a module that grows a
|
|
8
|
+
// dependency on Node turns this red on the day it is written.
|
|
9
|
+
//
|
|
10
|
+
// ⛔ IT REGISTERS NO HOST. Loading the engine, keeping state, reading the environment, reporting
|
|
11
|
+
// progress and expanding zstd all come from `host.ts`, and whoever imports this entry point
|
|
12
|
+
// puts one there first. `index.ts` registers the Node host on the way in, so every existing
|
|
13
|
+
// program is unaffected; the SDK's `/browser` entry registers a browser one.
|
|
14
|
+
//
|
|
15
|
+
// ⛔ WHAT IS NOT HERE, AND WHY. `credentials.ts` reads a home directory, `code-vault.ts` opens a
|
|
16
|
+
// passphrase-locked file, `fileSink`, `stdoutSink`, `fileSource` and `measureLocal` each take or
|
|
17
|
+
// give a path, and `Nmts.fromEnv` reads an environment. All of them are in `index.ts` and none
|
|
18
|
+
// of them has a meaning in a page.
|
|
19
|
+
// The host every module below reaches for. A program that imported this entry point registers one.
|
|
20
|
+
export { forgetHost, host, hostIsRegistered, registerHost } from "./host.js";
|
|
21
|
+
export { hostContract } from "./host-contract.js";
|
|
22
|
+
// Bytes to text and back, in the two spellings the format uses.
|
|
23
|
+
export { concat, fromBase64Url, fromUtf8, toBase64Url, utf8 } from "./bytes.js";
|
|
24
|
+
// The file-list codec's zstd register (NCF-3 §6.3.4). A host fills it with the encoder its runtime
|
|
25
|
+
// has; the bound a frame declares is read with `zstdContentSize` before anything is allocated.
|
|
26
|
+
export { setZstdCodec, zstdCodec, zstdContentSize } from "./shared/lib/drive/zstd.js";
|
|
27
|
+
// Errors — the one shape every failure below arrives in.
|
|
28
|
+
export { NmtsError, NotLoggedInError, renderError } from "./errors.js";
|
|
29
|
+
// The server: one request function, and what a refusal looks like.
|
|
30
|
+
export { request, ServerError, DEFAULT_TIMEOUT_MS } from "./api.js";
|
|
31
|
+
export { DEFAULT_SERVER, SERVER_ENV_VAR, resolveServer } from "./server.js";
|
|
32
|
+
export { NETWORKS, NETWORK_ENV_VAR, resolveNetwork } from "./network.js";
|
|
33
|
+
// The account: what a code derives, and the engine that derives it.
|
|
34
|
+
export { assertUsableCode, identityOf } from "./account.js";
|
|
35
|
+
export { AAD, DERIVED, loadCrypto } from "./crypto.js";
|
|
36
|
+
// The gate every engine load passes, whichever runtime found the build. A host that fetched one
|
|
37
|
+
// runs its answer through this rather than trusting it.
|
|
38
|
+
export { isCryptoGlue, missingExports } from "./crypto-surface.js";
|
|
39
|
+
// The sealed file list: reading it, editing it, and walking it by path.
|
|
40
|
+
export { readFileList } from "./manifest.js";
|
|
41
|
+
export { addEntry, applyManyToList, applyToList, planAddition } from "./manifest-write.js";
|
|
42
|
+
export { buildIndex, entryAt, folderIdFor, fullPathOf, isLive, KIND_FILE, KIND_FOLDER, namesIn, normalisePath, trashedAt, } from "./drive-paths.js";
|
|
43
|
+
export { setTrashed } from "./item-trash.js";
|
|
44
|
+
// The Platform's two credentials: a business's signature over one request, and the delegation
|
|
45
|
+
// token it mints for one of its users. Making either needs no host and no server — a business
|
|
46
|
+
// signs on its own machine, and a device that was handed a token presents it from a page — so
|
|
47
|
+
// they belong on this side of the line rather than behind the Node entry.
|
|
48
|
+
export { businessPublicKey, businessSigningInput, BUSINESS_CONTEXT, BUSINESS_PREFIX, delegationSigningInput, DELEGATION_CONTEXT, DELEGATION_MAX_TTL_SECS, DELEGATION_PREFIX, generateBusinessKeys, mintDelegation, NONCE_LEN, PUBKEY_LEN, rotationProof, rotationSigningInput, ROTATE_CONTEXT, SCOPE_ALL, SCOPE_BITS, scopeMask, signatureHolds, signBusinessRequest, SIGNATURE_LEN, } from "./platform-sign.js";
|
|
49
|
+
// What a brand-new account is registered with, derived from its NMTS key and from nothing else.
|
|
50
|
+
// ⚠ Here because a business registering a user makes the same pair the account door has always
|
|
51
|
+
// taken — a second derivation for the Platform would be a second answer to one question.
|
|
52
|
+
export { newAccountCode, registrationProofOf } from "./registration.js";
|
|
53
|
+
// Uploading: sealing here, buying storage with credits, pushing the sealed bytes.
|
|
54
|
+
export { partKeysOf, uploadFile } from "./upload-file.js";
|
|
55
|
+
export { createUploadApi } from "./upload-api.js";
|
|
56
|
+
export { CREDIT_BYTES, creditsFor, partSizeFor, planAndPrice, UPLOAD_EPOCHS } from "./upload-price.js";
|
|
57
|
+
export { clearItemRecord, clearReservation } from "./upload-store.js";
|
|
58
|
+
export { UploadError } from "./upload-wire.js";
|
|
59
|
+
export { DEFAULT_PART_BYTES } from "./seal.js";
|
|
60
|
+
export { createBlobProtocol, readCurrentEpoch } from "./walrus-write.js";
|
|
61
|
+
// Uploading paid by the account's OWN WALLET instead of credits: the same seal-buy-push-record
|
|
62
|
+
// path, priced and refused before anything is signed. `nmts put --pay wallet` is this plus a
|
|
63
|
+
// terminal; the spending ledger and the standing gift are the command's and are not here.
|
|
64
|
+
export { walletPut } from "./upload-wallet-put.js";
|
|
65
|
+
export { DEFAULT_UPLOAD_EPOCHS } from "./upload-wallet-plan.js";
|
|
66
|
+
// Downloading: fetching sealed parts from the storage network and opening them here.
|
|
67
|
+
export { fetchFile, fetchWithKey } from "./download.js";
|
|
68
|
+
export { AGGREGATOR_ENV_VAR, readBlob, RELAY_ENV_VAR, SUI_RPC_ENV_VAR } from "./walrus.js";
|
|
69
|
+
// The wallet the NMTS key derives: reading it, and signing with it.
|
|
70
|
+
export { coinAmount, readBalances, walCoinType, walletAddress } from "./wallet.js";
|
|
71
|
+
export { chainReader } from "./wallet-chain.js";
|
|
72
|
+
export { signerAddress, signExtension, signTransfer } from "./wallet-sign.js";
|
|
73
|
+
// WHICH of this key's wallets, and which of them have been used. One key derives a wallet at every
|
|
74
|
+
// index (NCF-3 §1.3), so the account's own number is read out of the sealed list and the rest is a
|
|
75
|
+
// walk. Both are here so that a library built on this package finds the SAME wallets under the same
|
|
76
|
+
// numbers as `nmts wallet list` — a second walk, or a second reading of the setting, would be a
|
|
77
|
+
// second answer to a question that has to have exactly one.
|
|
78
|
+
export { activeWalletOf, walletCountOf, WALLET_INDEX_LIMIT } from "./shared/lib/drive/manifest-settings.js";
|
|
79
|
+
export { discoverWallets, WALLET_SCAN_GAP } from "./shared/lib/wallet/discover.js";
|
|
80
|
+
export { hasHistory } from "./wallet-list-chain.js";
|
|
81
|
+
// What this package is.
|
|
82
|
+
export { HOME_URL, PRODUCT_NAME, SOURCE_URL, SUPPORT_EMAIL, VERSION } from "./product.js";
|
package/dist/product.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare const BINARY_NAME = "nmts";
|
|
|
9
9
|
* beside it. Here rather than in `main.ts` because the MCP server has to say it too, and a
|
|
10
10
|
* command importing the entry point is a cycle waiting to bite.
|
|
11
11
|
*/
|
|
12
|
-
export declare const VERSION = "0.
|
|
12
|
+
export declare const VERSION = "0.36.1";
|
|
13
13
|
/** Where the product lives, for messages that need to send somebody somewhere real. */
|
|
14
14
|
export declare const HOME_URL = "https://nmts.me";
|
|
15
15
|
/** The source, so a person holding only the built program can find what it was built from. */
|
package/dist/product.js
CHANGED
|
@@ -18,7 +18,7 @@ export const BINARY_NAME = "nmts";
|
|
|
18
18
|
* beside it. Here rather than in `main.ts` because the MCP server has to say it too, and a
|
|
19
19
|
* command importing the entry point is a cycle waiting to bite.
|
|
20
20
|
*/
|
|
21
|
-
export const VERSION = "0.
|
|
21
|
+
export const VERSION = "0.36.1";
|
|
22
22
|
/** Where the product lives, for messages that need to send somebody somewhere real. */
|
|
23
23
|
export const HOME_URL = "https://nmts.me";
|
|
24
24
|
/** The source, so a person holding only the built program can find what it was built from. */
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// This process's own stderr, as a progress reporter.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ STDERR, NOT STDOUT. Progress is not the answer, and a caller redirecting the answer to a file
|
|
4
|
+
// must not find it interleaved with percentages.
|
|
5
|
+
//
|
|
6
|
+
// ⛔ SEPARATE FROM `progress.ts` SO THE REPORTER RUNS IN A BROWSER. Everything about how often to
|
|
7
|
+
// print and what a pipe gets instead of a rewriting line is the same wherever the text goes;
|
|
8
|
+
// only this one destination is Node's.
|
|
9
|
+
export function stderrSink() {
|
|
10
|
+
return {
|
|
11
|
+
write: (text) => void process.stderr.write(text),
|
|
12
|
+
interactive: process.stderr.isTTY === true,
|
|
13
|
+
};
|
|
14
|
+
}
|
package/dist/progress.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ export interface ProgressSink {
|
|
|
4
4
|
/** True for a terminal that can rewrite its last line. */
|
|
5
5
|
interactive: boolean;
|
|
6
6
|
}
|
|
7
|
-
export declare function stderrSink(): ProgressSink;
|
|
8
7
|
/** A reporter that says nothing. What `--json` gets, and what a test gets by default. */
|
|
9
8
|
export declare function silentSink(): ProgressSink;
|
|
10
9
|
/**
|
package/dist/progress.js
CHANGED
|
@@ -7,14 +7,10 @@
|
|
|
7
7
|
//
|
|
8
8
|
// ⛔ IT NEVER WRITES TO STDOUT WHEN THE OUTPUT IS MACHINE-READABLE. `--json` promises one JSON
|
|
9
9
|
// document and nothing else; a progress line would break every parser that trusted that.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
write: (text) => void process.stderr.write(text),
|
|
15
|
-
interactive: process.stderr.isTTY === true,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
10
|
+
//
|
|
11
|
+
// ⛔ THE TERMINAL SINK IS NOT HERE. Writing to a real stderr is Node's, so it is in
|
|
12
|
+
// `progress-node.ts`; what is left runs in a page, where the same reporter drives a caller's
|
|
13
|
+
// own `onProgress`.
|
|
18
14
|
/** A reporter that says nothing. What `--json` gets, and what a test gets by default. */
|
|
19
15
|
export function silentSink() {
|
|
20
16
|
return { write: () => { }, interactive: false };
|
package/dist/redact.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
// shape and is not in this process's environment, and nothing here will find it. That is why the
|
|
20
20
|
// text a person reads before sending asks them to keep personal details out: the machine covers
|
|
21
21
|
// what a machine can recognise, and the sentence covers the rest.
|
|
22
|
+
import { host } from "./host.js";
|
|
22
23
|
/** An NMTS key is 32 data symbols and one check symbol. `crypto/src/codes.rs` is the origin. */
|
|
23
24
|
const ACCOUNT_CODE_SYMBOLS = 33;
|
|
24
25
|
/** Crockford base32, the data half: `0-9 A-H J-K M-N P-T V-Z`, either case. No `I L O U`. */
|
|
@@ -157,10 +158,10 @@ function escapeForRegExp(value) {
|
|
|
157
158
|
*/
|
|
158
159
|
export function environmentRules() {
|
|
159
160
|
const found = [];
|
|
160
|
-
for (const
|
|
161
|
+
for (const { name, value } of host().envEntries()) {
|
|
161
162
|
if (!name.startsWith("NMTS_"))
|
|
162
163
|
continue;
|
|
163
|
-
if (value
|
|
164
|
+
if (value.length < SHORTEST_ENV_VALUE)
|
|
164
165
|
continue;
|
|
165
166
|
found.push({ name, value });
|
|
166
167
|
}
|
package/dist/registration.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// secret, the key that opens the files, the file-list key, the wallet root. Two public fields
|
|
18
18
|
// are copied out and the rest is zeroed before this returns, the same discipline `identityOf`
|
|
19
19
|
// keeps and for the same reason: a live copy of that buffer is a live copy of the account.
|
|
20
|
+
import { toBase64Url } from "./bytes.js";
|
|
20
21
|
import { DERIVED, loadCrypto } from "./crypto.js";
|
|
21
22
|
import { NmtsError } from "./errors.js";
|
|
22
23
|
/**
|
|
@@ -72,8 +73,10 @@ export async function registrationProofOf(code) {
|
|
|
72
73
|
const [idFrom, idTo] = DERIVED.accountId;
|
|
73
74
|
const [secretFrom, secretTo] = DERIVED.authSecret;
|
|
74
75
|
return {
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
// ⚠ `bytes.ts` rather than `Buffer`, which is Node's alone: this module is reachable from
|
|
77
|
+
// the portable entry, where a business registers a user from wherever it runs.
|
|
78
|
+
accountId: toBase64Url(derived.slice(idFrom, idTo)),
|
|
79
|
+
authSecret: toBase64Url(derived.slice(secretFrom, secretTo)),
|
|
77
80
|
};
|
|
78
81
|
}
|
|
79
82
|
finally {
|
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.";
|
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
|
|
@@ -179,6 +187,8 @@ export function actOf(args) {
|
|
|
179
187
|
return args.publish ? "public-code.publish" : "public-code";
|
|
180
188
|
case "support":
|
|
181
189
|
return sub === "send" || sub === "reply" ? "support.send" : "support";
|
|
190
|
+
case "platform":
|
|
191
|
+
return sub === "keygen" ? "platform.keygen" : "platform";
|
|
182
192
|
case "on-collision":
|
|
183
193
|
return sub === "" ? "on-collision" : "on-collision.set";
|
|
184
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[]>;
|