@spatz/cli 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -1
  3. package/bin/spatz.js +40 -0
  4. package/package.json +26 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lorenz Hilpert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1 +1,251 @@
1
- Placeholder. See https://github.com/lorenzh/spatz
1
+ # spatz
2
+
3
+ **Choose a model and effort for your coding task. Learn from the result.**
4
+
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+ [![Version](https://img.shields.io/github/v/release/lorenzh/spatz)](https://github.com/lorenzh/spatz/releases/latest)
7
+ [![Bun 1.4](https://img.shields.io/badge/Bun-1.4-black?logo=bun)](https://bun.sh)
8
+
9
+ Do not use a cannon to shoot sparrows.
10
+
11
+ spatz ranks the model and effort pairs that you can use for a coding task.
12
+ It learns from task results to help choose cheaper pairs that succeed.
13
+ You or your coding agent run the task with the recommended pair.
14
+
15
+ [Quickstart](#quickstart) · [Documentation](#documentation) · [Contributing](#contributing) · [Releases](https://github.com/lorenzh/spatz/releases)
16
+
17
+ ## Why spatz?
18
+
19
+ Different tasks need different models and effort levels.
20
+ spatz combines task classification with results from your previous tasks.
21
+
22
+ - **Use your available models.** Pass the candidates with `--models`.
23
+ - **Learn from results.** Record outcomes through hooks or `spatz report`.
24
+ - **Compare performance.** Use `spatz stats` to see results per task type or routing scope.
25
+ - **Connect to coding agents.** Hooks support Claude Code and Codex CLI. Other agents can use the CLI directly.
26
+ - **Keep learning data local.** spatz stores results in `~/.spatz/spatz.db`. It does not store task text.
27
+
28
+ ## Quickstart
29
+
30
+ ### 1. Install
31
+
32
+ Install the stable CLI with Node.js 18 or newer:
33
+
34
+ ```bash
35
+ npm install -g @spatz/cli
36
+ spatz --version
37
+ ```
38
+
39
+ The npm package includes the Bun runtime. Keep optional dependencies enabled.
40
+ Linux needs glibc. spatz does not support Alpine Linux.
41
+ Windows x64 support is experimental. Some releases omit it.
42
+
43
+ For installation without Node.js, use a [release archive](#releases).
44
+ For the unstable nightly version, use `npm install -g @spatz/cli@nightly`.
45
+
46
+ ### 2. Ask for a recommendation
47
+
48
+ Pass the task and the model pairs that your agent can run:
49
+
50
+ ```bash
51
+ spatz "Fix the off-by-one error in src/list.ts" \
52
+ --models claude-opus-5-5:high+medium,claude-sonnet-5-5:medium+low
53
+ ```
54
+
55
+ Each candidate uses `<model-id>:<effort>+<effort>...`.
56
+ Use model IDs and efforts that your environment supports.
57
+ The output includes a `suggestion_id` and a ranked list of pairs.
58
+ Add `--json` for machine-readable output.
59
+
60
+ spatz works without an API key.
61
+ Without Jev or learned results, it recommends the most expensive candidate.
62
+ Jev is the TypeSafe AI model that classifies the task and estimates candidate suitability.
63
+ To enable Jev, set your TypeSafe AI key:
64
+
65
+ ```bash
66
+ export TYPESAFE_AI_API_KEY="<your-key>"
67
+ ```
68
+
69
+ When you enable Jev, spatz sends task text and candidate details to TypeSafe AI.
70
+ Read [Privacy](#privacy) before using sensitive task text.
71
+
72
+ ### 3. Run the task and report the result
73
+
74
+ Run the task with the chosen model and effort in your coding agent.
75
+ Then report the pair you actually used:
76
+
77
+ ```bash
78
+ spatz report <suggestion_id> \
79
+ --model claude-sonnet-5-5 --effort medium --result pass
80
+
81
+ spatz stats
82
+ ```
83
+
84
+ Replace `<suggestion_id>` with the ID from the recommendation.
85
+ Results can be `pass`, `partial`, or `fail`.
86
+ An explicit report overrides hook signals for that recommendation.
87
+
88
+ For experiments, add `--dry-run` to the recommendation command.
89
+ These suggestions never count toward learning or statistics.
90
+
91
+ ## How it works
92
+
93
+ 1. A local filter checks the task text for known secret patterns.
94
+ 2. Jev classifies the task. Without Jev, spatz uses local keyword rules.
95
+ 3. spatz ranks your candidates using classification and learned success estimates.
96
+ 4. You or your agent choose a pair and run the task.
97
+ 5. Hooks or `spatz report` record the outcome for future recommendations.
98
+
99
+ The CLI recommends pairs. The optional Claude Code mod can apply them automatically.
100
+ See [How it works](docs/how-it-works.md) and [Recommendation rules](docs/recommendation.md) for the decision process.
101
+
102
+ ## Agent integrations
103
+
104
+ | Integration | What it does | Setup |
105
+ | --- | --- | --- |
106
+ | Claude Code `spatz-hooks` plugin | Record task signals and model usage. | [Hooks guide](docs/hooks.md) |
107
+ | Codex `spatz-hooks` plugin | Record shell results and model usage. | [Hooks guide](docs/hooks.md) |
108
+ | Claude Code `spatz` plugin (mod) | Show recommendations or apply model and effort choices. | [Mod guide](docs/claude-mod.md) |
109
+ | Other agents | Request recommendations and report outcomes through the CLI. | [CLI reference](docs/cli.md) |
110
+
111
+ The Claude Code mod supports `step`, `turn`, `subagent`, `session`, and `escalate` routing scopes.
112
+ Hooks and the mod can run together. The mod's `record: auto` avoids duplicate usage recording with the `spatz-hooks` plugin.
113
+
114
+ ### Install the plugins
115
+
116
+ In Claude Code:
117
+
118
+ ```text
119
+ /plugin marketplace add lorenzh/spatz
120
+ /plugin install spatz-hooks@spatz
121
+ /plugin install spatz@spatz
122
+ ```
123
+
124
+ `spatz-hooks` records outcomes. `spatz` is the mod that recommends or applies model and effort. Install one or both.
125
+
126
+ In Codex:
127
+
128
+ ```bash
129
+ codex plugin marketplace add lorenzh/spatz
130
+ codex plugin add spatz-hooks@spatz
131
+ ```
132
+
133
+ Codex runs new hooks only after you trust them with `/hooks`.
134
+
135
+ Every plugin ships the `spatz` skill, which tells the agent when to ask spatz and how to report results, so you do not need to edit your agent instructions.
136
+ The plugins also ship a launcher. It uses `spatz` from your `PATH` if present. Otherwise it runs the matching `@spatz/cli` version through Bun or npx, which downloads about 60 MB on first use.
137
+ Codex hooks time out after 10 seconds, so run the launcher once before the first session: `"<plugin root>/bin/spatz" --version`.
138
+ The plugins need a POSIX shell. Native Windows is not supported yet.
139
+ If you added spatz hooks to `~/.claude/settings.json` or `~/.codex/hooks.json` by hand, remove them to avoid duplicate records.
140
+ See [Installation](docs/installation.md) for details.
141
+
142
+ ## Privacy
143
+
144
+ spatz stores learning data under `~/.spatz`. It does not store task text or tool output.
145
+ Hooks process task signals locally and make no network requests.
146
+
147
+ When you enable Jev, spatz sends task text and candidate details to TypeSafe AI.
148
+ The secret filter catches known patterns. It cannot detect every secret.
149
+ Do not put secrets in task text.
150
+
151
+ To disable Jev:
152
+
153
+ ```bash
154
+ export SPATZ_NO_JEV=1
155
+ ```
156
+
157
+ For a project, put `{"jev": false}` in `.spatz.json` in the working directory.
158
+ Without `TYPESAFE_AI_API_KEY`, Jev is also disabled.
159
+
160
+ Disabling Jev still allows OpenRouter requests for model prices. These requests contain no task data.
161
+ spatz caches prices for 24 hours. When the network is unavailable, spatz can still use cached prices.
162
+ The first `spatz stats` run downloads DuckDB's SQLite extension.
163
+
164
+ See the [Privacy guide](docs/privacy.md) for data flows and deletion instructions.
165
+ See [Configuration](docs/configuration.md) for environment variables and local files.
166
+
167
+ ## Releases
168
+
169
+ Download an archive and its matching `.sha256` file from [GitHub Releases](https://github.com/lorenzh/spatz/releases).
170
+ Archives include the runtime. You do not need Node.js or Bun installed.
171
+
172
+ Choose `linux` or `darwin` (macOS), then `x64` or `arm64`.
173
+ Apple Silicon uses `darwin-arm64`. Linux builds need glibc.
174
+ Windows x64 ZIP archives are experimental. The agent plugins do not support native Windows.
175
+
176
+ Check the checksum before extraction. This Linux x64 example uses version `0.1.0`:
177
+
178
+ ```bash
179
+ version=0.1.0
180
+ archive="spatz-cli-$version-linux-x64.tar.gz"
181
+ sha256sum --check "$archive.sha256"
182
+
183
+ mkdir -p ~/.local/lib/spatz ~/.local/bin
184
+ tar -xzf "$archive" -C ~/.local/lib/spatz
185
+ ln -sfn "$HOME/.local/lib/spatz/${archive%.tar.gz}/spatz" ~/.local/bin/spatz
186
+ export PATH="$HOME/.local/bin:$PATH"
187
+ spatz --version
188
+ ```
189
+
190
+ Use your downloaded version. On macOS, use `shasum -a 256 --check "$archive.sha256"`.
191
+ If your shell does not include `~/.local/bin`, add the `PATH` line to your shell profile.
192
+ Keep the executable and DuckDB libraries together. `spatz stats` needs these libraries beside the executable.
193
+
194
+ The [nightly release](https://github.com/lorenzh/spatz/releases/tag/nightly) is unstable.
195
+ See [Releasing spatz](RELEASING.md) for the release process.
196
+
197
+ ## Install from source
198
+
199
+ Install Bun 1.4, then clone the repository:
200
+
201
+ ```bash
202
+ git clone https://github.com/lorenzh/spatz.git
203
+ cd spatz
204
+ bun install
205
+ ```
206
+
207
+ Put a wrapper on your `PATH` so hooks can find `spatz` in non-interactive shells:
208
+
209
+ ```bash
210
+ mkdir -p ~/.local/bin
211
+ printf '#!/bin/sh\nexec bun "%s/packages/cli/src/cli.ts" "$@"\n' "$PWD" > ~/.local/bin/spatz
212
+ chmod +x ~/.local/bin/spatz
213
+ export PATH="$HOME/.local/bin:$PATH"
214
+ spatz --version
215
+ ```
216
+
217
+ A shell alias does not work for hooks.
218
+ See [Contributing](CONTRIBUTING.md) for the full development setup.
219
+
220
+ ## Documentation
221
+
222
+ | Guide | What you will find |
223
+ | --- | --- |
224
+ | [CLI reference](docs/cli.md) | Commands, flags, model IDs, output fields, and exit codes. |
225
+ | [How it works](docs/how-it-works.md) | Architecture and the flow from task to outcome. |
226
+ | [Recommendation rules](docs/recommendation.md) | Ranking, exploration, and control groups. |
227
+ | [Hooks](docs/hooks.md) | Claude Code and Codex CLI setup and recorded signals. |
228
+ | [Claude Code mod](docs/claude-mod.md) | Modes, routing scopes, and `/spatz` commands. |
229
+ | [Configuration](docs/configuration.md) | Environment variables and local files. |
230
+ | [Privacy](docs/privacy.md) | Network requests, stored data, and deletion. |
231
+
232
+ ## Contributing
233
+
234
+ Bug reports and pull requests are welcome.
235
+ For larger changes, [open an issue](https://github.com/lorenzh/spatz/issues) first to agree on the scope.
236
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and the test-first workflow.
237
+ Changes to `main` must go through a pull request.
238
+
239
+ Run the checks before submitting a change:
240
+
241
+ ```bash
242
+ bun test
243
+ bun run typecheck
244
+ bun run lint
245
+ ```
246
+
247
+ For vulnerabilities, follow [SECURITY.md](SECURITY.md). Do not open a public issue.
248
+
249
+ ## License
250
+
251
+ [MIT](LICENSE) © 2026 Lorenz Hilpert.
package/bin/spatz.js ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ const { spawnSync } = require("node:child_process");
3
+ const { constants } = require("node:os");
4
+ const { dirname, join } = require("node:path");
5
+
6
+ const packages = {
7
+ "linux-x64": "@spatz/cli-linux-x64",
8
+ "linux-arm64": "@spatz/cli-linux-arm64",
9
+ "darwin-arm64": "@spatz/cli-darwin-arm64",
10
+ "darwin-x64": "@spatz/cli-darwin-x64",
11
+ "win32-x64": "@spatz/cli-win32-x64",
12
+ };
13
+ const target = `${process.platform}-${process.arch}`;
14
+ const pkg = packages[target];
15
+ if (!pkg) {
16
+ console.error(`spatz: unsupported platform ${target}`);
17
+ process.exit(1);
18
+ }
19
+ let dir;
20
+ try {
21
+ dir = dirname(require.resolve(`${pkg}/package.json`));
22
+ } catch {
23
+ console.error(
24
+ `spatz: missing ${pkg}. Reinstall @spatz/cli with optional dependencies enabled (omit --no-optional / --omit=optional). The experimental Windows package may be unavailable for this release.`,
25
+ );
26
+ process.exit(1);
27
+ }
28
+ const child = spawnSync(
29
+ join(dir, process.platform === "win32" ? "spatz.exe" : "spatz"),
30
+ process.argv.slice(2),
31
+ { stdio: "inherit" },
32
+ );
33
+ if (child.error) {
34
+ console.error(`spatz: ${child.error.message}`);
35
+ process.exit(1);
36
+ }
37
+ if (child.signal) {
38
+ process.kill(process.pid, child.signal);
39
+ process.exit(128 + constants.signals[child.signal]);
40
+ } else process.exit(child.status ?? 1);
package/package.json CHANGED
@@ -1 +1,26 @@
1
- { "name": "@spatz/cli", "version": "0.0.0", "description": "Placeholder for spatz. Real releases are published from GitHub Actions.", "license": "MIT", "repository": { "type": "git", "url": "git+https://github.com/lorenzh/spatz.git" } }
1
+ {
2
+ "name": "@spatz/cli",
3
+ "version": "0.1.1",
4
+ "description": "Model and effort recommendations for coding agents",
5
+ "bin": {
6
+ "spatz": "bin/spatz.js"
7
+ },
8
+ "optionalDependencies": {
9
+ "@spatz/cli-linux-x64": "0.1.1",
10
+ "@spatz/cli-linux-arm64": "0.1.1",
11
+ "@spatz/cli-darwin-arm64": "0.1.1",
12
+ "@spatz/cli-darwin-x64": "0.1.1",
13
+ "@spatz/cli-win32-x64": "0.1.1"
14
+ },
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/lorenzh/spatz.git"
22
+ },
23
+ "files": [
24
+ "bin"
25
+ ]
26
+ }