@zenera/cli 1.1.16 → 1.1.18
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 +110 -19
- package/dist/banner.d.ts +19 -0
- package/dist/banner.js +12 -9
- package/dist/catalog.js +19 -20
- package/dist/command.d.ts +3 -0
- package/dist/commands/check.js +5 -1
- package/dist/commands/index.js +4 -1
- package/dist/commands/init.js +42 -7
- package/dist/commands/inspect.js +77 -13
- package/dist/commands/key.js +49 -1
- package/dist/commands/memory.js +16 -3
- package/dist/commands/meta.d.ts +3 -0
- package/dist/commands/meta.js +596 -0
- package/dist/commands/run.js +64 -35
- package/dist/engine.d.ts +15 -0
- package/dist/engine.js +26 -1
- package/dist/env.d.ts +20 -0
- package/dist/env.js +65 -0
- package/dist/home.d.ts +2 -0
- package/dist/home.js +2 -0
- package/dist/keys.d.ts +2 -0
- package/dist/keys.js +2 -0
- package/dist/main.js +1 -1
- package/dist/meta.d.ts +221 -0
- package/dist/meta.js +695 -0
- package/dist/podman.js +64 -2
- package/dist/projects.d.ts +9 -1
- package/dist/projects.js +19 -2
- package/dist/resolve.js +7 -1
- package/dist/sandbox.d.ts +11 -0
- package/dist/sandbox.js +24 -4
- package/dist/scaffold.d.ts +6 -0
- package/dist/scaffold.js +56 -15
- package/dist/session.d.ts +10 -0
- package/dist/session.js +19 -1
- package/dist/term.d.ts +7 -0
- package/dist/term.js +8 -1
- package/dist/tui/app.js +33 -5
- package/dist/tui/wrap.d.ts +1 -1
- package/dist/tui/wrap.js +1 -1
- package/dist/validate.js +57 -15
- package/package.json +3 -2
- package/templates/editor/.github/copilot-instructions.md +313 -94
- package/templates/editor/.github/prompts/project-review.prompt.md +74 -0
- package/templates/editor/.github/prompts/spec-apply-feedback.prompt.md +142 -0
- package/templates/editor/.github/prompts/spec-sync-project.prompt.md +392 -0
- package/templates/editor/.github/skills/zen-cli/references/check.md +6 -3
- package/templates/editor/.github/skills/zen-cli/references/frame.md +5 -3
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +4 -3
- package/templates/editor/.github/skills/zen-cli/references/memory.md +14 -0
- package/templates/editor/.github/skills/zen-cli/references/projects.md +19 -2
- package/templates/editor/.github/skills/zen-cli/references/run.md +75 -16
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +6 -0
- package/templates/editor/.github/skills/zen-memory/SKILL.md +153 -33
- package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +115 -0
- package/templates/editor/.github/skills/zen-review/SKILL.md +123 -0
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +119 -0
- package/templates/editor/.github/skills/zen-review/scripts/review.sh +104 -0
- package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +197 -0
- package/templates/editor/.github/skills/zen-spec-sync/scripts/snapshot.sh +200 -0
- package/templates/editor/.vscode/settings.json +44 -4
- package/templates/parts/memory-terms.yaml.tmpl +16 -0
- package/templates/parts/memory.yaml.tmpl +12 -0
- package/templates/project/SPECIFICATION.md.tmpl +32 -2
- package/templates/project/agents/memory-instructions.md +278 -0
- package/templates/project/agents.yaml.tmpl +12 -3
- package/templates/project/env +28 -0
- package/templates/project/gitignore +4 -4
- package/templates/editor/.github/prompts/review-project.prompt.md +0 -36
- package/templates/editor/.github/prompts/sync-with-spec.prompt.md +0 -205
- /package/templates/project/{INSTRUCTIONS.md → agents/instructions.md} +0 -0
package/dist/commands/inspect.js
CHANGED
|
@@ -5,31 +5,39 @@ import { readFile, writeFile } from 'node:fs/promises';
|
|
|
5
5
|
import { createServer } from 'node:http';
|
|
6
6
|
import { extname, join, normalize, resolve, sep } from 'node:path';
|
|
7
7
|
import { parse } from "../args.js";
|
|
8
|
+
import { sessionIds } from "../projects.js";
|
|
8
9
|
import { project as resolveProject } from "../resolve.js";
|
|
9
|
-
import { display,
|
|
10
|
-
import { bold, cyan, dim, invalidError, json, note, write } from "../term.js";
|
|
10
|
+
import { display, listRuns, listSessions, newestRun, requireSession, runPaths, sessionPaths, } from "../session.js";
|
|
11
|
+
import { ago, bold, choose, cyan, dim, invalidError, isInteractive, json, note, write, } from "../term.js";
|
|
11
12
|
const USAGE = 'zen inspect [run] [--session <id>] [--open] [--rebuild] [--serve [port]]';
|
|
12
13
|
export const inspect = {
|
|
13
14
|
summary: "Open or rebuild a run's report.html.",
|
|
14
15
|
usage: USAGE,
|
|
15
16
|
details: [
|
|
16
|
-
'With no arguments:
|
|
17
|
+
'With no arguments: asks which session and run, or takes the newest of',
|
|
18
|
+
'each when there is nothing to ask on.',
|
|
17
19
|
'--serve starts a local server, which the report needs for its assets.',
|
|
20
|
+
'--memory <dir> reads that memory instead of the project’s, for a run',
|
|
21
|
+
'that was given `zen run --memory`.',
|
|
18
22
|
],
|
|
19
23
|
run: async (ctx) => {
|
|
20
24
|
const { values, positionals } = parse(ctx.args, {
|
|
21
25
|
project: { type: 'string' },
|
|
22
26
|
session: { type: 'string' },
|
|
27
|
+
memory: { type: 'string' },
|
|
23
28
|
open: { type: 'boolean' },
|
|
24
29
|
rebuild: { type: 'boolean' },
|
|
25
30
|
serve: { type: 'string' },
|
|
26
31
|
}, USAGE);
|
|
27
32
|
const found = await resolveProject({ cwd: ctx.cwd, project: values.project });
|
|
28
33
|
const dir = found.dir;
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
// Asking is only possible at a terminal, and only honest when the
|
|
35
|
+
// answer is not being parsed by something.
|
|
36
|
+
const asking = isInteractive() && !ctx.json;
|
|
37
|
+
const session = await pickSession(dir, values.session, positionals[0], asking);
|
|
38
|
+
const run = await pickRun(session, positionals[0], asking);
|
|
31
39
|
if (values.rebuild || !existsSync(run.report)) {
|
|
32
|
-
await rebuild(session, run, await memory(dir));
|
|
40
|
+
await rebuild(session, run, await memory(dir, values.memory, ctx.cwd));
|
|
33
41
|
}
|
|
34
42
|
if (ctx.json) {
|
|
35
43
|
json({ session: session.id, run: run.id, report: run.report });
|
|
@@ -52,17 +60,73 @@ export const inspect = {
|
|
|
52
60
|
// ---------------------------------------------------------------------------
|
|
53
61
|
// Choosing what to show
|
|
54
62
|
// ---------------------------------------------------------------------------
|
|
55
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Which session to read. With nothing named and a terminal to ask on, the
|
|
65
|
+
* person picks: "the newest" is a guess about which of a dozen runs they meant,
|
|
66
|
+
* and a wrong guess looks the same as a broken command.
|
|
67
|
+
*/
|
|
68
|
+
async function pickSession(dir, asked, run, asking) {
|
|
56
69
|
if (asked) {
|
|
57
70
|
return requireSession(dir, asked);
|
|
58
71
|
}
|
|
59
|
-
|
|
60
|
-
if (!newest) {
|
|
72
|
+
if (sessionIds(dir).length === 0) {
|
|
61
73
|
throw invalidError('nothing has been run here yet', 'start one: zen run');
|
|
62
74
|
}
|
|
63
|
-
|
|
75
|
+
// A named run answers the question itself: it is in whichever session holds it.
|
|
76
|
+
if (!asking || run) {
|
|
77
|
+
return newestWorked(dir, run);
|
|
78
|
+
}
|
|
79
|
+
const worked = (await listSessions(dir)).filter((s) => s.runs > 0);
|
|
80
|
+
if (worked.length === 0) {
|
|
81
|
+
throw invalidError('no runs yet — every session is empty', 'start one: zen run');
|
|
82
|
+
}
|
|
83
|
+
return await choose('Which session?', worked.map((s) => ({
|
|
84
|
+
label: s.id,
|
|
85
|
+
detail: [
|
|
86
|
+
s.title,
|
|
87
|
+
`${s.runs} run${s.runs === 1 ? '' : 's'}`,
|
|
88
|
+
ago(s.lastRunAt ?? s.createdAt),
|
|
89
|
+
s.busy ? 'running' : '',
|
|
90
|
+
]
|
|
91
|
+
.filter(Boolean)
|
|
92
|
+
.join(' '),
|
|
93
|
+
value: sessionPaths(dir, s.id),
|
|
94
|
+
})));
|
|
64
95
|
}
|
|
65
|
-
|
|
96
|
+
/**
|
|
97
|
+
* The newest session that has a report to show — not simply the newest. A
|
|
98
|
+
* session exists before its first run and outlives one that never recorded
|
|
99
|
+
* anything, so the newest is routinely empty and picking it blindly answers
|
|
100
|
+
* "has no runs" about a project full of them.
|
|
101
|
+
*/
|
|
102
|
+
function newestWorked(dir, run) {
|
|
103
|
+
for (const id of sessionIds(dir).reverse()) {
|
|
104
|
+
const session = sessionPaths(dir, id);
|
|
105
|
+
if (run ? existsSync(runPaths(session, run).dir) : newestRun(session)) {
|
|
106
|
+
return session;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
throw invalidError(run ? `no run ${run} in any session` : 'no runs yet — every session is empty', run ? 'see: zen list --sessions' : 'start one: zen run');
|
|
110
|
+
}
|
|
111
|
+
async function pickRun(session, asked, asking) {
|
|
112
|
+
if (!asked && asking) {
|
|
113
|
+
const runs = await listRuns(session);
|
|
114
|
+
if (runs.length === 0) {
|
|
115
|
+
throw invalidError(`session ${session.id} has no runs`);
|
|
116
|
+
}
|
|
117
|
+
// One run is not a question.
|
|
118
|
+
return await choose(`Which run of ${session.id}?`, runs.map((r) => ({
|
|
119
|
+
label: r.id,
|
|
120
|
+
detail: [
|
|
121
|
+
r.agent,
|
|
122
|
+
r.error ? 'failed' : r.stopReason,
|
|
123
|
+
ago(r.finishedAt ?? r.startedAt),
|
|
124
|
+
]
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.join(' '),
|
|
127
|
+
value: runPaths(session, r.id),
|
|
128
|
+
})));
|
|
129
|
+
}
|
|
66
130
|
const id = asked ?? newestRun(session);
|
|
67
131
|
if (!id) {
|
|
68
132
|
throw invalidError(`session ${session.id} has no runs`);
|
|
@@ -98,9 +162,9 @@ async function rebuild(session, run, store) {
|
|
|
98
162
|
* has the shape of what the run recalled but not a word of it; a run that
|
|
99
163
|
* never touched memory pays nothing, because the report asks for no node.
|
|
100
164
|
*/
|
|
101
|
-
async function memory(dir) {
|
|
165
|
+
async function memory(dir, override, cwd) {
|
|
102
166
|
const { config } = readProjectConfig(dir);
|
|
103
|
-
const at = memoryDir(dir, config);
|
|
167
|
+
const at = memoryDir(dir, config, override && resolve(cwd, override));
|
|
104
168
|
if (!at || !existsSync(join(at, 'manifest.json'))) {
|
|
105
169
|
return undefined;
|
|
106
170
|
}
|
package/dist/commands/key.js
CHANGED
|
@@ -454,10 +454,57 @@ const env = async (ctx, args) => {
|
|
|
454
454
|
function shellQuote(value) {
|
|
455
455
|
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
456
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* One credential on stdout, alone, for a tool that wants a fresh one before
|
|
459
|
+
* every request rather than an export that goes stale — `zen meta` hands this
|
|
460
|
+
* to copilot as `COPILOT_PROVIDER_API_KEY_COMMAND`.
|
|
461
|
+
*
|
|
462
|
+
* A stored secret is simply printed. A service-account file is exchanged for
|
|
463
|
+
* an OAuth access token, which is the part worth having a command for: those
|
|
464
|
+
* last an hour, and a session does not.
|
|
465
|
+
*/
|
|
466
|
+
const token = async (ctx, args) => {
|
|
467
|
+
const { positionals } = parse(args, {}, 'zen key token <provider>[/name]');
|
|
468
|
+
const { provider, name } = parseRef(positionals[0] ?? 'vertex');
|
|
469
|
+
const store = await KeyStore.open();
|
|
470
|
+
const entry = name ? store.find(provider, name) : store.active(provider);
|
|
471
|
+
if (!entry) {
|
|
472
|
+
throw credentialError(`no ${provider} key`, `add one: zen key add ${provider}`);
|
|
473
|
+
}
|
|
474
|
+
if (entry.holds === 'secret') {
|
|
475
|
+
write(store.reveal(entry));
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const { GoogleAuth } = await import('google-auth-library');
|
|
479
|
+
const auth = new GoogleAuth({
|
|
480
|
+
keyFile: store.fileOf(entry),
|
|
481
|
+
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
|
|
482
|
+
});
|
|
483
|
+
const access = await (await auth.getClient()).getAccessToken();
|
|
484
|
+
if (!access.token) {
|
|
485
|
+
throw credentialError(`${keyId(entry)} minted no token`, 'check the service account is still enabled');
|
|
486
|
+
}
|
|
487
|
+
if (ctx.json) {
|
|
488
|
+
json({ key: keyId(entry), token: access.token });
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
write(access.token);
|
|
492
|
+
};
|
|
457
493
|
// ---------------------------------------------------------------------------
|
|
458
494
|
// Dispatch
|
|
459
495
|
// ---------------------------------------------------------------------------
|
|
460
|
-
const SUBS = {
|
|
496
|
+
const SUBS = {
|
|
497
|
+
ls,
|
|
498
|
+
list: ls,
|
|
499
|
+
add,
|
|
500
|
+
use,
|
|
501
|
+
check,
|
|
502
|
+
rm,
|
|
503
|
+
remove: rm,
|
|
504
|
+
show,
|
|
505
|
+
env,
|
|
506
|
+
token,
|
|
507
|
+
};
|
|
461
508
|
export const key = {
|
|
462
509
|
summary: 'The credential store: add, choose, verify and export API keys.',
|
|
463
510
|
usage: USAGE,
|
|
@@ -480,6 +527,7 @@ export const key = {
|
|
|
480
527
|
' zen key rm <provider>/<name> Forget one.',
|
|
481
528
|
' zen key show <ref> [--reveal] Masked by default.',
|
|
482
529
|
' zen key env [provider …] Shell exports, for other tools.',
|
|
530
|
+
' zen key token [ref] One credential on stdout, minted now.',
|
|
483
531
|
],
|
|
484
532
|
run: async (ctx) => {
|
|
485
533
|
const [name, ...rest] = ctx.args;
|
package/dist/commands/memory.js
CHANGED
|
@@ -2,7 +2,7 @@ import { ALL_AGENTS, buildMemoryReport, hostPath, memoryDir, MemoryIndex, Memory
|
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { writeFile } from 'node:fs/promises';
|
|
5
|
-
import { isAbsolute, join, resolve } from 'node:path';
|
|
5
|
+
import { basename, isAbsolute, join, resolve } from 'node:path';
|
|
6
6
|
import { parse } from "../args.js";
|
|
7
7
|
import { project as resolveProject } from "../resolve.js";
|
|
8
8
|
import { ago, bold, bytes, confirm, count, cyan, dim, green, isInteractive, json, note, table, usageError, write, writeAll, yellow, } from "../term.js";
|
|
@@ -39,6 +39,7 @@ export const memory = {
|
|
|
39
39
|
' forget <id...> Remove nodes, their vectors and their files.',
|
|
40
40
|
'',
|
|
41
41
|
' --project <name|dir> Which project. Defaults to the one you are in.',
|
|
42
|
+
' --dir <dir> Read this memory directory instead of the project’s.',
|
|
42
43
|
' --kind <name> Only this kind of node.',
|
|
43
44
|
' --audience <name> Only nodes committed under this label.',
|
|
44
45
|
' --files Only nodes that remember a file.',
|
|
@@ -59,6 +60,7 @@ export const memory = {
|
|
|
59
60
|
run: async (ctx) => {
|
|
60
61
|
const { values, positionals } = parse(ctx.args, {
|
|
61
62
|
project: { type: 'string' },
|
|
63
|
+
dir: { type: 'string' },
|
|
62
64
|
kind: { type: 'string' },
|
|
63
65
|
audience: { type: 'string' },
|
|
64
66
|
files: { type: 'boolean' },
|
|
@@ -73,7 +75,7 @@ export const memory = {
|
|
|
73
75
|
throw usageError(`unknown subcommand: ${what}`, USAGE);
|
|
74
76
|
}
|
|
75
77
|
const rest = positionals.slice(1);
|
|
76
|
-
const opened = await open(ctx.cwd, values.project);
|
|
78
|
+
const opened = await open(ctx.cwd, values.project, values.dir);
|
|
77
79
|
try {
|
|
78
80
|
switch (what) {
|
|
79
81
|
case 'stats':
|
|
@@ -98,8 +100,19 @@ export const memory = {
|
|
|
98
100
|
* project would resolve models and read every prompt file to inspect a graph
|
|
99
101
|
* that needs none of it — and would fail on a project whose credentials are
|
|
100
102
|
* missing, which is exactly when someone is debugging.
|
|
103
|
+
*
|
|
104
|
+
* A named directory is opened as it stands, project or no project: a graph
|
|
105
|
+
* copied out of a running session is the one most worth looking at, and it
|
|
106
|
+
* should not have to be given an `agents.yaml` first.
|
|
101
107
|
*/
|
|
102
|
-
async function open(cwd, want) {
|
|
108
|
+
async function open(cwd, want, at) {
|
|
109
|
+
if (at) {
|
|
110
|
+
const dir = resolve(cwd, at);
|
|
111
|
+
if (!existsSync(join(dir, 'manifest.json'))) {
|
|
112
|
+
throw usageError(`${dir} is not a memory`, 'no manifest.json in it');
|
|
113
|
+
}
|
|
114
|
+
return { store: await MemoryStore.open(dir), dir, project: basename(dir) };
|
|
115
|
+
}
|
|
103
116
|
const project = await resolveProject({ cwd, project: want });
|
|
104
117
|
const { config } = readProjectConfig(project.dir);
|
|
105
118
|
const dir = memoryDir(project.dir, config);
|