@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,194 @@
|
|
|
1
|
+
// Talking to the support routes, and reading what they answer.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ SPLIT OUT OF THE COMMAND, AND THE LENGTH GATE IS THE HONEST REASON — but the line it was cut
|
|
4
|
+
// on means something: everything here is about the SERVER (its addresses, its limits, its
|
|
5
|
+
// refusals and the shapes it sends back), and everything left in `commands/support.ts` is
|
|
6
|
+
// about the person (what they are shown, what they are asked, what is printed).
|
|
7
|
+
//
|
|
8
|
+
// ⛔ THE SERVER'S LIMITS ARE MIRRORED, NOT GUESSED. A message too long is refused here rather than
|
|
9
|
+
// after a round trip, so the words somebody typed are still in their terminal when they are
|
|
10
|
+
// told. ⚠ A mirror goes stale: the server is the authority, and its 400 is passed through with
|
|
11
|
+
// the categories named beside it.
|
|
12
|
+
//
|
|
13
|
+
// ⛔ NOTHING HERE OPENS THE NMTS KEY. The API key is the whole credential these four routes
|
|
14
|
+
// need, so this resolves that and the server address and nothing else.
|
|
15
|
+
import { request, ServerError } from "./api.js";
|
|
16
|
+
import { readCredentialsFile } from "./credentials.js";
|
|
17
|
+
import { NmtsError } from "./errors.js";
|
|
18
|
+
import { isRecord } from "./guards.js";
|
|
19
|
+
import { SHORTEST_OMIT } from "./redact.js";
|
|
20
|
+
import { resolveServer } from "./server.js";
|
|
21
|
+
import { requireApiKey } from "./session.js";
|
|
22
|
+
import { categoryCodes, SUPPORT_CATEGORIES } from "./support-copy.js";
|
|
23
|
+
/** The server's own ceilings on what one message may carry. */
|
|
24
|
+
export const MAX_MESSAGE_CHARS = 4000;
|
|
25
|
+
export const MAX_REPLY_CHARS = 8000;
|
|
26
|
+
export function open(options) {
|
|
27
|
+
const apiKey = requireApiKey();
|
|
28
|
+
const stored = readCredentialsFile();
|
|
29
|
+
return { server: resolveServer(options.server ?? stored?.server), apiKey };
|
|
30
|
+
}
|
|
31
|
+
export async function call(wire, path, init) {
|
|
32
|
+
try {
|
|
33
|
+
return await request(wire.server, path, { token: wire.apiKey, ...init });
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
throw translate(error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The refusals this command has something to add to.
|
|
41
|
+
*
|
|
42
|
+
* ⛔ 409 KEEPS THE SERVER'S OWN SENTENCE. It means this exact message was already sent, and the
|
|
43
|
+
* server is the half that knows why; a second wording here would be a guess at its reason.
|
|
44
|
+
*
|
|
45
|
+
* ⛔ A VALIDATION REFUSAL IS 400 AND NAMES ITS FIELD ("category: unknown", "message: too long").
|
|
46
|
+
* It is a command-line mistake, so it gets the command-line exit code rather than the generic
|
|
47
|
+
* one — and the category list is added only to the refusal that is about a category, because a
|
|
48
|
+
* list of categories under "message: too long" would be an answer to a question nobody asked.
|
|
49
|
+
*/
|
|
50
|
+
function translate(error) {
|
|
51
|
+
if (!(error instanceof ServerError))
|
|
52
|
+
return error;
|
|
53
|
+
if (error.status === 429) {
|
|
54
|
+
const wait = error.retryAfter;
|
|
55
|
+
return new NmtsError(wait === null
|
|
56
|
+
? error.message
|
|
57
|
+
: `NMTS is asking you to wait ${wait} seconds before another message.`, {
|
|
58
|
+
exitCode: 1,
|
|
59
|
+
nextStep: `Nothing was sent. This is a limit on how often, not a refusal of what was written.`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (error.status === 400 && error.code === "VALIDATION") {
|
|
63
|
+
return new NmtsError(error.message, {
|
|
64
|
+
exitCode: 2,
|
|
65
|
+
...(error.message.startsWith("category")
|
|
66
|
+
? { nextStep: `The categories are ${categoryCodes().join(" · ")}.` }
|
|
67
|
+
: {}),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// ⚠ The reports this rail lists are the ones FILED FROM IT. A ticket written in the browser, and
|
|
71
|
+
// any other account's, are both 404 here — so the refusal points at the list rather than
|
|
72
|
+
// suggesting the code was mistyped.
|
|
73
|
+
if (error.status === 404) {
|
|
74
|
+
return new NmtsError(error.message, {
|
|
75
|
+
exitCode: 4,
|
|
76
|
+
nextStep: `\`nmts support list\` prints the reports filed from the command line.`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return error;
|
|
80
|
+
}
|
|
81
|
+
function unreadable() {
|
|
82
|
+
return new NmtsError("The server answered with a report this version cannot read.", {
|
|
83
|
+
nextStep: "A newer version of this tool may understand it.",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function text(value, fallback = "") {
|
|
87
|
+
return typeof value === "string" ? value : fallback;
|
|
88
|
+
}
|
|
89
|
+
function optional(value) {
|
|
90
|
+
return typeof value === "string" ? value : null;
|
|
91
|
+
}
|
|
92
|
+
export function asTicket(value) {
|
|
93
|
+
if (!isRecord(value))
|
|
94
|
+
throw unreadable();
|
|
95
|
+
const id = value["id"];
|
|
96
|
+
const code = value["code"];
|
|
97
|
+
if (typeof id !== "string" || typeof code !== "string")
|
|
98
|
+
throw unreadable();
|
|
99
|
+
const rows = value["messages"];
|
|
100
|
+
return {
|
|
101
|
+
id,
|
|
102
|
+
code,
|
|
103
|
+
category: text(value["category"], "other"),
|
|
104
|
+
subcategory: optional(value["subcategory"]),
|
|
105
|
+
message: text(value["message"]),
|
|
106
|
+
status: text(value["status"], "open"),
|
|
107
|
+
createdAt: text(value["created_at"]),
|
|
108
|
+
lastReadAt: optional(value["last_read_at"]),
|
|
109
|
+
messages: Array.isArray(rows) ? rows.filter(isRecord).map(asMessage) : null,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function asMessage(row) {
|
|
113
|
+
return {
|
|
114
|
+
fromOperator: row["from_operator"] === true,
|
|
115
|
+
body: text(row["body"]),
|
|
116
|
+
createdAt: text(row["created_at"]),
|
|
117
|
+
deletedAt: optional(row["deleted_at"]),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export function asList(value) {
|
|
121
|
+
if (!isRecord(value))
|
|
122
|
+
throw unreadable();
|
|
123
|
+
const rows = value["tickets"];
|
|
124
|
+
if (!Array.isArray(rows))
|
|
125
|
+
throw unreadable();
|
|
126
|
+
const unread = value["unread"];
|
|
127
|
+
return {
|
|
128
|
+
tickets: rows.map(asTicket),
|
|
129
|
+
unread: typeof unread === "number" ? unread : 0,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** The date part of an instant. The time of day is noise in a list of reports. */
|
|
133
|
+
export function day(at) {
|
|
134
|
+
return at.slice(0, 10).padEnd(10);
|
|
135
|
+
}
|
|
136
|
+
export function requireCategory(given) {
|
|
137
|
+
const codes = categoryCodes();
|
|
138
|
+
if (given === undefined) {
|
|
139
|
+
throw new NmtsError("`support send` needs --category.", {
|
|
140
|
+
exitCode: 2,
|
|
141
|
+
nextStep: `The categories are ${codes.join(" · ")}.`,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (!codes.includes(given)) {
|
|
145
|
+
throw new NmtsError(`There is no support category called ${given}.`, {
|
|
146
|
+
exitCode: 2,
|
|
147
|
+
nextStep: `The categories are ${codes.join(" · ")}.`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return given;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The narrowing, checked against the category it was given with.
|
|
154
|
+
*
|
|
155
|
+
* ⚠ It is optional everywhere, and stays optional here: somebody who does not know which one
|
|
156
|
+
* applies must be able to send anyway.
|
|
157
|
+
*/
|
|
158
|
+
export function checkSub(category, given) {
|
|
159
|
+
if (given === undefined)
|
|
160
|
+
return null;
|
|
161
|
+
const subs = SUPPORT_CATEGORIES.find((c) => c.code === category)?.subs ?? [];
|
|
162
|
+
if (!subs.includes(given)) {
|
|
163
|
+
throw new NmtsError(`${category} has no part called ${given}.`, {
|
|
164
|
+
exitCode: 2,
|
|
165
|
+
nextStep: `Its parts are ${subs.join(" · ")}.`,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return given;
|
|
169
|
+
}
|
|
170
|
+
/** Values too short to name one thing would blank half the report, so they are refused. */
|
|
171
|
+
export function checkOmit(values) {
|
|
172
|
+
for (const value of values) {
|
|
173
|
+
if (value.length < SHORTEST_OMIT) {
|
|
174
|
+
throw new NmtsError(`--omit needs at least ${SHORTEST_OMIT} characters: "${value}" is shorter.`, {
|
|
175
|
+
exitCode: 2,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return [...values];
|
|
180
|
+
}
|
|
181
|
+
export function requireLength(message, most, what) {
|
|
182
|
+
if (message === "") {
|
|
183
|
+
throw new NmtsError(`There is no ${what} to send.`, {
|
|
184
|
+
exitCode: 2,
|
|
185
|
+
nextStep: `Pass --message <text>, --message-file <path>, or pipe it in.`,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
const characters = Array.from(message).length;
|
|
189
|
+
if (characters > most) {
|
|
190
|
+
throw new NmtsError(`A ${what} is at most ${most} characters, and this one is ${characters}.`, {
|
|
191
|
+
exitCode: 2,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type ManifestIndex } from "./drive-paths.ts";
|
|
2
|
+
import type { ManifestEntry } from "./shared/lib/drive/manifest-codec.ts";
|
|
3
|
+
/**
|
|
4
|
+
* How long a trashed item stays restorable.
|
|
5
|
+
*
|
|
6
|
+
* ⛔ It must match the browser's `web/src/lib/drive/manifest-views.ts::TRASH_RETENTION_DAYS`, and
|
|
7
|
+
* both of those describe the same promise the server enforces on its own rows. A tool that
|
|
8
|
+
* counted to a different number would either drop an entry whose file the person could still
|
|
9
|
+
* have restored, or leave one behind for a file whose key the server had already destroyed.
|
|
10
|
+
*/
|
|
11
|
+
export declare const TRASH_RETENTION_DAYS = 30;
|
|
12
|
+
/**
|
|
13
|
+
* Whole days left of the thirty, counting up rather than down.
|
|
14
|
+
*
|
|
15
|
+
* Rounded UP so a row with four hours left says "1 day left" rather than "0 days left" — the
|
|
16
|
+
* number is what somebody decides whether to restore on, and rounding it to nothing reads as
|
|
17
|
+
* "already gone".
|
|
18
|
+
*/
|
|
19
|
+
export declare function daysLeftInTrash(trashedAtMs: number, nowMs: number): number;
|
|
20
|
+
/**
|
|
21
|
+
* Entries whose thirty days have run out.
|
|
22
|
+
*
|
|
23
|
+
* Measured from the INHERITED instant, so a file inside a trashed folder is measured from the
|
|
24
|
+
* moment the folder was thrown away — which is also the moment the server stamped on its row.
|
|
25
|
+
* Reading each entry's own `deletedAt` would leave every file under a swept folder in the list.
|
|
26
|
+
*/
|
|
27
|
+
export declare function expiredTrashEntries(index: ManifestIndex, nowMs: number): ManifestEntry[];
|
|
28
|
+
/** What one sweep may do, and what it must not do yet. */
|
|
29
|
+
export interface PurgePlan {
|
|
30
|
+
/** Entries that may leave the file list now. */
|
|
31
|
+
drop: readonly ManifestEntry[];
|
|
32
|
+
/** Expired entries the server has not let go of yet. Nothing is wrong; it sweeps on a timer. */
|
|
33
|
+
waiting: readonly ManifestEntry[];
|
|
34
|
+
/** Expired entries held only because something underneath them is being kept. */
|
|
35
|
+
tangled: readonly ManifestEntry[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Split the expired entries into what may go and what may not.
|
|
39
|
+
*
|
|
40
|
+
* `stillOnServer` is every item id the server still has a row for, live or trashed. An id in that
|
|
41
|
+
* set has a key the server has not destroyed, so dropping this side's copy would hide a file that
|
|
42
|
+
* is still stored and still being paid for.
|
|
43
|
+
*
|
|
44
|
+
* ⚠ Folder entries never appear in that set — the server holds no row for a folder — so a folder
|
|
45
|
+
* is held back only by the branch rule below, which is exactly when it should be.
|
|
46
|
+
*/
|
|
47
|
+
export declare function planPurge(index: ManifestIndex, expired: readonly ManifestEntry[], stillOnServer: ReadonlySet<string>): PurgePlan;
|
|
48
|
+
/**
|
|
49
|
+
* How many of the entries about to be dropped hold bytes.
|
|
50
|
+
*
|
|
51
|
+
* Said out loud before the write, because a count of ENTRIES reads as harmless — most of a trashed
|
|
52
|
+
* branch is folders, which hold nothing at all — and the number that matters is how many keys are
|
|
53
|
+
* being destroyed.
|
|
54
|
+
*/
|
|
55
|
+
export declare function filesAmong(entries: readonly ManifestEntry[]): number;
|
package/dist/units.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export { checkingIsOff, NO_CHECK_ENV_VAR } from "./update-source.ts";
|
|
2
|
+
/** How long an answer is kept before another is asked for. */
|
|
3
|
+
export declare const CHECK_EVERY_MS: number;
|
|
4
|
+
/** What the last attempt found, or why it found nothing. */
|
|
5
|
+
export interface UpdateCheck {
|
|
6
|
+
/** When the last attempt happened — whether or not it answered. */
|
|
7
|
+
checkedAt: string;
|
|
8
|
+
/** The newest version the last attempt that DID answer found. */
|
|
9
|
+
latest?: string;
|
|
10
|
+
/** Why the last attempt did not answer. Absent when it did. */
|
|
11
|
+
failed?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function checkPath(): string;
|
|
14
|
+
/** What is on disk, or null when there is nothing readable there. */
|
|
15
|
+
export declare function readCheck(): UpdateCheck | null;
|
|
16
|
+
/** Write the attempt down. Failing to write is itself ignored: there is nothing to fall back to. */
|
|
17
|
+
export declare function writeCheck(record: UpdateCheck): void;
|
|
18
|
+
/** Is it time to ask again? Never having asked counts as due. */
|
|
19
|
+
export declare function dueForCheck(record: UpdateCheck | null, nowMs: number): boolean;
|
|
20
|
+
/** What one lookup produced. */
|
|
21
|
+
export type Lookup = {
|
|
22
|
+
version: string;
|
|
23
|
+
} | {
|
|
24
|
+
failed: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Ask which release is newest.
|
|
28
|
+
*
|
|
29
|
+
* ⛔ THE REDIRECT IS THE ANSWER, so it is not followed. `releases/latest` replies with the address
|
|
30
|
+
* of the tagged page and an empty body; following it would download a page this has no use for.
|
|
31
|
+
*/
|
|
32
|
+
export declare function lookupLatest(url?: string): Promise<Lookup>;
|
|
33
|
+
export interface NoteOptions {
|
|
34
|
+
/** The version running now. */
|
|
35
|
+
running: string;
|
|
36
|
+
/** The clock. Injected so a test does not depend on today. */
|
|
37
|
+
now?: Date;
|
|
38
|
+
/** Where the notice goes. Defaults to stderr. */
|
|
39
|
+
say?: (line: string) => void;
|
|
40
|
+
/** The lookup. Injected so a test does not depend on a host. */
|
|
41
|
+
lookup?: () => Promise<Lookup>;
|
|
42
|
+
env?: NodeJS.ProcessEnv;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Print the notice this run has earned, then refresh the file for the next one.
|
|
46
|
+
*
|
|
47
|
+
* The order is deliberate: whatever is already known is said first, so a slow or unreachable host
|
|
48
|
+
* cannot delay it, and the run that pays for the lookup is not the run that reads its result.
|
|
49
|
+
*/
|
|
50
|
+
export declare function noteUpdate(options: NoteOptions): Promise<void>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Setting this to anything stops the lookup and the notice, both. */
|
|
2
|
+
export declare const NO_CHECK_ENV_VAR = "NMTS_NO_UPDATE_CHECK";
|
|
3
|
+
/** Is the version check switched off here? */
|
|
4
|
+
export declare function checkingIsOff(env?: NodeJS.ProcessEnv): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* The address that names the newest release whatever it is called.
|
|
7
|
+
*
|
|
8
|
+
* It answers with a redirect to the tagged page, and the tag carries the version. That is one
|
|
9
|
+
* request with an empty body — cheaper than the release listing, and it needs no credential.
|
|
10
|
+
*/
|
|
11
|
+
export declare const LATEST_RELEASE_URL = "https://github.com/needmoretruth/nmts-cli/releases/latest";
|
|
12
|
+
/** Is this string a version this file is willing to reason about? */
|
|
13
|
+
export declare function isVersion(value: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The version a `releases/latest` redirect landed on, or null.
|
|
16
|
+
*
|
|
17
|
+
* ⛔ THE ADDRESS IS CHECKED, NOT JUST READ. It arrives from the network, and a redirect that
|
|
18
|
+
* pointed somewhere else entirely would otherwise decide what this tool calls the newest
|
|
19
|
+
* release. It has to be the tag page of this repository, over an encrypted connection, and the
|
|
20
|
+
* tag itself has to be a version.
|
|
21
|
+
*/
|
|
22
|
+
export declare function versionFromLocation(location: string): string | null;
|
|
23
|
+
/**
|
|
24
|
+
* -1, 0 or 1 — or null when either side is not a version this can compare.
|
|
25
|
+
*
|
|
26
|
+
* ⚠ Null is not "equal". A caller that treats it as equal says nothing; one that treats it as
|
|
27
|
+
* "newer" nags forever about a release that does not exist. Both callers here read it as
|
|
28
|
+
* "no answer".
|
|
29
|
+
*/
|
|
30
|
+
export declare function compareVersions(left: string, right: string): number | null;
|
|
31
|
+
/** Is `candidate` a release later than the one running? Unanswerable counts as no. */
|
|
32
|
+
export declare function isNewer(candidate: string, running: string): boolean;
|
|
33
|
+
/** The page a person can read about one release on. */
|
|
34
|
+
export declare function releasePageUrl(version: string): string | null;
|
|
35
|
+
/**
|
|
36
|
+
* Where one release's package is.
|
|
37
|
+
*
|
|
38
|
+
* ⛔ THE TAGGED ADDRESS, NEVER "LATEST". This is handed to another program to install, and
|
|
39
|
+
* "latest" would install whatever is newest at the moment that program runs — which is not
|
|
40
|
+
* necessarily the version this tool just named on the screen. Naming the release means what
|
|
41
|
+
* was reported and what gets installed are the same thing.
|
|
42
|
+
*/
|
|
43
|
+
export declare function packageUrl(version: string): string | null;
|
|
44
|
+
/**
|
|
45
|
+
* The exact command that installs one release, as an argument list.
|
|
46
|
+
*
|
|
47
|
+
* ⛔ A LIST AND NOT A STRING, because it is both printed and run. A string would have to be split
|
|
48
|
+
* again to be run, and splitting a string is where a shell gets involved — the one thing a
|
|
49
|
+
* command built partly from a value off the network must not touch. `packageUrl` has already
|
|
50
|
+
* refused anything that is not three numbers, so nothing here can carry a space, a quote or a
|
|
51
|
+
* semicolon, and it never needs to.
|
|
52
|
+
*/
|
|
53
|
+
export declare function installCommand(version: string): readonly string[] | null;
|
|
54
|
+
/** The same command as one line, for printing. */
|
|
55
|
+
export declare function installCommandLine(version: string): string | null;
|
|
56
|
+
/**
|
|
57
|
+
* The one line printed when a newer release exists.
|
|
58
|
+
*
|
|
59
|
+
* ⛔ IT SAYS WHAT IS PUBLISHED AND WHAT IS RUNNING, AND NOTHING ABOUT WHETHER TO UPGRADE. What is
|
|
60
|
+
* in a release is not known here, and a line that called it important, recommended or a fix
|
|
61
|
+
* would be inventing a reason to act.
|
|
62
|
+
*/
|
|
63
|
+
export declare function newerVersionLine(latest: string, running: string, binary: string): string;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type CryptoGlue } from "./crypto.ts";
|
|
2
|
+
import { type PaddingRule } from "./shared/lib/crypto/size-padding.ts";
|
|
3
|
+
import { entryOf } from "./upload.ts";
|
|
4
|
+
import type { BlobProtocol, PaidPart, UploadApi, UploadInput, UploadResult, UploadStep } from "./upload-wire.ts";
|
|
5
|
+
/**
|
|
6
|
+
* Where the plaintext comes from.
|
|
7
|
+
*
|
|
8
|
+
* ⛔ A SEAM, NOT A CONVENIENCE. The tests drive every branch of a multi-part upload — including
|
|
9
|
+
* the ones that resume after a part was paid for — without a file and without a network. A
|
|
10
|
+
* module that opened the path itself could only be tested by writing gigabytes to a disk.
|
|
11
|
+
*/
|
|
12
|
+
export interface PlaintextSource {
|
|
13
|
+
/** Total plaintext bytes. */
|
|
14
|
+
size: number;
|
|
15
|
+
/** Read `[offset, offset + length)`, in pieces small enough to hold. */
|
|
16
|
+
read(offset: number, length: number): AsyncIterable<Uint8Array>;
|
|
17
|
+
}
|
|
18
|
+
/** Read a file off the disk, a chunk at a time. */
|
|
19
|
+
export declare function fileSource(path: string, size: number): PlaintextSource;
|
|
20
|
+
export interface FileUploadInput {
|
|
21
|
+
api: UploadApi;
|
|
22
|
+
protocol: BlobProtocol;
|
|
23
|
+
crypt: CryptoGlue;
|
|
24
|
+
/** The account's data key. Borrowed — the caller wipes it. */
|
|
25
|
+
dataKey: Uint8Array;
|
|
26
|
+
source: PlaintextSource;
|
|
27
|
+
/** What the file is called in the account. */
|
|
28
|
+
name: string;
|
|
29
|
+
/** The folder id it goes in, or null for the root. */
|
|
30
|
+
parentId: string | null;
|
|
31
|
+
/** The destination AS TYPED. Part of the reservation key — see `upload-store.ts`. */
|
|
32
|
+
destination: string;
|
|
33
|
+
relayUrl: string;
|
|
34
|
+
epochs: number;
|
|
35
|
+
currentEpoch: number | null;
|
|
36
|
+
/** How much of the file goes into one part. */
|
|
37
|
+
partSize: number;
|
|
38
|
+
/**
|
|
39
|
+
* How coarsely the file's LAST part is rounded up before sealing, and the billing unit that
|
|
40
|
+
* decides how much of that rounding is free.
|
|
41
|
+
*
|
|
42
|
+
* ⛔ THE ACCOUNT'S CHOICE, NOT THIS TOOL'S. A stored stream states in the clear the length it was
|
|
43
|
+
* sealed from, so without this the exact byte length of everything this tool uploads is
|
|
44
|
+
* legible to anybody who fetches it from the storage network. Rounding differently from the
|
|
45
|
+
* browser would be worse than not rounding: it would say which program uploaded the file.
|
|
46
|
+
*/
|
|
47
|
+
padding: {
|
|
48
|
+
rule: PaddingRule;
|
|
49
|
+
unitBytes: number;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* How many credits this file sets aside as a deposit, 0 to 64. Absent on the wallet rail, which
|
|
53
|
+
* buys its own storage and has no treasury deposit to set aside against it.
|
|
54
|
+
*
|
|
55
|
+
* ⛔ ONE NUMBER FOR THE WHOLE FILE, sent with every part's reservation, because the deposit is a
|
|
56
|
+
* property of the FILE the person chose it for — not of how many parts it happened to need.
|
|
57
|
+
*/
|
|
58
|
+
depositCredits?: number;
|
|
59
|
+
onStep?: (step: FileUploadStep) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Who buys ONE part and gets its bytes onto the network. Absent = the credit rail
|
|
62
|
+
* (`buyAndPushPart`); the wallet rail (`upload-wallet.ts`) supplies its own.
|
|
63
|
+
*
|
|
64
|
+
* ⛔ A SEAM SO THERE IS ONE FILE DRIVER. Sealing, the reservation key, the resume of a file
|
|
65
|
+
* already committed and the one-commit-per-file rule are the same whoever pays; a second
|
|
66
|
+
* driver for the wallet would be a second place for those to drift.
|
|
67
|
+
*/
|
|
68
|
+
buy?: (input: UploadInput) => Promise<PaidPart>;
|
|
69
|
+
}
|
|
70
|
+
/** Told about each part as it starts, so a long upload visibly moves. */
|
|
71
|
+
export type FileUploadStep = {
|
|
72
|
+
step: "planning";
|
|
73
|
+
parts: number;
|
|
74
|
+
partSize: number;
|
|
75
|
+
} | {
|
|
76
|
+
step: "hashing";
|
|
77
|
+
parts: number;
|
|
78
|
+
} | {
|
|
79
|
+
step: "sealing";
|
|
80
|
+
partIndex: number;
|
|
81
|
+
parts: number;
|
|
82
|
+
bytes: number;
|
|
83
|
+
} | ({
|
|
84
|
+
partIndex: number;
|
|
85
|
+
parts: number;
|
|
86
|
+
} & UploadStep);
|
|
87
|
+
/**
|
|
88
|
+
* Upload one file and return what the caller must write into the account's file list.
|
|
89
|
+
*
|
|
90
|
+
* ⛔ IT DOES NOT WRITE THE FILE LIST, and the caller must — before clearing the records. A
|
|
91
|
+
* committed file the list does not name is invisible and, to the person, indistinguishable from
|
|
92
|
+
* one that never uploaded.
|
|
93
|
+
*/
|
|
94
|
+
export declare function uploadFile(input: FileUploadInput): Promise<UploadResult>;
|
|
95
|
+
/** The file key's records, so a caller can clear them once the list is written. */
|
|
96
|
+
export declare function partKeysOf(fileKey: string, parts: number): string[];
|
|
97
|
+
export { entryOf };
|
package/dist/upload-file.js
CHANGED
|
@@ -120,7 +120,7 @@ export async function uploadFile(input) {
|
|
|
120
120
|
const sealed = stored === null
|
|
121
121
|
? await sealPartOf(input, secrets.dek, range, plan.length, sealFrom)
|
|
122
122
|
: readReservationBytes(key);
|
|
123
|
-
paid.push(await buyAndPushPart({
|
|
123
|
+
paid.push(await (input.buy ?? buyAndPushPart)({
|
|
124
124
|
api: input.api,
|
|
125
125
|
protocol: input.protocol,
|
|
126
126
|
key,
|
|
@@ -130,6 +130,7 @@ export async function uploadFile(input) {
|
|
|
130
130
|
currentEpoch: input.currentEpoch,
|
|
131
131
|
// ⚠ The length the STREAM declares, which for a padded last part is more than the file
|
|
132
132
|
// contributes. The list entry keeps the file's real size; this is what was sealed.
|
|
133
|
+
depositCredits: input.depositCredits ?? 0,
|
|
133
134
|
part: { index: range.partIndex, total: plan.length, plaintextLen: sealFrom },
|
|
134
135
|
entry,
|
|
135
136
|
onStep: (step) => onStep?.({ ...step, partIndex: range.partIndex, parts: plan.length }),
|
|
@@ -145,7 +146,7 @@ export async function uploadFile(input) {
|
|
|
145
146
|
return {
|
|
146
147
|
itemId,
|
|
147
148
|
resumed: paid.every((part) => part.resumed),
|
|
148
|
-
ledgerIds: paid.
|
|
149
|
+
ledgerIds: paid.flatMap((part) => (part.ledgerId === null ? [] : [part.ledgerId])),
|
|
149
150
|
fileKey,
|
|
150
151
|
parts: plan.length,
|
|
151
152
|
entry,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type PaddingRule } from "./shared/lib/crypto/size-padding.ts";
|
|
2
|
+
import { type PartRange } from "./shared/lib/upload/part-plan.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Storage term, in storage-network epochs.
|
|
5
|
+
*
|
|
6
|
+
* ⛔ NOT CHOOSABLE HERE, and that is a property of credits rather than of this tool: one credit is
|
|
7
|
+
* defined as one mebibyte for exactly this term. Offering a duration picker would be offering
|
|
8
|
+
* to spend a multiple of a unit the credit surface does not speak in. A browser's own-wallet
|
|
9
|
+
* upload has a picker because it is paying in WAL, which does divide.
|
|
10
|
+
*/
|
|
11
|
+
export declare const UPLOAD_EPOCHS = 2;
|
|
12
|
+
/** Bytes one credit stores for `UPLOAD_EPOCHS`. */
|
|
13
|
+
export declare const CREDIT_BYTES: number;
|
|
14
|
+
/**
|
|
15
|
+
* What this upload will cost, in credits — the same arithmetic the server does.
|
|
16
|
+
*
|
|
17
|
+
* Printed BEFORE anything is spent so the number can be compared with what the account screen
|
|
18
|
+
* shows afterwards. The server is still the authority; this is a quote, not a promise.
|
|
19
|
+
*/
|
|
20
|
+
export declare function creditsFor(sealedBytes: number, epochs?: number): number;
|
|
21
|
+
/**
|
|
22
|
+
* How big the local file is, with the two refusals that are worth their own words.
|
|
23
|
+
*
|
|
24
|
+
* ⛔ IT IS NOT READ HERE. A file large enough to need several parts is a file too large to hold,
|
|
25
|
+
* and the size is all that is needed to plan the upload and quote its price. The bytes are read
|
|
26
|
+
* later, a slice at a time, by the part that is being sealed.
|
|
27
|
+
*/
|
|
28
|
+
export declare function measureLocal(path: string): number;
|
|
29
|
+
/**
|
|
30
|
+
* How much of the file goes into one part.
|
|
31
|
+
*
|
|
32
|
+
* ⛔ THE SAME NUMBER ON A RESUME OR NOTHING MATCHES. The parts already written down were sealed
|
|
33
|
+
* and paid for at one size; a second run that split the file differently would be asking to
|
|
34
|
+
* push different bytes under reservations that bought the first ones. `buyAndPushPart` refuses
|
|
35
|
+
* that rather than doing it, and this is where the number comes from.
|
|
36
|
+
*/
|
|
37
|
+
export declare function partSizeFor(chosen: string | number | undefined): number;
|
|
38
|
+
/**
|
|
39
|
+
* The plan, and what it will cost — one function so the price and the sealing cannot disagree.
|
|
40
|
+
*
|
|
41
|
+
* ⛔ ONLY THE LAST PART IS ROUNDED UP. The earlier ones are exactly the part size, which is what
|
|
42
|
+
* lets a reader work out where the padding is. Quoting any other way would price a file
|
|
43
|
+
* differently from how it is actually stored.
|
|
44
|
+
*/
|
|
45
|
+
export declare function planAndPrice(size: number, partSize: number, rule: PaddingRule): {
|
|
46
|
+
plan: PartRange[];
|
|
47
|
+
sealedBytes: number;
|
|
48
|
+
credits: number;
|
|
49
|
+
sealFor(range: PartRange): number;
|
|
50
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type CommitInput, type PaidPart, type UploadInput } from "./upload-wire.ts";
|
|
2
|
+
/** Push ONE part's bytes at the relay and report the certificate. Commits nothing. */
|
|
3
|
+
export declare function pushPart(input: UploadInput, step: {
|
|
4
|
+
ledgerId: number;
|
|
5
|
+
blobId: string;
|
|
6
|
+
nonce: Uint8Array;
|
|
7
|
+
registerTxDigest: string;
|
|
8
|
+
blobObjectId: string;
|
|
9
|
+
/** ⛔ The bytes the reservation bought — from the record on a resume. Never re-sealed ones. */
|
|
10
|
+
sealed: Uint8Array;
|
|
11
|
+
/** ⛔ The relay the tip was paid to. From the record on a resume. */
|
|
12
|
+
relayUrl: string;
|
|
13
|
+
}): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Make the file real: one `POST /v1/items` naming EVERY part and the reservation that paid for it.
|
|
16
|
+
*
|
|
17
|
+
* ⛔ ONE CALL FOR THE WHOLE FILE. The server derives the item's size as the sum of the parts'
|
|
18
|
+
* sealed lengths and refuses a set whose indices are not a contiguous 0..n — so a file cannot
|
|
19
|
+
* be committed a piece at a time, and a part left out is not a smaller file, it is a rejection.
|
|
20
|
+
*
|
|
21
|
+
* ⛔ EVERY FACT HERE COMES OFF A RECORD. The parts were read back from what was written down
|
|
22
|
+
* before each reservation; the key and the content hash come from whichever record the caller
|
|
23
|
+
* read them from. A resumed run that used its own freshly generated key would produce a file
|
|
24
|
+
* that is paid for, present, correctly named — and impossible to open.
|
|
25
|
+
*/
|
|
26
|
+
export declare function commitItem(input: CommitInput, fileKey: string, parts: readonly PaidPart[]): Promise<string>;
|
package/dist/upload-steps.js
CHANGED
|
@@ -12,6 +12,8 @@ const STORAGE_KIND_DEDICATED_BLOB = 0;
|
|
|
12
12
|
const NETWORK_WALRUS = 0;
|
|
13
13
|
/** Only this account can read it. Sharing is a separate act, made in a browser. */
|
|
14
14
|
const VISIBILITY_PERSONAL = 0;
|
|
15
|
+
/** ITEM PARTS `owner_kind`: the person's own wallet paid. ⚠ The server decides this itself, from the absence of a reservation; the field is sent for wire compatibility, as the browser sends it. */
|
|
16
|
+
const OWNER_USER_WALLET = 0;
|
|
15
17
|
function why(error) {
|
|
16
18
|
return error instanceof Error ? error.message : String(error);
|
|
17
19
|
}
|
|
@@ -83,15 +85,7 @@ export async function commitItem(input, fileKey, parts) {
|
|
|
83
85
|
dek_wrapped: input.entry.dekWrapped,
|
|
84
86
|
content_hash_ct: input.entry.contentHashCt,
|
|
85
87
|
visibility: VISIBILITY_PERSONAL,
|
|
86
|
-
parts: parts.map((part) => (
|
|
87
|
-
part_index: part.partIndex,
|
|
88
|
-
storage_kind: STORAGE_KIND_DEDICATED_BLOB,
|
|
89
|
-
network: NETWORK_WALRUS,
|
|
90
|
-
blob_id: part.blobId,
|
|
91
|
-
sealed_len: part.sealedLen,
|
|
92
|
-
expiry_epoch: expiryEpoch,
|
|
93
|
-
sponsored_ledger_id: part.ledgerId,
|
|
94
|
-
})),
|
|
88
|
+
parts: parts.map((part) => describePart(part, expiryEpoch)),
|
|
95
89
|
}, `nmts-cli-commit-${fileKey}-${attempt}`);
|
|
96
90
|
}
|
|
97
91
|
catch (error) {
|
|
@@ -109,3 +103,29 @@ export async function commitItem(input, fileKey, parts) {
|
|
|
109
103
|
writeItemRecord(fileKey, { attempt, itemId: view.id });
|
|
110
104
|
return view.id;
|
|
111
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* One part as `POST /v1/items` takes it.
|
|
108
|
+
*
|
|
109
|
+
* ⛔ WHO PAID IS SAID BY ONE FIELD AND NOTHING ELSE. A part the treasury paid for names its
|
|
110
|
+
* reservation and the server checks it; a part the person's wallet paid for names none, and
|
|
111
|
+
* carries instead what the recovery list needs — the blob object and the epoch the chain says
|
|
112
|
+
* the storage ends at — exactly as the browser's wallet-paid commit does.
|
|
113
|
+
*/
|
|
114
|
+
function describePart(part, expiryEpoch) {
|
|
115
|
+
const common = {
|
|
116
|
+
part_index: part.partIndex,
|
|
117
|
+
storage_kind: STORAGE_KIND_DEDICATED_BLOB,
|
|
118
|
+
network: NETWORK_WALRUS,
|
|
119
|
+
blob_id: part.blobId,
|
|
120
|
+
sealed_len: part.sealedLen,
|
|
121
|
+
};
|
|
122
|
+
if (part.ledgerId !== null) {
|
|
123
|
+
return { ...common, expiry_epoch: expiryEpoch, sponsored_ledger_id: part.ledgerId };
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
...common,
|
|
127
|
+
owner_kind: OWNER_USER_WALLET,
|
|
128
|
+
expiry_epoch: part.endEpoch ?? expiryEpoch,
|
|
129
|
+
...(part.suiObjectId !== undefined ? { sui_object_id: part.suiObjectId } : {}),
|
|
130
|
+
};
|
|
131
|
+
}
|