@turri-dev/cli 0.2.2-next.368861b → 0.2.3-next.4c00ff0

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/README.md CHANGED
@@ -203,6 +203,8 @@ Start at the [documentation index](docs/README.md), or go straight to a page:
203
203
  | [The \*\*\*plain parser](docs/parser.md) | the TypeScript port, `turri parse`, fidelity to the reference parser |
204
204
  | [Theming](docs/theming.md) | the two brand themes |
205
205
  | [Development](docs/development.md) | tests, layout, known edges |
206
+ | [Contributing](CONTRIBUTING.md) | reporting a bug, making a change, the conventions a pull request follows |
207
+ | [Security](SECURITY.md) | reporting a vulnerability, privately |
206
208
 
207
209
  ## License
208
210
 
package/dist/cli.js CHANGED
@@ -88,6 +88,25 @@ const SETUP_COMMANDS = {
88
88
  "init-config": "write a quick start config.turri.yaml here, from the vendor you use: a strong model to talk to, a smaller one to write the code",
89
89
  doctor: "check what turri needs - in a project, what it needs of this machine, and that its models answer",
90
90
  };
91
+ /**
92
+ * pi's package commands, run by pi itself: someone who installs turri has no `pi` on the PATH - npm links only turri's
93
+ * own command - and turri reads the packages pi installs (installed-providers.ts). `update` updates packages only: pi's
94
+ * own `update` with no source updates pi, and turri's pi is a pinned dependency (`updatesPi`).
95
+ */
96
+ const PACKAGE_COMMANDS = {
97
+ install: "install a pi package for every turri, -l for this project only - pi-claude-code for a Claude subscription, say",
98
+ update: "update a pi package, or --extensions for all of them",
99
+ remove: "remove a pi package `turri install` put in",
100
+ list: "the pi packages installed, yours and this project's",
101
+ };
102
+ /** Whether `update <args>` would update pi itself, as pi reads it: no package named, `self` or `pi`, `--self` or `--all`. */
103
+ function updatesPi(args) {
104
+ if (args.some((arg) => arg === "--self" || arg === "--all" || arg === "self" || arg === "pi"))
105
+ return true;
106
+ if (args.some((arg) => arg === "-h" || arg === "--help" || arg === "--extensions" || arg === "--extension" || arg === "--models"))
107
+ return false;
108
+ return !args.some((arg) => !arg.startsWith("-"));
109
+ }
91
110
  const USAGE = [
92
111
  "usage: turri [options] open the conversation here: pi, with turri's tools and themes loaded",
93
112
  " turri <command> [args]",
@@ -101,6 +120,7 @@ const USAGE = [
101
120
  ...Object.entries(COMMANDS).map(([name, { blurb }]) => ` ${name.padEnd(12)} ${blurb}`),
102
121
  "",
103
122
  ...Object.entries(SETUP_COMMANDS).map(([name, blurb]) => ` ${name.padEnd(12)} ${blurb}`),
123
+ ...Object.entries(PACKAGE_COMMANDS).map(([name, blurb]) => ` ${name.padEnd(12)} ${blurb}`),
104
124
  "",
105
125
  "Run `turri <command> --help` for that command's options.",
106
126
  "",
@@ -152,7 +172,7 @@ if (command === "-v" || command === "--version") {
152
172
  // The AWS region the project's Bedrock models need, set before any model is called - here, and in the pi the chat starts,
153
173
  // which inherits it - unless the person set one (bedrock-region.ts). The models named on the command line count too. A
154
174
  // command that calls no model is left alone.
155
- if (!["parse", "drift", "lineage", "ledger", "init-config"].includes(command ?? "")) {
175
+ if (!["parse", "drift", "lineage", "ledger", "init-config", ...Object.keys(PACKAGE_COMMANDS)].includes(command ?? "")) {
156
176
  const { applyBedrockRegion } = await import("./bedrock-region.js");
157
177
  const named = argv.flatMap((arg, at) => ["--harness-model", "--coding-model", "--model"].includes(arg) ? [argv[at + 1] ?? ""] : arg.startsWith("--harness-model=") || arg.startsWith("--coding-model=") || arg.startsWith("--model=") ? [arg.slice(arg.indexOf("=") + 1)] : []);
158
178
  applyBedrockRegion(process.cwd(), { also: named.filter((model) => model !== "") });
@@ -169,6 +189,16 @@ if (command === "doctor") {
169
189
  const { main } = await import("./setup.js");
170
190
  process.exit(await main(rest));
171
191
  }
192
+ // pi's own main answers them, as `pi install` would - into the same ~/.pi/agent turri reads - and exits.
193
+ if (Object.hasOwn(PACKAGE_COMMANDS, command)) {
194
+ if (command === "update" && updatesPi(rest)) {
195
+ console.error("turri update updates pi packages: name one (turri update <source>), or --extensions for all of them. pi is part of turri - npm install -g @turri-dev/cli@latest updates both.");
196
+ process.exit(1);
197
+ }
198
+ const { main } = await import("@earendil-works/pi-coding-agent");
199
+ await main(argv);
200
+ process.exit(process.exitCode ?? 0);
201
+ }
172
202
  const entry = COMMANDS[command];
173
203
  if (entry === undefined) {
174
204
  console.error(`unknown command: ${command}\n\n${USAGE}`);
@@ -19,7 +19,7 @@ export const LOADING_PROVIDERS = "TURRI_LOADING_PROVIDERS";
19
19
  export const EXTENSION_PROVIDERS = {
20
20
  "claude-code": {
21
21
  label: "Claude subscription (Claude Code)",
22
- install: "pi install https://github.com/earendil-works/pi-claude-code",
22
+ install: "turri install https://github.com/earendil-works/pi-claude-code",
23
23
  signIn: "Claude Code installed (npm install -g @anthropic-ai/claude-code) and signed in to your Claude Pro or Max subscription: claude auth login",
24
24
  },
25
25
  };
@@ -105,7 +105,7 @@ export const TOOLS_NOT_CLI = [
105
105
  "run your own command line (turri regenerate, glimpse, conform, change-plain, cultivate, release, turri itself; npm run",
106
106
  "regenerate and the like) or start another pi through bash:",
107
107
  "that is a separate headless run the user cannot see or stop. Only when the user explicitly asks for the",
108
- "command line, run it. Configuring pi itself - pi install, update, list, config, auth, or its settings files -",
108
+ "command line, run it. Configuring pi itself - turri install, update, remove, list; pi install, update, list, config, auth; or its settings files -",
109
109
  "is fine; it starts no agent.",
110
110
  "</turri-tools>",
111
111
  ].join("\n");
@@ -21,13 +21,13 @@ turri does not depend on the extension: it finds it as pi does, once you have in
21
21
  claude auth login
22
22
  ```
23
23
 
24
- 2. **The extension**, installed into pi - turri runs on pi, and reads what pi has installed:
24
+ 2. **The extension**, installed with turri - turri runs on [pi](https://pi.dev), and `turri install` is pi's own `install`, run by the pi inside turri: no `pi` of your own needed:
25
25
 
26
26
  ```bash
27
- pi install https://github.com/earendil-works/pi-claude-code
27
+ turri install https://github.com/earendil-works/pi-claude-code
28
28
  ```
29
29
 
30
- `pi list` shows it; `pi update` updates it; `pi remove https://github.com/earendil-works/pi-claude-code` takes it out.
30
+ `turri list` shows it; `turri update https://github.com/earendil-works/pi-claude-code` updates it; `turri remove https://github.com/earendil-works/pi-claude-code` takes it out. A `pi` you installed yourself sees the same packages, as they are kept in pi's own folder, `~/.pi/agent`.
31
31
 
32
32
  3. **Nothing in the environment that points Claude Code elsewhere.** The extension refuses to run while any of these is set - each sends Claude Code to an API key or another backend, not the subscription: `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `ANTHROPIC_FOUNDRY_API_KEY`, `CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX`, `CLAUDE_CODE_USE_FOUNDRY`. Unset them where turri starts - your shell's profile, say. A project that also uses `anthropic/...` models with a key cannot run both from one shell.
33
33
 
@@ -74,10 +74,10 @@ Each step says what you should see; the first one that does not is where it stop
74
74
 
75
75
  `"loggedIn": true`, and a `"subscriptionType"` - `pro`, `max` or `team`. None, and Claude Code is signed in with a Console API key instead: `claude auth logout`, then `claude auth login` with the subscription's account.
76
76
 
77
- 2. **pi has the extension.**
77
+ 2. **The extension is installed.**
78
78
 
79
79
  ```bash
80
- pi list
80
+ turri list
81
81
  ```
82
82
 
83
83
  lists `https://github.com/earendil-works/pi-claude-code`.
@@ -121,7 +121,7 @@ The desktop app runs turri's chats itself, and finds the extension as the termin
121
121
 
122
122
  | what you see | what to do |
123
123
  | --- | --- |
124
- | `claude-code/... need pi-claude-code, pi's extension for a Claude subscription, which is not installed` | `pi install https://github.com/earendil-works/pi-claude-code` |
124
+ | `claude-code/... need pi-claude-code, pi's extension for a Claude subscription, which is not installed` | `turri install https://github.com/earendil-works/pi-claude-code` |
125
125
  | `claude-code - Claude subscription (Claude Code) - is not there` | the same: the extension is not installed, so pi has no `claude-code` provider |
126
126
  | `Claude Code (claude) is not on the PATH` | `npm install -g @anthropic-ai/claude-code`, or name it in `PI_CLAUDE_CODE_COMMAND` |
127
127
  | `Claude Code (...) is not signed in` | `claude auth login` |
package/docs/config.md CHANGED
@@ -28,7 +28,7 @@ Beside a `config.yaml` - another tool's, say - the new file takes from it the ke
28
28
 
29
29
  ### A Claude subscription
30
30
 
31
- `--vendor claude-code` runs every agent on your Claude Pro, Max or Team subscription instead of an API key, through Claude Code and [pi-claude-code](https://github.com/earendil-works/pi-claude-code), a [pi](https://pi.dev) extension: `claude-code/claude-opus-5-5` to talk to, `claude-code/claude-sonnet-5` to code. It needs the extension installed (`pi install https://github.com/earendil-works/pi-claude-code`) and Claude Code signed in (`claude auth login`), and none of `ANTHROPIC_API_KEY` and the like set; `turri doctor` checks all of it. Setting it up, its models, checking it yourself and what to know: [a Claude subscription](claude-subscription.md).
31
+ `--vendor claude-code` runs every agent on your Claude Pro, Max or Team subscription instead of an API key, through Claude Code and [pi-claude-code](https://github.com/earendil-works/pi-claude-code), a [pi](https://pi.dev) extension: `claude-code/claude-opus-5-5` to talk to, `claude-code/claude-sonnet-5` to code. It needs the extension installed (`turri install https://github.com/earendil-works/pi-claude-code`) and Claude Code signed in (`claude auth login`), and none of `ANTHROPIC_API_KEY` and the like set; `turri doctor` checks all of it. Setting it up, its models, checking it yourself and what to know: [a Claude subscription](claude-subscription.md).
32
32
 
33
33
  ## Where it is read from
34
34
 
@@ -7,6 +7,8 @@ npm test # the whole suite, about a minute
7
7
 
8
8
  The suite runs the projects' testing scripts on this machine, as turri does. The fixture scripts stage each run in a folder of their own, as test files running side by side would otherwise share one.
9
9
 
10
+ `.github/workflows/test.yml` runs both on GitHub for every pull request and every push to `main`, and `.github/workflows/commits.yml` checks that each of a pull request's commits is a Conventional Commit, with no fixup and no merge commit - a pull request is rebased onto `main`, each commit as it is. A pull request from a fork runs them once the maintainer has approved its run - see [contributing](../CONTRIBUTING.md).
11
+
10
12
  ## Mutation testing
11
13
 
12
14
  `npm run mutation` runs [Stryker](https://stryker-mutator.io) over the core of regeneration - the parts in `stryker.config.mjs` - one part at a time, each part's report in `reports/mutation/<part>/`. A part with a report is skipped, so a run that is stopped goes on from the part it was in; `npm run mutation -- <part>` runs one again, and `npm run mutation -- --summary` sums up the reports there are. It takes hours: each part runs the suite once to find which tests reach which mutant (`vitest.stryker.config.ts`, the suite without the files that run real commands behind the sandbox), then each mutant against those tests. `scripts/mutation.mjs` sets two things in Stryker's vitest runner the suite needs - forked workers, and a test-name filter that matches vitest 5's names - again after every `npm install`.
@@ -22,7 +24,7 @@ The package on npm is built: Node runs no `.ts` file inside a `node_modules`, so
22
24
  `.github/workflows/publish.yml` publishes `@turri-dev/cli`, after the typecheck and the suite pass:
23
25
 
24
26
  - every push or merge into `main` publishes a prerelease under the `next` dist-tag - the patch after the `latest` on npm, with the commit's sha: `0.1.2-next.<sha>`. `latest` is not touched; `npm install -g @turri-dev/cli@next` gets it.
25
- - a GitHub Release tagged `vX.Y.Z`, published by hand, publishes `X.Y.Z` as `latest` (or under `next`, if the release is marked a pre-release). The tag is the version: it is set in the runner and never committed, so `package.json`'s version on `main` is stale by design.
27
+ - a GitHub Release tagged `vX.Y.Z`, published by hand, publishes `X.Y.Z` as `latest` (or under `next`, if the release is marked a pre-release). The tag is the version: it is set in the runner and never committed, so `package.json`'s version on `main` is stale by design. Once npm has a release on `latest`, it is posted to the Discord channel whose webhook is the repository secret `DISCORD_RELEASE_WEBHOOK` - its name, its notes and the command that updates; without the secret nothing is posted.
26
28
 
27
29
  The workflow signs in to npm with npm's trusted publishing (OIDC), not a token: the package's settings on npmjs.com trust `turri-oss/turri`, the workflow `publish.yml` and the environment `npm`. Renaming the workflow file breaks it.
28
30
 
@@ -22,6 +22,10 @@ turri # pi, with turri's tools, skills and themes loaded
22
22
 
23
23
  The package is `@turri-dev/cli` - npm keeps the name `turri` back, as too like another package's - and the command it puts on your PATH is `turri`. `npm install -g @turri-dev/cli@next` gets the latest prerelease instead, built from the last commit on `main`. Installed again - `@latest` or `@next` - it replaces the one there. `turri --version` says which turri runs, and the [pi](https://pi.dev) under it: `turri 0.1.6 (pi 1.3.0)`, and from a clone its commit too.
24
24
 
25
+ ## pi packages
26
+
27
+ turri installs no `pi` command of its own - npm puts only `turri` on your PATH - so [pi](https://pi.dev)'s package commands are turri's: `turri install <source>` installs a pi package - an extension, such as [pi-claude-code](https://github.com/earendil-works/pi-claude-code) for a [Claude subscription](claude-subscription.md) - for every turri (`-l` for this project only), `turri list` lists them, `turri update <source>` updates one and `turri update --extensions` all of them, and `turri remove <source>` takes one out. They are pi's own commands, run by the pi inside turri, and keep the packages in pi's folder, `~/.pi/agent`, where a `pi` you installed yourself finds them too. `turri update` never updates pi itself - pi is part of turri, and installing turri again updates both.
28
+
25
29
  ## An older turri runs
26
30
 
27
31
  The install worked, and a bare `turri` still runs an older turri: another one comes before it on your PATH - installed under another Node version (nvm, fnm, volta and the like give each its own global folder), or an old `npm link` of a clone. The shell takes the first it finds.
package/docs/using.md CHANGED
@@ -45,7 +45,7 @@ What each regeneration does, in order, is in [regenerating the code](regeneratin
45
45
 
46
46
  The conversation knows the \*\*\*plain standard library from its first turn: the stdlib rule in its system prompt lists every stack and where the templates are, so "build this as a Flask backend" becomes an import of `stdlib/backend/python-flask`, not a stack written out by hand. The banner at start says how many stacks there are, and **`/stdlib`** lists them, each with what it is (see [the standard library](authoring-plain.md#the-standard-library)).
47
47
 
48
- The agent does this work with these tools, never by running turri's command line - or another [pi](https://pi.dev) - through its shell: that would be a separate, headless run outside regen and `/halt`. The harness refuses such a command unless your message asks for the command line ("run it headless", "use the CLI", or the command itself). [pi](https://pi.dev) configuring itself - `pi install`, `update`, `list`, `config`, `auth`, its settings - is not affected.
48
+ The agent does this work with these tools, never by running turri's command line - or another [pi](https://pi.dev) - through its shell: that would be a separate, headless run outside regen and `/halt`. The harness refuses such a command unless your message asks for the command line ("run it headless", "use the CLI", or the command itself). [pi](https://pi.dev) configuring itself - `turri install`, `update`, `remove`, `list`, or `pi install`, `update`, `list`, `config`, `auth`, its settings - is not affected.
49
49
 
50
50
  ## Pulses
51
51
 
@@ -146,7 +146,7 @@ A provider failure - no credits, bad key, rate limit - is raised with the provid
146
146
 
147
147
  ### Models from a pi extension
148
148
 
149
- A provider a [pi](https://pi.dev) extension registers - a Claude subscription through Claude Code ([pi-claude-code](https://github.com/earendil-works/pi-claude-code), `claude-code/...`, see [a Claude subscription](claude-subscription.md)), your company's own LLM gateway - works everywhere turri needs a model. Install the extension as pi installs any (`pi install <source>`), and name its models in `config.turri.yaml` as any. In the chat, pi loads it, and every agent the conversation starts is given the providers the conversation's extensions registered, as they are when it starts - the agents themselves load no extensions, which would load a second copy of turri. A run with no conversation - `turri regenerate`, `turri change-plain`, `turri release` and the rest - loads the installed extensions itself, once, as pi loads them, and keeps only the providers they register; so do `turri doctor` and `turri init-config`, which check a model against them. An older turri that installed itself into every pi is among them, and is loaded as nothing.
149
+ A provider a [pi](https://pi.dev) extension registers - a Claude subscription through Claude Code ([pi-claude-code](https://github.com/earendil-works/pi-claude-code), `claude-code/...`, see [a Claude subscription](claude-subscription.md)), your company's own LLM gateway - works everywhere turri needs a model. Install the extension with `turri install <source>` - pi's own `install`, run by the pi inside turri, so no `pi` of your own is needed; `turri list`, `turri update <source>` and `turri remove <source>` are pi's too - and name its models in `config.turri.yaml` as any. In the chat, pi loads it, and every agent the conversation starts is given the providers the conversation's extensions registered, as they are when it starts - the agents themselves load no extensions, which would load a second copy of turri. A run with no conversation - `turri regenerate`, `turri change-plain`, `turri release` and the rest - loads the installed extensions itself, once, as pi loads them, and keeps only the providers they register; so do `turri doctor` and `turri init-config`, which check a model against them. An older turri that installed itself into every pi is among them, and is loaded as nothing.
150
150
 
151
151
  ## Module history
152
152
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turri-dev/cli",
3
- "version": "0.2.2-next.368861b",
3
+ "version": "0.2.3-next.4c00ff0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {