@needmoretruth/nmts-cli 0.17.2 → 0.34.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +125 -517
- package/CHANGELOG.md +104 -0
- package/README.ko.md +377 -469
- package/README.md +394 -474
- 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,193 @@
|
|
|
1
|
+
// `nmts erase <paths>` — erase files for good: the server's record, and this account's key to
|
|
2
|
+
// them in the sealed file list. ⛔ IRREVERSIBLE, and the one act above high besides erasing the
|
|
3
|
+
// account.
|
|
4
|
+
//
|
|
5
|
+
// ⛔ TWO THINGS ARE DESTROYED AND THIS COMMAND ALWAYS DESTROYS THE FIRST. The server row carries
|
|
6
|
+
// the wrapped key that opens the bytes; the list entry carries this account's own copy. Both
|
|
7
|
+
// go here. The BYTES on the storage network are a third thing: bought by the wallet, they stay
|
|
8
|
+
// until their term runs out (burning them is a signed transaction the browser makes); bought
|
|
9
|
+
// with credits, `--release-storage` asks the server to destroy the treasury's storage under
|
|
10
|
+
// each file first, which it does on the chain one blob at a time and reports per file.
|
|
11
|
+
//
|
|
12
|
+
// ⛔ THE SERVER GOES FIRST, THE LIST LAST. A row erased before the list entry leaves a file the
|
|
13
|
+
// person can see and never open; that is the same order the trash keeps, for the same reason.
|
|
14
|
+
// A release that fails leaves the file whole — nothing is erased behind a failed release.
|
|
15
|
+
//
|
|
16
|
+
// ⛔ THE SENTENCE IS TYPED IN EVERY MODE BUT SKIP-PERMISSIONS, where the tier gate's `--reason`
|
|
17
|
+
// and `--yes` stand for it — the same rule as `delete-account`, because it is the same tier.
|
|
18
|
+
import { accountProofFor } from "../account-proof.js";
|
|
19
|
+
import { request, ServerError } from "../api.js";
|
|
20
|
+
import { currentMode } from "../autonomy.js";
|
|
21
|
+
import { CONFIRM_SENTENCE } from "./delete-account.js";
|
|
22
|
+
import { buildIndex, fullPathOf, KIND_FILE } from "../drive-paths.js";
|
|
23
|
+
import { NmtsError } from "../errors.js";
|
|
24
|
+
import { readFileList } from "../manifest.js";
|
|
25
|
+
import { applyToList, batchTargets } from "../manifest-write.js";
|
|
26
|
+
import { BINARY_NAME } from "../product.js";
|
|
27
|
+
import { promptLine, stdinIsATerminal } from "../prompt.js";
|
|
28
|
+
import { openSession } from "../session.js";
|
|
29
|
+
import { filesUnder } from "./trash.js";
|
|
30
|
+
/** The server takes at most this many ids in one erase (`ERASE_BATCH_MAX`). */
|
|
31
|
+
const BATCH = 200;
|
|
32
|
+
/** The refusal a no-deposit release gets when the balance cannot cover the doubled fee. */
|
|
33
|
+
const FEE_INSUFFICIENT = "DEPOSIT_FEE_INSUFFICIENT";
|
|
34
|
+
export async function erase(paths, options = {}) {
|
|
35
|
+
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
36
|
+
if (paths.length === 0) {
|
|
37
|
+
throw new NmtsError(`\`${BINARY_NAME} erase\` needs the path of at least one thing in the drive.`, {
|
|
38
|
+
exitCode: 2,
|
|
39
|
+
nextStep: `\`${BINARY_NAME} ls --all\` prints the paths as this expects them.`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const typedFor = options.yes === true && currentMode() === "skip-permissions";
|
|
43
|
+
const ask = options.readLine ?? promptLine;
|
|
44
|
+
if (!typedFor && options.readLine === undefined && !stdinIsATerminal()) {
|
|
45
|
+
throw new NmtsError("There is no terminal to type into (stdin is not a TTY).", {
|
|
46
|
+
exitCode: 3,
|
|
47
|
+
nextStep: `Run this where a person can type: erasing is confirmed by typing a sentence.`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const session = await openSession(options);
|
|
51
|
+
const list = await readFileList(session.server, session.apiKey, session.code, session.accountId);
|
|
52
|
+
const entries = list.manifest?.entries ?? [];
|
|
53
|
+
const index = buildIndex(entries);
|
|
54
|
+
const targets = batchTargets(entries, paths, { includeTrashed: true, nothingHappened: "Nothing was erased." });
|
|
55
|
+
const files = uniqueById(targets.flatMap((t) => (t.kind === KIND_FILE ? [t] : filesUnder(entries, t.id))));
|
|
56
|
+
const going = uniqueById([...targets, ...files]);
|
|
57
|
+
if (files.length === 0) {
|
|
58
|
+
throw new NmtsError(`Nothing named holds a file; empty folders are removed with \`${BINARY_NAME} rm\`.`, {
|
|
59
|
+
exitCode: 4,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
say(`This erases ${files.length} file${files.length === 1 ? "" : "s"} for good. It cannot be undone, and not by the trash.`);
|
|
63
|
+
for (const f of files)
|
|
64
|
+
say(` ${fullPathOf(index, f)}`);
|
|
65
|
+
say(``);
|
|
66
|
+
say(` Erased: the server's record of each file and this account's key to it, and its shares.`);
|
|
67
|
+
if (options.releaseStorage === true) {
|
|
68
|
+
say(` Destroyed: the storage bought with credits under each file, on the chain, before the erase.`);
|
|
69
|
+
say(` Storage bought by the wallet is not touched — it stays until its term ends.`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
say(` Not erased: the bytes on the storage network. They stay, unreadable, until their term ends;`);
|
|
73
|
+
say(` \`--release-storage\` also destroys the storage bought with credits under them.`);
|
|
74
|
+
}
|
|
75
|
+
say(` Not refunded: storage already paid for.`);
|
|
76
|
+
say(``);
|
|
77
|
+
const typed = typedFor ? CONFIRM_SENTENCE : (await ask(`Type exactly: ${CONFIRM_SENTENCE}\n> `)).trim();
|
|
78
|
+
if (typed !== CONFIRM_SENTENCE) {
|
|
79
|
+
say(`Nothing was erased.`);
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
82
|
+
// ⛔ THE PROOF IS BUILT FOR THIS ONE RUN AND NOTHING KEEPS IT.
|
|
83
|
+
const accountProof = await accountProofFor({ code: session.code, source: session.source });
|
|
84
|
+
const releases = [];
|
|
85
|
+
if (options.releaseStorage === true) {
|
|
86
|
+
for (const f of files) {
|
|
87
|
+
const path = fullPathOf(index, f);
|
|
88
|
+
try {
|
|
89
|
+
const reply = await request(session.server, `/v1/items/${encodeURIComponent(f.id)}/release-storage`, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
body: {},
|
|
92
|
+
token: session.apiKey,
|
|
93
|
+
accountProof,
|
|
94
|
+
});
|
|
95
|
+
releases.push({ path, refused: null, ...counts(reply), ...fee(reply) });
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
// ⛔ THE ONE REFUSAL THAT STOPS THE RUN. A file with no deposit pays twice the chain fee
|
|
99
|
+
// out of the balance, and a balance that cannot cover it means the release did not
|
|
100
|
+
// happen — erasing behind it would destroy the account's key to bytes that are still
|
|
101
|
+
// being served and still being paid for. Nothing is erased, and the two numbers say
|
|
102
|
+
// exactly how far short the balance is.
|
|
103
|
+
if (error instanceof ServerError && error.code === FEE_INSUFFICIENT) {
|
|
104
|
+
throw new NmtsError(`${path}: releasing its storage costs ${amount(error, "needed_credits")} credits and this ` +
|
|
105
|
+
`account has ${amount(error, "balance_credits")}. It has no deposit, so the fee comes out of the balance.`, {
|
|
106
|
+
exitCode: 4,
|
|
107
|
+
nextStep: `Nothing was erased. Buy credits and run this again, or leave --release-storage off ` +
|
|
108
|
+
`to erase the file and let its storage run out on its own.`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// ⚠ "Not ours to destroy" is an answer, not a failure: the storage was bought by the
|
|
112
|
+
// wallet, and the erase goes on. A refusal of the KEY or the proof, and anything the
|
|
113
|
+
// server could not do, stops the run before a row is touched.
|
|
114
|
+
if (error instanceof ServerError && error.status !== 401 && error.status !== 403 && error.status < 500) {
|
|
115
|
+
releases.push({ path, released: 0, alreadyReleased: 0, failed: 0, feeCredits: 0, fromDeposit: false, refused: error.message });
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
let erased = 0;
|
|
123
|
+
for (let i = 0; i < files.length; i += BATCH) {
|
|
124
|
+
const reply = await request(session.server, "/v1/items/erase", {
|
|
125
|
+
method: "POST",
|
|
126
|
+
body: { item_ids: files.slice(i, i + BATCH).map((f) => f.id) },
|
|
127
|
+
token: session.apiKey,
|
|
128
|
+
accountProof,
|
|
129
|
+
});
|
|
130
|
+
erased += typeof reply === "object" && reply !== null && typeof Reflect.get(reply, "erased") === "number"
|
|
131
|
+
? Number(Reflect.get(reply, "erased"))
|
|
132
|
+
: 0;
|
|
133
|
+
}
|
|
134
|
+
// ⛔ THE LIST GOES LAST, and re-decided against the list as it is on this attempt: only the
|
|
135
|
+
// ids this run erased leave it, whatever another device wrote in between.
|
|
136
|
+
const ids = new Set(going.map((e) => e.id));
|
|
137
|
+
const result = await applyToList(session, (current) => {
|
|
138
|
+
const still = current.filter((e) => ids.has(e.id)).map((e) => e.id);
|
|
139
|
+
return still.length === 0 ? null : { op: "purge", ids: still };
|
|
140
|
+
});
|
|
141
|
+
if (options.json) {
|
|
142
|
+
say(JSON.stringify({ erased, files: files.map((f) => ({ id: f.id, path: fullPathOf(index, f) })), releases, seq: result.seq }));
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
say(`Erased ${erased} file${erased === 1 ? "" : "s"}. Their entries are out of the file list.`);
|
|
146
|
+
for (const r of releases) {
|
|
147
|
+
if (r.refused !== null)
|
|
148
|
+
say(` ${r.path}: storage not released — ${r.refused}`);
|
|
149
|
+
else if (r.failed > 0)
|
|
150
|
+
say(` ${r.path}: ${r.released} released, ${r.failed} could not be — those bytes are still being served.`);
|
|
151
|
+
else
|
|
152
|
+
say(` ${r.path}: storage released (${r.released} destroyed${r.alreadyReleased > 0 ? `, ${r.alreadyReleased} already gone` : ""}).`);
|
|
153
|
+
// ⛔ ONLY WHEN THE SERVER NAMED A NUMBER. Every release the new ledger charges costs at least
|
|
154
|
+
// one credit, so a zero here is a server that did not say rather than a release that was
|
|
155
|
+
// free — and "no credits were charged" is the wrong sentence to invent about money.
|
|
156
|
+
if (r.refused === null && r.feeCredits > 0)
|
|
157
|
+
say(` ${r.path}: ${feeLine(r)}`);
|
|
158
|
+
}
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
/** What the release cost and where it came from, in one clause. */
|
|
162
|
+
function feeLine(r) {
|
|
163
|
+
const credits = `${r.feeCredits} credit${r.feeCredits === 1 ? "" : "s"}`;
|
|
164
|
+
return r.fromDeposit
|
|
165
|
+
? `${credits} taken from that file's deposit — nothing came out of the balance`
|
|
166
|
+
: `${credits} taken from the balance — that file had no deposit, so the fee is doubled`;
|
|
167
|
+
}
|
|
168
|
+
/** The three counts a release answers with, read defensively. */
|
|
169
|
+
function counts(reply) {
|
|
170
|
+
const n = (name) => {
|
|
171
|
+
const v = typeof reply === "object" && reply !== null ? Reflect.get(reply, name) : undefined;
|
|
172
|
+
return typeof v === "number" ? v : 0;
|
|
173
|
+
};
|
|
174
|
+
return { released: n("released"), alreadyReleased: n("already_released"), failed: n("failed") };
|
|
175
|
+
}
|
|
176
|
+
/** What it cost, read the same defensive way. An older server says neither, which reads as 0. */
|
|
177
|
+
function fee(reply) {
|
|
178
|
+
const at = (name) => typeof reply === "object" && reply !== null ? Reflect.get(reply, name) : undefined;
|
|
179
|
+
const charged = at("fee_credits");
|
|
180
|
+
return {
|
|
181
|
+
feeCredits: typeof charged === "number" ? charged : 0,
|
|
182
|
+
fromDeposit: at("from_deposit") === true,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** One credit amount out of a refusal's details, or `?` when the server did not name it. */
|
|
186
|
+
function amount(error, field) {
|
|
187
|
+
const value = error.details[field];
|
|
188
|
+
return typeof value === "number" ? String(value) : "?";
|
|
189
|
+
}
|
|
190
|
+
function uniqueById(list) {
|
|
191
|
+
const seen = new Set();
|
|
192
|
+
return list.filter((e) => (seen.has(e.id) ? false : (seen.add(e.id), true)));
|
|
193
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type EpochClock } from "../expiry.ts";
|
|
2
|
+
export interface ExpiringOptions {
|
|
3
|
+
server?: string | undefined;
|
|
4
|
+
network?: string | undefined;
|
|
5
|
+
json?: boolean;
|
|
6
|
+
write?: (line: string) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Read the storage network's epoch clock.
|
|
9
|
+
*
|
|
10
|
+
* ⚠ A SEAM, NOT AN OPTION: there is no flag for it and no way to supply one from a command line.
|
|
11
|
+
* It exists because the alternative is a test that either talks to a real storage network — and
|
|
12
|
+
* so cannot be run in an epoch change, on a halted chain, or at all offline — or does not
|
|
13
|
+
* exercise the arithmetic that decides what a person is warned about.
|
|
14
|
+
*/
|
|
15
|
+
readClock?: () => Promise<EpochClock | null>;
|
|
16
|
+
/** The instant to measure against. Passed in so one run reports one moment. */
|
|
17
|
+
now?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function expiring(options?: ExpiringOptions): Promise<number>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ExtendReads, type SignExtension } from "../extend-plan.ts";
|
|
2
|
+
import type { StandingTipInput } from "../standing-tip.ts";
|
|
3
|
+
export interface ExtendOptions {
|
|
4
|
+
server?: string | undefined;
|
|
5
|
+
network?: string | undefined;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
write?: (line: string) => void;
|
|
8
|
+
/** How many of the storage network's epochs to add. Default `DEFAULT_EXTEND_EPOCHS`. */
|
|
9
|
+
epochs?: string | number | undefined;
|
|
10
|
+
/** Say what it would cost and stop. ⛔ Nothing is signed and no key is touched. */
|
|
11
|
+
dryRun?: boolean;
|
|
12
|
+
/** Extend a file that is not near its deadline. Said out loud because it spends money early. */
|
|
13
|
+
yes?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The chain reads.
|
|
16
|
+
*
|
|
17
|
+
* ⚠ A SEAM, NOT AN OPTION — no flag reaches it. See `ExtendReads`: a test that talked to a live
|
|
18
|
+
* storage network could not run offline and could never be asked to be at its own ceiling.
|
|
19
|
+
*/
|
|
20
|
+
readChain?: (network: string) => Promise<ExtendReads> | ExtendReads;
|
|
21
|
+
/**
|
|
22
|
+
* The signature.
|
|
23
|
+
*
|
|
24
|
+
* ⛔ SEPARATE FROM THE READS SO A TEST CAN PROVE THE THING THAT MATTERS: that `--dry-run` and a
|
|
25
|
+
* missing agreement both stop before anything reaches this.
|
|
26
|
+
*/
|
|
27
|
+
sign?: SignExtension;
|
|
28
|
+
/** The instant to measure against. Passed in so one run reports one moment. */
|
|
29
|
+
now?: number;
|
|
30
|
+
/** ⚠ SEAMS, NOT OPTIONS — the standing tip's own read and signature. No flag reaches them. */
|
|
31
|
+
tip?: Pick<StandingTipInput, "readDonation" | "sign">;
|
|
32
|
+
}
|
|
33
|
+
export declare function extend(target: string | undefined, options?: ExtendOptions): Promise<number>;
|
package/dist/commands/extend.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
//
|
|
3
3
|
// ⛔ THE ONLY COMMAND IN THIS TOOL THAT SIGNS ANYTHING, and the only one that spends from a
|
|
4
4
|
// WALLET. Every other purchase here is made with credits, which are a promise this service
|
|
5
|
-
// made; this one moves WAL out of the wallet the
|
|
5
|
+
// made; this one moves WAL out of the wallet the NMTS key derives, on a public chain, and
|
|
6
6
|
// nobody — NMTS included — can reverse it. That difference is said out loud, in the output,
|
|
7
7
|
// before the agreement is asked for.
|
|
8
8
|
//
|
|
9
9
|
// ⛔ IT PRICES BEFORE IT SPENDS, ALWAYS. The reads and the quote are free and happen first, so
|
|
10
10
|
// `--dry-run` answers with a real number and never reaches the key. Nothing below the quote can
|
|
11
|
-
// run without `
|
|
11
|
+
// run without `requireWalletGrant("extend", …)` having passed.
|
|
12
12
|
//
|
|
13
13
|
// ⛔ THE SERVER DOES NOT EXTEND ANYTHING, and this command is shaped by that. `POST
|
|
14
14
|
// /v1/items/{id}/extended` means "record an extension the device already signed": the storage is
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// ⛔ AND A FILE THAT IS NOT RUNNING OUT IS NOT EXTENDED BY ACCIDENT. Extending early loses nothing
|
|
20
20
|
// — epochs are added to what is left — so this is not a refusal on principle; it is a refusal
|
|
21
21
|
// to spend money on a deadline nobody is near, unless somebody says so with `--yes`.
|
|
22
|
-
import {
|
|
22
|
+
import { recordWalletSpend, requireWalletGrant } from "../wallet-grant.js";
|
|
23
23
|
import { request } from "../api.js";
|
|
24
24
|
import { buildIndex, entryAt, fullPathOf, KIND_FILE, normalisePath } from "../drive-paths.js";
|
|
25
25
|
import { NmtsError } from "../errors.js";
|
|
@@ -27,9 +27,11 @@ import { daysLeftInWords, daysLeftUntilEpoch, stageOf, } from "../expiry.js";
|
|
|
27
27
|
import { asExtendPreview, chooseEpochs, headroom, soonestEnd, } from "../extend-plan.js";
|
|
28
28
|
import { isRecord } from "../guards.js";
|
|
29
29
|
import { readFileList } from "../manifest.js";
|
|
30
|
+
import { resolveNetwork } from "../network.js";
|
|
30
31
|
import { BINARY_NAME } from "../product.js";
|
|
31
32
|
import { openSession } from "../session.js";
|
|
32
|
-
import { coinAmount } from "../wallet.js";
|
|
33
|
+
import { coinAmount, walletAddress } from "../wallet.js";
|
|
34
|
+
import { budgetFacts, describeBudget, readBudget, shortfallNextStep } from "../extend-budget.js";
|
|
33
35
|
export async function extend(target, options = {}) {
|
|
34
36
|
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
35
37
|
const now = options.now ?? Date.now();
|
|
@@ -45,6 +47,8 @@ export async function extend(target, options = {}) {
|
|
|
45
47
|
throw new NmtsError("This account has no file list, so there is nothing to extend.", { exitCode: 4 });
|
|
46
48
|
}
|
|
47
49
|
const entries = list.manifest.entries;
|
|
50
|
+
// The standing share is the person's, and it is read from the same sealed list this file came from.
|
|
51
|
+
const settings = list.manifest.settings;
|
|
48
52
|
const entry = entryAt(entries, normalisePath(target), {
|
|
49
53
|
nothingHappened: "Nothing was signed and nothing was charged.",
|
|
50
54
|
});
|
|
@@ -105,6 +109,14 @@ export async function extend(target, options = {}) {
|
|
|
105
109
|
const frost = await reads.quote(leases, epochs);
|
|
106
110
|
const cohort = Math.max(0, ...preview.targets.map((t) => t.sharedItems));
|
|
107
111
|
const unreachable = preview.treasuryParts + preview.untrackedParts;
|
|
112
|
+
// What the wallet holds and what the chain would charge — read, never assumed (`extend-budget.ts`).
|
|
113
|
+
const address = await walletAddress(session.code);
|
|
114
|
+
const budget = await readBudget(reads, {
|
|
115
|
+
address,
|
|
116
|
+
objectIds: preview.targets.map((t) => t.objectId),
|
|
117
|
+
epochs,
|
|
118
|
+
priceFrost: frost,
|
|
119
|
+
});
|
|
108
120
|
const facts = {
|
|
109
121
|
file: path,
|
|
110
122
|
itemId: entry.id,
|
|
@@ -121,6 +133,7 @@ export async function extend(target, options = {}) {
|
|
|
121
133
|
paidFrom: "wallet",
|
|
122
134
|
filesOnTheSameBlobs: cohort,
|
|
123
135
|
partsThatCannotBeExtended: unreachable,
|
|
136
|
+
...budgetFacts(budget),
|
|
124
137
|
};
|
|
125
138
|
if (options.dryRun === true) {
|
|
126
139
|
// ⛔ NOTHING BELOW THIS BRANCH RUNS. No key is derived, no agreement is asked for, and the
|
|
@@ -130,15 +143,20 @@ export async function extend(target, options = {}) {
|
|
|
130
143
|
return 0;
|
|
131
144
|
}
|
|
132
145
|
describe(say, facts, cohort, unreachable);
|
|
146
|
+
describeBudget(say, budget);
|
|
133
147
|
say(``);
|
|
148
|
+
if (budget.shortfall !== null)
|
|
149
|
+
say(` ⚠ ${budget.shortfall} As it stands, it would be refused.`);
|
|
134
150
|
say(` Nothing was signed and nothing was charged. Run the same command without --dry-run to`);
|
|
135
151
|
say(` buy it.`);
|
|
136
152
|
if (stage === "later")
|
|
137
153
|
say(` It is not near its deadline, so buying it also needs --yes.`);
|
|
138
154
|
return 0;
|
|
139
155
|
}
|
|
140
|
-
if (!options.json)
|
|
156
|
+
if (!options.json) {
|
|
141
157
|
describe(say, facts, cohort, unreachable);
|
|
158
|
+
describeBudget(say, budget);
|
|
159
|
+
}
|
|
142
160
|
// ⛔ ASKED AFTER THE PRICE IS KNOWN AND BEFORE ANYTHING IS SIGNED. Extending early loses nothing,
|
|
143
161
|
// so this is not a refusal on principle — it is a refusal to spend on a deadline that is not
|
|
144
162
|
// close, unless somebody says otherwise out loud.
|
|
@@ -150,16 +168,26 @@ export async function extend(target, options = {}) {
|
|
|
150
168
|
`Add --yes to buy it anyway. \`${BINARY_NAME} expiring\` lists what is actually running out.`,
|
|
151
169
|
});
|
|
152
170
|
}
|
|
171
|
+
// ⛔ A WALLET KNOWN TO BE SHORT IS REFUSED BEFORE THE AGREEMENT IS ASKED FOR — a person should
|
|
172
|
+
// not grant signing in order to be told there is nothing to spend (`extend-budget.ts`).
|
|
173
|
+
if (budget.shortfall !== null) {
|
|
174
|
+
throw new NmtsError(budget.shortfall, { exitCode: 4, nextStep: shortfallNextStep(budget) });
|
|
175
|
+
}
|
|
153
176
|
// ⛔ THE ONE GATE THAT STANDS BETWEEN A PROGRAM AND SOMEBODY'S WALLET. Everything above this line
|
|
154
|
-
// is a read; nothing below it can be undone.
|
|
155
|
-
|
|
156
|
-
const
|
|
177
|
+
// is a read; nothing below it can be undone. The grant names a scope, runs out, and may carry
|
|
178
|
+
// a ceiling — this signature is held against all three (`wallet-grant.ts`).
|
|
179
|
+
const spend = { walFrost: frost, suiMist: budget.feeMist ?? 0n };
|
|
180
|
+
requireWalletGrant("extend", spend, new Date(now));
|
|
181
|
+
const sign = options.sign ?? (await import("../wallet-sign.js")).signExtension;
|
|
157
182
|
const digest = await sign({
|
|
158
183
|
network: session.network,
|
|
159
184
|
code: session.code,
|
|
160
185
|
objectIds: preview.targets.map((t) => t.objectId),
|
|
161
186
|
epochs,
|
|
162
187
|
});
|
|
188
|
+
// What left the wallet is added to the grant's ledger first — the fee as estimated, since the
|
|
189
|
+
// amount actually charged is not read back here.
|
|
190
|
+
recordWalletSpend(spend);
|
|
163
191
|
// From here the storage IS extended. Recording it is bookkeeping, and a failure to record must
|
|
164
192
|
// never be reported as a failure to extend — that reading invites a second run, which pays again.
|
|
165
193
|
let replay = false;
|
|
@@ -184,14 +212,25 @@ export async function extend(target, options = {}) {
|
|
|
184
212
|
}
|
|
185
213
|
if (options.json) {
|
|
186
214
|
say(JSON.stringify({ ...facts, dryRun: false, signed: true, digest, recorded: true, replay }));
|
|
187
|
-
return 0;
|
|
188
215
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
216
|
+
else {
|
|
217
|
+
say(``);
|
|
218
|
+
say(` Extended. The storage now ends at epoch ${newEndEpoch} — ${daysLeftInWords(after)}.`);
|
|
219
|
+
say(` Transaction ${digest}`);
|
|
220
|
+
if (replay)
|
|
221
|
+
say(` The server had already recorded this transaction, so nothing was written twice.`);
|
|
194
222
|
}
|
|
223
|
+
// ⛔ AFTER THE PAYMENT, NEVER INSIDE IT, and it cannot change the answer above. In --json the
|
|
224
|
+
// tip speaks on stderr: stdout carries the machine's one answer and nothing else.
|
|
225
|
+
await (await import("../standing-tip.js")).standingTipAfter({
|
|
226
|
+
server: session.server,
|
|
227
|
+
network: resolveNetwork(session.server, session.network),
|
|
228
|
+
code: session.code,
|
|
229
|
+
settings,
|
|
230
|
+
paidWalFrost: frost,
|
|
231
|
+
say: options.json === true ? (line) => void process.stderr.write(`${line}\n`) : say,
|
|
232
|
+
...(options.tip ?? {}),
|
|
233
|
+
});
|
|
195
234
|
return 0;
|
|
196
235
|
}
|
|
197
236
|
/** What the numbers say, for a person, in the order somebody deciding needs them. */
|
|
@@ -213,7 +252,7 @@ function describe(say, facts, cohort, unreachable) {
|
|
|
213
252
|
say(``);
|
|
214
253
|
// ⛔ REQUIRED, AND IT COMES BEFORE THE AGREEMENT. Every other purchase in this tool spends
|
|
215
254
|
// credits; this one spends assets out of a wallet, and nobody can put them back.
|
|
216
|
-
say(` This is paid in WAL from the wallet this
|
|
255
|
+
say(` This is paid in WAL from the wallet this NMTS key derives — not from credits, which`);
|
|
217
256
|
say(` is what every other command in this tool spends. \`${BINARY_NAME} wallet\` shows what is in it.`);
|
|
218
257
|
}
|
|
219
258
|
/** Why a file has nothing to extend, said as the two different things it can be. */
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ByteDestination } from "../stdout.ts";
|
|
2
|
+
export interface GetOptions {
|
|
3
|
+
server?: string | undefined;
|
|
4
|
+
network?: string | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Where to write it. Defaults to the file's own name, in the working directory.
|
|
7
|
+
*
|
|
8
|
+
* `-` means stdout: nothing is written and the bytes go to whatever is reading this program.
|
|
9
|
+
*/
|
|
10
|
+
out?: string | undefined;
|
|
11
|
+
/** Overwrite an existing file. Off by default, and saying so is the point. */
|
|
12
|
+
force?: boolean;
|
|
13
|
+
json?: boolean;
|
|
14
|
+
write?: (line: string) => void;
|
|
15
|
+
/** Where the file's own bytes go when `out` is `-`. Injectable so a test can read them. */
|
|
16
|
+
stdout?: ByteDestination;
|
|
17
|
+
}
|
|
18
|
+
export declare function get(target: string | undefined, options?: GetOptions): Promise<number>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ParsedArgs } from "../args.ts";
|
|
2
|
+
export interface KeyManageOptions {
|
|
3
|
+
server?: string | undefined;
|
|
4
|
+
json?: boolean;
|
|
5
|
+
write?: (line: string) => void;
|
|
6
|
+
/** Injected in tests: answers the tier gate's y/N. */
|
|
7
|
+
readLine?: ((question: string) => Promise<string>) | undefined;
|
|
8
|
+
}
|
|
9
|
+
export declare function keyList(options?: KeyManageOptions): Promise<number>;
|
|
10
|
+
/** `nmts key revoke <id|all>`: the refusals, the tier gate's question, then the proof and the cut. */
|
|
11
|
+
export declare function keyRevoke(target: string | undefined, args: ParsedArgs, options?: KeyManageOptions): Promise<number>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// `nmts key list` and `nmts key revoke <id|all>` — the account's keys, seen and cut from a
|
|
2
|
+
// terminal that holds the NMTS key.
|
|
3
|
+
//
|
|
4
|
+
// ⛔ THE CODE, NEVER THE KEY. Both doors here are sessionless and take the NMTS key's derived
|
|
5
|
+
// proof, exactly as `key new` does (`key.ts`). A key is refused by the server on purpose: a key
|
|
6
|
+
// that could list keys hands whoever stole it the owner's whole automation inventory, and a
|
|
7
|
+
// key that could revoke keys could switch every other program off while staying alive. So the
|
|
8
|
+
// credential this command presents is the one thing a key cannot forge, and the server judges
|
|
9
|
+
// it the way it judges a sign-in — same lockout, same cost to guess.
|
|
10
|
+
//
|
|
11
|
+
// ⛔ REVOKING IS A MEDIUM ACT (`risk.ts` "key.revoke"): the tier gate asks a person once in mode
|
|
12
|
+
// default and lets an auto mode through with --yes, because whatever used that key stops the
|
|
13
|
+
// moment it is cut and there is no way to bring it back — only to make another.
|
|
14
|
+
import { identityOf } from "../account.js";
|
|
15
|
+
import { accountProofFor } from "../account-proof.js";
|
|
16
|
+
import { request, ServerError } from "../api.js";
|
|
17
|
+
import { requireAccountCode } from "../code-access.js";
|
|
18
|
+
import { readCredentialsFile } from "../credentials.js";
|
|
19
|
+
import { NmtsError } from "../errors.js";
|
|
20
|
+
import { gate } from "../gate.js";
|
|
21
|
+
import { isRecord } from "../guards.js";
|
|
22
|
+
import { BINARY_NAME } from "../product.js";
|
|
23
|
+
import { resolveServer } from "../server.js";
|
|
24
|
+
import { scopeNames } from "./key.js";
|
|
25
|
+
function asKeys(value) {
|
|
26
|
+
if (!isRecord(value) || !Array.isArray(value["keys"])) {
|
|
27
|
+
throw new NmtsError("The server listed the keys in a shape this version cannot read.", {
|
|
28
|
+
nextStep: `Update this tool — \`npm install -g ${BINARY_NAME}\` — or read them in a browser.`,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const row of value["keys"]) {
|
|
33
|
+
if (!isRecord(row))
|
|
34
|
+
continue;
|
|
35
|
+
const id = row["key_id"];
|
|
36
|
+
const scopes = row["scopes"];
|
|
37
|
+
const created = row["created_at"];
|
|
38
|
+
const expires = row["expires_at"];
|
|
39
|
+
if (typeof id !== "string" || typeof scopes !== "number" || typeof created !== "string")
|
|
40
|
+
continue;
|
|
41
|
+
out.push({
|
|
42
|
+
key_id: id,
|
|
43
|
+
scopes,
|
|
44
|
+
created_at: created,
|
|
45
|
+
expires_at: typeof expires === "string" ? expires : created,
|
|
46
|
+
last_used_at: typeof row["last_used_at"] === "string" ? row["last_used_at"] : null,
|
|
47
|
+
revoked_at: typeof row["revoked_at"] === "string" ? row["revoked_at"] : null,
|
|
48
|
+
uses: typeof row["uses"] === "number" ? row["uses"] : 0,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/** The account named and proven, the way `key new` names and proves it. */
|
|
54
|
+
async function provenAccount() {
|
|
55
|
+
const held = await requireAccountCode();
|
|
56
|
+
const identity = await identityOf(held.code);
|
|
57
|
+
const authSecret = await accountProofFor({ code: held.code, source: held.source });
|
|
58
|
+
return { account_id: identity.accountId, auth_secret: authSecret };
|
|
59
|
+
}
|
|
60
|
+
export async function keyList(options = {}) {
|
|
61
|
+
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
62
|
+
const stored = readCredentialsFile();
|
|
63
|
+
const server = resolveServer(options.server ?? stored?.server);
|
|
64
|
+
const proof = await provenAccount();
|
|
65
|
+
const keys = asKeys(await request(server, "/v1/account/api-keys/list-by-code", {
|
|
66
|
+
method: "POST",
|
|
67
|
+
body: proof,
|
|
68
|
+
retryBudgetMs: 0,
|
|
69
|
+
}));
|
|
70
|
+
// The key string on this machine carries its id after the prefix (`nmts_ak1_<id>_<secret>`),
|
|
71
|
+
// so the row that is "this one" is known without the secret ever being compared or printed.
|
|
72
|
+
const mine = stored?.apiKey ?? "";
|
|
73
|
+
if (options.json === true) {
|
|
74
|
+
say(JSON.stringify({
|
|
75
|
+
keys: keys.map((k) => ({
|
|
76
|
+
key_id: k.key_id,
|
|
77
|
+
scopes: scopeNames(k.scopes),
|
|
78
|
+
created_at: k.created_at,
|
|
79
|
+
expires_at: k.expires_at,
|
|
80
|
+
last_used_at: k.last_used_at,
|
|
81
|
+
revoked_at: k.revoked_at,
|
|
82
|
+
uses: k.uses,
|
|
83
|
+
this_machine: mine.includes(k.key_id),
|
|
84
|
+
})),
|
|
85
|
+
}));
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
if (keys.length === 0) {
|
|
89
|
+
say(`This account has no API keys.`);
|
|
90
|
+
say(``);
|
|
91
|
+
say(` \`${BINARY_NAME} key new\` makes one for this machine from the NMTS key it holds.`);
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
const live = keys.filter((k) => k.revoked_at === null);
|
|
95
|
+
for (const k of keys) {
|
|
96
|
+
say(`${k.key_id}${mine.includes(k.key_id) ? " ← this machine" : ""}`);
|
|
97
|
+
say(` may ${scopeNames(k.scopes).join(", ")}`);
|
|
98
|
+
say(` made ${k.created_at}`);
|
|
99
|
+
if (k.revoked_at !== null) {
|
|
100
|
+
say(` revoked ${k.revoked_at}`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
say(` stops ${k.expires_at}`);
|
|
104
|
+
}
|
|
105
|
+
say(` last used ${k.last_used_at ?? "never"} (${k.uses} ${k.uses === 1 ? "use" : "uses"})`);
|
|
106
|
+
}
|
|
107
|
+
say(``);
|
|
108
|
+
say(` ${live.length} live of ${keys.length}. The key strings are not here: NMTS keeps no copy of them.`);
|
|
109
|
+
say(` \`${BINARY_NAME} key revoke <id>\` (or \`all\`) cuts one — whatever uses it stops at once.`);
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
/** `nmts key revoke <id|all>`: the refusals, the tier gate's question, then the proof and the cut. */
|
|
113
|
+
export async function keyRevoke(target, args, options = {}) {
|
|
114
|
+
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
115
|
+
const wanted = (target ?? "").trim();
|
|
116
|
+
if (wanted === "") {
|
|
117
|
+
throw new NmtsError(`\`${BINARY_NAME} key revoke\` needs a key id, or \`all\`.`, {
|
|
118
|
+
exitCode: 2,
|
|
119
|
+
nextStep: `\`${BINARY_NAME} key list\` shows the ids.`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
await gate("key.revoke", args, { write: say, readLine: options.readLine });
|
|
123
|
+
const stored = readCredentialsFile();
|
|
124
|
+
const server = resolveServer(options.server ?? stored?.server);
|
|
125
|
+
const proof = await provenAccount();
|
|
126
|
+
const all = wanted === "all";
|
|
127
|
+
let answered;
|
|
128
|
+
try {
|
|
129
|
+
answered = await request(server, "/v1/account/api-keys/revoke-by-code", {
|
|
130
|
+
method: "POST",
|
|
131
|
+
body: all ? proof : { ...proof, key_id: wanted },
|
|
132
|
+
retryBudgetMs: 0,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (error instanceof ServerError && error.status === 404) {
|
|
137
|
+
throw new NmtsError(`No live key ${wanted} on this account.`, {
|
|
138
|
+
exitCode: 1,
|
|
139
|
+
nextStep: `\`${BINARY_NAME} key list\` shows the ids that exist and which are already revoked.`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
const revoked = isRecord(answered) && typeof answered["revoked"] === "number" ? answered["revoked"] : null;
|
|
145
|
+
if (options.json === true) {
|
|
146
|
+
say(JSON.stringify({ revoked: revoked ?? (all ? null : 1) }));
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
if (all) {
|
|
150
|
+
say(revoked === null ? `Revoked every key.` : `Revoked ${revoked} key${revoked === 1 ? "" : "s"}.`);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
say(`Revoked key ${wanted}.`);
|
|
154
|
+
}
|
|
155
|
+
const mine = stored?.apiKey ?? "";
|
|
156
|
+
if (all || mine.includes(wanted)) {
|
|
157
|
+
say(``);
|
|
158
|
+
say(` The key stored on this machine was among them, so commands that need a key will be`);
|
|
159
|
+
say(` refused until \`${BINARY_NAME} key new\` makes another.`);
|
|
160
|
+
}
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ParsedArgs } from "../args.ts";
|
|
2
|
+
export interface KeyOptions {
|
|
3
|
+
server?: string | undefined;
|
|
4
|
+
network?: string | undefined;
|
|
5
|
+
/** Comma-separated permission names: read, write, spend. */
|
|
6
|
+
scopes?: string | undefined;
|
|
7
|
+
/** How many days the key should last. The server clamps at its own ceiling. */
|
|
8
|
+
days?: string | undefined;
|
|
9
|
+
/** Put the key string on the screen as well. A person's act — see the header. */
|
|
10
|
+
print?: boolean;
|
|
11
|
+
json?: boolean;
|
|
12
|
+
write?: (line: string) => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* `nmts key <verb>`.
|
|
16
|
+
*
|
|
17
|
+
* ⛔ A VERB AND NOT A BARE COMMAND. Three verbs: `new` mints, `list` shows, `revoke` cuts
|
|
18
|
+
* (`key-manage.ts` for the last two). All three present the NMTS key's proof and none is
|
|
19
|
+
* reachable with a key — a key cannot cut another key off, and that is what makes revoking
|
|
20
|
+
* mean something. `key` with no verb says which ones exist rather than doing one of them, so
|
|
21
|
+
* `nmts key` never turns out to have made something.
|
|
22
|
+
*/
|
|
23
|
+
export declare function key(verb: string | undefined, args: ParsedArgs): Promise<number>;
|
|
24
|
+
/** Turn `read,write` into the bitmask the server takes, refusing anything it does not define. */
|
|
25
|
+
export declare function scopeMask(spelled: string): number;
|
|
26
|
+
/** The permission names a bitmask stands for, in the order they are defined. */
|
|
27
|
+
export declare function scopeNames(mask: number): string[];
|
|
28
|
+
export declare function keyNew(options?: KeyOptions): Promise<number>;
|