@needmoretruth/nmts-cli 0.17.2 → 0.34.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 +124 -516
- package/CHANGELOG.md +98 -0
- package/README.ko.md +374 -467
- package/README.md +384 -465
- package/dist/account-proof.d.ts +29 -0
- package/dist/account-proof.js +6 -6
- package/dist/account.d.ts +18 -0
- package/dist/account.js +5 -5
- package/dist/agent-host.d.ts +71 -0
- package/dist/agent-host.js +169 -0
- package/dist/api-advice.d.ts +2 -0
- package/dist/api-advice.js +210 -0
- package/dist/api-key.d.ts +79 -0
- package/dist/api-key.js +7 -7
- package/dist/api-refusal.d.ts +14 -0
- package/dist/api-refusal.js +8 -0
- package/dist/api.d.ts +114 -0
- package/dist/api.js +59 -162
- package/dist/args.d.ts +160 -0
- package/dist/args.js +118 -30
- package/dist/artifact-about.d.ts +49 -0
- package/dist/autonomy.d.ts +39 -0
- package/dist/autonomy.js +110 -32
- package/dist/code-access.d.ts +29 -0
- package/dist/code-access.js +3 -3
- package/dist/code-vault.d.ts +38 -0
- package/dist/code-vault.js +8 -8
- package/dist/collision.d.ts +67 -0
- package/dist/collision.js +1 -1
- package/dist/commands/accept-terms.d.ts +11 -0
- package/dist/commands/accept-terms.js +106 -0
- package/dist/commands/balance.d.ts +7 -0
- package/dist/commands/balance.js +54 -1
- package/dist/commands/create-code-file.d.ts +24 -0
- package/dist/commands/create-code-file.js +93 -0
- package/dist/commands/create-link.d.ts +10 -0
- package/dist/commands/create-link.js +232 -0
- package/dist/commands/create.d.ts +20 -0
- package/dist/commands/create.js +43 -102
- package/dist/commands/credits.d.ts +9 -0
- package/dist/commands/credits.js +110 -0
- package/dist/commands/delete-account.d.ts +11 -0
- package/dist/commands/delete-account.js +63 -0
- package/dist/commands/deposit.d.ts +7 -0
- package/dist/commands/deposit.js +85 -0
- package/dist/commands/devices.d.ts +12 -0
- package/dist/commands/devices.js +144 -0
- package/dist/commands/documents.d.ts +35 -0
- package/dist/commands/documents.js +212 -0
- package/dist/commands/env.d.ts +5 -0
- package/dist/commands/env.js +10 -2
- package/dist/commands/erase.d.ts +13 -0
- package/dist/commands/erase.js +193 -0
- package/dist/commands/expiring.d.ts +19 -0
- package/dist/commands/extend.d.ts +33 -0
- package/dist/commands/extend.js +54 -15
- package/dist/commands/get.d.ts +18 -0
- package/dist/commands/key-manage.d.ts +11 -0
- package/dist/commands/key-manage.js +162 -0
- package/dist/commands/key.d.ts +28 -0
- package/dist/commands/key.js +249 -0
- package/dist/commands/kit.d.ts +11 -0
- package/dist/commands/kit.js +7 -7
- package/dist/commands/listfile.d.ts +13 -0
- package/dist/commands/listfile.js +12 -5
- package/dist/commands/login.d.ts +18 -0
- package/dist/commands/login.js +19 -19
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +1 -1
- package/dist/commands/losses.d.ts +12 -0
- package/dist/commands/losses.js +180 -0
- package/dist/commands/ls.d.ts +23 -0
- package/dist/commands/marks-dispatch.d.ts +2 -0
- package/dist/commands/marks-dispatch.js +51 -0
- package/dist/commands/marks.d.ts +46 -0
- package/dist/commands/marks.js +80 -0
- package/dist/commands/mcp.d.ts +24 -0
- package/dist/commands/mcp.js +70 -5
- package/dist/commands/mode.d.ts +8 -0
- package/dist/commands/mode.js +60 -24
- package/dist/commands/on-collision.d.ts +6 -0
- package/dist/commands/organise.d.ts +46 -0
- package/dist/commands/padding.d.ts +7 -0
- package/dist/commands/padding.js +114 -0
- package/dist/commands/public-code.d.ts +9 -0
- package/dist/commands/public-code.js +8 -8
- package/dist/commands/pull.d.ts +27 -0
- package/dist/commands/push-wallet.d.ts +19 -0
- package/dist/commands/push-wallet.js +69 -0
- package/dist/commands/push.d.ts +47 -0
- package/dist/commands/push.js +37 -7
- package/dist/commands/put-wallet.d.ts +77 -0
- package/dist/commands/put-wallet.js +316 -0
- package/dist/commands/put.d.ts +56 -0
- package/dist/commands/put.js +53 -6
- package/dist/commands/rebuild.d.ts +18 -0
- package/dist/commands/rebuild.js +108 -10
- package/dist/commands/receive.d.ts +24 -0
- package/dist/commands/recovery-dispatch.d.ts +2 -0
- package/dist/commands/recovery-dispatch.js +34 -0
- package/dist/commands/recovery-list.d.ts +11 -0
- package/dist/commands/recovery-list.js +6 -6
- package/dist/commands/recovery.d.ts +25 -0
- package/dist/commands/recovery.js +2 -2
- package/dist/commands/rollback.d.ts +9 -0
- package/dist/commands/rollback.js +118 -0
- package/dist/commands/s3.d.ts +23 -0
- package/dist/commands/s3.js +6 -6
- package/dist/commands/settings.d.ts +5 -0
- package/dist/commands/settings.js +21 -6
- package/dist/commands/share-dispatch.d.ts +2 -0
- package/dist/commands/share-dispatch.js +46 -0
- package/dist/commands/share.d.ts +14 -0
- package/dist/commands/share.js +13 -3
- package/dist/commands/shares-sent.d.ts +18 -0
- package/dist/commands/shares-sent.js +95 -0
- package/dist/commands/support.d.ts +24 -0
- package/dist/commands/support.js +321 -0
- package/dist/commands/sweep.d.ts +11 -0
- package/dist/commands/tip.d.ts +11 -0
- package/dist/commands/tip.js +86 -0
- package/dist/commands/trash.d.ts +17 -0
- package/dist/commands/trash.js +1 -1
- package/dist/commands/trial.d.ts +7 -0
- package/dist/commands/trial.js +16 -18
- package/dist/commands/unlock.d.ts +16 -0
- package/dist/commands/unlock.js +159 -0
- package/dist/commands/update.d.ts +42 -0
- package/dist/commands/update.js +1 -1
- package/dist/commands/usage.d.ts +8 -0
- package/dist/commands/verify.d.ts +18 -0
- package/dist/commands/verify.js +10 -6
- package/dist/commands/wallet-activity.d.ts +26 -0
- package/dist/commands/wallet-activity.js +117 -0
- package/dist/commands/wallet-donate.d.ts +26 -0
- package/dist/commands/wallet-donate.js +163 -0
- package/dist/commands/wallet-hall.d.ts +35 -0
- package/dist/commands/wallet-hall.js +214 -0
- package/dist/commands/wallet-send.d.ts +22 -0
- package/dist/commands/wallet-send.js +153 -0
- package/dist/commands/wallet-storage-ops.d.ts +24 -0
- package/dist/commands/wallet-storage-ops.js +215 -0
- package/dist/commands/wallet-storage.d.ts +18 -0
- package/dist/commands/wallet-storage.js +95 -0
- package/dist/commands/wallet-swap.d.ts +32 -0
- package/dist/commands/wallet-swap.js +287 -0
- package/dist/commands/wallet.d.ts +36 -0
- package/dist/commands/wallet.js +57 -15
- package/dist/commands/whoami.d.ts +10 -0
- package/dist/commands/whoami.js +24 -6
- package/dist/consent.d.ts +140 -0
- package/dist/consent.js +89 -30
- package/dist/credentials.d.ts +146 -0
- package/dist/credentials.js +10 -10
- package/dist/crypto-surface.d.ts +197 -0
- package/dist/crypto.d.ts +81 -0
- package/dist/crypto.js +13 -0
- package/dist/deposit.d.ts +25 -0
- package/dist/deposit.js +78 -0
- package/dist/download-part.d.ts +40 -0
- package/dist/download-sink.d.ts +76 -0
- package/dist/download.d.ts +79 -0
- package/dist/download.js +3 -3
- package/dist/drive-paths.d.ts +73 -0
- package/dist/environment.d.ts +62 -0
- package/dist/environment.js +29 -9
- package/dist/errors.d.ts +22 -0
- package/dist/errors.js +3 -3
- package/dist/exit.d.ts +44 -0
- package/dist/expiry.d.ts +58 -0
- package/dist/extend-budget.d.ts +35 -0
- package/dist/extend-budget.js +81 -0
- package/dist/extend-chain.d.ts +43 -0
- package/dist/extend-chain.js +40 -3
- package/dist/extend-plan.d.ts +148 -0
- package/dist/extend-plan.js +1 -1
- package/dist/gate.d.ts +18 -0
- package/dist/gate.js +100 -0
- package/dist/guards.d.ts +2 -0
- package/dist/help.d.ts +20 -0
- package/dist/help.js +199 -33
- package/dist/human-check.d.ts +23 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +49 -0
- package/dist/item-trash.d.ts +1 -0
- package/dist/kit-file.d.ts +56 -0
- package/dist/kit-file.js +10 -10
- package/dist/list-file.d.ts +88 -0
- package/dist/list-file.js +90 -7
- package/dist/list-view-find.d.ts +15 -0
- package/dist/list-view-order.d.ts +32 -0
- package/dist/main.d.ts +12 -0
- package/dist/main.js +112 -113
- package/dist/manifest-chunk-cache.d.ts +14 -0
- package/dist/manifest-chunk-cache.js +103 -0
- package/dist/manifest-chunk-flow.d.ts +81 -0
- package/dist/manifest-chunk-flow.js +307 -0
- package/dist/manifest-create.d.ts +23 -0
- package/dist/manifest-create.js +26 -30
- package/dist/manifest-write.d.ts +161 -0
- package/dist/manifest-write.js +42 -33
- package/dist/manifest.d.ts +82 -0
- package/dist/manifest.js +69 -21
- package/dist/mark-render.d.ts +26 -0
- package/dist/mcp-args.d.ts +17 -0
- package/dist/mcp-ask.d.ts +43 -0
- package/dist/mcp-ask.js +100 -0
- package/dist/mcp-tools/context.d.ts +46 -0
- package/dist/mcp-tools/credits.d.ts +3 -0
- package/dist/mcp-tools/credits.js +61 -0
- package/dist/mcp-tools/files.d.ts +3 -0
- package/dist/mcp-tools/files.js +34 -0
- package/dist/mcp-tools/organise.d.ts +3 -0
- package/dist/mcp-tools/organise.js +82 -2
- package/dist/mcp-tools/reads.d.ts +3 -0
- package/dist/mcp-tools/reads.js +132 -4
- package/dist/mcp-tools/share.d.ts +3 -0
- package/dist/mcp-tools/share.js +26 -7
- package/dist/mcp-tools/support.d.ts +3 -0
- package/dist/mcp-tools/support.js +140 -0
- package/dist/mcp-tools/tiers.d.ts +31 -0
- package/dist/mcp-tools/tiers.js +147 -0
- package/dist/mcp.d.ts +80 -0
- package/dist/mcp.js +110 -9
- package/dist/net-retry.d.ts +38 -0
- package/dist/network.d.ts +10 -0
- package/dist/notice.d.ts +7 -0
- package/dist/notice.js +7 -7
- package/dist/product.d.ts +29 -0
- package/dist/product.js +1 -1
- package/dist/progress.d.ts +37 -0
- package/dist/prompt.d.ts +31 -0
- package/dist/prompt.js +1 -1
- package/dist/rebuild-key-check.d.ts +26 -0
- package/dist/rebuild-key-check.js +90 -0
- package/dist/rebuild.d.ts +87 -0
- package/dist/rebuild.js +26 -4
- package/dist/recovery-assemble.d.ts +40 -0
- package/dist/recovery-assemble.js +1 -1
- package/dist/recovery-build.d.ts +40 -0
- package/dist/recovery-map-file.d.ts +53 -0
- package/dist/recovery-map-file.js +4 -4
- package/dist/recovery-map.d.ts +197 -0
- package/dist/recovery-release.d.ts +104 -0
- package/dist/recovery-seq.d.ts +10 -0
- package/dist/recovery-source.d.ts +50 -0
- package/dist/redact.d.ts +70 -0
- package/dist/redact.js +212 -0
- package/dist/registration.d.ts +30 -0
- package/dist/registration.js +5 -5
- package/dist/risk.d.ts +380 -0
- package/dist/risk.js +206 -0
- package/dist/run-log.d.ts +78 -0
- package/dist/run-log.js +301 -0
- package/dist/s3/listing.d.ts +47 -0
- package/dist/s3/multipart.d.ts +21 -0
- package/dist/s3/response-sink.d.ts +8 -0
- package/dist/s3/same-file.d.ts +54 -0
- package/dist/s3/server.d.ts +58 -0
- package/dist/s3/server.js +2 -2
- package/dist/s3/sigv4.d.ts +46 -0
- package/dist/s3/sigv4.js +1 -1
- package/dist/s3/staging.d.ts +10 -0
- package/dist/s3/xml.d.ts +28 -0
- package/dist/safe-path.d.ts +41 -0
- package/dist/seal.d.ts +120 -0
- package/dist/secret-reader.d.ts +28 -0
- package/dist/server.d.ts +9 -0
- package/dist/session.d.ts +32 -0
- package/dist/session.js +1 -1
- package/dist/setup-questions.d.ts +7 -0
- package/dist/share.d.ts +108 -0
- package/dist/share.js +1 -1
- package/dist/shared/lib/crypto/size-padding.d.ts +55 -0
- package/dist/shared/lib/crypto/size-padding.js +5 -0
- package/dist/shared/lib/drive/manifest-chunks.d.ts +95 -0
- package/dist/shared/lib/drive/manifest-chunks.js +252 -0
- package/dist/shared/lib/drive/manifest-codec.d.ts +214 -0
- package/dist/shared/lib/drive/manifest-codec.js +7 -7
- package/dist/shared/lib/drive/manifest-index.d.ts +98 -0
- package/dist/shared/lib/drive/manifest-ops.d.ts +167 -0
- package/dist/shared/lib/drive/manifest-ops.js +9 -4
- package/dist/shared/lib/drive/manifest-pack.d.ts +65 -0
- package/dist/shared/lib/drive/manifest-pack.js +287 -0
- package/dist/shared/lib/drive/manifest-settings.d.ts +113 -0
- package/dist/shared/lib/drive/manifest-settings.js +87 -6
- package/dist/shared/lib/drive/name-conflict.d.ts +76 -0
- package/dist/shared/lib/drive/rebuild-verify.d.ts +73 -0
- package/dist/shared/lib/drive/rebuild-verify.js +112 -0
- package/dist/shared/lib/drive/unique-name.d.ts +9 -0
- package/dist/shared/lib/drive/zstd.d.ts +27 -0
- package/dist/shared/lib/drive/zstd.js +74 -0
- package/dist/shared/lib/extend/epochs.d.ts +205 -0
- package/dist/shared/lib/net/retry-budget.d.ts +79 -0
- package/dist/shared/lib/share/shared-file-info.d.ts +21 -0
- package/dist/shared/lib/storage-control/chain.d.ts +57 -0
- package/dist/shared/lib/storage-control/chain.js +82 -0
- package/dist/shared/lib/storage-control/plan.d.ts +40 -0
- package/dist/shared/lib/storage-control/plan.js +72 -0
- package/dist/shared/lib/storage-control/reuse.d.ts +72 -0
- package/dist/shared/lib/storage-control/reuse.js +95 -0
- package/dist/shared/lib/storage-network.d.ts +25 -0
- package/dist/shared/lib/upload/part-plan.d.ts +24 -0
- package/dist/shared/lib/wallet/activity.d.ts +138 -0
- package/dist/shared/lib/wallet/activity.js +208 -0
- package/dist/shared/lib/wallet/send-rules.d.ts +68 -0
- package/dist/shared/lib/wallet/send-rules.js +116 -0
- package/dist/shared/lib/wallet/swap-rules.d.ts +115 -0
- package/dist/shared/lib/wallet/swap-rules.js +199 -0
- package/dist/shared/lib/wallet/tip.d.ts +14 -0
- package/dist/shared/lib/wallet/tip.js +34 -0
- package/dist/shared/lib/wallet/venue-ids.d.ts +23 -0
- package/dist/shared/lib/wallet/venue-ids.js +53 -0
- package/dist/standing-tip.d.ts +24 -0
- package/dist/standing-tip.js +52 -0
- package/dist/stdout.d.ts +38 -0
- package/dist/storage-control-chain.d.ts +38 -0
- package/dist/storage-control-chain.js +78 -0
- package/dist/sui-rpc.d.ts +15 -0
- package/dist/sui-rpc.js +59 -0
- package/dist/support-attach.d.ts +24 -0
- package/dist/support-attach.js +72 -0
- package/dist/support-copy.d.ts +42 -0
- package/dist/support-copy.js +109 -0
- package/dist/support-wire.d.ts +49 -0
- package/dist/support-wire.js +194 -0
- package/dist/trash-sweep.d.ts +55 -0
- package/dist/units.d.ts +2 -0
- package/dist/update-check.d.ts +50 -0
- package/dist/update-source.d.ts +63 -0
- package/dist/upload-api.d.ts +3 -0
- package/dist/upload-file.d.ts +97 -0
- package/dist/upload-file.js +3 -2
- package/dist/upload-price.d.ts +50 -0
- package/dist/upload-steps.d.ts +26 -0
- package/dist/upload-steps.js +29 -9
- package/dist/upload-store.d.ts +182 -0
- package/dist/upload-wallet-chain.d.ts +54 -0
- package/dist/upload-wallet-chain.js +203 -0
- package/dist/upload-wallet-plan.d.ts +128 -0
- package/dist/upload-wallet-plan.js +184 -0
- package/dist/upload-wallet.d.ts +24 -0
- package/dist/upload-wallet.js +208 -0
- package/dist/upload-wire.d.ts +244 -0
- package/dist/upload.d.ts +18 -0
- package/dist/upload.js +10 -0
- package/dist/usage-report.d.ts +35 -0
- package/dist/wallet-activity-chain.d.ts +5 -0
- package/dist/wallet-activity-chain.js +31 -0
- package/dist/wallet-chain.d.ts +19 -0
- package/dist/wallet-chain.js +2 -2
- package/dist/wallet-grant.d.ts +50 -0
- package/dist/wallet-grant.js +218 -0
- package/dist/wallet-send-chain.d.ts +24 -0
- package/dist/wallet-send-chain.js +49 -0
- package/dist/wallet-sign.d.ts +133 -0
- package/dist/wallet-sign.js +317 -0
- package/dist/wallet-storage-chain.d.ts +3 -0
- package/dist/wallet-storage-chain.js +20 -0
- package/dist/wallet-swap-chain.d.ts +52 -0
- package/dist/wallet-swap-chain.js +186 -0
- package/dist/wallet-swap-quote.d.ts +22 -0
- package/dist/wallet-swap-quote.js +126 -0
- package/dist/wallet.d.ts +135 -0
- package/dist/wallet.js +5 -5
- package/dist/walrus-write.d.ts +56 -0
- package/dist/walrus-write.js +5 -4
- package/dist/walrus.d.ts +86 -0
- package/dist/walrus.js +48 -10
- package/dist/zstd-node.d.ts +11 -0
- package/dist/zstd-node.js +71 -0
- package/docs/commands/accept-terms.md +15 -0
- package/docs/commands/create.md +17 -0
- package/docs/commands/credits.md +34 -0
- package/docs/commands/delete-account.md +14 -0
- package/docs/commands/deposit.md +27 -0
- package/docs/commands/devices.md +9 -0
- package/docs/commands/documents.md +10 -0
- package/docs/commands/env.md +21 -0
- package/docs/commands/extend.md +27 -0
- package/docs/commands/folders.md +10 -0
- package/docs/commands/get.md +14 -0
- package/docs/commands/key.md +14 -0
- package/docs/commands/login.md +23 -0
- package/docs/commands/logout.md +11 -0
- package/docs/commands/losses.md +13 -0
- package/docs/commands/ls.md +16 -0
- package/docs/commands/marks.md +9 -0
- package/docs/commands/mcp.md +33 -0
- package/docs/commands/mode.md +29 -0
- package/docs/commands/on-collision.md +13 -0
- package/docs/commands/padding.md +13 -0
- package/docs/commands/public-code.md +9 -0
- package/docs/commands/pull.md +9 -0
- package/docs/commands/push.md +12 -0
- package/docs/commands/put.md +36 -0
- package/docs/commands/rebuild.md +13 -0
- package/docs/commands/recovery.md +18 -0
- package/docs/commands/s3.md +17 -0
- package/docs/commands/share.md +15 -0
- package/docs/commands/support.md +16 -0
- package/docs/commands/tip.md +21 -0
- package/docs/commands/trash.md +25 -0
- package/docs/commands/trial.md +12 -0
- package/docs/commands/unlock.md +25 -0
- package/docs/commands/update.md +14 -0
- package/docs/commands/usage.md +14 -0
- package/docs/commands/verify.md +18 -0
- package/docs/commands/wallet.md +67 -0
- package/docs/commands/whoami.md +12 -0
- package/package.json +67 -8
- package/server.json +28 -0
- package/vendor/nmts-crypto/nmts_crypto_wasm.d.ts +23 -2
- package/vendor/nmts-crypto/nmts_crypto_wasm.js +44 -2
- package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm +0 -0
- package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm.d.ts +1 -0
- package/vendor/nmts-crypto/package.json +1 -1
- package/dist/commands/consent.js +0 -77
- package/dist/extend-sign.js +0 -130
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** The fixed, greppable prefix. `ak1` is the format version. ⚠ Must match what the server issues. */
|
|
2
|
+
export declare const KEY_PREFIX = "nmts_ak1_";
|
|
3
|
+
/** base64url over 9 random bytes. PUBLIC — this is the handle the account screen lists. */
|
|
4
|
+
export declare const KEY_HANDLE_LEN = 12;
|
|
5
|
+
/** One line, exactly this long: 9 + 12 + 1 + 43. */
|
|
6
|
+
export declare const KEY_LEN: number;
|
|
7
|
+
/**
|
|
8
|
+
* What a string turns out to be.
|
|
9
|
+
*
|
|
10
|
+
* ⛔ NO VARIANT CARRIES THE VALUE. A shape that held the offending string would be a credential in
|
|
11
|
+
* every error message built from it, which is the hole `errors.ts` spends a paragraph on.
|
|
12
|
+
*/
|
|
13
|
+
export type KeyShape = {
|
|
14
|
+
readonly kind: "key";
|
|
15
|
+
readonly handle: string;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: "malformed";
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: "not-a-key";
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Decide what a string is, without touching the network.
|
|
23
|
+
*
|
|
24
|
+
* ⛔ PARSED BY FIXED OFFSETS, NEVER BY SPLITTING ON `_`. The base64url alphabet contains `_`, so
|
|
25
|
+
* splitting cuts the string in a place that depends on its random bytes: the same code would
|
|
26
|
+
* accept one key and mangle the next. The Rust side says the same thing in the same words.
|
|
27
|
+
*/
|
|
28
|
+
export declare function wellFormed(value: string): KeyShape;
|
|
29
|
+
/** Where an offered key came from. Reported, never guessed. */
|
|
30
|
+
export type KeyOffer = "secret-file" | "env" | "terminal";
|
|
31
|
+
/**
|
|
32
|
+
* What `login` decided about the API key.
|
|
33
|
+
*
|
|
34
|
+
* ⛔ FOUR CASES AND NOT A NULLABLE STRING. "No key at all" and "the key that was already here" are
|
|
35
|
+
* different things to a person setting this tool up for the first time: one of them means every
|
|
36
|
+
* command that talks to the server is about to refuse, and that is the sentence they need. A
|
|
37
|
+
* single `string | undefined` would compile everywhere and say nothing.
|
|
38
|
+
*/
|
|
39
|
+
export type KeyOutcome = {
|
|
40
|
+
readonly kind: "none";
|
|
41
|
+
} | {
|
|
42
|
+
readonly kind: "unchanged";
|
|
43
|
+
readonly apiKey: string;
|
|
44
|
+
} | {
|
|
45
|
+
readonly kind: "stored";
|
|
46
|
+
readonly apiKey: string;
|
|
47
|
+
readonly handle: string;
|
|
48
|
+
/** Whether a person's periodic check is live for this account right now. */
|
|
49
|
+
readonly verified: boolean;
|
|
50
|
+
readonly from: KeyOffer;
|
|
51
|
+
} | {
|
|
52
|
+
readonly kind: "kept";
|
|
53
|
+
readonly apiKey: string;
|
|
54
|
+
readonly from: KeyOffer;
|
|
55
|
+
};
|
|
56
|
+
export interface KeyIntake {
|
|
57
|
+
/** The server the key will be checked against, and the one it belongs to. */
|
|
58
|
+
server: string;
|
|
59
|
+
/** What is already stored on this machine, if anything. */
|
|
60
|
+
stored: string | undefined;
|
|
61
|
+
/** Injected in tests so the terminal is not involved. An empty answer means "not now". */
|
|
62
|
+
readKey?: (() => Promise<string>) | undefined;
|
|
63
|
+
/** Injected in tests. Answers the question that replaces a stored key. */
|
|
64
|
+
confirmReplace?: (() => Promise<string>) | undefined;
|
|
65
|
+
}
|
|
66
|
+
/** The key to write down, or nothing. */
|
|
67
|
+
export declare function keyToStore(outcome: KeyOutcome): string | undefined;
|
|
68
|
+
/** The name of the place a key was offered from, for a message that has to say which one. */
|
|
69
|
+
export declare function keySourceName(from: KeyOffer): string;
|
|
70
|
+
/**
|
|
71
|
+
* Work out which key this machine should end up with, checking any new one before it is written.
|
|
72
|
+
*
|
|
73
|
+
* ⛔ A KEY ALREADY HERE IS NEVER REPLACED BY A RUN THAT DID NOT SAY SO. `login` is a command about
|
|
74
|
+
* the NMTS key; a person re-sealing their code with a new passphrase, on a machine where an
|
|
75
|
+
* old variable is still set in some shell profile, has not asked for their working key to be
|
|
76
|
+
* swapped for whatever that variable holds. Silently overwriting it would break every agent on
|
|
77
|
+
* the machine at a moment nobody would connect to the command they ran.
|
|
78
|
+
*/
|
|
79
|
+
export declare function settleApiKey(intake: KeyIntake): Promise<KeyOutcome>;
|
package/dist/api-key.js
CHANGED
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
// ⛔ NEVER FROM THE COMMAND LINE, for the reason written at the top of `credentials.ts`: on Linux
|
|
11
11
|
// any process can read another's /proc/<pid>/cmdline for as long as it lives, and the shell
|
|
12
12
|
// writes it to a history file. So there is no --api-key flag here and there never should be.
|
|
13
|
-
// The ways in are the three the
|
|
13
|
+
// The ways in are the three the NMTS key already has — a file an environment variable
|
|
14
14
|
// NAMES, the variable itself, and a prompt that echoes nothing.
|
|
15
15
|
//
|
|
16
16
|
// ⛔ THE SHAPE IS JUDGED HERE, BEFORE ANYTHING LEAVES THIS MACHINE, and that is not tidiness. The
|
|
17
|
-
// likeliest wrong thing to paste where a key is asked for is the
|
|
17
|
+
// likeliest wrong thing to paste where a key is asked for is the NMTS KEY, and sending it
|
|
18
18
|
// as a bearer token would put the one secret this product promises never travels onto the wire,
|
|
19
19
|
// into whatever sits in front of the server, and into its logs. The server does classify it and
|
|
20
20
|
// refuse (`ACCOUNT_CODE_NOT_A_CREDENTIAL`) rather than store it — but that answer arrives after
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
// half-accepting.
|
|
29
29
|
//
|
|
30
30
|
// ⛔ AND NO FIFTH CONSENT KEY. `consent.ts` allows four and says why: the bar is that the thing
|
|
31
|
-
// cannot be undone, costs money, or puts the
|
|
31
|
+
// cannot be undone, costs money, or puts the NMTS KEY somewhere that is not this tool's
|
|
32
32
|
// sealed file. A key is none of the three — it opens no ciphertext, the account screen revokes
|
|
33
33
|
// it, it expires by itself, and every command in this tool already reads it out of the
|
|
34
34
|
// environment without asking. A fifth question here is a fifth chance to teach somebody to
|
|
@@ -94,7 +94,7 @@ export function keySourceName(from) {
|
|
|
94
94
|
* Work out which key this machine should end up with, checking any new one before it is written.
|
|
95
95
|
*
|
|
96
96
|
* ⛔ A KEY ALREADY HERE IS NEVER REPLACED BY A RUN THAT DID NOT SAY SO. `login` is a command about
|
|
97
|
-
* the
|
|
97
|
+
* the NMTS key; a person re-sealing their code with a new passphrase, on a machine where an
|
|
98
98
|
* old variable is still set in some shell profile, has not asked for their working key to be
|
|
99
99
|
* swapped for whatever that variable holds. Silently overwriting it would break every agent on
|
|
100
100
|
* the machine at a moment nobody would connect to the command they ran.
|
|
@@ -270,9 +270,9 @@ async function refuse(shape, value) {
|
|
|
270
270
|
});
|
|
271
271
|
}
|
|
272
272
|
if (await looksLikeAnAccountCode(value)) {
|
|
273
|
-
return new NmtsError(`That is an
|
|
273
|
+
return new NmtsError(`That is an NMTS key, not an API key.`, {
|
|
274
274
|
exitCode: 2,
|
|
275
|
-
nextStep: `It was NOT sent anywhere: the
|
|
275
|
+
nextStep: `It was NOT sent anywhere: the NMTS key stays on this machine, and it is what opens ` +
|
|
276
276
|
`your files. An API key is a different thing — it makes the server answer a program, and ` +
|
|
277
277
|
`it opens nothing. Make one on the account screen at ${HOME_URL}; it begins ` +
|
|
278
278
|
`\`${KEY_PREFIX}\`. ${NOTHING_WAS_WRITTEN}`,
|
|
@@ -285,7 +285,7 @@ async function refuse(shape, value) {
|
|
|
285
285
|
});
|
|
286
286
|
}
|
|
287
287
|
/**
|
|
288
|
-
* Is this the
|
|
288
|
+
* Is this the NMTS key, pasted where the key goes?
|
|
289
289
|
*
|
|
290
290
|
* ⛔ ASKED OF THE ENGINE'S OWN PARSER, which verifies the code's trailing check symbol. Copying the
|
|
291
291
|
* alphabet here would be a second implementation of a format this repo keeps in exactly one
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ServerRefusal {
|
|
2
|
+
code: string;
|
|
3
|
+
message: string;
|
|
4
|
+
/**
|
|
5
|
+
* Whatever the refusal carries beside its words — a limit that was hit, an address to go to.
|
|
6
|
+
*
|
|
7
|
+
* ⚠ Values are not all numbers: `AGENT_VERIFY_REQUIRED` names the page a person opens. Nothing
|
|
8
|
+
* here validates the shape, so a narrower type than the wire's would be a claim, not a check.
|
|
9
|
+
*/
|
|
10
|
+
details?: Record<string, string | number>;
|
|
11
|
+
}
|
|
12
|
+
export declare function isRefusal(value: unknown): value is {
|
|
13
|
+
error: ServerRefusal;
|
|
14
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function isRefusal(value) {
|
|
2
|
+
if (typeof value !== "object" || value === null || !("error" in value))
|
|
3
|
+
return false;
|
|
4
|
+
const error = Reflect.get(value, "error");
|
|
5
|
+
if (typeof error !== "object" || error === null)
|
|
6
|
+
return false;
|
|
7
|
+
return typeof Reflect.get(error, "code") === "string" && typeof Reflect.get(error, "message") === "string";
|
|
8
|
+
}
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { NmtsError } from "./errors.ts";
|
|
2
|
+
import { type WaitReporter } from "./net-retry.ts";
|
|
3
|
+
/** Default deadline for a request that is not moving file bytes. */
|
|
4
|
+
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
5
|
+
export type { ServerRefusal } from "./api-refusal.ts";
|
|
6
|
+
import type { ServerRefusal } from "./api-refusal.ts";
|
|
7
|
+
/** A refusal the server explained. Carries its code so a caller can branch without string matching. */
|
|
8
|
+
export declare class ServerError extends NmtsError {
|
|
9
|
+
readonly status: number;
|
|
10
|
+
readonly code: string;
|
|
11
|
+
/** Seconds to wait. ⛔ `Retry-After` is the only place the server sends it — never the body. */
|
|
12
|
+
readonly retryAfter: number | null;
|
|
13
|
+
/**
|
|
14
|
+
* Whatever the refusal carried beside its words — the two credit amounts a doubled release fee
|
|
15
|
+
* is refused with, a limit that was hit, an address to go to.
|
|
16
|
+
*
|
|
17
|
+
* ⚠ NOT VALIDATED HERE. It arrives from the network and every reader checks the one field it
|
|
18
|
+
* wants before printing it; a narrower type would be a claim rather than a check.
|
|
19
|
+
*/
|
|
20
|
+
readonly details: Readonly<Record<string, string | number>>;
|
|
21
|
+
constructor(status: number, refusal: ServerRefusal, nextStep: string | null, retryAfter?: number | null);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A failure the server did NOT explain: a status with no refusal body behind it.
|
|
25
|
+
*
|
|
26
|
+
* ⛔ IT CARRIES THE STATUS SO A CALLER NEED NOT MATCH ON THE MESSAGE. The routes that serve
|
|
27
|
+
* documents answer 404 with an empty body for an id nothing has — there is no code to branch
|
|
28
|
+
* on, and "read the message and look for 404 in it" is how a refusal ends up being decided by
|
|
29
|
+
* a sentence somebody later rewrote. It extends `NmtsError` and changes nothing about how this
|
|
30
|
+
* failure is retried, reported or exited: only that a caller can now ask what the status was.
|
|
31
|
+
*/
|
|
32
|
+
export declare class HttpError extends NmtsError {
|
|
33
|
+
readonly status: number;
|
|
34
|
+
constructor(status: number, message: string);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A body the server sent as text, and the name its `Content-Disposition` gives that text.
|
|
38
|
+
*
|
|
39
|
+
* ⛔ THE NAME IS THE SERVER'S, NOT THIS TOOL'S. A notice kept from a terminal and the same notice
|
|
40
|
+
* kept from the browser's download button should be the same bytes under the same name, and
|
|
41
|
+
* the only way to be sure of that is to take the name from the one place both clients read it.
|
|
42
|
+
* ⚠ It is still a name that arrived over the network: whatever writes a file with it puts it
|
|
43
|
+
* through `safe-path.ts` first.
|
|
44
|
+
*/
|
|
45
|
+
export interface TextAnswer {
|
|
46
|
+
readonly text: string;
|
|
47
|
+
/** Null when the answer named no file. */
|
|
48
|
+
readonly filename: string | null;
|
|
49
|
+
}
|
|
50
|
+
export interface RequestOptions {
|
|
51
|
+
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
52
|
+
body?: unknown;
|
|
53
|
+
/** Session bearer token. Sent in the Authorization header and nowhere else. */
|
|
54
|
+
token?: string | undefined;
|
|
55
|
+
timeoutMs?: number;
|
|
56
|
+
signal?: AbortSignal | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Told before each wait between attempts, so a terminal can say the tool is waiting.
|
|
59
|
+
*
|
|
60
|
+
* ⛔ NOTHING IS RETRIED SILENTLY. A person watching and an agent reading the output both need to
|
|
61
|
+
* know the difference between a tool that is waiting and one that is stuck.
|
|
62
|
+
*/
|
|
63
|
+
onWait?: WaitReporter;
|
|
64
|
+
/**
|
|
65
|
+
* How long to keep trying a request that is safe to repeat. Omit for the default.
|
|
66
|
+
*
|
|
67
|
+
* ⛔ 0 MEANS ONE ATTEMPT. That is what a caller measuring the shape of a single failure wants,
|
|
68
|
+
* and it is the only way to ask for it — there is no separate "no retry" flag to fall out of
|
|
69
|
+
* step with this one.
|
|
70
|
+
*/
|
|
71
|
+
retryBudgetMs?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Make this request safe to repeat.
|
|
74
|
+
*
|
|
75
|
+
* ⛔ A NARROW OPTION RATHER THAN ARBITRARY HEADERS. The two calls that need it are the two that
|
|
76
|
+
* SPEND -- committing a file and reserving storage -- and a general header bag on a client
|
|
77
|
+
* that carries a bearer token is a way to send that token somewhere it was not meant to go.
|
|
78
|
+
*/
|
|
79
|
+
idempotencyKey?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Proof that this run holds the NMTS key, for the three routes that ask for one.
|
|
82
|
+
*
|
|
83
|
+
* ⛔ A NAMED OPTION, FOR THE SAME REASON `idempotencyKey` IS ONE. A general header bag on a
|
|
84
|
+
* client that carries a bearer token is a way to send that token somewhere it was not meant
|
|
85
|
+
* to go; this is one field, filled by one module, and it reaches exactly one header.
|
|
86
|
+
*
|
|
87
|
+
* ⛔ ITS VALUE IS NEVER IN A MESSAGE, A URL OR A LOG. `account-proof.ts` says what it is and
|
|
88
|
+
* what it can still do if it leaks. The server refuses to log it either — see
|
|
89
|
+
* `ACCOUNT_PROOF_HEADER` in `api/src/auth/api_key_auth.rs`.
|
|
90
|
+
*/
|
|
91
|
+
accountProof?: string;
|
|
92
|
+
/**
|
|
93
|
+
* What the answer is. Absent means JSON, which is what every `/v1` route sends.
|
|
94
|
+
*
|
|
95
|
+
* ⛔ ONE OPTION RATHER THAN A SECOND CLIENT. The site serves three documents as text — the
|
|
96
|
+
* notice board's rows, one notice, one legal document — and a separate fetch for them would
|
|
97
|
+
* be a second place holding the deadline, the retry rule, the run log and the refusal
|
|
98
|
+
* reading. Two of those going out of step is not hypothetical here: `check:cli-routes` exists
|
|
99
|
+
* because a command once called an address the server did not have, and it can only see
|
|
100
|
+
* calls that come through this function.
|
|
101
|
+
*/
|
|
102
|
+
as?: "text";
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* One request to the NMTS server, returning parsed JSON or throwing a named refusal.
|
|
106
|
+
*
|
|
107
|
+
* `path` starts with `/v1/` for the API, or `/api/` for the three documents the site itself
|
|
108
|
+
* serves. It is joined to the base without any normalising, so a caller cannot accidentally send
|
|
109
|
+
* a request to a different host by passing an absolute URL.
|
|
110
|
+
*/
|
|
111
|
+
export declare function request(base: string, path: string, options: RequestOptions & {
|
|
112
|
+
as: "text";
|
|
113
|
+
}): Promise<TextAnswer>;
|
|
114
|
+
export declare function request(base: string, path: string, options?: RequestOptions): Promise<unknown>;
|
package/dist/api.js
CHANGED
|
@@ -10,181 +10,53 @@
|
|
|
10
10
|
// that hangs forever is worse than one that fails, because nothing ever reports it.
|
|
11
11
|
//
|
|
12
12
|
// ⛔ THE TOKEN IS NEVER IN A MESSAGE, A URL OR A LOG. It goes in one header and nowhere else.
|
|
13
|
+
import { adviseFor } from "./api-advice.js";
|
|
13
14
|
import { NmtsError } from "./errors.js";
|
|
14
15
|
import { isTransient, keepTrying } from "./net-retry.js";
|
|
16
|
+
import { noteRequest } from "./run-log.js";
|
|
15
17
|
/** Default deadline for a request that is not moving file bytes. */
|
|
16
18
|
export const DEFAULT_TIMEOUT_MS = 30_000;
|
|
19
|
+
import { isRefusal } from "./api-refusal.js";
|
|
17
20
|
/** A refusal the server explained. Carries its code so a caller can branch without string matching. */
|
|
18
21
|
export class ServerError extends NmtsError {
|
|
19
22
|
status;
|
|
20
23
|
code;
|
|
21
|
-
|
|
24
|
+
/** Seconds to wait. ⛔ `Retry-After` is the only place the server sends it — never the body. */
|
|
25
|
+
retryAfter;
|
|
26
|
+
/**
|
|
27
|
+
* Whatever the refusal carried beside its words — the two credit amounts a doubled release fee
|
|
28
|
+
* is refused with, a limit that was hit, an address to go to.
|
|
29
|
+
*
|
|
30
|
+
* ⚠ NOT VALIDATED HERE. It arrives from the network and every reader checks the one field it
|
|
31
|
+
* wants before printing it; a narrower type would be a claim rather than a check.
|
|
32
|
+
*/
|
|
33
|
+
details;
|
|
34
|
+
constructor(status, refusal, nextStep, retryAfter = null) {
|
|
22
35
|
super(refusal.message, { exitCode: 1, nextStep });
|
|
23
36
|
this.name = "ServerError";
|
|
24
37
|
this.status = status;
|
|
25
38
|
this.code = refusal.code;
|
|
39
|
+
this.retryAfter = retryAfter;
|
|
40
|
+
this.details = refusal.details ?? {};
|
|
26
41
|
}
|
|
27
42
|
}
|
|
28
|
-
/** What a caller does next about a refusal, when the tool knows something the message does not. */
|
|
29
|
-
function adviseFor(code) {
|
|
30
|
-
switch (code) {
|
|
31
|
-
case "CLEARANCE_REQUIRED":
|
|
32
|
-
case "TURNSTILE_FAILED":
|
|
33
|
-
return ("This account needs a human check, which a command-line tool cannot pass. An API key " +
|
|
34
|
-
"made on the account screen is what waives it — put it in NMTS_API_KEY. If that screen " +
|
|
35
|
-
"has no place to make one, this server does not have API keys switched on.");
|
|
36
|
-
case "UNAUTHORIZED":
|
|
37
|
-
return "The credential is missing or expired. Check NMTS_API_KEY, or make a new key.";
|
|
38
|
-
// ⛔ Each of these says something different on purpose, because the remedies are different
|
|
39
|
-
// and a program that cannot tell them apart will retry the one thing that cannot work.
|
|
40
|
-
case "SESSION_REVOKED":
|
|
41
|
-
return "That is a browser session somebody ended from another device, not an API key. A key is what a program should carry; NMTS_API_KEY is where it goes.";
|
|
42
|
-
case "API_KEY_REVOKED":
|
|
43
|
-
return "Somebody revoked this key. It will not start working again — make a new one.";
|
|
44
|
-
case "API_KEY_EXPIRED":
|
|
45
|
-
return "This key reached the end of the lifetime it was given. Make a new one.";
|
|
46
|
-
case "API_KEY_SCOPE":
|
|
47
|
-
return ("The key is valid and was not given permission for this. Nothing here will succeed with " +
|
|
48
|
-
"it — a key with the right permissions has to be made on the account screen.");
|
|
49
|
-
case "API_KEY_MALFORMED":
|
|
50
|
-
return ("What was sent is not a well-formed key. Check that the whole string was copied, with " +
|
|
51
|
-
"no quotes or line break — it is one line of exactly 65 characters.");
|
|
52
|
-
case "ACCOUNT_CODE_NOT_A_CREDENTIAL":
|
|
53
|
-
return ("That was an account code, not an API key. The code never goes to the server; it stays " +
|
|
54
|
-
"on this machine and opens the files. Put the code in NMTS_ACCOUNT_CODE and the key in " +
|
|
55
|
-
"NMTS_API_KEY.");
|
|
56
|
-
case "AGENT_VERIFY_REQUIRED":
|
|
57
|
-
return ("This was refused because nothing has checked lately that a person is behind this " +
|
|
58
|
-
"account's key. Ask the person to run `nmts verify` and to follow what it prints — it " +
|
|
59
|
-
"gives them a code to type at a browser, and nothing here can pass that check for them.");
|
|
60
|
-
// ⛔ THE REFUSAL IS CORRECT AND THERE IS NOTHING HERE TO WORK AROUND. Accepting terms is a
|
|
61
|
-
// person reading a document and agreeing to it; a program doing it for them would be
|
|
62
|
-
// signing on somebody else's behalf, and this tool holds an API key, not a person. So the
|
|
63
|
-
// only thing missing was the advice — without it an agent gets a bare 403 and starts
|
|
64
|
-
// trying credentials, which is the one thing that cannot be the cause.
|
|
65
|
-
//
|
|
66
|
-
// ⚠ It does not say WHICH requests are refused. The server gates some and not others (reading
|
|
67
|
-
// and deleting are not gated today), that line has moved twice, and a sentence here naming
|
|
68
|
-
// the list would be a copy of it that nothing keeps true.
|
|
69
|
-
case "TERMS_ACCEPTANCE_REQUIRED":
|
|
70
|
-
return ("This account has not accepted the terms now in force, and the server refuses this " +
|
|
71
|
-
"request until it does. Nothing on this machine can accept them. Ask the person to open " +
|
|
72
|
-
"the account screen at nmts.me and accept there. Other requests may still work in the " +
|
|
73
|
-
"meantime.");
|
|
74
|
-
// ⛔ A KEY IS NOT ENOUGH HERE AND NEVER WILL BE. These routes rebuild what makes the account
|
|
75
|
-
// recoverable without NMTS, and the owner's rule is that the code is re-entered for them.
|
|
76
|
-
// An agent that reads this as "my key is wrong" starts making new keys, which is the one
|
|
77
|
-
// remedy that cannot work.
|
|
78
|
-
case "ACCOUNT_PROOF_REQUIRED":
|
|
79
|
-
return ("This request needs proof of the account code as well as the key, and what was sent was " +
|
|
80
|
-
"missing or did not match. Check that the code this machine is holding belongs to the " +
|
|
81
|
-
"same account as the key. Wrong attempts are counted, and three of them lock these " +
|
|
82
|
-
"routes for a while.");
|
|
83
|
-
case "ACCOUNT_BANNED":
|
|
84
|
-
return "This account is suspended. Nothing here will succeed until that is lifted.";
|
|
85
|
-
// ── Getting to the starting line ──────────────────────────────────────────────────────────
|
|
86
|
-
case "ACCOUNT_EXISTS":
|
|
87
|
-
return "An account already exists for that. Use the one you have rather than making another.";
|
|
88
|
-
case "ALPHA_NOT_OPEN":
|
|
89
|
-
return ("This build asks the server for a channel it does not open. This is not something to " +
|
|
90
|
-
"retry or to fix with a different credential — use a release build.");
|
|
91
|
-
case "API_KEY_CAP":
|
|
92
|
-
return ("The account holds as many live keys as it is allowed. Nothing here can raise the limit: " +
|
|
93
|
-
"the person has to revoke a key they no longer use, on the account screen at nmts.me.");
|
|
94
|
-
case "API_KEY_CHANNEL":
|
|
95
|
-
return ("This account is enrolled on a preview build, and keys are not issued while it is. Ask " +
|
|
96
|
-
"the person to leave the preview on the account screen, then make the key.");
|
|
97
|
-
case "INVALID_CREDENTIALS":
|
|
98
|
-
return ("The server did not accept what was sent. Check the key rather than the account code — " +
|
|
99
|
-
"the code never goes to the server and cannot be the cause.");
|
|
100
|
-
case "LOCKED_OUT":
|
|
101
|
-
return ("Too many failed attempts, so this is shut for a while. Retrying now makes it longer, " +
|
|
102
|
-
"not shorter. The refusal carries the moment it lifts; wait for it.");
|
|
103
|
-
case "RATE_LIMITED":
|
|
104
|
-
return ("Too many requests too quickly. Wait and send fewer — the refusal carries how long. This " +
|
|
105
|
-
"is not a credential problem, so changing keys will not help.");
|
|
106
|
-
case "SURFACE_MISMATCH":
|
|
107
|
-
return ("This account acts through a different build than the one calling. The refusal names " +
|
|
108
|
-
"which; nothing on this machine can change it, and the person switches it at nmts.me.");
|
|
109
|
-
// ── The terms ─────────────────────────────────────────────────────────────────────────────
|
|
110
|
-
case "TERMS_VERSION_MISMATCH":
|
|
111
|
-
return ("The versions sent are not the ones in force; the refusal carries the ones that are. " +
|
|
112
|
-
"This is a stale copy, not a refusal to serve — read the current versions and send those.");
|
|
113
|
-
case "TERMS_NOT_IN_FORCE":
|
|
114
|
-
return ("There is nothing to accept, so accepting cannot be what is missing. This is a server " +
|
|
115
|
-
"condition; report it rather than retrying.");
|
|
116
|
-
// ── Credits and the free trial ────────────────────────────────────────────────────────────
|
|
117
|
-
case "CREDIT_FILE_CAP":
|
|
118
|
-
return ("One file may cost at most the published cap in credits, and this one costs more. The " +
|
|
119
|
-
"refusal carries both numbers. Splitting the file is the way through; more credits is not.");
|
|
120
|
-
case "CREDIT_DAILY_CAP":
|
|
121
|
-
return ("The account has spent its allowance for today. The refusal carries the cap and what is " +
|
|
122
|
-
"spent. Waiting for the day to turn is the only remedy — buying credits does not lift it.");
|
|
123
|
-
case "TRIAL_CLOSED":
|
|
124
|
-
return "The free trial is not open at all right now. Credits have to come from a funded wallet.";
|
|
125
|
-
case "TRIAL_FULL":
|
|
126
|
-
return "This week's free-trial places are taken. Applying again this week cannot succeed; next week can.";
|
|
127
|
-
case "TRIAL_ALREADY":
|
|
128
|
-
return "This account already took the free trial this week. It comes round weekly, not once.";
|
|
129
|
-
case "TRIAL_HELD":
|
|
130
|
-
return "Free-trial applications are paused pending review. Retrying does not move it.";
|
|
131
|
-
case "TRIAL_LINE_CAPPED":
|
|
132
|
-
return ("This internet connection has taken its share of this week's places today — the limit is " +
|
|
133
|
-
"on the connection, not on the account, so another account here hits it too.");
|
|
134
|
-
// ── Storage, the chain, and what is safe to retry ─────────────────────────────────────────
|
|
135
|
-
// ⛔ THE THREE OUTCOMES ARE DIFFERENT AND AN AGENT MUST NOT COLLAPSE THEM. Refused means it did
|
|
136
|
-
// not happen. Failed means it did not finish. Uncertain means nobody knows — and that is the
|
|
137
|
-
// one where retrying blindly can spend money twice.
|
|
138
|
-
case "CHAIN_REQUEST_REFUSED":
|
|
139
|
-
return ("The storage service refused the request itself, so nothing was spent and nothing was " +
|
|
140
|
-
"stored. Retrying the same request will be refused the same way.");
|
|
141
|
-
case "CHAIN_REGISTER_FAILED":
|
|
142
|
-
return "Registering the storage did not go through. Nothing is stored; the upload can be tried again.";
|
|
143
|
-
case "CHAIN_CERTIFY_FAILED":
|
|
144
|
-
return ("The bytes went out but the storage was never certified, so the file is not safely stored. " +
|
|
145
|
-
"Try the upload again.");
|
|
146
|
-
case "CHAIN_UNCERTAIN":
|
|
147
|
-
return ("⛔ Nobody knows whether the storage was registered. Do NOT simply retry: doing so can pay " +
|
|
148
|
-
"twice for the same file. Run `nmts ls` first and see whether the file is there.");
|
|
149
|
-
case "CHAIN_SPEND_CAP":
|
|
150
|
-
return ("The service has stopped spending on storage for today. This is not about this account " +
|
|
151
|
-
"and no credential or credit changes it. Try tomorrow.");
|
|
152
|
-
case "CHAIN_DELETE_FAILED":
|
|
153
|
-
return ("The storage could not be released. The file's record is gone from this side either way, " +
|
|
154
|
-
"so nothing here is stuck — the storage runs out on its own when its time is up.");
|
|
155
|
-
case "RELEASE_NOT_SPONSORED":
|
|
156
|
-
return ("This file's storage was not paid for with credits, so it is not the server's to release. " +
|
|
157
|
-
"Storage bought from a wallet is released by that wallet.");
|
|
158
|
-
case "SPONSORED_STATE":
|
|
159
|
-
return ("The upload is not at the step that call belongs to — the steps have an order and one was " +
|
|
160
|
-
"skipped or already done. Start the upload again rather than repeating this call.");
|
|
161
|
-
// ── Two callers, one drive ────────────────────────────────────────────────────────────────
|
|
162
|
-
case "VERSION_CONFLICT":
|
|
163
|
-
return ("Something else changed the drive since this was read. Nothing is lost and nothing is " +
|
|
164
|
-
"wrong with the credential: read the current state and apply the change to that.");
|
|
165
|
-
case "ERASE_BLOCKED":
|
|
166
|
-
return ("The account cannot be erased while retained records still point at it. This will not " +
|
|
167
|
-
"clear by retrying; the records have their own retention and it has to run out.");
|
|
168
|
-
case "CREDITS_SHORT":
|
|
169
|
-
return "The account does not have enough credits for this upload.";
|
|
170
|
-
default:
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
function isRefusal(value) {
|
|
175
|
-
if (typeof value !== "object" || value === null || !("error" in value))
|
|
176
|
-
return false;
|
|
177
|
-
const error = Reflect.get(value, "error");
|
|
178
|
-
if (typeof error !== "object" || error === null)
|
|
179
|
-
return false;
|
|
180
|
-
return typeof Reflect.get(error, "code") === "string" && typeof Reflect.get(error, "message") === "string";
|
|
181
|
-
}
|
|
182
43
|
/**
|
|
183
|
-
*
|
|
44
|
+
* A failure the server did NOT explain: a status with no refusal body behind it.
|
|
184
45
|
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
46
|
+
* ⛔ IT CARRIES THE STATUS SO A CALLER NEED NOT MATCH ON THE MESSAGE. The routes that serve
|
|
47
|
+
* documents answer 404 with an empty body for an id nothing has — there is no code to branch
|
|
48
|
+
* on, and "read the message and look for 404 in it" is how a refusal ends up being decided by
|
|
49
|
+
* a sentence somebody later rewrote. It extends `NmtsError` and changes nothing about how this
|
|
50
|
+
* failure is retried, reported or exited: only that a caller can now ask what the status was.
|
|
187
51
|
*/
|
|
52
|
+
export class HttpError extends NmtsError {
|
|
53
|
+
status;
|
|
54
|
+
constructor(status, message) {
|
|
55
|
+
super(message, { exitCode: 1 });
|
|
56
|
+
this.name = "HttpError";
|
|
57
|
+
this.status = status;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
188
60
|
export async function request(base, path, options = {}) {
|
|
189
61
|
if (!path.startsWith("/"))
|
|
190
62
|
throw new NmtsError(`A request path must start with "/": ${path}`);
|
|
@@ -211,7 +83,9 @@ async function once(base, path, options) {
|
|
|
211
83
|
const deadline = setTimeout(() => controller.abort(), timeoutMs);
|
|
212
84
|
if (options.signal)
|
|
213
85
|
options.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
214
|
-
const headers = {
|
|
86
|
+
const headers = {
|
|
87
|
+
accept: options.as === "text" ? "text/plain, text/markdown, */*" : "application/json",
|
|
88
|
+
};
|
|
215
89
|
if (body !== undefined)
|
|
216
90
|
headers["content-type"] = "application/json";
|
|
217
91
|
if (token !== undefined && token.length > 0)
|
|
@@ -247,8 +121,12 @@ async function once(base, path, options) {
|
|
|
247
121
|
clearTimeout(deadline);
|
|
248
122
|
}
|
|
249
123
|
const text = await response.text();
|
|
124
|
+
// ⛔ A TEXT ANSWER IS ONLY TEXT WHEN THE SERVER AGREED. A refusal is JSON however the request
|
|
125
|
+
// asked, so a failing document fetch still goes through the reading below and still reaches
|
|
126
|
+
// the caller as a named refusal rather than as a page of HTML pretending to be a notice.
|
|
127
|
+
const asText = options.as === "text" && response.ok;
|
|
250
128
|
let parsed = null;
|
|
251
|
-
if (text.length > 0) {
|
|
129
|
+
if (text.length > 0 && !asText) {
|
|
252
130
|
try {
|
|
253
131
|
parsed = JSON.parse(text);
|
|
254
132
|
}
|
|
@@ -261,11 +139,30 @@ async function once(base, path, options) {
|
|
|
261
139
|
});
|
|
262
140
|
}
|
|
263
141
|
}
|
|
142
|
+
// ⛔ The run log's one hook: here, where the outcome is known and nothing has yet been thrown.
|
|
143
|
+
noteRequest(method, path, response.status, isRefusal(parsed) ? parsed.error.message : undefined);
|
|
264
144
|
if (!response.ok) {
|
|
265
145
|
if (isRefusal(parsed)) {
|
|
266
|
-
|
|
146
|
+
const wait = response.headers.get("retry-after");
|
|
147
|
+
throw new ServerError(response.status, parsed.error, adviseFor(parsed.error.code), wait !== null && /^\d+$/.test(wait.trim()) ? Number(wait) : null);
|
|
267
148
|
}
|
|
268
|
-
throw new
|
|
149
|
+
throw new HttpError(response.status, `${base} answered ${response.status}.`);
|
|
269
150
|
}
|
|
151
|
+
if (asText)
|
|
152
|
+
return { text, filename: filenameFrom(response.headers.get("content-disposition")) };
|
|
270
153
|
return parsed;
|
|
271
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* The file name a `Content-Disposition` header gives a body, or null when it names none.
|
|
157
|
+
*
|
|
158
|
+
* ⛔ THE LAST SEGMENT AND NOTHING ELSE. This value came over the network and is on its way to a
|
|
159
|
+
* name on somebody's disk. `safe-path.ts` contains it as well, and this is the first of the
|
|
160
|
+
* two: a header saying `filename="../../etc/passwd"` must not survive even as far as being
|
|
161
|
+
* joined to a directory. Anything that reduces to nothing, `.` or `..` is treated as no name
|
|
162
|
+
* at all, which the caller answers by refusing rather than by inventing one.
|
|
163
|
+
*/
|
|
164
|
+
function filenameFrom(header) {
|
|
165
|
+
const quoted = header === null ? null : /filename="([^"]*)"/u.exec(header);
|
|
166
|
+
const name = (quoted?.[1] ?? "").split(/[/\\]/u).at(-1) ?? "";
|
|
167
|
+
return name === "" || name === "." || name === ".." ? null : name;
|
|
168
|
+
}
|