@zenera/cli 1.1.24 → 1.1.25
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 +53 -0
- package/dist/ask.js +28 -5
- package/dist/banner.js +2 -2
- package/dist/batch.d.ts +6 -0
- package/dist/batch.js +76 -1
- package/dist/catalog.d.ts +11 -1
- package/dist/catalog.js +80 -7
- package/dist/commands/inspect.d.ts +47 -1
- package/dist/commands/inspect.js +167 -18
- package/dist/commands/memory.js +128 -29
- package/dist/commands/meta.js +311 -21
- package/dist/commands/models.js +96 -12
- package/dist/commands/open.js +4 -4
- package/dist/commands/run.js +38 -4
- package/dist/commands/sandbox.js +76 -14
- package/dist/engine.d.ts +5 -0
- package/dist/engine.js +1 -0
- package/dist/eventlog.d.ts +59 -0
- package/dist/eventlog.js +146 -0
- package/dist/liveness.d.ts +7 -0
- package/dist/liveness.js +15 -1
- package/dist/meta/editor.d.ts +9 -0
- package/dist/meta/editor.js +60 -0
- package/dist/meta/finetune/children.d.ts +8 -0
- package/dist/meta/finetune/children.js +76 -0
- package/dist/meta/finetune/command.d.ts +30 -0
- package/dist/meta/finetune/command.js +338 -0
- package/dist/meta/finetune/dataset/anchors.d.ts +41 -0
- package/dist/meta/finetune/dataset/anchors.js +239 -0
- package/dist/meta/finetune/dataset/changes.d.ts +36 -0
- package/dist/meta/finetune/dataset/changes.js +319 -0
- package/dist/meta/finetune/dataset/command.d.ts +17 -0
- package/dist/meta/finetune/dataset/command.js +1011 -0
- package/dist/meta/finetune/dataset/drift.d.ts +33 -0
- package/dist/meta/finetune/dataset/drift.js +71 -0
- package/dist/meta/finetune/dataset/export.d.ts +26 -0
- package/dist/meta/finetune/dataset/export.js +51 -0
- package/dist/meta/finetune/dataset/index.d.ts +2 -0
- package/dist/meta/finetune/dataset/index.js +2 -0
- package/dist/meta/finetune/dataset/sample.d.ts +50 -0
- package/dist/meta/finetune/dataset/sample.js +197 -0
- package/dist/meta/finetune/dataset/schema.d.ts +33 -0
- package/dist/meta/finetune/dataset/schema.js +216 -0
- package/dist/meta/finetune/dataset/store.d.ts +35 -0
- package/dist/meta/finetune/dataset/store.js +177 -0
- package/dist/meta/finetune/dataset/types.d.ts +125 -0
- package/dist/meta/finetune/dataset/types.js +39 -0
- package/dist/meta/finetune/failure.d.ts +15 -0
- package/dist/meta/finetune/failure.js +26 -0
- package/dist/meta/finetune/feedback.d.ts +45 -0
- package/dist/meta/finetune/feedback.js +85 -0
- package/dist/meta/finetune/improvements.d.ts +51 -0
- package/dist/meta/finetune/improvements.js +244 -0
- package/dist/meta/finetune/index.d.ts +2 -0
- package/dist/meta/finetune/index.js +2 -0
- package/dist/meta/finetune/journal.d.ts +35 -0
- package/dist/meta/finetune/journal.js +135 -0
- package/dist/meta/finetune/live.d.ts +74 -0
- package/dist/meta/finetune/live.js +226 -0
- package/dist/meta/finetune/loop.d.ts +3 -0
- package/dist/meta/finetune/loop.js +114 -0
- package/dist/meta/finetune/memories.d.ts +47 -0
- package/dist/meta/finetune/memories.js +154 -0
- package/dist/meta/finetune/report.d.ts +33 -0
- package/dist/meta/finetune/report.js +1284 -0
- package/dist/meta/finetune/retry.d.ts +24 -0
- package/dist/meta/finetune/retry.js +96 -0
- package/dist/meta/finetune/sampler.d.ts +18 -0
- package/dist/meta/finetune/sampler.js +41 -0
- package/dist/meta/finetune/system.d.ts +27 -0
- package/dist/meta/finetune/system.js +132 -0
- package/dist/meta/finetune/train.d.ts +39 -0
- package/dist/meta/finetune/train.js +391 -0
- package/dist/meta/finetune/tui.d.ts +16 -0
- package/dist/meta/finetune/tui.js +291 -0
- package/dist/meta/finetune/tuning.d.ts +167 -0
- package/dist/meta/finetune/tuning.js +289 -0
- package/dist/meta/finetune/usage.d.ts +62 -0
- package/dist/meta/finetune/usage.js +225 -0
- package/dist/meta/host.d.ts +14 -0
- package/dist/meta/host.js +20 -0
- package/dist/meta/index.d.ts +11 -0
- package/dist/meta/index.js +11 -0
- package/dist/meta/launch.d.ts +81 -0
- package/dist/meta/launch.js +286 -0
- package/dist/meta/model.d.ts +44 -0
- package/dist/meta/model.js +119 -0
- package/dist/meta/prompts.d.ts +18 -0
- package/dist/meta/prompts.js +61 -0
- package/dist/meta/relay.d.ts +20 -0
- package/dist/meta/relay.js +123 -0
- package/dist/meta/tokens.d.ts +52 -0
- package/dist/meta/tokens.js +226 -0
- package/dist/meta/window.d.ts +61 -0
- package/dist/meta/window.js +198 -0
- package/dist/meta/wire.d.ts +42 -0
- package/dist/meta/wire.js +129 -0
- package/dist/modelref.d.ts +16 -0
- package/dist/modelref.js +41 -0
- package/dist/narrate.js +5 -2
- package/dist/podman.d.ts +26 -0
- package/dist/podman.js +83 -3
- package/dist/projects.d.ts +0 -5
- package/dist/projects.js +2 -15
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.js +10 -3
- package/dist/scaffold.d.ts +15 -6
- package/dist/scaffold.js +5 -60
- package/dist/session.js +4 -29
- package/dist/term.d.ts +2 -0
- package/dist/term.js +11 -2
- package/dist/trace.js +53 -2
- package/dist/tui/models.d.ts +33 -0
- package/dist/tui/models.js +389 -0
- package/dist/tui/wrap.d.ts +5 -0
- package/dist/tui/wrap.js +12 -0
- package/dist/usage.d.ts +78 -0
- package/dist/usage.js +51 -0
- package/package.json +3 -2
- package/templates/editor/.github/copilot-instructions.md +31 -102
- package/templates/editor/.github/prompts/analyze.prompt.md +31 -0
- package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
- package/templates/editor/.github/prompts/finetune-apply.prompt.md +91 -0
- package/templates/editor/.github/prompts/finetune.prompt.md +691 -198
- package/templates/editor/.github/prompts/new-agent.prompt.md +3 -1
- package/templates/editor/.github/prompts/new-skill.prompt.md +2 -0
- package/templates/editor/.github/prompts/project-review.prompt.md +4 -3
- package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +483 -0
- package/templates/editor/.github/skills/zen-cli/SKILL.md +9 -1
- package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
- package/templates/editor/.github/skills/zen-cli/references/finetune.md +77 -0
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +47 -9
- package/templates/editor/.github/skills/zen-cli/references/memory.md +33 -10
- package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
- package/templates/editor/.github/skills/zen-cli/references/models.md +5 -1
- package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +10 -1
- package/templates/editor/.github/skills/zen-code-python/SKILL.md +123 -0
- package/templates/editor/.github/skills/zen-code-python/references/code_python.md +48 -0
- package/templates/editor/.github/skills/zen-dataset/SKILL.md +253 -0
- package/templates/editor/.github/skills/zen-finetune/SKILL.md +1449 -658
- package/templates/editor/.github/skills/zen-finetune/references/readme-template.md +268 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/batch.mjs +390 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/difficult.mjs +243 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/memory.mjs +280 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/next.mjs +398 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/report.mjs +797 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/select.mjs +354 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/usage.mjs +722 -0
- package/templates/editor/.github/skills/zen-inspect/SKILL.md +60 -25
- package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +270 -0
- package/templates/editor/.github/skills/zen-memory/SKILL.md +18 -14
- package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
- package/templates/editor/.github/skills/zen-review/SKILL.md +5 -3
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -4
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +7 -3
- package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -2
- package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
- package/templates/editor/.vscode/settings.json +1 -0
- package/templates/project/agents/files-instructions.md +29 -20
- package/templates/project/agents/memory-instructions.md +31 -32
- package/templates/project/agents/tools-instructions.md +7 -4
- package/templates/project/gitignore +2 -0
- package/dist/meta.d.ts +0 -221
- package/dist/meta.js +0 -684
- package/templates/editor/.github/skills/zen-finetune/scripts/collect.sh +0 -432
- package/templates/editor/.github/skills/zen-finetune/scripts/sample.sh +0 -199
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// zen models browse
|
|
4
|
+
//
|
|
5
|
+
// The catalog as something to walk rather than something to grep: providers,
|
|
6
|
+
// then what one of them serves, then one model in full — with a key to ask it
|
|
7
|
+
// a real question and a key to take it.
|
|
8
|
+
//
|
|
9
|
+
// Drawn on STDERR, like the banner and every other piece of narration, so the
|
|
10
|
+
// one thing that reaches stdout is the ref picked at the end. That is what
|
|
11
|
+
// makes `ref=$(zen models browse)` work the way `zen models pick` does: the
|
|
12
|
+
// person sees the interface, the shell sees the answer.
|
|
13
|
+
//
|
|
14
|
+
// Nothing here knows how to list or probe a model. Both arrive as callbacks
|
|
15
|
+
// from commands/models.ts, which already owns credentials and the catalog, so
|
|
16
|
+
// this file is layout and keys and nothing else.
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
import { Box, render, Text, useApp, useInput, useStdout } from 'ink';
|
|
19
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
20
|
+
import { byGroup, isPartner, matches, } from "../catalog.js";
|
|
21
|
+
import { ago } from "../term.js";
|
|
22
|
+
import { resolveTheme } from "./theme.js";
|
|
23
|
+
import { clip, scrollTop, wrap } from "./wrap.js";
|
|
24
|
+
const ROLES = ['chat', 'embedding', 'image', 'audio'];
|
|
25
|
+
const message = (err) => (err instanceof Error ? err.message : String(err));
|
|
26
|
+
const thousands = (n) => n === undefined ? '' : n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);
|
|
27
|
+
const ms = (n) => (n < 1000 ? `${n}ms` : `${(n / 1000).toFixed(1)}s`);
|
|
28
|
+
function freshness(origin, fetchedAt) {
|
|
29
|
+
switch (origin) {
|
|
30
|
+
case 'curated':
|
|
31
|
+
return 'built-in list';
|
|
32
|
+
case 'stale':
|
|
33
|
+
return `stale, ${ago(fetchedAt)}`;
|
|
34
|
+
case 'live':
|
|
35
|
+
return 'fetched now';
|
|
36
|
+
default:
|
|
37
|
+
return `cached ${ago(fetchedAt)}`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function said(verdict, theme) {
|
|
41
|
+
if (verdict === undefined) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
if (verdict === 'asking') {
|
|
45
|
+
return { text: '… asking', color: theme.warn };
|
|
46
|
+
}
|
|
47
|
+
switch (verdict.state) {
|
|
48
|
+
case 'live':
|
|
49
|
+
return {
|
|
50
|
+
text: `✓ answers ${verdict.ms === undefined ? '' : ms(verdict.ms)}`.trim(),
|
|
51
|
+
color: theme.code.color,
|
|
52
|
+
};
|
|
53
|
+
case 'blocked':
|
|
54
|
+
return { text: '! blocked', color: theme.warn };
|
|
55
|
+
case 'dead':
|
|
56
|
+
return { text: '✗ refused', color: theme.line.error.color };
|
|
57
|
+
default:
|
|
58
|
+
return { text: '? no answer', color: theme.chrome.color };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** One row, never wrapped: a wrapped row is a row the frame did not budget for. */
|
|
62
|
+
function Row({ children, ...style }) {
|
|
63
|
+
return (_jsx(Text, { wrap: "truncate-end", ...style, children: children }));
|
|
64
|
+
}
|
|
65
|
+
function Keys({ theme, text }) {
|
|
66
|
+
return _jsx(Row, { color: theme.chrome.color, children: text });
|
|
67
|
+
}
|
|
68
|
+
function Browser({ options, theme, onPick }) {
|
|
69
|
+
const { exit } = useApp();
|
|
70
|
+
const { stdout } = useStdout();
|
|
71
|
+
const [size, setSize] = useState({ columns: stdout.columns || 80, rows: stdout.rows || 24 });
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const resized = () => setSize({ columns: stdout.columns || 80, rows: stdout.rows || 24 });
|
|
74
|
+
stdout.on('resize', resized);
|
|
75
|
+
return () => {
|
|
76
|
+
stdout.off('resize', resized);
|
|
77
|
+
};
|
|
78
|
+
}, [stdout]);
|
|
79
|
+
const start = options.providers.findIndex((p) => p.provider === options.initial);
|
|
80
|
+
const [screen, setScreen] = useState(start >= 0 ? 'models' : 'providers');
|
|
81
|
+
const [at, setAt] = useState(Math.max(0, start));
|
|
82
|
+
const [catalogs, setCatalogs] = useState(new Map());
|
|
83
|
+
const [loading, setLoading] = useState();
|
|
84
|
+
const [failure, setFailure] = useState();
|
|
85
|
+
const [query, setQuery] = useState('');
|
|
86
|
+
const [typing, setTyping] = useState(false);
|
|
87
|
+
const [role, setRole] = useState(0);
|
|
88
|
+
const [selected, setSelected] = useState(0);
|
|
89
|
+
const [verdicts, setVerdicts] = useState(new Map());
|
|
90
|
+
const [closing, setClosing] = useState(false);
|
|
91
|
+
const top = useRef(0);
|
|
92
|
+
const provider = options.providers[at].provider;
|
|
93
|
+
const catalog = catalogs.get(provider);
|
|
94
|
+
const load = (p, refresh) => {
|
|
95
|
+
setLoading(p);
|
|
96
|
+
setFailure(undefined);
|
|
97
|
+
options.load(p, refresh).then((cat) => {
|
|
98
|
+
setCatalogs((prev) => new Map(prev).set(p, cat));
|
|
99
|
+
setLoading((now) => (now === p ? undefined : now));
|
|
100
|
+
}, (err) => {
|
|
101
|
+
setFailure(message(err));
|
|
102
|
+
setLoading((now) => (now === p ? undefined : now));
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (start >= 0) {
|
|
107
|
+
load(provider, false);
|
|
108
|
+
}
|
|
109
|
+
// Once, for `zen models browse <provider>`; every later open is a key.
|
|
110
|
+
}, []);
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (closing) {
|
|
113
|
+
exit();
|
|
114
|
+
}
|
|
115
|
+
}, [closing, exit]);
|
|
116
|
+
// Only the roles this listing actually has: cycling through `audio` on a
|
|
117
|
+
// provider with no audio models is a keypress spent on an empty screen.
|
|
118
|
+
const present = useMemo(() => [
|
|
119
|
+
undefined,
|
|
120
|
+
...ROLES.filter((r) => catalog?.entries.some((e) => e.roles.includes(r))),
|
|
121
|
+
], [catalog]);
|
|
122
|
+
const only = present[role % present.length];
|
|
123
|
+
const rows = useMemo(() => (catalog?.entries ?? [])
|
|
124
|
+
.filter((e) => matches(e, query, { roles: only ? [only] : [] }))
|
|
125
|
+
.sort(byGroup), [catalog, query, only]);
|
|
126
|
+
const index = Math.min(selected, Math.max(0, rows.length - 1));
|
|
127
|
+
const current = rows[index];
|
|
128
|
+
const open = (next) => {
|
|
129
|
+
const p = options.providers[next].provider;
|
|
130
|
+
setAt(next);
|
|
131
|
+
setScreen('models');
|
|
132
|
+
setQuery('');
|
|
133
|
+
setTyping(false);
|
|
134
|
+
setRole(0);
|
|
135
|
+
setSelected(0);
|
|
136
|
+
top.current = 0;
|
|
137
|
+
if (!catalogs.has(p)) {
|
|
138
|
+
load(p, false);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const ask = (entry) => {
|
|
142
|
+
if (verdicts.get(entry.ref) === 'asking') {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
setVerdicts((prev) => new Map(prev).set(entry.ref, 'asking'));
|
|
146
|
+
options.test(entry).then((v) => setVerdicts((prev) => new Map(prev).set(entry.ref, v)), (err) => setVerdicts((prev) => new Map(prev).set(entry.ref, { state: 'unknown', detail: message(err) })));
|
|
147
|
+
};
|
|
148
|
+
const pick = (entry) => {
|
|
149
|
+
onPick(entry);
|
|
150
|
+
setClosing(true);
|
|
151
|
+
};
|
|
152
|
+
// Everything but the list is a fixed number of rows, and the list gets
|
|
153
|
+
// what is left. A row is kept in hand: a frame as tall as the viewport is
|
|
154
|
+
// the one Ink cannot erase (see tui/app.tsx).
|
|
155
|
+
const problem = catalog?.problem;
|
|
156
|
+
const chrome = 6 + (problem ? 1 : 0);
|
|
157
|
+
const height = Math.max(3, size.rows - 1 - chrome);
|
|
158
|
+
top.current = scrollTop(index, top.current, height, rows.length);
|
|
159
|
+
useInput((input, key) => {
|
|
160
|
+
if (closing) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const quit = () => setClosing(true);
|
|
164
|
+
if (screen === 'providers') {
|
|
165
|
+
const n = options.providers.length;
|
|
166
|
+
if (key.upArrow || input === 'k') {
|
|
167
|
+
setAt((i) => (i + n - 1) % n);
|
|
168
|
+
}
|
|
169
|
+
else if (key.downArrow || input === 'j') {
|
|
170
|
+
setAt((i) => (i + 1) % n);
|
|
171
|
+
}
|
|
172
|
+
else if (key.return || key.rightArrow || input === 'l') {
|
|
173
|
+
open(at);
|
|
174
|
+
}
|
|
175
|
+
else if (key.escape || input === 'q') {
|
|
176
|
+
quit();
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (screen === 'detail') {
|
|
181
|
+
if (!current) {
|
|
182
|
+
setScreen('models');
|
|
183
|
+
}
|
|
184
|
+
else if (input === 't') {
|
|
185
|
+
ask(current);
|
|
186
|
+
}
|
|
187
|
+
else if (key.return || input === 'p') {
|
|
188
|
+
pick(current);
|
|
189
|
+
}
|
|
190
|
+
else if (key.escape || key.leftArrow || input === 'h') {
|
|
191
|
+
setScreen('models');
|
|
192
|
+
}
|
|
193
|
+
else if (input === 'q') {
|
|
194
|
+
quit();
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const move = (by) => setSelected(Math.max(0, Math.min(rows.length - 1, index + by)));
|
|
199
|
+
if (key.upArrow) {
|
|
200
|
+
return move(-1);
|
|
201
|
+
}
|
|
202
|
+
if (key.downArrow) {
|
|
203
|
+
return move(1);
|
|
204
|
+
}
|
|
205
|
+
if (key.pageUp) {
|
|
206
|
+
return move(-height);
|
|
207
|
+
}
|
|
208
|
+
if (key.pageDown) {
|
|
209
|
+
return move(height);
|
|
210
|
+
}
|
|
211
|
+
if (typing) {
|
|
212
|
+
if (key.return) {
|
|
213
|
+
setTyping(false);
|
|
214
|
+
}
|
|
215
|
+
else if (key.escape) {
|
|
216
|
+
setQuery('');
|
|
217
|
+
setTyping(false);
|
|
218
|
+
}
|
|
219
|
+
else if (key.backspace || key.delete) {
|
|
220
|
+
setQuery((q) => q.slice(0, -1));
|
|
221
|
+
setSelected(0);
|
|
222
|
+
}
|
|
223
|
+
else if (input && !key.ctrl && !key.meta && !key.tab) {
|
|
224
|
+
// Fast typing or a paste can arrive as one chunk with the Enter
|
|
225
|
+
// inside it: keep the text, honour the Enter, drop the rest.
|
|
226
|
+
const [text = '', ...after] = input.split(/[\r\n]/);
|
|
227
|
+
setQuery((q) => q + text.replace(/[\u0000-\u001f\u007f]/g, ''));
|
|
228
|
+
setSelected(0);
|
|
229
|
+
if (after.length > 0) {
|
|
230
|
+
setTyping(false);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (input === 'k') {
|
|
236
|
+
move(-1);
|
|
237
|
+
}
|
|
238
|
+
else if (input === 'j') {
|
|
239
|
+
move(1);
|
|
240
|
+
}
|
|
241
|
+
else if (key.home || input === 'g') {
|
|
242
|
+
setSelected(0);
|
|
243
|
+
}
|
|
244
|
+
else if (key.end || input === 'G') {
|
|
245
|
+
setSelected(Math.max(0, rows.length - 1));
|
|
246
|
+
}
|
|
247
|
+
else if (input.startsWith('/')) {
|
|
248
|
+
// A pasted `/gpt-5` arrives as one chunk, not as a slash and then
|
|
249
|
+
// the rest.
|
|
250
|
+
setTyping(true);
|
|
251
|
+
setQuery(input.slice(1));
|
|
252
|
+
setSelected(0);
|
|
253
|
+
}
|
|
254
|
+
else if (key.tab) {
|
|
255
|
+
setRole((r) => (r + 1) % present.length);
|
|
256
|
+
setSelected(0);
|
|
257
|
+
}
|
|
258
|
+
else if ((key.return || key.rightArrow || input === 'l') && current) {
|
|
259
|
+
setScreen('detail');
|
|
260
|
+
}
|
|
261
|
+
else if (input === 't' && current) {
|
|
262
|
+
ask(current);
|
|
263
|
+
}
|
|
264
|
+
else if (input === 'p' && current) {
|
|
265
|
+
pick(current);
|
|
266
|
+
}
|
|
267
|
+
else if (input === 'r') {
|
|
268
|
+
load(provider, true);
|
|
269
|
+
}
|
|
270
|
+
else if (key.escape && query) {
|
|
271
|
+
setQuery('');
|
|
272
|
+
setSelected(0);
|
|
273
|
+
}
|
|
274
|
+
else if (key.escape || key.leftArrow || input === 'h') {
|
|
275
|
+
setScreen('providers');
|
|
276
|
+
}
|
|
277
|
+
else if (input === 'q') {
|
|
278
|
+
quit();
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
if (closing) {
|
|
282
|
+
// An empty frame erases the last one, so the shell is left as it was
|
|
283
|
+
// found rather than with a stale picture of a list on it.
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
const width = Math.max(20, size.columns - 1);
|
|
287
|
+
if (screen === 'providers') {
|
|
288
|
+
const nameW = Math.max(...options.providers.map((p) => p.provider.length)) + 2;
|
|
289
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Row, { bold: true, color: theme.accent, children: "Providers" }), _jsx(Text, { children: " " }), options.providers.map((p, i) => {
|
|
290
|
+
const here = i === at;
|
|
291
|
+
const cat = catalogs.get(p.provider);
|
|
292
|
+
const count = cat?.entries.length ?? p.models;
|
|
293
|
+
const fresh = cat
|
|
294
|
+
? freshness(cat.origin, cat.fetchedAt)
|
|
295
|
+
: freshness(p.origin, p.fetchedAt);
|
|
296
|
+
return (_jsxs(Row, { children: [_jsxs(Text, { color: here ? theme.accent : undefined, bold: here, children: [here ? '› ' : ' ', p.provider.padEnd(nameW)] }), p.credential ? (_jsx(Text, { color: theme.code.color, children: p.credential.padEnd(15) })) : (_jsx(Text, { color: theme.line.error.color, children: 'no credential'.padEnd(15) })), _jsxs(Text, { color: theme.chrome.color, children: [`${count} model${count === 1 ? '' : 's'}`.padEnd(13), p.credential ? fresh : `zen key add ${p.provider}`] })] }, p.provider));
|
|
297
|
+
}), _jsx(Text, { children: " " }), _jsx(Keys, { theme: theme, text: "\u2191\u2193 move \u00B7 enter open \u00B7 q quit" })] }));
|
|
298
|
+
}
|
|
299
|
+
if (screen === 'detail' && current) {
|
|
300
|
+
return (_jsx(Detail, { entry: current, catalog: catalog, verdict: verdicts.get(current.ref), theme: theme, width: width, rows: size.rows - 1 }));
|
|
301
|
+
}
|
|
302
|
+
const header = catalog
|
|
303
|
+
? `${provider} · ${catalog.entries.length} models · ${freshness(catalog.origin, catalog.fetchedAt)}`
|
|
304
|
+
: provider;
|
|
305
|
+
const idW = Math.min(44, Math.max(8, ...rows.map((e) => e.id.length))) + 2;
|
|
306
|
+
const shown = rows.slice(top.current, top.current + height);
|
|
307
|
+
const filter = typing ? `/${query}▌` : query ? `/${query}` : '';
|
|
308
|
+
let body;
|
|
309
|
+
if (failure) {
|
|
310
|
+
body = _jsx(Row, { color: theme.line.error.color, children: failure });
|
|
311
|
+
}
|
|
312
|
+
else if (loading === provider || !catalog) {
|
|
313
|
+
body = _jsxs(Row, { color: theme.warn, children: ["listing ", provider, " \u2026"] });
|
|
314
|
+
}
|
|
315
|
+
else if (rows.length === 0) {
|
|
316
|
+
body = (_jsx(Row, { color: theme.chrome.color, children: query ? `nothing matches "${query}"` : 'nothing listed' }));
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
body = (_jsx(_Fragment, { children: shown.map((e, i) => {
|
|
320
|
+
const here = top.current + i === index;
|
|
321
|
+
const v = said(verdicts.get(e.ref), theme);
|
|
322
|
+
return (_jsxs(Row, { children: [_jsxs(Text, { color: here ? theme.accent : undefined, bold: here, children: [here ? '› ' : ' ', clip(e.id, idW - 2).padEnd(idW)] }), e.publisher ? (_jsx(Text, { color: isPartner(e) ? theme.warn : theme.code.color, children: clip(e.publisher, 11).padEnd(12) })) : null, _jsxs(Text, { color: theme.chrome.color, children: [e.roles.join('+').padEnd(16), thousands(e.contextLength).padStart(6), (e.pricing?.free ? ' free' : '').padEnd(7)] }), v ? _jsx(Text, { color: v.color, children: v.text }) : null] }, e.ref));
|
|
323
|
+
}) }));
|
|
324
|
+
}
|
|
325
|
+
const preview = current ? [current.name, current.description].filter(Boolean).join(' — ') : '';
|
|
326
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Row, { children: [_jsx(Text, { bold: true, color: theme.accent, children: header }), _jsxs(Text, { color: theme.chrome.color, children: [' role: ', only ?? 'all', filter ? ' ' : ''] }), _jsx(Text, { color: theme.code.color, children: filter })] }), problem ? (_jsxs(Row, { color: theme.warn, children: ["could not be listed: ", problem.detail ?? 'no reason given'] })) : null, _jsx(Text, { children: " " }), _jsx(Box, { flexDirection: "column", height: height, children: body }), _jsx(Row, { color: theme.chrome.color, children: clip(preview, width) || ' ' }), _jsx(Row, { color: theme.chrome.color, children: rows.length === 0
|
|
327
|
+
? ' '
|
|
328
|
+
: `${index + 1} of ${rows.length}${rows.length < (catalog?.entries.length ?? 0) ? ` (${catalog.entries.length} in all)` : ''}` }), _jsx(Keys, { theme: theme, text: typing
|
|
329
|
+
? 'type to filter · enter keep · esc clear · ↑↓ move'
|
|
330
|
+
: '↑↓ · / filter · tab role · ⏎ more · t test · p pick · r reload · esc back' })] }));
|
|
331
|
+
}
|
|
332
|
+
function Detail({ entry, catalog, verdict, theme, width, rows }) {
|
|
333
|
+
const facts = [];
|
|
334
|
+
const add = (label, value) => {
|
|
335
|
+
if (value) {
|
|
336
|
+
facts.push([label, value]);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
add('name', entry.name);
|
|
340
|
+
add('roles', entry.roles.join(', '));
|
|
341
|
+
add('context', entry.contextLength ? `${entry.contextLength.toLocaleString()} tokens` : undefined);
|
|
342
|
+
add('max output', entry.maxOutputTokens ? `${entry.maxOutputTokens.toLocaleString()} tokens` : undefined);
|
|
343
|
+
add('dimensions', entry.dimensions ? String(entry.dimensions) : undefined);
|
|
344
|
+
add('input', entry.modalities?.input?.join(', '));
|
|
345
|
+
add('output', entry.modalities?.output?.join(', '));
|
|
346
|
+
add('supports', Object.entries(entry.supports ?? {})
|
|
347
|
+
.filter(([, on]) => on)
|
|
348
|
+
.map(([k]) => k)
|
|
349
|
+
.join(', '));
|
|
350
|
+
add('pricing', entry.pricing?.free
|
|
351
|
+
? 'free'
|
|
352
|
+
: entry.pricing?.prompt
|
|
353
|
+
? `$${entry.pricing.prompt}/token in, $${entry.pricing.completion ?? '?'}/token out`
|
|
354
|
+
: undefined);
|
|
355
|
+
add('released', entry.created);
|
|
356
|
+
add('source', entry.source === 'live' && catalog
|
|
357
|
+
? `${entry.provider}, ${freshness(catalog.origin, catalog.fetchedAt)}`
|
|
358
|
+
: 'built-in list');
|
|
359
|
+
const v = said(verdict, theme);
|
|
360
|
+
const reason = verdict && verdict !== 'asking'
|
|
361
|
+
? [
|
|
362
|
+
verdict.dimensions ? `${verdict.dimensions} dims` : '',
|
|
363
|
+
verdict.state === 'live' ? '' : (verdict.detail ?? ''),
|
|
364
|
+
verdict.fix ? `fix: ${verdict.fix}` : '',
|
|
365
|
+
]
|
|
366
|
+
.filter(Boolean)
|
|
367
|
+
.join(' · ')
|
|
368
|
+
: '';
|
|
369
|
+
// Header, blank, facts, the verdict row, blank, keys — the description has
|
|
370
|
+
// whatever is left, and is cut rather than allowed to push the keys off.
|
|
371
|
+
const room = Math.max(0, rows - facts.length - 6);
|
|
372
|
+
const about = entry.description ? wrap(entry.description, width - 2).slice(0, room) : [];
|
|
373
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Row, { bold: true, color: theme.accent, children: entry.ref }), _jsx(Text, { children: " " }), facts.map(([label, value]) => (_jsxs(Row, { children: [_jsx(Text, { color: theme.chrome.color, children: ` ${label.padEnd(12)}` }), _jsx(Text, { children: value })] }, label))), _jsxs(Row, { children: [_jsxs(Text, { color: theme.chrome.color, children: [' ', 'asked'.padEnd(12)] }), v ? (_jsx(Text, { color: v.color, children: v.text })) : (_jsx(Text, { color: theme.chrome.color, children: "not yet \u2014 t to ask it one question" })), reason ? _jsx(Text, { color: v?.color, children: ` ${reason}` }) : null] }), about.length > 0 ? _jsx(Text, { children: " " }) : null, about.map((line, i) => (_jsx(Row, { color: theme.chrome.color, children: ` ${line}` }, i))), _jsx(Text, { children: " " }), _jsx(Keys, { theme: theme, text: "t test \u00B7 p pick (prints the ref) \u00B7 esc back \u00B7 q quit" })] }));
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Runs the browser until the person picks a model or leaves. Resolves to the
|
|
377
|
+
* picked entry, or undefined when nothing was picked.
|
|
378
|
+
*/
|
|
379
|
+
export async function browse(options) {
|
|
380
|
+
// Asked before Ink takes the terminal, as tui/app.tsx does.
|
|
381
|
+
const theme = await resolveTheme(options.theme);
|
|
382
|
+
let chosen;
|
|
383
|
+
const instance = render(_jsx(Browser, { options: options, theme: theme, onPick: (entry) => {
|
|
384
|
+
chosen = entry;
|
|
385
|
+
} }), { stdout: process.stderr });
|
|
386
|
+
await instance.waitUntilExit();
|
|
387
|
+
return chosen;
|
|
388
|
+
}
|
|
389
|
+
//# sourceMappingURL=models.js.map
|
package/dist/tui/wrap.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ export declare function windowOf(text: string, width: number, rows: number): str
|
|
|
15
15
|
export declare function clip(text: string, width: number): string;
|
|
16
16
|
/** Word wrap. Every returned row is at most `width` columns wide. */
|
|
17
17
|
export declare function wrap(text: string, width: number): string[];
|
|
18
|
+
/**
|
|
19
|
+
* The first row of a scrolled list, moved as little as possible so that row
|
|
20
|
+
* `selected` is inside a window of `height` rows over `count` of them.
|
|
21
|
+
*/
|
|
22
|
+
export declare function scrollTop(selected: number, top: number, height: number, count: number): number;
|
|
18
23
|
/** The three footer rows, its margin, the prompt, and a row in hand. */
|
|
19
24
|
export declare const CHROME_ROWS = 7;
|
|
20
25
|
/**
|
package/dist/tui/wrap.js
CHANGED
|
@@ -74,6 +74,18 @@ export function wrap(text, width) {
|
|
|
74
74
|
}
|
|
75
75
|
return out;
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* The first row of a scrolled list, moved as little as possible so that row
|
|
79
|
+
* `selected` is inside a window of `height` rows over `count` of them.
|
|
80
|
+
*/
|
|
81
|
+
export function scrollTop(selected, top, height, count) {
|
|
82
|
+
const h = Math.max(1, height);
|
|
83
|
+
let first = Math.min(top, selected);
|
|
84
|
+
if (selected >= first + h) {
|
|
85
|
+
first = selected - h + 1;
|
|
86
|
+
}
|
|
87
|
+
return Math.max(0, Math.min(first, count - h));
|
|
88
|
+
}
|
|
77
89
|
// ---------------------------------------------------------------------------
|
|
78
90
|
// Dividing the frame
|
|
79
91
|
//
|
package/dist/usage.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ModelUsage, TokenUsage } from '@zenera/neo';
|
|
2
|
+
/** Set by `zen meta` for everything its agent runs, so all of it lands in one ledger. */
|
|
3
|
+
export declare const LEDGER_ENV = "ZENERA_USAGE_LEDGER";
|
|
4
|
+
/** The meta session whose agent started this process, if one did. */
|
|
5
|
+
export declare const META_SESSION_ENV = "ZENERA_META_SESSION";
|
|
6
|
+
/** The stored prompt that meta session was started with, if any. */
|
|
7
|
+
export declare const META_PROMPT_ENV = "ZENERA_META_PROMPT";
|
|
8
|
+
/**
|
|
9
|
+
* Where this project's usage goes: the variable when set, else the fine-tuning's
|
|
10
|
+
* own ledger when the project is being tuned. Elsewhere nothing is recorded.
|
|
11
|
+
*/
|
|
12
|
+
export declare function ledgerPath(projectDir: string): string | undefined;
|
|
13
|
+
interface RowBase {
|
|
14
|
+
/** when the thing ended */
|
|
15
|
+
ts: string;
|
|
16
|
+
}
|
|
17
|
+
export interface MetaCallRow extends RowBase {
|
|
18
|
+
kind: 'meta.call';
|
|
19
|
+
session?: string;
|
|
20
|
+
model: string;
|
|
21
|
+
usage: TokenUsage;
|
|
22
|
+
cacheWriteTokens?: number;
|
|
23
|
+
startedAt: string;
|
|
24
|
+
durationMs: number;
|
|
25
|
+
traceId?: string;
|
|
26
|
+
spanId?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface MetaSessionRow extends RowBase {
|
|
29
|
+
kind: 'meta.session';
|
|
30
|
+
session: string;
|
|
31
|
+
/** copilot's own totals for the whole session so far - cumulative across resumes */
|
|
32
|
+
models: ModelUsage[];
|
|
33
|
+
exitCode: number;
|
|
34
|
+
resumes: number;
|
|
35
|
+
startedAt: string;
|
|
36
|
+
durationMs: number;
|
|
37
|
+
}
|
|
38
|
+
export interface RunRow extends RowBase {
|
|
39
|
+
kind: 'run';
|
|
40
|
+
runDir?: string;
|
|
41
|
+
session?: string;
|
|
42
|
+
batchDir?: string;
|
|
43
|
+
item?: string;
|
|
44
|
+
ok: boolean;
|
|
45
|
+
error?: string;
|
|
46
|
+
stopReason?: string;
|
|
47
|
+
durationMs?: number;
|
|
48
|
+
models: ModelUsage[];
|
|
49
|
+
}
|
|
50
|
+
export interface BatchRow extends RowBase {
|
|
51
|
+
kind: 'batch';
|
|
52
|
+
batchDir: string;
|
|
53
|
+
input: string;
|
|
54
|
+
items: number;
|
|
55
|
+
ok: number;
|
|
56
|
+
failed: number;
|
|
57
|
+
concurrency: number;
|
|
58
|
+
memory: string;
|
|
59
|
+
startedAt: string;
|
|
60
|
+
durationMs: number;
|
|
61
|
+
models: ModelUsage[];
|
|
62
|
+
}
|
|
63
|
+
export interface AskRow extends RowBase {
|
|
64
|
+
kind: 'ask';
|
|
65
|
+
runDir: string;
|
|
66
|
+
node: string;
|
|
67
|
+
nodeAgent: string;
|
|
68
|
+
nodeModel: string;
|
|
69
|
+
model: string;
|
|
70
|
+
usage?: TokenUsage;
|
|
71
|
+
startedAt: string;
|
|
72
|
+
durationMs: number;
|
|
73
|
+
}
|
|
74
|
+
export type UsageRow = MetaCallRow | MetaSessionRow | RunRow | BatchRow | AskRow;
|
|
75
|
+
/** Appends one row. Never throws: losing a line of accounting must not fail a run. */
|
|
76
|
+
export declare function appendUsage(projectDir: string, row: UsageRow): void;
|
|
77
|
+
export {};
|
|
78
|
+
//# sourceMappingURL=usage.d.ts.map
|
package/dist/usage.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { warn } from "./term.js";
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// The usage ledger
|
|
6
|
+
//
|
|
7
|
+
// One JSON line per thing that spent tokens: a meta agent call, a run, a batch,
|
|
8
|
+
// an `inspect ask`. Append-only, so several processes - the meta agent, the
|
|
9
|
+
// batch it started, the ask it is grading with - can write to one file at once.
|
|
10
|
+
// Reports are folded out of it later; nothing here aggregates.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/** Set by `zen meta` for everything its agent runs, so all of it lands in one ledger. */
|
|
13
|
+
export const LEDGER_ENV = 'ZENERA_USAGE_LEDGER';
|
|
14
|
+
/** The meta session whose agent started this process, if one did. */
|
|
15
|
+
export const META_SESSION_ENV = 'ZENERA_META_SESSION';
|
|
16
|
+
/** The stored prompt that meta session was started with, if any. */
|
|
17
|
+
export const META_PROMPT_ENV = 'ZENERA_META_PROMPT';
|
|
18
|
+
/**
|
|
19
|
+
* Where this project's usage goes: the variable when set, else the fine-tuning's
|
|
20
|
+
* own ledger when the project is being tuned. Elsewhere nothing is recorded.
|
|
21
|
+
*/
|
|
22
|
+
export function ledgerPath(projectDir) {
|
|
23
|
+
const set = process.env[LEDGER_ENV]?.trim();
|
|
24
|
+
if (set) {
|
|
25
|
+
return set;
|
|
26
|
+
}
|
|
27
|
+
return existsSync(join(projectDir, 'finetune'))
|
|
28
|
+
? join(projectDir, 'finetune', 'usage', 'ledger.jsonl')
|
|
29
|
+
: undefined;
|
|
30
|
+
}
|
|
31
|
+
let warned = false;
|
|
32
|
+
/** Appends one row. Never throws: losing a line of accounting must not fail a run. */
|
|
33
|
+
export function appendUsage(projectDir, row) {
|
|
34
|
+
const path = ledgerPath(projectDir);
|
|
35
|
+
if (!path) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const meta = process.env[META_SESSION_ENV]?.trim();
|
|
39
|
+
const line = { v: 1, ...row, project: projectDir, pid: process.pid, ...(meta ? { meta } : {}) };
|
|
40
|
+
try {
|
|
41
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
42
|
+
appendFileSync(path, `${JSON.stringify(line)}\n`);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (!warned) {
|
|
46
|
+
warned = true;
|
|
47
|
+
warn(`could not record usage in ${path} - ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=usage.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenera/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.25",
|
|
4
4
|
"description": "Command-line front end for @zenera/neo: agentic projects you can run, share and commit.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -52,11 +52,12 @@
|
|
|
52
52
|
"@google/genai": "^2.18.0",
|
|
53
53
|
"@inkjs/ui": "^2.0.0",
|
|
54
54
|
"@openrouter/sdk": "^1.2.80",
|
|
55
|
-
"@zenera/neo": "^1.1.
|
|
55
|
+
"@zenera/neo": "^1.1.25",
|
|
56
56
|
"google-auth-library": "^10.0.0",
|
|
57
57
|
"ink": "^7.1.1",
|
|
58
58
|
"openai": "^6.0.0",
|
|
59
59
|
"react": "^19.2.8",
|
|
60
|
+
"yaml": "^2.9.0",
|
|
60
61
|
"yauzl": "^3.4.0",
|
|
61
62
|
"yazl": "^3.3.1"
|
|
62
63
|
},
|