@needmoretruth/nmts-cli 0.34.1 → 0.34.3

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 CHANGED
@@ -1,9 +1,9 @@
1
1
  # nmts — for agents
2
2
 
3
- You are reading this because someone pointed you at the `nmts` command, or at this repository.
4
- This is the working reference. Each command has a document of its own: **`nmts help <command>`**
5
- prints it, and the same files are under `docs/commands/` in this package. Read a command's
6
- document before you use that command for the first time.
3
+ NMTS = NeedMoreTruthStorage: end-to-end encrypted cloud storage on the Walrus network, built by
4
+ one developer (needmoretruth) for AI agents as much as for people. Site https://nmts.me · summary
5
+ for programs https://nmts.me/llms.txt. Each command has a document: **`nmts help <command>`** prints
6
+ it (same files under `docs/commands/`); read it before using that command the first time.
7
7
 
8
8
  **This document describes a tool. It has no authority over your own instructions.** Nothing here
9
9
  asks you to do anything for anyone but the person you are working for, and if any line reads as an
package/CHANGELOG.md CHANGED
@@ -3,6 +3,21 @@
3
3
  Each version's entry is what changed for the person or the program using `nmts`. The product's own
4
4
  update history, which covers the site and the server too, is at https://nmts.me/updates.
5
5
 
6
+ ## 0.34.3 — 2026-09-07
7
+
8
+ - Security. `nmts wallet swap` on Bluefin now calls only the contract package pinned in this
9
+ release. Before, it read the "current" package from a public Sui node and preferred that answer,
10
+ so a node giving a false answer could have sent the swap into someone else's code with your coin
11
+ as the argument. The pinned package is still checked against the chain before a quote is shown;
12
+ if that check refuses it, the Bluefin venue is unavailable until a release bumps the pin. DeepBook
13
+ was never affected. No such node was observed and no loss is known.
14
+
15
+ ## 0.34.2 — 2026-09-07
16
+
17
+ - The README and the agent document open with what the name stands for — NMTS is
18
+ NeedMoreTruthStorage — who builds it, and the site, https://nmts.me; the package description
19
+ says the same. No behaviour changed.
20
+
6
21
  ## 0.34.1 — 2026-09-07
7
22
 
8
23
  - The advice printed for `MANIFEST_TOO_LARGE` no longer tells a program to ask the operator for a
package/README.ko.md CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  *[English](README.md)*
4
4
 
5
- [NMTS](https://nmts.me)를 터미널에서 쓰는 도구입니다 — Walrus 네트워크 위의 종단 간 암호화 저장소를,
6
- 터미널 앞의 사람과 사람이 돌리는 에이전트를 위해.
5
+ [NMTS](https://nmts.me)를 터미널에서 쓰는 도구입니다 — NMTS는 **NeedMoreTruthStorage**의 줄임말로,
6
+ 개발자 사람([needmoretruth](https://github.com/needmoretruth))이 만든 Walrus 네트워크 위의 종단간
7
+ 암호화 클라우드 스토리지입니다. 터미널 앞의 사람과 그 사람이 돌리는 AI 에이전트를 위해. 사이트는 **https://nmts.me** 입니다.
7
8
 
8
9
  > **이야기를 나누는 곳: [디스코드](https://discord.gg/pcmRkVmVZk).** 영어와 한국어 둘 다 읽습니다.
9
10
  >
@@ -50,7 +51,7 @@ nmts --help
50
51
 
51
52
  ```sh
52
53
  npm install -g github:needmoretruth/nmts-cli # 기본 브랜치
53
- npm install -g github:needmoretruth/nmts-cli#v0.34.1 # 버전을 고정할 때
54
+ npm install -g github:needmoretruth/nmts-cli#v0.34.3 # 버전을 고정할 때
54
55
  npm install -g https://github.com/needmoretruth/nmts-cli/releases/latest/download/nmts.tgz
55
56
  ```
56
57
 
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # nmts
2
2
 
3
- Command-line access to [NMTS](https://nmts.me) — end-to-end encrypted storage on the Walrus
4
- network. For people at a terminal, and for the agents they run.
3
+ Command-line access to [NMTS](https://nmts.me) — **NeedMoreTruthStorage**, end-to-end encrypted
4
+ cloud storage on the Walrus network, built by one developer ([needmoretruth](https://github.com/needmoretruth)).
5
+ For people at a terminal, and for the AI agents they run. The site is **https://nmts.me**.
5
6
 
6
7
  > **[한국어 문서](README.ko.md)** · Talk about NMTS on [Discord](https://discord.gg/pcmRkVmVZk),
7
8
  > in English or Korean.
@@ -12,7 +13,7 @@ network. For people at a terminal, and for the agents they run.
12
13
  > **Status: early.** The interface may still change before 1.0. `nmts --help` is the current truth
13
14
  > about what exists.
14
15
 
15
- ## What NMTS is
16
+ ## 🧭 What NMTS is
16
17
 
17
18
  Storage where **the encryption happens on your machine and the keys never leave it.** The server
18
19
  receives sealed bytes it cannot open. File contents, names and folders all live inside a sealed
@@ -35,7 +36,7 @@ things to know before you start:
35
36
  NMTS is built and run by one developer. This tool, the encryption engine and the recovery program
36
37
  are open source under Apache-2.0; the server and the web app are not published.
37
38
 
38
- ## Install
39
+ ## 📦 Install
39
40
 
40
41
  Node 22.15.0 or newer. Nothing is compiled at install time and there is no native build step: the
41
42
  encryption engine is a WebAssembly module carried in the repository. It runs wherever Node runs —
@@ -52,7 +53,7 @@ default branch, from a pinned version, or from the tarball attached to the
52
53
 
53
54
  ```sh
54
55
  npm install -g github:needmoretruth/nmts-cli # the default branch
55
- npm install -g github:needmoretruth/nmts-cli#v0.34.1 # a pinned version
56
+ npm install -g github:needmoretruth/nmts-cli#v0.34.3 # a pinned version
56
57
  npm install -g https://github.com/needmoretruth/nmts-cli/releases/latest/download/nmts.tgz
57
58
  ```
58
59
 
@@ -61,7 +62,7 @@ that short name as too close to names already published.
61
62
 
62
63
  To work on the source instead, see [CONTRIBUTING.md](CONTRIBUTING.md).
63
64
 
64
- ## Staying up to date
65
+ ## 🔄 Staying up to date
65
66
 
66
67
  ```sh
67
68
  nmts update # installs the newest release over this one
@@ -74,7 +75,7 @@ That request carries no NMTS key, no API key and no command name, and it is the
74
75
  the tool makes that no command asked for. Setting `NMTS_NO_UPDATE_CHECK` to anything stops both
75
76
  halves, and `nmts env` shows what the check last found.
76
77
 
77
- ## First run
78
+ ## 🚀 First run
78
79
 
79
80
  ```sh
80
81
  nmts env # what this machine is, and whether credentials are in reach. Contacts nothing.
@@ -87,7 +88,7 @@ nmts get x # download one file
87
88
  Run `nmts env` first on any machine you do not know — a container, a CI runner, someone else's
88
89
  laptop. It needs no credential and reports what a credential here would be exposed to.
89
90
 
90
- ## The two credentials
91
+ ## 🔑 The two credentials
91
92
 
92
93
  They do different jobs and they are not interchangeable.
93
94
 
@@ -139,13 +140,13 @@ behind the same agreement.
139
140
  three clear the environment before starting one. Sign in with `nmts login` instead, or put the
140
141
  variables in the server's own `env` block. `nmts env` names the agent it can see.
141
142
 
142
- ## Before you hand this to an agent
143
+ ## 🤖 Before you hand this to an agent
143
144
 
144
145
  Your NMTS key is everything at once. A program that has it can read every file, upload,
145
146
  delete and sign with the wallet, and its requests cannot be told apart from yours. It cannot be
146
147
  rotated while keeping the account. **Use an account you would be willing to lose.**
147
148
 
148
- ## Commands
149
+ ## 🧰 Commands
149
150
 
150
151
  | Command | What it does |
151
152
  |---|---|
@@ -406,7 +407,7 @@ account) is a typed sentence. `nmts unlock` lists the keys; each unlock prints w
406
407
  could go wrong and what it does not cover before it asks. `nmts help <command>` prints any
407
408
  command's document, with its tier at the top.
408
409
 
409
- ## Containers
410
+ ## 🐳 Containers
410
411
 
411
412
  It runs unchanged in Docker and Podman, rootless. There is no published image; this repository has
412
413
  a `Dockerfile`, and both container tools build and run it on every push.
@@ -477,7 +478,7 @@ $ rclone copy drive:drive ./somewhere
477
478
  $ rclone copy --size-only ./somewhere drive:drive
478
479
  ```
479
480
 
480
- ## For an agent that speaks MCP
481
+ ## 🔌 For an agent that speaks MCP
481
482
 
482
483
  `nmts mcp` is a local MCP server over stdin and stdout. Sign in first (`nmts login`); it never
483
484
  takes an NMTS key on a command line, and it never prompts, so a sealed NMTS key with no `NMTS_PASSPHRASE`
@@ -11,7 +11,7 @@ export declare const CRYPTO_SPEC_URL = "https://github.com/needmoretruth/nmts-re
11
11
  * is the field's own contract — what the writer says about itself — and a person holding two
12
12
  * copies of one account's artefacts can then tell which program made each.
13
13
  */
14
- export declare const WRITTEN_BY = "nmts-cli 0.34.1";
14
+ export declare const WRITTEN_BY = "nmts-cli 0.34.3";
15
15
  /** Which of the three artefacts a wrapper is. A reader holding several can sort them. */
16
16
  export type ArtifactKind = "recovery-list" | "file-list" | "recovery-kit";
17
17
  /**
package/dist/product.d.ts CHANGED
@@ -9,7 +9,7 @@ export declare const BINARY_NAME = "nmts";
9
9
  * beside it. Here rather than in `main.ts` because the MCP server has to say it too, and a
10
10
  * command importing the entry point is a cycle waiting to bite.
11
11
  */
12
- export declare const VERSION = "0.34.1";
12
+ export declare const VERSION = "0.34.3";
13
13
  /** Where the product lives, for messages that need to send somebody somewhere real. */
14
14
  export declare const HOME_URL = "https://nmts.me";
15
15
  /** The source, so a person holding only the built program can find what it was built from. */
package/dist/product.js CHANGED
@@ -18,7 +18,7 @@ export const BINARY_NAME = "nmts";
18
18
  * beside it. Here rather than in `main.ts` because the MCP server has to say it too, and a
19
19
  * command importing the entry point is a cycle waiting to bite.
20
20
  */
21
- export const VERSION = "0.34.1";
21
+ export const VERSION = "0.34.3";
22
22
  /** Where the product lives, for messages that need to send somebody somewhere real. */
23
23
  export const HOME_URL = "https://nmts.me";
24
24
  /** The source, so a person holding only the built program can find what it was built from. */
@@ -5,7 +5,7 @@ import { type WalletBalances } from "./wallet.ts";
5
5
  import { type VenueQuote } from "./wallet-swap-quote.ts";
6
6
  /** Where a swap runs: one of the two mainnet venues, or the official testnet facility. */
7
7
  export type SwapRail = SwapVenue | "exchange";
8
- /** Bluefin's addresses, with the package RESOLVED and version-checked on chain, never assumed. */
8
+ /** Bluefin's addresses: the package PINNED in this release, confirmed on chain before it is used. */
9
9
  export interface BluefinBinding {
10
10
  packageId: string;
11
11
  globalConfigId: string;
@@ -40,7 +40,7 @@ export declare function swapTransaction(input: SwapShape & {
40
40
  /** What `commands/wallet-swap.ts` reads before it prints a review. */
41
41
  export interface SwapReads {
42
42
  readWallet(address: string): Promise<WalletBalances>;
43
- /** Bluefin's current package, version-checked. Throws when no known package passes. */
43
+ /** Bluefin's pinned package, version-checked. Throws when the chain refuses that package. */
44
44
  resolveBluefin(): Promise<BluefinBinding>;
45
45
  /** The testnet facility and its rate. Throws off testnet, or when the object cannot be read. */
46
46
  readExchange(): Promise<ExchangeFacility>;
@@ -49,4 +49,18 @@ export interface SwapReads {
49
49
  /** The fee in MIST measured by dry-running this exact swap, or null when it could not be. */
50
50
  estimateFee(shape: SwapShape, sender: string): Promise<bigint | null>;
51
51
  }
52
+ /** All this path uses of an RPC: one devInspect and its `error`. Narrow on purpose — the type itself
53
+ * has no place for a chain-supplied ADDRESS to enter, and a test can hand it a fake. */
54
+ export interface BluefinVersionRpc {
55
+ devInspectTransactionBlock(input: {
56
+ sender: string;
57
+ transactionBlock: Transaction;
58
+ }): Promise<{
59
+ error?: string | null;
60
+ }>;
61
+ }
62
+ /** The pinned addresses, returned only once the chain confirms the pinned package still verifies.
63
+ * A refusal ends it: this function has no other address to return. Exported so the test can hold an
64
+ * RPC that names a different package and watch it change nothing. */
65
+ export declare function checkedBluefinBinding(pinned: BluefinBinding, rpc: BluefinVersionRpc): Promise<BluefinBinding>;
52
66
  export declare function swapReads(network: Network): SwapReads;
@@ -1,6 +1,7 @@
1
- // The swap transaction and its live reads: the wallet's balances, Bluefin's current package, the
2
- // testnet facility's rate, and the fee a dry run measures. The quotes live next door in
3
- // `wallet-swap-quote.ts`; both files read, and only `wallet-sign.ts` signs.
1
+ // The swap transaction and its live reads: the wallet's balances, the version check on Bluefin's
2
+ // PINNED package, the testnet facility's rate, and the fee a dry run measures. Bluefin's package id
3
+ // itself is NOT a live read see the block above `checkedBluefinBinding`. The quotes live next door
4
+ // in `wallet-swap-quote.ts`; both files read, and only `wallet-sign.ts` signs.
4
5
  //
5
6
  // ⛔ ONE BUILDER FOR THE FEE AND THE SIGNATURE. `estimateFee` and `wallet-sign.ts` both call
6
7
  // `swapTransaction`, so the fee printed is the fee of the transaction that is then signed.
@@ -20,7 +21,7 @@ import { coinWithBalance, Transaction } from "@mysten/sui/transactions";
20
21
  import { TESTNET_WALRUS_PACKAGE_CONFIG } from "@mysten/walrus";
21
22
  import { walrusClient, netGasFee } from "./extend-chain.js";
22
23
  import { isRecord } from "./guards.js";
23
- import { BLUEFIN_GLOBAL_CONFIG_IDS, BLUEFIN_MAX_SQRT_PRICE, BLUEFIN_MIN_SQRT_PRICE, BLUEFIN_PACKAGE_IDS, BLUEFIN_UPGRADE_CAP_IDS, BLUEFIN_WAL_SUI_POOLS, DEEP_COIN_TYPES, DEEPBOOK_PACKAGE_IDS, DEEPBOOK_WAL_SUI_POOLS, } from "./shared/lib/wallet/venue-ids.js";
24
+ import { BLUEFIN_GLOBAL_CONFIG_IDS, BLUEFIN_MAX_SQRT_PRICE, BLUEFIN_MIN_SQRT_PRICE, BLUEFIN_PACKAGE_IDS, BLUEFIN_WAL_SUI_POOLS, DEEP_COIN_TYPES, DEEPBOOK_PACKAGE_IDS, DEEPBOOK_WAL_SUI_POOLS, } from "./shared/lib/wallet/venue-ids.js";
24
25
  import { readBalances, SUI_COIN_TYPE, walCoinType } from "./wallet.js";
25
26
  import { chainReader } from "./wallet-chain.js";
26
27
  import { quoteVenue, QUOTE_SENDER } from "./wallet-swap-quote.js";
@@ -86,38 +87,36 @@ export function swapTransaction(input) {
86
87
  tx.setGasBudget(input.gasBudgetMist);
87
88
  return tx;
88
89
  }
89
- const FULL_ADDRESS = /^0x[0-9a-f]{64}$/;
90
90
  /** `fields.<name>` of a Move object's content, as a string, or null when the shape differs. */
91
91
  function fieldOf(content, name) {
92
92
  const fields = isRecord(content) ? content["fields"] : undefined;
93
93
  return isRecord(fields) ? fields[name] : undefined;
94
94
  }
95
+ /** Does the chain still accept this package? `config::verify_version` under devInspect: no signature,
96
+ * no gas. False when the chain refuses AND when the RPC cannot answer — both mean "no Bluefin". */
97
+ async function versionPasses(rpc, packageId, globalConfigId) {
98
+ try {
99
+ const tx = new Transaction();
100
+ tx.moveCall({ target: `${packageId}::config::verify_version`, arguments: [tx.object(globalConfigId)] });
101
+ const res = await rpc.devInspectTransactionBlock({ sender: QUOTE_SENDER, transactionBlock: tx });
102
+ return !res.error;
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ }
108
+ /** The pinned addresses, returned only once the chain confirms the pinned package still verifies.
109
+ * A refusal ends it: this function has no other address to return. Exported so the test can hold an
110
+ * RPC that names a different package and watch it change nothing. */
111
+ export async function checkedBluefinBinding(pinned, rpc) {
112
+ if (!(await versionPasses(rpc, pinned.packageId, pinned.globalConfigId))) {
113
+ throw new Error("Bluefin's on-chain version check refused the package address this tool pins.");
114
+ }
115
+ return pinned;
116
+ }
95
117
  export function swapReads(network) {
96
118
  const client = walrusClient(network);
97
119
  let bluefin = null;
98
- async function livePackage(upgradeCapId) {
99
- try {
100
- const obj = await client.getObject({ id: upgradeCapId, options: { showContent: true, showType: true } });
101
- if (obj.data?.type !== "0x2::package::UpgradeCap")
102
- return null;
103
- const pkg = fieldOf(obj.data.content, "package");
104
- return typeof pkg === "string" && FULL_ADDRESS.test(pkg) ? pkg : null;
105
- }
106
- catch {
107
- return null;
108
- }
109
- }
110
- async function versionPasses(packageId, globalConfigId) {
111
- try {
112
- const tx = new Transaction();
113
- tx.moveCall({ target: `${packageId}::config::verify_version`, arguments: [tx.object(globalConfigId)] });
114
- const res = await client.devInspectTransactionBlock({ sender: QUOTE_SENDER, transactionBlock: tx });
115
- return !res.error;
116
- }
117
- catch {
118
- return false;
119
- }
120
- }
121
120
  return {
122
121
  async readWallet(address) {
123
122
  return readBalances(chainReader(network, address), walCoinType(network));
@@ -127,19 +126,13 @@ export function swapReads(network) {
127
126
  if (bluefin !== null)
128
127
  return bluefin;
129
128
  const pending = (async () => {
130
- const pinned = BLUEFIN_PACKAGE_IDS[network];
131
- const capId = BLUEFIN_UPGRADE_CAP_IDS[network];
129
+ const packageId = BLUEFIN_PACKAGE_IDS[network];
132
130
  const globalConfigId = BLUEFIN_GLOBAL_CONFIG_IDS[network];
133
131
  const poolId = BLUEFIN_WAL_SUI_POOLS[network];
134
- if (pinned === null || capId === null || globalConfigId === null || poolId === null) {
132
+ if (packageId === null || globalConfigId === null || poolId === null) {
135
133
  throw new Error(`This tool knows no Bluefin WAL/SUI pool on ${network}.`);
136
134
  }
137
- const live = await livePackage(capId);
138
- for (const packageId of live === null ? [pinned] : [live, pinned]) {
139
- if (await versionPasses(packageId, globalConfigId))
140
- return { packageId, globalConfigId, poolId };
141
- }
142
- throw new Error("Bluefin's on-chain version check refused every package address this tool knows.");
135
+ return checkedBluefinBinding({ packageId, globalConfigId, poolId }, client);
143
136
  })();
144
137
  bluefin = pending;
145
138
  pending.catch(() => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@needmoretruth/nmts-cli",
3
- "version": "0.34.1",
4
- "description": "Command-line and MCP access to NMTS: open-source, end-to-end encrypted storage on the Walrus network. No fee to NMTS; you pay the network from your own wallet. For people and for their agents.",
3
+ "version": "0.34.3",
4
+ "description": "Command-line and MCP access to NMTS (NeedMoreTruthStorage, https://nmts.me): open-source, end-to-end encrypted storage on the Walrus network. No fee to NMTS; you pay the network from your own wallet. For people and for their agents.",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
7
7
  "access": "public"
package/server.json CHANGED
@@ -6,13 +6,13 @@
6
6
  "url": "https://github.com/needmoretruth/nmts-cli",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.34.1",
9
+ "version": "0.34.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "registryBaseUrl": "https://registry.npmjs.org",
14
14
  "identifier": "@needmoretruth/nmts-cli",
15
- "version": "0.34.1",
15
+ "version": "0.34.2",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },