@turri-dev/cli 0.1.9 → 0.1.10-next.2abf2db
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/dist/doctor-tool.d.ts +11 -0
- package/dist/doctor-tool.js +33 -0
- package/dist/extension.js +3 -1
- package/dist/host.d.ts +3 -1
- package/dist/host.js +6 -2
- package/dist/init-config.d.ts +87 -27
- package/dist/init-config.js +342 -71
- package/dist/model-check.d.ts +23 -0
- package/dist/model-check.js +40 -16
- package/dist/regenerate/os-sandbox.d.ts +2 -0
- package/dist/regenerate/os-sandbox.js +6 -1
- package/dist/regenerate/test-scripts.d.ts +2 -2
- package/dist/regenerate/test-scripts.js +3 -3
- package/dist/setup.d.ts +24 -2
- package/dist/setup.js +51 -21
- package/docs/config.md +3 -1
- package/docs/development.md +1 -1
- package/docs/hosting.md +42 -1
- package/docs/installation.md +1 -1
- package/docs/using.md +1 -0
- package/package.json +1 -1
package/dist/model-check.d.ts
CHANGED
|
@@ -34,6 +34,29 @@ export interface ModelAccess {
|
|
|
34
34
|
/** One short request: the error the provider gave, or nothing when it answered. */
|
|
35
35
|
ask(model: unknown): Promise<string | undefined>;
|
|
36
36
|
}
|
|
37
|
+
/** A model in pi's catalogue: what turri chooses among, and checks a model it is given against. */
|
|
38
|
+
export interface CatalogueModel {
|
|
39
|
+
provider: string;
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
/** chat for a model turri's agents can talk to; pi's image and classifier models are in the catalogue too. */
|
|
43
|
+
type: string;
|
|
44
|
+
reasoning: boolean;
|
|
45
|
+
/** Dollars a million tokens. */
|
|
46
|
+
cost: {
|
|
47
|
+
input: number;
|
|
48
|
+
output: number;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** pi's model catalogue, as turri's agents have it - its built-in models and models.json's. */
|
|
52
|
+
export interface ModelCatalogue {
|
|
53
|
+
/** A provider's models, every type - none for a provider pi does not know. */
|
|
54
|
+
models(provider: string): CatalogueModel[];
|
|
55
|
+
}
|
|
56
|
+
/** pi's catalogue, read once: no request, no model asked. */
|
|
57
|
+
export declare function piModelCatalogue(): Promise<ModelCatalogue>;
|
|
58
|
+
/** The ids of `known` closest to `id`, at most three: the most of its words in common - claude, haiku - then the most of its characters in order. */
|
|
59
|
+
export declare function closestModels(id: string, known: string[]): string[];
|
|
37
60
|
/** pi's own model runtime, with pi's credentials - auth.json, models.json and the environment - as turri's agents have them. */
|
|
38
61
|
export declare function piModelAccess(): Promise<ModelAccess>;
|
|
39
62
|
/** What to do about a provider's error, from what it says. */
|
package/dist/model-check.js
CHANGED
|
@@ -22,10 +22,47 @@ export function modelsInUse(config) {
|
|
|
22
22
|
add(config.models?.default?.model, "models.default");
|
|
23
23
|
return [...used].map(([spec, usedBy]) => ({ spec, usedBy: [...new Set(usedBy)] }));
|
|
24
24
|
}
|
|
25
|
+
/** pi's own model runtime: its catalogue with models.json's, and pi's credentials - auth.json and the environment. */
|
|
26
|
+
async function piRuntime() {
|
|
27
|
+
const agentDir = getAgentDir();
|
|
28
|
+
return ModelRuntime.create({ authPath: join(agentDir, "auth.json"), modelsPath: join(agentDir, "models.json") });
|
|
29
|
+
}
|
|
30
|
+
/** pi's catalogue, read once: no request, no model asked. */
|
|
31
|
+
export async function piModelCatalogue() {
|
|
32
|
+
const runtime = await piRuntime();
|
|
33
|
+
return {
|
|
34
|
+
models: (provider) => runtime.getAllModels(provider).map((model) => {
|
|
35
|
+
const entry = model;
|
|
36
|
+
return {
|
|
37
|
+
provider: model.provider,
|
|
38
|
+
id: model.id,
|
|
39
|
+
name: entry.name ?? model.id,
|
|
40
|
+
type: entry.type ?? "chat",
|
|
41
|
+
reasoning: entry.reasoning === true,
|
|
42
|
+
cost: { input: entry.cost?.input ?? 0, output: entry.cost?.output ?? 0 },
|
|
43
|
+
};
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** The ids of `known` closest to `id`, at most three: the most of its words in common - claude, haiku - then the most of its characters in order. */
|
|
48
|
+
export function closestModels(id, known) {
|
|
49
|
+
const words = id.toLowerCase().split(/[^a-z0-9]+/).filter((word) => word !== "");
|
|
50
|
+
const score = (other) => {
|
|
51
|
+
const theirs = other.toLowerCase().split(/[^a-z0-9]+/);
|
|
52
|
+
return words.filter((word) => theirs.includes(word)).length * 100 + commonPrefix(id.toLowerCase(), other.toLowerCase());
|
|
53
|
+
};
|
|
54
|
+
const scored = known.map((other) => ({ other, score: score(other) })).filter((entry) => entry.score >= 100);
|
|
55
|
+
// Only those sharing as many words as the best: claude-haiku-* for claude-haiku-9-9, not every claude.
|
|
56
|
+
const most = Math.max(0, ...scored.map((entry) => Math.floor(entry.score / 100)));
|
|
57
|
+
return scored
|
|
58
|
+
.filter((entry) => Math.floor(entry.score / 100) === most)
|
|
59
|
+
.sort((a, b) => b.score - a.score)
|
|
60
|
+
.slice(0, 3)
|
|
61
|
+
.map((entry) => entry.other);
|
|
62
|
+
}
|
|
25
63
|
/** pi's own model runtime, with pi's credentials - auth.json, models.json and the environment - as turri's agents have them. */
|
|
26
64
|
export async function piModelAccess() {
|
|
27
|
-
const
|
|
28
|
-
const runtime = await ModelRuntime.create({ authPath: join(agentDir, "auth.json"), modelsPath: join(agentDir, "models.json") });
|
|
65
|
+
const runtime = await piRuntime();
|
|
29
66
|
const registry = new ModelRegistry(runtime);
|
|
30
67
|
return {
|
|
31
68
|
find: (provider, id) => registry.find(provider, id),
|
|
@@ -91,20 +128,7 @@ export async function checkModels(models, access) {
|
|
|
91
128
|
const known = access.known(provider);
|
|
92
129
|
if (known.length === 0)
|
|
93
130
|
return { ok: false, spec, usedBy, problem: `pi knows no provider ${provider}`, fix: "use a provider pi supports - https://pi.dev" };
|
|
94
|
-
|
|
95
|
-
const words = id.toLowerCase().split(/[^a-z0-9]+/).filter((word) => word !== "");
|
|
96
|
-
const score = (other) => {
|
|
97
|
-
const theirs = other.toLowerCase().split(/[^a-z0-9]+/);
|
|
98
|
-
return words.filter((word) => theirs.includes(word)).length * 100 + commonPrefix(id.toLowerCase(), other.toLowerCase());
|
|
99
|
-
};
|
|
100
|
-
const scored = known.map((other) => ({ other, score: score(other) })).filter((entry) => entry.score >= 100);
|
|
101
|
-
// Only those sharing as many words as the best: claude-haiku-* for claude-haiku-9-9, not every claude.
|
|
102
|
-
const most = Math.max(0, ...scored.map((entry) => Math.floor(entry.score / 100)));
|
|
103
|
-
const close = scored
|
|
104
|
-
.filter((entry) => Math.floor(entry.score / 100) === most)
|
|
105
|
-
.sort((a, b) => b.score - a.score)
|
|
106
|
-
.slice(0, 3)
|
|
107
|
-
.map((entry) => entry.other);
|
|
131
|
+
const close = closestModels(id, known);
|
|
108
132
|
return { ok: false, spec, usedBy, problem: `pi knows no model ${id} at ${provider}`, fix: close.length > 0 ? `did you mean ${close.map((other) => `${provider}/${other}`).join(", ")}?` : `change it in config.turri.yaml to a model pi knows at ${provider}` };
|
|
109
133
|
}
|
|
110
134
|
const started = Date.now();
|
|
@@ -80,6 +80,8 @@ export declare function setSandboxForTests(wall: Wall | undefined): void;
|
|
|
80
80
|
* and how to give it one.
|
|
81
81
|
*/
|
|
82
82
|
export declare function sandboxAvailable(): Wall;
|
|
83
|
+
/** Which wall would be up with `env`'s PATH - doctor's for a host whose engine runs with another PATH; tried each time. */
|
|
84
|
+
export declare function sandboxWith(env: NodeJS.ProcessEnv): Wall;
|
|
83
85
|
/** Whether `path` is one of the fence's roots or under one - for the tests and the audit. */
|
|
84
86
|
export declare function underAny(path: string, roots: string[]): boolean;
|
|
85
87
|
/** Where the agents' commands run, in one line for the person: which walls are up, or that only the shared ones are. */
|
|
@@ -203,7 +203,11 @@ export function sandboxAvailable() {
|
|
|
203
203
|
probed = probe();
|
|
204
204
|
return probed;
|
|
205
205
|
}
|
|
206
|
-
|
|
206
|
+
/** Which wall would be up with `env`'s PATH - doctor's for a host whose engine runs with another PATH; tried each time. */
|
|
207
|
+
export function sandboxWith(env) {
|
|
208
|
+
return env === process.env ? sandboxAvailable() : probe(env);
|
|
209
|
+
}
|
|
210
|
+
function probe(env = process.env) {
|
|
207
211
|
if (process.platform === "darwin") {
|
|
208
212
|
if (!existsSync("/usr/bin/sandbox-exec"))
|
|
209
213
|
return { kind: "none", why: "this macOS has no sandbox-exec (Seatbelt)" };
|
|
@@ -214,6 +218,7 @@ function probe() {
|
|
|
214
218
|
const tryProc = (proc) => spawnSync("bwrap", ["--ro-bind", "/", "/", ...(proc === "mount" ? ["--proc", "/proc"] : ["--ro-bind", "/proc", "/proc"]), "--dev", "/dev", "--unshare-pid", "--", "true"], {
|
|
215
219
|
encoding: "utf8",
|
|
216
220
|
timeout: 10_000,
|
|
221
|
+
env,
|
|
217
222
|
});
|
|
218
223
|
const mounted = tryProc("mount");
|
|
219
224
|
if (mounted.status === 0)
|
|
@@ -208,8 +208,8 @@ export declare function runTiming(run: Pick<ScriptRun, "exitCode" | "timedOut" |
|
|
|
208
208
|
export declare function missingToolchain(run: Pick<ScriptRun, "exitCode" | "output">): {
|
|
209
209
|
command?: string;
|
|
210
210
|
} | undefined;
|
|
211
|
-
/** Where `command` is on this machine's PATH, or undefined when it is not there. */
|
|
212
|
-
export declare function onThisMachine(command: string): string | undefined;
|
|
211
|
+
/** Where `command` is on this machine's PATH - `env`'s, by default turri's own - or undefined when it is not there. */
|
|
212
|
+
export declare function onThisMachine(command: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
213
213
|
/** What a failed script run means, for the agent that has to fix it. */
|
|
214
214
|
export declare function scriptFailure(run: ScriptRun, what: string): string;
|
|
215
215
|
/** How many rounds running the same failure, taken the same way, is nothing changing. */
|
|
@@ -738,9 +738,9 @@ export function missingToolchain(run) {
|
|
|
738
738
|
const command = last !== undefined ? named(last) : undefined;
|
|
739
739
|
return command !== undefined ? { command } : undefined;
|
|
740
740
|
}
|
|
741
|
-
/** Where `command` is on this machine's PATH, or undefined when it is not there. */
|
|
742
|
-
export function onThisMachine(command) {
|
|
743
|
-
for (const dir of (
|
|
741
|
+
/** Where `command` is on this machine's PATH - `env`'s, by default turri's own - or undefined when it is not there. */
|
|
742
|
+
export function onThisMachine(command, env = process.env) {
|
|
743
|
+
for (const dir of (env.PATH ?? "").split(delimiter)) {
|
|
744
744
|
if (dir === "")
|
|
745
745
|
continue;
|
|
746
746
|
const path = join(dir, command);
|
package/dist/setup.d.ts
CHANGED
|
@@ -8,8 +8,10 @@ import { type ModelAccess } from "./model-check.ts";
|
|
|
8
8
|
* implementation reqs name and what its testing scripts run, and whether each model its config names answers with this
|
|
9
9
|
* machine's credentials - one short request a model (model-check.ts). It changes one thing, asked first: a plain `pi`
|
|
10
10
|
* that an older turri made load turri too is put back to plain pi.
|
|
11
|
+
*
|
|
12
|
+
* `doctor()` is the checks alone, for the CLI and for a host (src/host.ts): it prints nothing and asks nothing.
|
|
11
13
|
*/
|
|
12
|
-
type Level = "ok" | "missing" | "limited" | "info";
|
|
14
|
+
export type Level = "ok" | "missing" | "limited" | "info";
|
|
13
15
|
export interface Check {
|
|
14
16
|
level: Level;
|
|
15
17
|
name: string;
|
|
@@ -39,5 +41,25 @@ export declare function checkProject(project: string, machine?: {
|
|
|
39
41
|
* request. A model that answers is ready; one that does not says what the provider said, and what to do about it.
|
|
40
42
|
*/
|
|
41
43
|
export declare function checkProjectModels(project: string, access?: ModelAccess): Promise<Check[]>;
|
|
44
|
+
export interface DoctorOptions {
|
|
45
|
+
/** The project to check. Without it, only the machine. */
|
|
46
|
+
dir?: string;
|
|
47
|
+
/** Ask each model the project's config names for a word - a request a model, which costs money. Off by default. */
|
|
48
|
+
models?: boolean;
|
|
49
|
+
/** The environment whose PATH is looked in - bash, git, the sandbox, the toolchains, what the testing scripts run. */
|
|
50
|
+
env?: NodeJS.ProcessEnv;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Whether this machine - and, with `dir`, that project - is ready for turri, as checks. It prints nothing and asks
|
|
54
|
+
* nothing, so a host can show them in its own way; `turri doctor` prints the same.
|
|
55
|
+
*/
|
|
56
|
+
export declare function doctor(options?: DoctorOptions): Promise<Check[]>;
|
|
57
|
+
/** What the checks add up to: whether turri can run here, and a line saying so - with its marks, uncoloured. */
|
|
58
|
+
export declare function verdictOf(checks: Check[]): {
|
|
59
|
+
ready: boolean;
|
|
60
|
+
limited: boolean;
|
|
61
|
+
text: string;
|
|
62
|
+
};
|
|
63
|
+
/** The checks as plain text, a line each and what to do under it, and the verdict - for a reader that is not a terminal. */
|
|
64
|
+
export declare function describeChecks(checks: Check[]): string;
|
|
42
65
|
export declare function main(args: string[]): Promise<number>;
|
|
43
|
-
export {};
|
package/dist/setup.js
CHANGED
|
@@ -4,8 +4,8 @@ import { join, relative } from "node:path";
|
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
5
|
import { ModelRegistry, ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { globalExtensionFile, MIN_NODE, nodeVersionOk, PACKAGE_ROOT, turriOnPath } from "./environment.js";
|
|
7
|
-
import { sandboxAvailable } from "./regenerate/os-sandbox.js";
|
|
8
|
-
import { findTestScripts } from "./regenerate/test-scripts.js";
|
|
7
|
+
import { sandboxAvailable, sandboxWith } from "./regenerate/os-sandbox.js";
|
|
8
|
+
import { findTestScripts, onThisMachine } from "./regenerate/test-scripts.js";
|
|
9
9
|
import { configKeyOptions, parseArgs } from "./cli-options.js";
|
|
10
10
|
import { hasProjectConfig, readProjectConfig, stackOf } from "./config.js";
|
|
11
11
|
import { checkModels, modelsInUse, piModelAccess } from "./model-check.js";
|
|
@@ -168,20 +168,52 @@ export async function checkProjectModels(project, access) {
|
|
|
168
168
|
: { level: "missing", name, detail: verdict.spec, more: [verdict.problem], fix: verdict.fix };
|
|
169
169
|
});
|
|
170
170
|
}
|
|
171
|
-
/**
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
/** Whether doctor asks the models: asked to, and a config that names them - or models named on the command line. */
|
|
172
|
+
function asksModels(options) {
|
|
173
|
+
return options.models === true && options.dir !== undefined && (hasProjectConfig(options.dir) || options.modelsGiven === true);
|
|
174
|
+
}
|
|
175
|
+
/** Whether `command` runs with `env` - its PATH looked in, as the agents' shells and the testing scripts would. */
|
|
176
|
+
function runs(command, env) {
|
|
177
|
+
return spawnSync(command, ["--version"], { stdio: "ignore", windowsHide: true, env }).status === 0;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Whether this machine - and, with `dir`, that project - is ready for turri, as checks. It prints nothing and asks
|
|
181
|
+
* nothing, so a host can show them in its own way; `turri doctor` prints the same.
|
|
182
|
+
*/
|
|
183
|
+
export async function doctor(options = {}) {
|
|
184
|
+
return allChecks({ ...options, cli: false, modelsGiven: false });
|
|
185
|
+
}
|
|
186
|
+
async function allChecks(options) {
|
|
187
|
+
const env = options.env ?? process.env;
|
|
188
|
+
const checks = [checkNode(), checkShell(runs("bash", env)), checkGit(runs("git", env)), checkSandbox(sandboxWith(env)), await checkProvider()];
|
|
189
|
+
if (options.cli === true)
|
|
190
|
+
checks.push(checkPath(), ...checkGlobal());
|
|
174
191
|
// In a project: what it needs of this machine, and whether its models answer.
|
|
175
|
-
const project =
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
192
|
+
const project = options.dir;
|
|
193
|
+
if (project === undefined)
|
|
194
|
+
return checks;
|
|
195
|
+
if (hasProjectConfig(project) || existsSync(join(project, "plain_modules")))
|
|
196
|
+
checks.push(...checkProject(project, { has: (command) => onThisMachine(command, env) !== undefined }));
|
|
197
|
+
if (asksModels(options))
|
|
181
198
|
checks.push(...(await checkProjectModels(project)));
|
|
182
|
-
}
|
|
183
199
|
return checks;
|
|
184
200
|
}
|
|
201
|
+
/** What the checks add up to: whether turri can run here, and a line saying so - with its marks, uncoloured. */
|
|
202
|
+
export function verdictOf(checks) {
|
|
203
|
+
const missing = checks.filter((check) => check.level === "missing").length;
|
|
204
|
+
if (missing > 0)
|
|
205
|
+
return { ready: false, limited: false, text: `Not ready: ${missing} thing${missing === 1 ? "" : "s"} to fix (✗ above).` };
|
|
206
|
+
return checks.some((check) => check.level === "limited") ? { ready: true, limited: true, text: "Ready, with limits (! above)." } : { ready: true, limited: false, text: "Ready." };
|
|
207
|
+
}
|
|
208
|
+
/** The checks as plain text, a line each and what to do under it, and the verdict - for a reader that is not a terminal. */
|
|
209
|
+
export function describeChecks(checks) {
|
|
210
|
+
const lines = checks.flatMap((check) => [
|
|
211
|
+
`${MARK[check.level]} ${check.name}: ${check.detail}`,
|
|
212
|
+
...(check.more ?? []).map((line) => ` ${line}`),
|
|
213
|
+
...(check.fix && check.level !== "ok" ? [` → ${check.fix}`] : []),
|
|
214
|
+
]);
|
|
215
|
+
return `${lines.join("\n")}\n\n${verdictOf(checks).text}`;
|
|
216
|
+
}
|
|
185
217
|
function report(checks) {
|
|
186
218
|
const paint = { ok: brand.success, missing: brand.error, limited: brand.warning, info: brand.muted };
|
|
187
219
|
// The names in one column, however long the longest - `judge-model, models.judge` - is.
|
|
@@ -196,15 +228,10 @@ function report(checks) {
|
|
|
196
228
|
if (check.fix && check.level !== "ok")
|
|
197
229
|
console.log(`${under}${brand.accent(`→ ${check.fix}`)}`);
|
|
198
230
|
}
|
|
199
|
-
const
|
|
200
|
-
const limited = checks.filter((check) => check.level === "limited");
|
|
231
|
+
const verdict = verdictOf(checks);
|
|
201
232
|
console.log("");
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return 1;
|
|
205
|
-
}
|
|
206
|
-
console.log(limited.length > 0 ? brand.warning("Ready, with limits (! above).") : brand.success("Ready."));
|
|
207
|
-
return 0;
|
|
233
|
+
console.log((verdict.ready ? (verdict.limited ? brand.warning : brand.success) : brand.error)(verdict.text));
|
|
234
|
+
return verdict.ready ? 0 : 1;
|
|
208
235
|
}
|
|
209
236
|
/**
|
|
210
237
|
* Ask a yes/no question. With --yes, yes. With no terminal to ask on, no - nothing is changed
|
|
@@ -254,7 +281,10 @@ export async function main(args) {
|
|
|
254
281
|
if (!nodeVersionOk())
|
|
255
282
|
return report([checkNode()]);
|
|
256
283
|
const modelsGiven = ["harness-model", "model", "judge-model"].some((name) => options[name] !== undefined);
|
|
257
|
-
const
|
|
284
|
+
const checking = { dir: process.cwd(), models: options["no-models"] === undefined, cli: true, modelsGiven };
|
|
285
|
+
if (asksModels(checking))
|
|
286
|
+
console.log(brand.muted("Asking each model the config names for a word..."));
|
|
287
|
+
const code = report(await allChecks(checking));
|
|
258
288
|
// turri and pi are kept apart: a plain pi does not start turri. An older turri's file that made it do so goes, asked.
|
|
259
289
|
const global = globalExtensionFile();
|
|
260
290
|
if (global !== undefined && (await confirm(`\nA plain \`pi\` loads turri too, through ${global}. Remove it, so pi stays pi?`, true, options.yes !== undefined || args.includes("-y")))) {
|
package/docs/config.md
CHANGED
|
@@ -13,7 +13,9 @@ A project says how its specs are built in `config.turri.yaml`, at the project ro
|
|
|
13
13
|
| `amazon-bedrock`, OpenAI | `amazon-bedrock/<region>.openai.gpt-6-astra` | `amazon-bedrock/<region>.openai.gpt-6-luna` | `AWS_PROFILE` or `AWS_BEARER_TOKEN_BEDROCK`, and `AWS_REGION` |
|
|
14
14
|
| `other` | `anthropic/claude-opus-5-5` | `google/gemini-3.1-flash-lite` | `ANTHROPIC_API_KEY`, `GEMINI_API_KEY` |
|
|
15
15
|
|
|
16
|
-
Amazon Bedrock asks
|
|
16
|
+
Amazon Bedrock asks three more things, from pi's catalogue: whose models (`--maker`) - any maker pi has there, Anthropic, OpenAI, xAI, Meta, Mistral, Qwen, DeepSeek, Amazon, Moonshot, Z.ai, ... - the geography of the inference profile they run in (`--region`) - `global`, `us`, `eu`, `jp`, `au`, `apac`, `in`, `ca` or `us-gov`, as the maker's models are served, or `in-region`, a model served in the AWS region it is called in - and which two. The pair offered is turri's own where it has one - the table's, for Anthropic's and OpenAI's in the geographies they are served in - and otherwise the catalogue's: the strongest model that reasons to talk to, by price, and the cheapest costing at least a fifth of it to code, ties to the newest. Enter takes it; `c` chooses each from the maker's models there. It says which `AWS_REGION` to set for the geography: pi calls Bedrock in `AWS_REGION`, else the profile's region, else `us-east-1` - so every geography but `global`, `us` and `in-region` needs it set (`eu-central-1` for `eu`, `ap-northeast-1` for `jp`, `ap-southeast-2` for `au`, `ap-southeast-1` for `apac`, `ap-south-1` for `in`, `ca-central-1` for `ca`, `us-gov-west-1` for `us-gov`). A `config.turri.yaml` already there keeps everything but its two models, which it sets; `--force` writes the whole file again.
|
|
17
|
+
|
|
18
|
+
`--harness-model` and `--coding-model` name the two models instead, for any vendor: provider/model-id without a thinking level - `turri init-config --harness-model amazon-bedrock/global.xai.grok-4.7 --coding-model amazon-bedrock/global.xai.grok-4.6`. They are written wherever the vendor's own would be - the two models with their thinking, the commented-out `judge-model` and `models:` examples - and the vendor, maker and geography are read from them. `--judge-model` names the strong one the judging examples use, the harness model unless given. Each is checked against pi's catalogue first: one it does not have, or that is not a chat model, is named, with the closest it has, and nothing is written.
|
|
17
19
|
|
|
18
20
|
Each is written with how hard it thinks - `harness-model: openai/gpt-6-astra:medium`, `coding-model: openai/gpt-6-luna:low` - so the file shows it. The reviews and judges run on the coding model but think medium (see [models per role](#models-per-role)).
|
|
19
21
|
|
package/docs/development.md
CHANGED
|
@@ -32,7 +32,7 @@ The workflow signs in to npm with npm's trusted publishing (OIDC), not a token:
|
|
|
32
32
|
|---|---|
|
|
33
33
|
| `bin/turri.mjs` | the launcher: checks the Node version, then loads `src/cli.ts` |
|
|
34
34
|
| `src/cli.ts` | the `turri` command and its subcommands |
|
|
35
|
-
| `src/setup.ts`, `src/environment.ts`, `src/stack.ts` | `turri doctor
|
|
35
|
+
| `src/setup.ts`, `src/doctor-tool.ts`, `src/environment.ts`, `src/stack.ts` | `turri doctor` and the chat's `doctor` tool, and what they check - this machine, and in a project what its modules need of it |
|
|
36
36
|
| `src/extension.ts` | the pi extension: every tool and command the conversation has |
|
|
37
37
|
| `src/skill.ts` | parses `SKILL.md` into steps and gates |
|
|
38
38
|
| `src/graph.ts` | compiles a skill into a LangGraph `StateGraph`: step -> gate -> route |
|
package/docs/hosting.md
CHANGED
|
@@ -12,16 +12,57 @@
|
|
|
12
12
|
| `pi` | pi itself, as turri's extension is built against it. Make the session with this one - `pi.createAgentSession`, `pi.DefaultResourceLoader` - not another copy. |
|
|
13
13
|
| `readProjectConfig`, `projectModels`, `modelSpec`, `configForSpec`, `hasProjectConfig`, `stacksOf` | the project's settings, as [config.turri.yaml](config.md) has them. |
|
|
14
14
|
| `debugMode` | whether turri keeps, in a project, what it writes only for debugging it - `debug:`, or `TURRI_DEBUG` over it (see [debug mode](config.md#debug-mode)). |
|
|
15
|
-
| `initConfig(dir, { vendor, bedrock, force })`, `VENDORS`, `BEDROCK_REGIONS`, `BEDROCK_DEFAULT
|
|
15
|
+
| `initConfig(dir, { vendor, bedrock, models, force, catalogue })`, `VENDORS`, `BEDROCK_REGIONS`, `BEDROCK_DEFAULT` | a project's first config.turri.yaml, as `turri init-config` writes it - async, it may read pi's catalogue - see [a project's first config](#a-projects-first-config). |
|
|
16
|
+
| `piModelCatalogue`, `bedrockMakers`, `bedrockModelsIn`, `bedrockDefaultModels`, `bedrockModels`, `bedrockPlaceOf`, `makerLabel`, `BEDROCK_GEOGRAPHIES`, `BEDROCK_IN_REGION` | pi's model catalogue, read without a request, and Bedrock in it: whose models it serves, in which geographies, which two turri offers for a maker and geography. |
|
|
17
|
+
| `bedrockAwsRegion`, `bedrockNeedsAwsRegion` | the AWS region to set `AWS_REGION` to for a Bedrock geography, and whether it has to be set. |
|
|
16
18
|
| `checkPlainFile`, `plainFileParser`, `resolveTemplateDirs`, `iterateFunctionalSpecs`, `FUNCTIONAL_REQUIREMENTS` | a spec checked and parsed, its functional specs one by one - as `turri validate` reads it. |
|
|
17
19
|
| `highlightLines`, `roleOf` | a spec's lines coloured by turri's own \*\*\*plain grammar. |
|
|
18
20
|
| `functionalityCompletion`, `createWorkLoop`, `createJobRegistry`, `promptOf`, `describeModules` | how far each functional spec is built, and - on a loop of the host's own with `dryRun` - the work a spec change would plan, as `turri glimpse` does. |
|
|
19
21
|
| `onModelEvent`, `describeTurnIncident` | a provider failing and waited out, a turn turri ended itself. |
|
|
20
22
|
| `onLedgerEvent`, `readLedger`, `ledgerDir`, `runViews`, `pulseView`, `describePulseView`, `describeRuns`, `ledgerProjects`, `earlierPulses`, `asJob` | what turri did, as facts - the [ledger](using.md#the-ledger): each event heard as it is recorded, and every run and pulse read back, earlier sessions' too - see [the ledger in a host](#the-ledger-in-a-host). |
|
|
23
|
+
| `doctor({ dir, models, env })`, `Check`, `Level` | whether the machine - and, with `dir`, that project - is ready for turri, as `turri doctor` checks it: a list of checks to show in the host's own way - see [doctor in a host](#doctor-in-a-host). |
|
|
21
24
|
| `sweepOrphanedGroups` | stop what a turri killed outright left running, before anything starts. |
|
|
22
25
|
|
|
23
26
|
It is turri's own code, as the terminal runs it: a 0.x release may change any of it.
|
|
24
27
|
|
|
28
|
+
## A project's first config
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import { initConfig, piModelCatalogue, bedrockMakers, bedrockModelsIn, bedrockDefaultModels } from "@turri-dev/cli/host";
|
|
32
|
+
|
|
33
|
+
const catalogue = await piModelCatalogue();
|
|
34
|
+
bedrockMakers(catalogue); // [{ maker: "xai", label: "xAI", regions: ["global", "us", "in-region"] }, ...]
|
|
35
|
+
bedrockModelsIn(catalogue, { maker: "xai", region: "global" }); // the maker's chat models there, with their prices
|
|
36
|
+
const offered = bedrockDefaultModels(catalogue, { maker: "xai", region: "global" });
|
|
37
|
+
const { written, message } = await initConfig(project, { models: { harness, coding }, catalogue });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`initConfig` writes config.turri.yaml as `turri init-config` does, and returns what it did - `written`, and `message`, the lines the terminal prints. It is async.
|
|
41
|
+
|
|
42
|
+
- `vendor` - `anthropic`, `openai`, `google`, `amazon-bedrock` or `other` - fills in that vendor's own two models (`VENDORS` has each one's label and models).
|
|
43
|
+
- `bedrock: { maker?, region }`, on Amazon Bedrock, says whose and where. turri's own pair is written where it has one - Anthropic's and OpenAI's in `BEDROCK_REGIONS` - and otherwise the pair `bedrockDefaultModels` offers from pi's catalogue: any maker and geography it has.
|
|
44
|
+
- `models: { harness, coding, strong? }` writes those two instead - any pair pi's catalogue has as chat models, full specs without a thinking level (`amazon-bedrock/global.xai.grok-4.7`). They go wherever the vendor's own would: the two models with the thinking turri writes, the commented-out `judge-model` and `models:` examples, `strong` - the judging examples' model - being `harness` unless given. The vendor, the Bedrock maker and the geography are read from them, so neither `vendor` nor `bedrock` is needed. A model the catalogue does not have, or that is not a chat model, writes nothing: `written` is false and `message` says which, with the closest the catalogue has.
|
|
45
|
+
- `catalogue`, the one the host already read, saves reading it again.
|
|
46
|
+
|
|
47
|
+
The message says whose models were written - "with xAI's models on Amazon Bedrock (global)" - and how to sign in, with the `AWS_REGION` for the geography: `bedrockAwsRegion(geography)` is that region, and `bedrockNeedsAwsRegion(geography)` whether it has to be set - every geography but `global`, `us` and `in-region`, pi calling Bedrock in `us-east-1` unless `AWS_REGION` or the profile names another.
|
|
48
|
+
|
|
49
|
+
## doctor in a host
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import { doctor } from "@turri-dev/cli/host";
|
|
53
|
+
|
|
54
|
+
const checks = await doctor({ dir: project, env: engineEnv });
|
|
55
|
+
// [{ level: "ok" | "missing" | "limited" | "info", name, detail, more?, fix? }, ...]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`doctor` runs the checks `turri doctor` prints, and returns them: Node, bash, git, which OS sandbox is up under the agents' commands, and whether any model provider has credentials. With `dir`, the project too: whether this machine has the toolchains its modules' implementation reqs name, and what in its testing scripts it does not have. It prints nothing and asks nothing; each check's `fix` is plain text, to show as it is. A check `missing` means turri cannot run until it is fixed, `limited` that it runs with less.
|
|
59
|
+
|
|
60
|
+
- `dir` is the project to check - not the process's working folder, which in a host is not the project. Without it, only the machine is checked.
|
|
61
|
+
- `models: true` asks each model the project's config.turri.yaml names for a word, with this machine's credentials - a request a model, which costs money. It is off by default: run it when the person asks.
|
|
62
|
+
- `env` is the environment whose PATH is looked in - for bash, git, the sandbox, the toolchains and what the testing scripts run - by default the process's own. A Mac app opened from Finder runs with `/usr/bin:/bin:/usr/sbin:/sbin`, not the person's shell's PATH: pass the environment the host's engine runs turri with, so doctor checks the PATH turri will actually have.
|
|
63
|
+
|
|
64
|
+
What matters only in a terminal - `turri` on the PATH, a plain `pi` an older turri made load turri - is left to `turri doctor`.
|
|
65
|
+
|
|
25
66
|
## The conversation in a host
|
|
26
67
|
|
|
27
68
|
```js
|
package/docs/installation.md
CHANGED
|
@@ -58,7 +58,7 @@ In a project - a folder with `config.turri.yaml` (or the old `config.yaml`) or `
|
|
|
58
58
|
| the testing scripts | what in them this machine does not have: a program named by its whole path that is not here (a macOS-only `/usr/libexec/java_home` on Linux), a toolchain they run that is not on the PATH |
|
|
59
59
|
| the models | each model `config.turri.yaml` names - `harness-model`, `coding-model`, `judge-model`, every role under `models:` - sent one short request through pi, with the credentials turri's agents will use, for any provider pi supports. One that answers is ready; for one that does not, it says what the provider said and what to do: the credentials pi reads for that provider (`AWS_PROFILE` and `AWS_REGION` for Amazon Bedrock, `OPENAI_API_KEY`, `/login`, ...), model access in the Bedrock console, the closest model ids pi knows for one it does not. `--harness-model`, `--coding-model` (`--model`) and `--judge-model` name models as the config's keys, as `turri regenerate` reads them - checked in a project or not. `--no-models` skips it |
|
|
60
60
|
|
|
61
|
-
Removing that file is the one thing `doctor` changes, and it asks first; `turri doctor --yes` says yes. Run without a terminal - piped, or in CI - it asks nothing and changes nothing. Run it again whenever something breaks; `turri doctor --no-models` leaves out the models' requests.
|
|
61
|
+
Removing that file is the one thing `doctor` changes, and it asks first; `turri doctor --yes` says yes. Run without a terminal - piped, or in CI - it asks nothing and changes nothing. Run it again whenever something breaks; `turri doctor --no-models` leaves out the models' requests. In the chat, ask for a self check: the `doctor` tool runs the same checks, the models only when you ask. A host - a desktop app - runs the same checks through `doctor` and shows them itself (see [doctor in a host](hosting.md#doctor-in-a-host)).
|
|
62
62
|
|
|
63
63
|
The chat is told every turn which wall is up and what the project's modules need of this machine that it lacks. What they need is worked out from every spec of the project - seconds, on a project of long requires chains - so it is worked out once the chat is up and again after a turn in which a spec changed, never while your message waits: a turn is told what was known last.
|
|
64
64
|
|
package/docs/using.md
CHANGED
|
@@ -38,6 +38,7 @@ While a skill runs, a line above the input shows its phases, every one by name -
|
|
|
38
38
|
| `halt` | halt a pulse, a module's planned pulses, or what `activate` started |
|
|
39
39
|
| `diagnose` | why a pulse did not land, from its transcript: how it ended, the stages that went wrong, the last failing test run, the last review, the tool calls that failed - and what the [ledger](#the-ledger) recorded of it |
|
|
40
40
|
| `ledger` | what turri did, from the [ledger](#the-ledger), this session's and every earlier one's: a pulse as facts, by its id - its rounds, test runs, verdicts, healers, refusals and commits; events of some kinds counted and listed, for a module or since a time - `healer.failed`, or `healer` for the group; or the last runs, a line a pulse |
|
|
41
|
+
| `doctor` | turri's self check, as `turri doctor` runs it: Node, bash, git, the OS sandbox, a model provider, and the project's toolchains and testing scripts - against the PATH turri's pulses run with, not the agent's shell's. The project's models are asked for a word only when you ask (a request a model); it changes nothing |
|
|
41
42
|
| `lineage` | a module's lineage: what each regeneration and conformance run changed in it, and what it was regenerated from |
|
|
42
43
|
| `cultivate` | run a gated skill, its steps and review gates enforced |
|
|
43
44
|
| `release`, `activate` | try what is regenerated: release it to `dist/<module>` and activate it on this machine, installing what it needs - see [deploy](deploy.md) |
|