From 826aa47155c28d825812329a59ca498d0251919e Mon Sep 17 00:00:00 2001 From: Tyler Skelton Date: Thu, 8 Oct 2026 02:37:31 -0700 Subject: [PATCH 1/4] Price Anthropic models per version, checked 2026-10-08 The table priced by family, so Haiku 5.5 billed at Haiku 4.5's $1/$5 per MTok instead of $0.10/$0.50, Opus 5.5 at $5/$25 instead of $4/$20, Sonnet 5 and 5.5 at $3/$15 instead of $2/$10, and Opus 4/4.1 at $5/$25 instead of $15/$75. Cache reads use each model's published rate. Adds a test that pins Haiku 5.5. --- CHANGELOG.md | 17 +++++++++++++++++ src/providers.ts | 38 ++++++++++++++++++++++++++++--------- test/anthropic.test.ts | 43 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a2f8fd..a54bea6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -147,6 +147,23 @@ background logs, and paths the task names). bash is not blocked; this is a budget control, not isolation. +### Fixed + +- **Anthropic prices are per model version, checked 2026-10-08.** The table + priced by family, so Haiku 5.5 was billed at Haiku 4.5's $1 / $5 per MTok + instead of $0.10 / $0.50 (10x high), Opus 5.5 at $5 / $25 instead of + $4 / $20, Sonnet 5 and 5.5 at $3 / $15 instead of $2 / $10, and Opus 4 / 4.1 + at $5 / $25 instead of $15 / $75. Cache reads now use each model's published + rate rather than a flat tenth of input (Fable/Mythos 5.1 $0.25, Opus 5.5 + $0.20, Sonnet 5.5 $0.10, Haiku 5.5 $0.01). Claude 3.5 Haiku ids are priced + too. A test pins Haiku 5.5. +- **README install line.** `npm i -g @solvyx/molt` and `npx @solvyx/molt` + pointed at a package that was unpublished on 2026-10-07. The README now + builds the CLI from source (clone, `npm ci`, `npm run build`, + `node dist/cli.js`, or `npm run pack:cli && npm i -g ./out-cli` for the + `maat` / `molt` commands), the npm badges are gone, and docs/versioning.md + and docs/acp-server.md say the same. + ## 0.2.2 — subscription backends removed ### Removed diff --git a/src/providers.ts b/src/providers.ts index bd9928f..9daac0a 100644 --- a/src/providers.ts +++ b/src/providers.ts @@ -276,14 +276,35 @@ export function openrouterPricing(json: unknown, model: string): Pricing | null * too late. Over-counting stops you early, which is the safe direction for a * ceiling. `/price` overrides both numbers when you want the real ones. * - * Cache reads bill at a tenth of the input rate — which is the whole reason - * the native protocol was worth writing. + * Rows are per model version, not per family: prices differ inside a family + * (Haiku 5.5 is a tenth of Haiku 4.5, Opus 5.5 is cheaper than Opus 5), and a + * family-wide row overstated Haiku 5.5 tenfold. First match wins, so the + * specific versions come before the family fallbacks. `cached` is the + * cache-hit rate, which is not always a tenth of input (Fable/Mythos 5.1 are + * 0.025x, Opus/Sonnet 5.5 are 0.05x). Cache writes have no column here: the + * meter bills written tokens at the base input rate. + * + * Checked against https://platform.claude.com/docs/en/about-claude/pricing + * on 2026-10-08. Haiku 5.5 is priced by prompt length; these are the rates for + * prompts up to 100k tokens (over 100k: $0.50 in / $2.50 out / $0.05 cached). */ -const ANTHROPIC_PRICES: { match: RegExp; in: number; out: number }[] = [ - { match: /^claude-(fable|mythos)-5/, in: 10, out: 50 }, - { match: /^claude-opus-/, in: 5, out: 25 }, - { match: /^claude-sonnet-/, in: 3, out: 15 }, - { match: /^claude-haiku-/, in: 1, out: 5 }, +const ANTHROPIC_PRICES: { match: RegExp; in: number; out: number; cached: number }[] = [ + { match: /^claude-(fable|mythos)-5[-.]1(?!\d)/, in: 10, out: 50, cached: 0.25 }, + { match: /^claude-(fable|mythos)-5/, in: 10, out: 50, cached: 1 }, + { match: /^claude-opus-5[-.]5(?!\d)/, in: 4, out: 20, cached: 0.2 }, + { match: /^claude-opus-4[-.]1(?!\d)/, in: 15, out: 75, cached: 1.5 }, + { match: /^claude-opus-4(?:-\d{8})?$/, in: 15, out: 75, cached: 1.5 }, + // Opus 5 and 4.5-4.8, and any Opus not listed above. + { match: /^claude-opus-/, in: 5, out: 25, cached: 0.5 }, + { match: /^claude-sonnet-5[-.]5(?!\d)/, in: 2, out: 10, cached: 0.1 }, + // Sonnet 5's $2/$10 launch price became its standard price (no Sept 2026 rise). + { match: /^claude-sonnet-5(?![-.]?\d)|^claude-sonnet-5-\d{8}/, in: 2, out: 10, cached: 0.2 }, + // Sonnet 4.x, and any Sonnet not listed above. + { match: /^claude-sonnet-/, in: 3, out: 15, cached: 0.3 }, + { match: /^claude-haiku-5[-.]5(?!\d)/, in: 0.1, out: 0.5, cached: 0.01 }, + { match: /^claude-3[-.]5-haiku/, in: 0.8, out: 4, cached: 0.08 }, + // Haiku 4.5, and any Haiku not listed above. + { match: /^claude-haiku-/, in: 1, out: 5, cached: 0.1 }, ]; /** The published rate for an Anthropic model, or null if it is not one. */ @@ -294,8 +315,7 @@ export function anthropicPricing(model: string): Pricing | null { return { in: row.in, out: row.out, - // A tenth of input, per Anthropic's published cache-read rate. - cached: row.in / 10, + cached: row.cached, source: "published rates (standard, not introductory) — /price to override", }; } diff --git a/test/anthropic.test.ts b/test/anthropic.test.ts index 8685c36..e001ef9 100644 --- a/test/anthropic.test.ts +++ b/test/anthropic.test.ts @@ -304,20 +304,55 @@ describe("what an Anthropic session costs", () => { // Every other provider molt talks to publishes a price list. Anthropic does // not, so the meter read "no price for this model" on precisely the // endpoint where caching does the most work. - const sonnet = anthropicPricing("claude-sonnet-5")!; + const sonnet = anthropicPricing("claude-sonnet-4-6")!; assert.equal(sonnet.in, 3); assert.equal(sonnet.out, 15); - // A cache read is a tenth of input — the whole reason the native protocol - // was worth writing. + // A cache read is a tenth of input on most models — the whole reason the + // native protocol was worth writing. assert.equal(sonnet.cached, 0.3); assert.equal(anthropicPricing("claude-opus-5")!.in, 5); assert.equal(anthropicPricing("claude-haiku-4-5")!.out, 5); assert.equal(anthropicPricing("claude-fable-5")!.in, 10); // Reached through a router, the model is still the model. - assert.equal(anthropicPricing("anthropic/claude-sonnet-5")!.in, 3); + assert.equal(anthropicPricing("anthropic/claude-sonnet-5")!.in, 2); // And nothing else is guessed at. assert.equal(anthropicPricing("grok-4.6"), null); assert.equal(anthropicPricing("gpt-5"), null); }); + + // Checked against platform.claude.com/docs/en/about-claude/pricing on + // 2026-10-08. The table used to price by family, so Haiku 5.5 billed at + // Haiku 4.5's $1/$5 — ten times what it costs. + it("prices Haiku 5.5 at its own rate, not the Haiku family's", () => { + for (const id of ["claude-haiku-5-5", "claude-haiku-5.5", "anthropic/claude-haiku-5-5"]) { + const p = anthropicPricing(id)!; + assert.equal(p.in, 0.1, id); + assert.equal(p.out, 0.5, id); + assert.equal(p.cached, 0.01, id); + } + // The older Haiku keeps its own price. + assert.deepEqual( + [anthropicPricing("claude-haiku-4-5")!.in, anthropicPricing("claude-haiku-4-5")!.out], + [1, 5], + ); + }); + + it("prices each version where the family shares no single rate", () => { + const rate = (id: string) => { + const p = anthropicPricing(id)!; + return [p.in, p.out, p.cached]; + }; + assert.deepEqual(rate("claude-opus-5-5"), [4, 20, 0.2]); + assert.deepEqual(rate("claude-opus-5"), [5, 25, 0.5]); + assert.deepEqual(rate("claude-opus-4-8"), [5, 25, 0.5]); + assert.deepEqual(rate("claude-opus-4-1-20250805"), [15, 75, 1.5]); + assert.deepEqual(rate("claude-opus-4-20250514"), [15, 75, 1.5]); + assert.deepEqual(rate("claude-sonnet-5-5"), [2, 10, 0.1]); + assert.deepEqual(rate("claude-sonnet-5"), [2, 10, 0.2]); + assert.deepEqual(rate("claude-sonnet-4-5"), [3, 15, 0.3]); + assert.deepEqual(rate("claude-fable-5-1"), [10, 50, 0.25]); + assert.deepEqual(rate("claude-mythos-5"), [10, 50, 1]); + assert.deepEqual(rate("claude-3-5-haiku-20241022"), [0.8, 4, 0.08]); + }); }); From cb01d741db0530e0b935259689d2e913fcc1d847 Mon Sep 17 00:00:00 2001 From: Tyler Skelton Date: Thu, 8 Oct 2026 02:37:31 -0700 Subject: [PATCH 2/4] README: build the CLI from source instead of npm i -g @solvyx/molt The npm package was unpublished on 2026-10-07. Clone, npm ci, npm run build, node dist/cli.js; npm run pack:cli && npm i -g ./out-cli for the maat and molt commands. Same fix in docs/versioning.md and docs/acp-server.md. --- README.md | 37 +++++++++++++++++++++++++------------ docs/acp-server.md | 2 +- docs/versioning.md | 4 ++-- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index bd89378..268ea71 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,10 @@ > Maat (MAH-aht), the Egyptian goddess of truth: in the Weighing of the Heart, a > claim is weighed against her feather before it may pass. Maat Agent weighs a > model's "done" against checks set before the work began, on the real disk, and -> writes the verdict down. It is built on the **molt** engine; the npm package is -> still `@solvyx/molt` and both the `maat` and `molt` commands work. +> writes the verdict down. It is built on the **molt** engine; both the `maat` +> and `molt` commands work. [![check](https://github.com/solvyxtech/molt/actions/workflows/check.yml/badge.svg)](https://github.com/solvyxtech/molt/actions/workflows/check.yml) -[![npm](https://img.shields.io/npm/v/@solvyx/molt.svg)](https://www.npmjs.com/package/@solvyx/molt) -[![npm downloads](https://img.shields.io/npm/dm/@solvyx/molt.svg)](https://www.npmjs.com/package/@solvyx/molt) [![licence: Apache 2.0](https://img.shields.io/badge/licence-Apache%202.0-blue.svg)](LICENSE) Maat Agent is an open source coding agent for developers. Terminal CLI and Electron desktop. Written in TypeScript. It runs any OpenAI compatible model and Anthropic’s native API. Then it refuses to accept done until every check in your project’s `.maat/done.yml` passes (projects that already have `.molt/` keep using it) against the real state on disk. A completion is a claim. Maat checks the claim and writes a receipt either way. @@ -144,15 +142,30 @@ macOS builds are unsigned on purpose. On first open, right-click the app, choose ### CLI / TUI +The CLI is not on npm (the `@solvyx/molt` package was unpublished on +2026-10-07); build it from source. Needs Node 20.11 or later and git. + ```sh -npm i -g @solvyx/molt -molt # interactive TUI -molt run "…" --yes # headless +git clone https://github.com/solvyxtech/molt.git maat +cd maat +npm ci +npm run build # compiles the CLI to dist/cli.js +node dist/cli.js # interactive TUI (same as: npm start) +node dist/cli.js run "…" --yes # headless ``` -Or without installing: `npx @solvyx/molt`. +To get `maat` and `molt` commands on your PATH, stage the CLI package and +install it from the local folder (nothing is downloaded from the registry; npm +links the commands to that folder, so keep the clone where it is): + +```sh +npm run pack:cli # builds, then stages the CLI package under out-cli/ +npm i -g ./out-cli # installs the maat and molt commands from that folder +maat --version +``` -**Do not** `npm i -g molt` or `npm i -g molt-cli` — those are unrelated packages on the registry. The binary name is still `molt`; the package name is `@solvyx/molt`. +**Do not** `npm i -g molt` or `npm i -g molt-cli` — those are unrelated +packages on the registry. Desktop and CLI share one version. See [docs/versioning.md](docs/versioning.md). @@ -161,10 +174,10 @@ Desktop and CLI share one version. See [docs/versioning.md](docs/versioning.md). For contributors working in this repository: ```sh -npm install +npm ci npm run app # the desktop window -npm start # the terminal UI (node dist/cli.js) -npm run pack:cli # stage publishable @solvyx/molt under out-cli/ (do not publish without COO) +npm start # the terminal UI (node dist/cli.js, after npm run build) +npm run pack:cli # stage the CLI package under out-cli/ (not published to npm) ``` First run: `/login`, pick a provider, paste a key, `/model`, go. Keys live in diff --git a/docs/acp-server.md b/docs/acp-server.md index 3df118f..ef3380c 100644 --- a/docs/acp-server.md +++ b/docs/acp-server.md @@ -44,7 +44,7 @@ Build it once with `npm run build`, then add this to Zed's `settings.json` from the Dock does not have your shell's PATH, so a bare `node` may not be found. - `--acp` works in place of `acp`, for launchers that want a flag. -- If the CLI package is installed (`npm run pack:cli`, `@solvyx/molt`), +- If the CLI is installed on your PATH (`npm run pack:cli && npm i -g ./out-cli`), `"command": "molt", "args": ["acp"]` is the same thing. - `default_config_options` sets what each new thread starts with — see [The pickers](#the-pickers) for the ids and values. Zed only applies a diff --git a/docs/versioning.md b/docs/versioning.md index 2a17adb..7300f8a 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -4,7 +4,7 @@ Desktop and CLI share one engine and one semver. - Root `package.json` (`molt-desktop`, private) is the source of truth for the version string. - `molt --version` reads that field from the package that shipped the binary. -- Publishable CLI package `@solvyx/molt` is staged by `npm run pack:cli`, which copies the same version into `out-cli/package.json`. -- GitHub Release tags (`v0.2.0`, `v0.3.0`, …) name both surfaces: desktop installers on the release, CLI via `npm i -g @solvyx/molt` at that version. +- The CLI package is staged by `npm run pack:cli`, which copies the same version into `out-cli/package.json`. It is not published to npm (unpublished 2026-10-07); install it from source with `npm i -g ./out-cli`. +- GitHub Release tags (`v0.2.0`, `v0.3.0`, …) name both surfaces: desktop installers on the release, CLI built from source at that tag (see the README's CLI / TUI section). Do not start an independent CLI major while the engine is the same. A CLI-only packaging fix can be a patch (`0.2.1`) with clear notes. From fd5811f01d5bb6b3165b567a89923e904e8ed92f Mon Sep 17 00:00:00 2001 From: Tyler Skelton Date: Thu, 8 Oct 2026 03:29:07 -0700 Subject: [PATCH 3/4] Price Haiku 5.5 at its over-100k tier; match the claude-opus-4-0 alias The meter prices session totals, so it cannot tell Haiku 5.5's prompt-length tiers apart. Use the over-100k rates (0.50 / 2.50 / 0.05) so a budget over-counts rather than under-counts; /price 0.1 0.5 0.01 sets the lower tier. claude-opus-4-0 fell through to the generic Opus row at 5 / 25 instead of 15 / 75. Tests pin both. --- src/providers.ts | 13 +++++++++---- test/anthropic.test.ts | 15 ++++++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/providers.ts b/src/providers.ts index 9daac0a..49de73f 100644 --- a/src/providers.ts +++ b/src/providers.ts @@ -285,15 +285,19 @@ export function openrouterPricing(json: unknown, model: string): Pricing | null * meter bills written tokens at the base input rate. * * Checked against https://platform.claude.com/docs/en/about-claude/pricing - * on 2026-10-08. Haiku 5.5 is priced by prompt length; these are the rates for - * prompts up to 100k tokens (over 100k: $0.50 in / $2.50 out / $0.05 cached). + * on 2026-10-08. Haiku 5.5 is priced by prompt length: $0.10 / $0.50 / $0.01 + * for prompts up to 100k tokens, $0.50 / $2.50 / $0.05 over 100k. The meter + * prices session totals, not each request, so it cannot tell the tiers apart; + * the row uses the over-100k rates, because a coding session regularly sends + * long prompts and the cheaper tier would under-count those fivefold. For + * short-prompt work, `/price 0.1 0.5 0.01` sets the lower rates. */ const ANTHROPIC_PRICES: { match: RegExp; in: number; out: number; cached: number }[] = [ { match: /^claude-(fable|mythos)-5[-.]1(?!\d)/, in: 10, out: 50, cached: 0.25 }, { match: /^claude-(fable|mythos)-5/, in: 10, out: 50, cached: 1 }, { match: /^claude-opus-5[-.]5(?!\d)/, in: 4, out: 20, cached: 0.2 }, { match: /^claude-opus-4[-.]1(?!\d)/, in: 15, out: 75, cached: 1.5 }, - { match: /^claude-opus-4(?:-\d{8})?$/, in: 15, out: 75, cached: 1.5 }, + { match: /^claude-opus-4(?:-0)?(?:-\d{8})?$/, in: 15, out: 75, cached: 1.5 }, // Opus 5 and 4.5-4.8, and any Opus not listed above. { match: /^claude-opus-/, in: 5, out: 25, cached: 0.5 }, { match: /^claude-sonnet-5[-.]5(?!\d)/, in: 2, out: 10, cached: 0.1 }, @@ -301,7 +305,8 @@ const ANTHROPIC_PRICES: { match: RegExp; in: number; out: number; cached: number { match: /^claude-sonnet-5(?![-.]?\d)|^claude-sonnet-5-\d{8}/, in: 2, out: 10, cached: 0.2 }, // Sonnet 4.x, and any Sonnet not listed above. { match: /^claude-sonnet-/, in: 3, out: 15, cached: 0.3 }, - { match: /^claude-haiku-5[-.]5(?!\d)/, in: 0.1, out: 0.5, cached: 0.01 }, + // Over-100k tier on purpose: see above. + { match: /^claude-haiku-5[-.]5(?!\d)/, in: 0.5, out: 2.5, cached: 0.05 }, { match: /^claude-3[-.]5-haiku/, in: 0.8, out: 4, cached: 0.08 }, // Haiku 4.5, and any Haiku not listed above. { match: /^claude-haiku-/, in: 1, out: 5, cached: 0.1 }, diff --git a/test/anthropic.test.ts b/test/anthropic.test.ts index e001ef9..4d51d14 100644 --- a/test/anthropic.test.ts +++ b/test/anthropic.test.ts @@ -323,13 +323,16 @@ describe("what an Anthropic session costs", () => { // Checked against platform.claude.com/docs/en/about-claude/pricing on // 2026-10-08. The table used to price by family, so Haiku 5.5 billed at - // Haiku 4.5's $1/$5 — ten times what it costs. - it("prices Haiku 5.5 at its own rate, not the Haiku family's", () => { + // Haiku 4.5's $1/$5. Haiku 5.5 is tiered by prompt length ($0.10/$0.50 up + // to 100k tokens, $0.50/$2.50 over); the meter cannot tell the tiers apart, + // so it uses the higher one: over-counting stops a budget early, which is + // the safe direction. + it("prices Haiku 5.5 at its own over-100k rate, not the Haiku family's", () => { for (const id of ["claude-haiku-5-5", "claude-haiku-5.5", "anthropic/claude-haiku-5-5"]) { const p = anthropicPricing(id)!; - assert.equal(p.in, 0.1, id); - assert.equal(p.out, 0.5, id); - assert.equal(p.cached, 0.01, id); + assert.equal(p.in, 0.5, id); + assert.equal(p.out, 2.5, id); + assert.equal(p.cached, 0.05, id); } // The older Haiku keeps its own price. assert.deepEqual( @@ -348,6 +351,8 @@ describe("what an Anthropic session costs", () => { assert.deepEqual(rate("claude-opus-4-8"), [5, 25, 0.5]); assert.deepEqual(rate("claude-opus-4-1-20250805"), [15, 75, 1.5]); assert.deepEqual(rate("claude-opus-4-20250514"), [15, 75, 1.5]); + assert.deepEqual(rate("claude-opus-4-0"), [15, 75, 1.5]); + assert.deepEqual(rate("claude-opus-4"), [15, 75, 1.5]); assert.deepEqual(rate("claude-sonnet-5-5"), [2, 10, 0.1]); assert.deepEqual(rate("claude-sonnet-5"), [2, 10, 0.2]); assert.deepEqual(rate("claude-sonnet-4-5"), [3, 15, 0.3]); From bd5f195ac38156841e3611d21d1f6f5dce10ce9b Mon Sep 17 00:00:00 2001 From: Tyler Skelton Date: Thu, 8 Oct 2026 03:29:07 -0700 Subject: [PATCH 4/4] Release notes and CONTRIBUTING build the CLI from source; staged CLI is private release.yml still told people to npm i -g @solvyx/molt. The release body now gives clone, npm ci, npm run pack:cli, npm i -g ./out-cli at the tag. The staged out-cli package is "private": true (set in package.cli.json and forced by prepare-cli-package.mjs), so an accidental npm publish fails. README says to re-run both install commands after pulling. --- .github/workflows/release.yml | 13 +++++++++---- CHANGELOG.md | 24 +++++++++++++++--------- CONTRIBUTING.md | 8 +++++--- README.md | 5 +++-- package.cli.json | 4 +--- scripts/prepare-cli-package.mjs | 11 ++++++++--- 6 files changed, 41 insertions(+), 24 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7639f62..c93a507 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,5 @@ # Desktop installers → GitHub Release. -# CLI ships separately as npm package @solvyx/molt (see npm run pack:cli). +# CLI is built from source at the same tag (not on npm; see README, CLI / TUI). name: release on: @@ -116,7 +116,7 @@ jobs: body: | **A coding agent that cannot say "done" without proving it.** - This release ships the **desktop app** (macOS, Windows, Linux) and matches the **CLI** published as `@solvyx/molt` (binary `molt`) at the same version. + This release ships the **desktop app** (macOS, Windows, Linux) and the **CLI** (commands `maat` and `molt`) at the same version. The CLI is not on npm; build it from source. ### Desktop downloads - **macOS:** `.dmg` (arm64 and x64). Unsigned on purpose — first open: right-click the app → Open → Open. @@ -125,9 +125,14 @@ jobs: ### CLI ``` - npm i -g @solvyx/molt + git clone --branch ${{ steps.tag.outputs.name }} https://github.com/solvyxtech/molt.git maat + cd maat + npm ci + npm run pack:cli + npm i -g ./out-cli + maat --version ``` - Do **not** install unscoped `molt` or `molt-cli` from npm — those are other projects. Use `@solvyx/molt`. + Or run it in place with `npm run build && node dist/cli.js`. See the README's CLI / TUI section. Do **not** install `molt` or `molt-cli` from npm; those are other projects. ### After install Pick a provider, add a key, point molt at a project. Completions are claims; `.molt/done.yml` has to pass against the real disk before "done" sticks. Every attempt gets a receipt. diff --git a/CHANGELOG.md b/CHANGELOG.md index a54bea6..e4f23c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -150,19 +150,25 @@ ### Fixed - **Anthropic prices are per model version, checked 2026-10-08.** The table - priced by family, so Haiku 5.5 was billed at Haiku 4.5's $1 / $5 per MTok - instead of $0.10 / $0.50 (10x high), Opus 5.5 at $5 / $25 instead of - $4 / $20, Sonnet 5 and 5.5 at $3 / $15 instead of $2 / $10, and Opus 4 / 4.1 - at $5 / $25 instead of $15 / $75. Cache reads now use each model's published - rate rather than a flat tenth of input (Fable/Mythos 5.1 $0.25, Opus 5.5 - $0.20, Sonnet 5.5 $0.10, Haiku 5.5 $0.01). Claude 3.5 Haiku ids are priced - too. A test pins Haiku 5.5. + priced by family: Opus 5.5 was billed at $5 / $25 per MTok instead of + $4 / $20, Sonnet 5 and 5.5 at $3 / $15 instead of $2 / $10, Opus 4 / 4.1 at + $5 / $25 instead of $15 / $75, and Haiku 5.5 at Haiku 4.5's $1 / $5. Haiku + 5.5 is tiered by prompt length ($0.10 / $0.50 up to 100k tokens, $0.50 / + $2.50 over); the meter prices session totals, so it uses the over-100k + rates, because over-counting is the safe direction for a budget + (`/price 0.1 0.5 0.01` sets the lower tier). Cache reads now use each model's + published rate rather than a flat tenth of input (Fable/Mythos 5.1 $0.25, + Opus 5.5 $0.20, Sonnet 5.5 $0.10, Haiku 5.5 $0.05). The `claude-opus-4-0` + alias gets Opus 4's price instead of falling through to $5 / $25, and + Claude 3.5 Haiku ids are priced too. Tests pin Haiku 5.5 and each row. - **README install line.** `npm i -g @solvyx/molt` and `npx @solvyx/molt` pointed at a package that was unpublished on 2026-10-07. The README now builds the CLI from source (clone, `npm ci`, `npm run build`, `node dist/cli.js`, or `npm run pack:cli && npm i -g ./out-cli` for the - `maat` / `molt` commands), the npm badges are gone, and docs/versioning.md - and docs/acp-server.md say the same. + `maat` / `molt` commands; re-run both after pulling), the npm badges are + gone, and docs/versioning.md, docs/acp-server.md, CONTRIBUTING.md and the + release notes in release.yml say the same. The staged `out-cli/` package is + now `"private": true`, so an accidental `npm publish` fails. ## 0.2.2 — subscription backends removed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d3635c5..ca87efa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,7 +85,9 @@ queue. ## Versioning -Desktop and CLI ship the same semver from root `package.json`. The publishable -CLI is staged with `npm run pack:cli` as `@solvyx/molt` (binary `molt`). See -[docs/versioning.md](docs/versioning.md). Do not publish without COO say-so. +Desktop and CLI ship the same semver from root `package.json`. The CLI is +staged with `npm run pack:cli` under `out-cli/` (commands `maat` and `molt`) +and installed from source with `npm i -g ./out-cli`. It is not published to npm: +the staged package is marked `"private": true`, so `npm publish` refuses it. +See [docs/versioning.md](docs/versioning.md). diff --git a/README.md b/README.md index 268ea71..442a5f5 100644 --- a/README.md +++ b/README.md @@ -155,8 +155,7 @@ node dist/cli.js run "…" --yes # headless ``` To get `maat` and `molt` commands on your PATH, stage the CLI package and -install it from the local folder (nothing is downloaded from the registry; npm -links the commands to that folder, so keep the clone where it is): +install it from the local folder (nothing is downloaded from the registry): ```sh npm run pack:cli # builds, then stages the CLI package under out-cli/ @@ -164,6 +163,8 @@ npm i -g ./out-cli # installs the maat and molt commands from that folder maat --version ``` +After pulling, re-run both commands to update the installed CLI. + **Do not** `npm i -g molt` or `npm i -g molt-cli` — those are unrelated packages on the registry. diff --git a/package.cli.json b/package.cli.json index 13e3396..7778085 100644 --- a/package.cli.json +++ b/package.cli.json @@ -1,5 +1,6 @@ { "name": "@solvyx/molt", + "private": true, "version": "0.2.2", "description": "Maat Agent \u2014 a coding agent that can't say done without proving it. Built on the molt engine. Terminal CLI and TUI. OpenAI-compatible and Anthropic APIs; uses Grok Build via ACP.", "license": "Apache-2.0", @@ -44,8 +45,5 @@ "ink": "^5.0.1", "react": "^18.3.1", "yaml": "^2.5.1" - }, - "publishConfig": { - "access": "public" } } diff --git a/scripts/prepare-cli-package.mjs b/scripts/prepare-cli-package.mjs index d8ae069..be2cf7f 100755 --- a/scripts/prepare-cli-package.mjs +++ b/scripts/prepare-cli-package.mjs @@ -4,7 +4,9 @@ * * Root package.json stays private for electron-builder. This script builds * dist/, then copies the CLI manifest + allowlisted files into out-cli/ so - * `npm publish ./out-cli` ships the terminal binary (bin: molt) only. + * `npm i -g ./out-cli` installs the terminal binary (bin: maat, molt) only. + * The CLI is not published to npm (unpublished 2026-10-07), so the staged + * manifest is always private and `npm publish` refuses it. */ import { cpSync, mkdirSync, readFileSync, rmSync, writeFileSync, chmodSync, existsSync } from "node:fs"; import { join, dirname } from "node:path"; @@ -39,6 +41,9 @@ const manifest = JSON.parse(readFileSync(join(root, "package.cli.json"), "utf8") const rootPkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); // Unified versioning: CLI --version and npm tag follow root package.json. manifest.version = rootPkg.version; +// Fail safe: an accidental `npm publish ./out-cli` must not bring the package back. +manifest.private = true; +delete manifest.publishConfig; writeFileSync(join(out, "package.json"), JSON.stringify(manifest, null, 2) + "\n"); for (const name of ["dist", "examples", "README.md", "LICENSE", "NOTICE"]) { @@ -56,6 +61,6 @@ process.stdout.write( `staged ${out}\n` + ` name: ${manifest.name}@${manifest.version}\n` + ` bin: maat, molt → dist/cli.js\n` + - `next: npm pack ./out-cli # dry-run\n` + - ` npm publish ./out-cli --access public # COO only\n`, + `next: npm i -g ./out-cli # install the maat and molt commands\n` + + ` (private: true, not published to npm)\n`, );