@zenera/cli 1.1.14 → 1.1.15
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 +481 -188
- package/dist/commands/init.js +46 -2
- package/dist/validate.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,25 +1,96 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/andreyryabov/ZeneraNeo/main/docs/imgs/banner-neo-dark.svg">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/andreyryabov/ZeneraNeo/main/docs/imgs/banner-neo-light.svg" alt="ZENERA NEO" width="720">
|
|
5
|
+
</picture>
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@zenera/cli)
|
|
8
|
+
[](https://github.com/andreyryabov/ZeneraNeo/blob/main/LICENSE)
|
|
9
|
+
[](https://nodejs.org)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
1
13
|
# @zenera/cli
|
|
2
14
|
|
|
3
|
-
**`zen` —
|
|
15
|
+
**`zen` — build specialist agents. Share them. Run them from the command line.**
|
|
4
16
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
things up when they need to, and hand work to each other until the job is done.
|
|
8
|
-
No framework to learn, no application to build, no glue code to maintain.
|
|
17
|
+
Not one general assistant that is passable at everything — a team built for the
|
|
18
|
+
work you actually keep doing, under a name you can type:
|
|
9
19
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
```sh
|
|
21
|
+
zen run accountant "Q3 — match the receipts drawer to the ledger, then update the return"
|
|
22
|
+
zen run accountant "this MSA against our playbook — redlines, and what I must not sign"
|
|
23
|
+
zen run analyst "why churn doubled in the EU accounts — the exports are in this folder"
|
|
24
|
+
zen run analyst "what changed in EU battery regulation this year, with sources"
|
|
25
|
+
zen run devops "this week's advisories — which of them actually reach our code"
|
|
26
|
+
zen run devops "the 03:12 outage — timeline, contributing factors, owners"
|
|
27
|
+
```
|
|
14
28
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
Three agents there, six things asked of them: an agent you have built is not a
|
|
30
|
+
script with one job, it is a specialist you keep going back to. And it is work
|
|
31
|
+
that comes back — every quarter, every sprint, every deal — which is exactly the
|
|
32
|
+
work nobody has shipped you a product for.
|
|
33
|
+
|
|
34
|
+
Once the agent exists it is a command: it works on the files you are standing
|
|
35
|
+
in, runs real commands in a container, searches the web when it has to, and
|
|
36
|
+
records every token it spent doing it.
|
|
37
|
+
|
|
38
|
+
Getting there is three beats:
|
|
39
|
+
|
|
40
|
+
1. **Describe it.** You write `SPECIFICATION.md` — what the system is for,
|
|
41
|
+
which specialists it needs, what each may reach for, what _done_ means. The
|
|
42
|
+
rest of the folder is built to implement it.
|
|
43
|
+
2. **Test it.** `zen check` validates the project and every file it names,
|
|
44
|
+
`zen run` exercises it for real, `zen inspect` opens the record of what it
|
|
45
|
+
actually did. When it comes out wrong you fix the specification, not the
|
|
46
|
+
prompts, and go round again.
|
|
47
|
+
3. **Share it.** It is a folder — commit it, review it in a pull request, send
|
|
48
|
+
it. One `zen init <dir>` on a project that arrived by clone registers it
|
|
49
|
+
without touching a thing, and it is their command now too.
|
|
18
50
|
|
|
19
51
|
> Part of [ZeneraNeo](https://github.com/andreyryabov/ZeneraNeo). This is an
|
|
20
52
|
> open-source side project for experimentation and chore work — **not** the
|
|
21
53
|
> official Zenera AI Platform. It carries no support or stability promises.
|
|
22
54
|
|
|
55
|
+
## What you build
|
|
56
|
+
|
|
57
|
+
Half a page of specification turns into a system like one of these. Nothing here
|
|
58
|
+
ships with the CLI — each is a folder somebody wrote, and could send you.
|
|
59
|
+
|
|
60
|
+
| Project | The team inside it |
|
|
61
|
+
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| **accountant** | A bookkeeper that reads and classifies every receipt, statement, invoice and agreement in the folder; a reconciler that holds each against a ledger line or a playbook position and lists what has no pair; a preparer that fills the return or drafts the redlines; a checker that refuses anything no document substantiates. |
|
|
63
|
+
| **analyst** | A planner that splits the question into branches; researchers that run in parallel over your files and the web; a statistician that writes and runs Python in the sandbox; an editor that joins it into one answer where every claim carries its source. |
|
|
64
|
+
| **devops** | A gatherer that pulls advisories, logs, alerts and deploys into one picture; a tracer that works out whether a vulnerable path is reachable at all, or which change actually broke it; an engineer that plans the fix, applies it a step at a time, and stops the moment a check fails. |
|
|
65
|
+
|
|
66
|
+
Build one, commit it, and it runs the same on anyone else's machine — on
|
|
67
|
+
whichever models they prefer, and without carrying your keys.
|
|
68
|
+
|
|
69
|
+
## What makes it different
|
|
70
|
+
|
|
71
|
+
A coding agent with custom instructions can already play several roles, and a
|
|
72
|
+
framework can already wire agents together. What neither gives you:
|
|
73
|
+
|
|
74
|
+
- **A system that outlives the session.** Custom instructions and subagents live
|
|
75
|
+
in a chat that ends. Here the system is a directory — versioned, reviewable,
|
|
76
|
+
diffable, sendable. Improving it is a commit, not a paste.
|
|
77
|
+
- **A specification that is enforced, not decorative.** `SPECIFICATION.md` is
|
|
78
|
+
the source; where it and the files disagree, it wins. `/sync-with-spec`
|
|
79
|
+
reconciles the two in both directions and `zen check` fails the project when
|
|
80
|
+
they drift — with a code, a location and a fix per finding. A prose document
|
|
81
|
+
nobody checks is a README; this one is checked.
|
|
82
|
+
- **No application to build.** Frameworks hand you a library and leave you
|
|
83
|
+
owning a program: graph code, state plumbing, retries, transport. Here an
|
|
84
|
+
agent is an instruction, a model, tools, skills, who it may hand work to, and
|
|
85
|
+
how it fans out. That is the whole list, and it is YAML. There is no glue code
|
|
86
|
+
to maintain because there is no glue.
|
|
87
|
+
- **A complete record, by default.** Every run writes its input, output and
|
|
88
|
+
state, plus a self-contained `report.html` — the graph, every request, every
|
|
89
|
+
token, every cost. Not a tracing vendor you sign up for; a file in the folder.
|
|
90
|
+
- **Your keys stay yours.** They live in `~/.zenera`, never in the project. What
|
|
91
|
+
you share is the system; the recipient runs it on their own credentials and
|
|
92
|
+
their own choice of vendor.
|
|
93
|
+
|
|
23
94
|
## Install
|
|
24
95
|
|
|
25
96
|
Node.js 24+. One command — the OpenAI, Anthropic, Google and OpenRouter SDKs
|
|
@@ -43,44 +114,165 @@ Four commands, from nothing to an answer:
|
|
|
43
114
|
npm i -g @zenera/cli # every vendor SDK comes with it
|
|
44
115
|
zen key add openai # asks for the key without showing it; stored in ~/.zenera
|
|
45
116
|
zen init my-project # scaffolds a project and registers it
|
|
46
|
-
|
|
117
|
+
zen run my-project "introduce yourself"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The name works from anywhere after that, and the directory you are standing in
|
|
121
|
+
is what it works on:
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
cd ~/code/some-repo
|
|
125
|
+
zen run my-project "summarise this repo and write NOTES.md"
|
|
47
126
|
```
|
|
48
127
|
|
|
49
|
-
Then
|
|
128
|
+
Then, day to day:
|
|
50
129
|
|
|
51
130
|
```sh
|
|
52
|
-
zen run
|
|
53
|
-
zen check
|
|
131
|
+
zen run my-project # no prompt given: opens a full-screen terminal app (a TUI)
|
|
132
|
+
zen check my-project # validate the project and every file it names
|
|
54
133
|
zen inspect # open the last run's report.html
|
|
55
134
|
zen list --sessions # every project, its sessions and last run
|
|
56
|
-
echo "triage this" | zen run --quiet | jq
|
|
135
|
+
echo "triage this" | zen run my-project --quiet | jq
|
|
57
136
|
```
|
|
58
137
|
|
|
59
|
-
|
|
138
|
+
Standing inside the project, the name is optional: a bare `zen run`, `zen check`
|
|
139
|
+
or `zen inspect` means the one you are in.
|
|
140
|
+
|
|
141
|
+
Giving a prompt on the command line skips every question: it starts a fresh
|
|
142
|
+
session and uses the current directory as the workspace, with write access.
|
|
143
|
+
`--session`, `--workspace` and `--read-only` override that.
|
|
144
|
+
|
|
145
|
+
To change what the system does, open the project in your editor with
|
|
146
|
+
`zen open`, edit `SPECIFICATION.md` to say what you actually want built, and
|
|
147
|
+
send `/sync-with-spec` in the editor's chat. That is the next section.
|
|
148
|
+
|
|
149
|
+
## You write the specification; a coding agent writes the system
|
|
150
|
+
|
|
151
|
+
`zen init` writes `SPECIFICATION.md` first, and everything else in the folder
|
|
152
|
+
implements it. Where the two disagree the specification wins, so a change to
|
|
153
|
+
what the system does starts there and not in `agents.yaml`.
|
|
154
|
+
|
|
155
|
+
The one `zen init` writes is not a heading list — it is a true specification of
|
|
156
|
+
the project just scaffolded, so you can read it against the files beside it
|
|
157
|
+
before changing a word:
|
|
158
|
+
|
|
159
|
+
```markdown
|
|
160
|
+
## Agents
|
|
161
|
+
|
|
162
|
+
One agent, `default`, which is the entry point and the whole system. It has
|
|
163
|
+
nobody to hand work to, because there is no second job to hand on.
|
|
164
|
+
|
|
165
|
+
## Done means
|
|
166
|
+
|
|
167
|
+
- The change asked for is in the workspace, or the question is answered from
|
|
168
|
+
what is actually in it.
|
|
169
|
+
- Everything the agent claims a command did, that command actually did — it was
|
|
170
|
+
run, and its output was read.
|
|
171
|
+
- The reply names what changed, file by file, and says what it deliberately did
|
|
172
|
+
not do.
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### How you actually edit it
|
|
176
|
+
|
|
177
|
+
`SPECIFICATION.md` is ordinary Markdown, so any editor will do — but the loop is
|
|
178
|
+
meant to be driven from a coding agent's chat, and `zen init` installs the
|
|
179
|
+
prompts that make each step a single command:
|
|
60
180
|
|
|
61
181
|
```sh
|
|
62
|
-
|
|
63
|
-
|
|
182
|
+
zen open my-project # or, standing in the folder: zen open
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
That launches the editor you already use: the one this terminal belongs to, or
|
|
186
|
+
`$ZENERA_EDITOR`, or the first of VS Code, Cursor, Windsurf, Zed, Sublime Text
|
|
187
|
+
or IntelliJ it can find. `--editor <cmd>` names one outright.
|
|
188
|
+
|
|
189
|
+
In the window that opens:
|
|
190
|
+
|
|
191
|
+
1. **Edit `SPECIFICATION.md`** — what the system is for, which agents exist,
|
|
192
|
+
what each may reach for, and what _done_ means.
|
|
193
|
+
2. **Open the chat panel and send `/sync-with-spec`.** It reads the
|
|
194
|
+
specification and every file implementing it, works out the difference in
|
|
195
|
+
both directions, and changes the smallest thing that closes each gap.
|
|
196
|
+
3. **Read `SPECIFICATION-FEEDBACK.md`**, which it writes for anything it could
|
|
197
|
+
not do without guessing. Answer its questions by editing `SPECIFICATION.md`
|
|
198
|
+
— not by editing prompts — and send `/sync-with-spec` again.
|
|
199
|
+
|
|
200
|
+
Back in the terminal: `zen check`, then `zen run`.
|
|
201
|
+
|
|
202
|
+
<details>
|
|
203
|
+
<summary>The chat commands <code>zen init</code> installs</summary>
|
|
204
|
+
|
|
205
|
+
They are prompt files under `.github/prompts/`, which VS Code and its forks
|
|
206
|
+
offer as chat slash-commands. Both `zen init` and `zen open` write them fresh,
|
|
207
|
+
so they never go stale — and edits to them do not survive.
|
|
208
|
+
|
|
209
|
+
| In chat | Does |
|
|
210
|
+
| ----------------- | ----------------------------------------------------------- |
|
|
211
|
+
| `/sync-with-spec` | Makes every file match `SPECIFICATION.md`, both directions. |
|
|
212
|
+
| `/review-project` | Reads the project as a reviewer would, and reports. |
|
|
213
|
+
| `/new-agent` | Adds an agent — prompt, wiring and hand-offs. |
|
|
214
|
+
| `/new-skill` | Adds a skill under `agents/skills/`. |
|
|
215
|
+
|
|
216
|
+
In an editor that does not support prompt files, paste the contents of
|
|
217
|
+
`.github/prompts/sync-with-spec.prompt.md` into its chat instead — it is only a
|
|
218
|
+
prompt. Alongside them, `.github/copilot-instructions.md` is the standing brief
|
|
219
|
+
that explains this runtime to whatever agent is reading, and
|
|
220
|
+
`.github/skills/zen-cli/` is the CLI's own reference for it.
|
|
221
|
+
|
|
222
|
+
</details>
|
|
223
|
+
|
|
224
|
+
### The loop
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
edit SPECIFICATION.md → /sync-with-spec → zen check → scripts/_setup.sh
|
|
228
|
+
↑ ↓
|
|
229
|
+
└──────────────── read the report ←──────── zen run ─────┘
|
|
64
230
|
```
|
|
65
231
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
232
|
+
The last turn is the one that matters. When a run comes out wrong, the fix is
|
|
233
|
+
the sentence that was missing from the specification; the prompt edit follows
|
|
234
|
+
from that. Prompts patched directly drift away from the document meant to
|
|
235
|
+
describe them, and a project whose specification is no longer true is a project
|
|
236
|
+
with no specification.
|
|
69
237
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
238
|
+
You are not expected to hand-author any of this. `zen check` is written to be
|
|
239
|
+
read by a model as much as by a person — every finding carries a code, a
|
|
240
|
+
location and its fix — so "fix my project" is one instruction. Full guide:
|
|
241
|
+
[docs/specification.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/specification.md).
|
|
73
242
|
|
|
74
243
|
## A worked example
|
|
75
244
|
|
|
76
|
-
|
|
77
|
-
whole thing, in three files.
|
|
245
|
+
From a sentence to a working two-agent system, without hand-writing any wiring.
|
|
78
246
|
|
|
79
247
|
```sh
|
|
80
248
|
zen init repo-notes && cd repo-notes
|
|
249
|
+
zen open # opens this folder in your editor
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Replace `SPECIFICATION.md` with what you want. This is the whole input — no
|
|
253
|
+
YAML, no prompt files:
|
|
254
|
+
|
|
255
|
+
```markdown
|
|
256
|
+
# Specification
|
|
257
|
+
|
|
258
|
+
## Purpose
|
|
259
|
+
|
|
260
|
+
Read a codebase and leave a short written note about it in the workspace.
|
|
261
|
+
|
|
262
|
+
## Agents
|
|
263
|
+
|
|
264
|
+
- `reader` — explores the workspace and forms a picture of it. May read files
|
|
265
|
+
and list directories, and nothing else. Hands off to `writer`.
|
|
266
|
+
- `writer` — turns that picture into `NOTES.md`. May write to the workspace.
|
|
267
|
+
|
|
268
|
+
## Done means
|
|
269
|
+
|
|
270
|
+
- `NOTES.md` exists, is Markdown, and is under 40 lines.
|
|
271
|
+
- Every claim in it comes from a file that was actually read.
|
|
81
272
|
```
|
|
82
273
|
|
|
83
|
-
`
|
|
274
|
+
Send `/sync-with-spec` in the editor's chat. It writes `agents.yaml` — who
|
|
275
|
+
exists, and what each may reach for:
|
|
84
276
|
|
|
85
277
|
```yaml
|
|
86
278
|
default: reader
|
|
@@ -100,6 +292,11 @@ agents:
|
|
|
100
292
|
handoffs: [reader] # a hand-off does not return by itself; give it a way back
|
|
101
293
|
```
|
|
102
294
|
|
|
295
|
+
— and one prompt file per agent.
|
|
296
|
+
|
|
297
|
+
<details>
|
|
298
|
+
<summary>The prompt files it writes</summary>
|
|
299
|
+
|
|
103
300
|
`agents/prompts/reader.md`:
|
|
104
301
|
|
|
105
302
|
```markdown
|
|
@@ -115,6 +312,28 @@ You write the summary you were handed to `NOTES.md`, in Markdown, under 40
|
|
|
115
312
|
lines. Then say where you put it and stop.
|
|
116
313
|
```
|
|
117
314
|
|
|
315
|
+
</details>
|
|
316
|
+
|
|
317
|
+
<details>
|
|
318
|
+
<summary>What each <code>agents.yaml</code> key means</summary>
|
|
319
|
+
|
|
320
|
+
| Key | Is |
|
|
321
|
+
| ------------- | --------------------------------------------------------------------- |
|
|
322
|
+
| `name` | how everything else refers to this agent |
|
|
323
|
+
| `description` | what it is for — read by the _other_ agents when deciding to hand off |
|
|
324
|
+
| `system` | its own brief, in prose, at `agents/prompts/<name>.md` |
|
|
325
|
+
| `model` | this agent's model; the top-level one otherwise |
|
|
326
|
+
| `tools` | what it may reach for — `workspace:*`, `sandbox:*`, a named tool |
|
|
327
|
+
| `handoffs` | who it may pass the work to |
|
|
328
|
+
| `skills` | knowledge pulled in mid-run instead of carried in every prompt |
|
|
329
|
+
|
|
330
|
+
The file is validated strictly at load: an unknown tool, a handoff to nobody, a
|
|
331
|
+
missing prompt file — each fails immediately, naming the offending key, rather
|
|
332
|
+
than surfacing three turns into a run as a confused model. Full reference:
|
|
333
|
+
[docs/agents-yaml.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/agents-yaml.md).
|
|
334
|
+
|
|
335
|
+
</details>
|
|
336
|
+
|
|
118
337
|
Check it, then point it at a real directory:
|
|
119
338
|
|
|
120
339
|
```sh
|
|
@@ -124,13 +343,19 @@ zen run repo-notes "summarise this repo" # this directory is the workspace
|
|
|
124
343
|
zen inspect --project repo-notes --open # what it actually did
|
|
125
344
|
```
|
|
126
345
|
|
|
127
|
-
|
|
346
|
+
That is the end of it: `repo-notes` is now a command like any other, in any
|
|
347
|
+
directory, for as long as the folder exists. Commit the folder and whoever
|
|
348
|
+
clones it types `zen init repo-notes` once — a directory that is already a
|
|
349
|
+
project is registered, not rebuilt, and nothing in it is touched — and the name
|
|
350
|
+
is a command on their machine too.
|
|
128
351
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
352
|
+
## A project is a folder
|
|
353
|
+
|
|
354
|
+
Prompts, agent wiring, skills and tool selections are all files — Markdown and
|
|
355
|
+
YAML — rather than code buried inside an application. So the folder can be
|
|
356
|
+
committed, copied to another machine, reviewed in a pull request, and handed to
|
|
357
|
+
someone else who runs it with one command. Credentials live in `$HOME`, never in
|
|
358
|
+
the project, so there is no secret to strip before sharing it.
|
|
134
359
|
|
|
135
360
|
```
|
|
136
361
|
my-project/
|
|
@@ -149,15 +374,33 @@ my-project/
|
|
|
149
374
|
runs/<id>/ input, output, state, report.html, meta
|
|
150
375
|
```
|
|
151
376
|
|
|
152
|
-
`
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
[docs/specification.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/specification.md).
|
|
377
|
+
Everything above the `sessions/` line is the system; everything below it is what
|
|
378
|
+
happened when it ran. Nothing here is tied to the machine it was written on,
|
|
379
|
+
which is the point of the CLI: it makes agentic systems shareable the way
|
|
380
|
+
repositories are shareable.
|
|
157
381
|
|
|
158
|
-
`zen init` also writes `.github
|
|
159
|
-
explains this runtime to whatever coding agent you
|
|
160
|
-
|
|
382
|
+
`zen init` also writes `.vscode/` and a `.github/` tree — a standing brief that
|
|
383
|
+
explains this runtime to whatever coding agent you open the folder with, plus
|
|
384
|
+
the prompts and skills it needs to keep the project matching its specification.
|
|
385
|
+
|
|
386
|
+
## Concepts
|
|
387
|
+
|
|
388
|
+
- **Project** — a named directory holding a complete agent definition and the
|
|
389
|
+
sessions that have run against it. It is self-describing: `agents.yaml` is
|
|
390
|
+
what makes a directory a project, so moving or cloning it loses nothing.
|
|
391
|
+
- **Session** — a context that persists across runs: one workspace, one memory,
|
|
392
|
+
one store for large files, and a record of everything that happened, added to
|
|
393
|
+
as it goes. Resumable.
|
|
394
|
+
- **Run** — one prompt in, one answer out, inside a session. Recorded in full,
|
|
395
|
+
whether or not you were watching.
|
|
396
|
+
- **Workspace** — the directory the agents may read and write. A prompt given on
|
|
397
|
+
the command line uses the current directory; the TUI offers the session's own
|
|
398
|
+
empty folder instead, and asks before using anything outside it.
|
|
399
|
+
- **Keyring** — `~/.zenera/neo`, readable only by you. See
|
|
400
|
+
[Credentials](#credentials).
|
|
401
|
+
- **Cache** — `~/.zenera/neo/cache`, one place for work already done: vectors,
|
|
402
|
+
parses, model listings, generated mocks. Shared by every project on the
|
|
403
|
+
machine, and never emptied by anything but you. See [Cache](#cache--work-you-have-already-paid-for).
|
|
161
404
|
|
|
162
405
|
## Commands
|
|
163
406
|
|
|
@@ -176,12 +419,14 @@ you are not expected to hand-author `agents.yaml`.
|
|
|
176
419
|
| `cache` | What work has been kept, and getting rid of it. |
|
|
177
420
|
| `version` | CLI, library and Node versions. |
|
|
178
421
|
|
|
179
|
-
Commands
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
422
|
+
### Commands from other packages
|
|
423
|
+
|
|
424
|
+
A command can also come from a package installed alongside this one, so a new
|
|
425
|
+
capability arrives as a subcommand instead of another binary to remember: one
|
|
426
|
+
thing on your path, one keyring, one name. `zen --help` lists these whether or
|
|
427
|
+
not they are installed, and tells you what to install if not. Nothing is
|
|
428
|
+
imported until you type the command, so an uninstalled one costs you nothing and
|
|
429
|
+
an installed one costs nothing until it is used.
|
|
185
430
|
|
|
186
431
|
| Command | Package | Does |
|
|
187
432
|
| ------- | --------------- | ---------------------------------------------- |
|
|
@@ -203,15 +448,25 @@ configure. Details:
|
|
|
203
448
|
·
|
|
204
449
|
[@zenera/rag](https://github.com/andreyryabov/ZeneraNeo/blob/main/packages/rag/README.md).
|
|
205
450
|
|
|
206
|
-
|
|
207
|
-
ok, `1` the run failed, `2` bad invocation, `3` invalid project, `4` no usable
|
|
208
|
-
credential, `5` sandbox unavailable.
|
|
209
|
-
|
|
210
|
-
`stdout` is the answer, `stderr` is the narration, and `--json` is on every
|
|
211
|
-
command — so `zen run … | jq` is a supported way to use it, not an accident.
|
|
451
|
+
### Conventions
|
|
212
452
|
|
|
213
453
|
The binary is installed under three names: `zen`, `zn` and `zenera`.
|
|
214
454
|
|
|
455
|
+
Global flags: `-h/--help`, `-v/--version`, `--json`, `-C <dir>`.
|
|
456
|
+
|
|
457
|
+
`stdout` carries the answer and `stderr` carries the narration, and `--json`
|
|
458
|
+
works on every command — so `zen run … | jq` is a supported way to use this, not
|
|
459
|
+
an accident.
|
|
460
|
+
|
|
461
|
+
| Exit code | Meaning |
|
|
462
|
+
| --------- | -------------------- |
|
|
463
|
+
| `0` | ok |
|
|
464
|
+
| `1` | the run failed |
|
|
465
|
+
| `2` | bad invocation |
|
|
466
|
+
| `3` | invalid project |
|
|
467
|
+
| `4` | no usable credential |
|
|
468
|
+
| `5` | sandbox unavailable |
|
|
469
|
+
|
|
215
470
|
## Credentials
|
|
216
471
|
|
|
217
472
|
One keyring serves every provider, and a key goes in the same way whatever it
|
|
@@ -222,12 +477,14 @@ zen key add <provider> # asks for the key without showing it
|
|
|
222
477
|
zen key add <provider> < key.txt # or pipe it in
|
|
223
478
|
```
|
|
224
479
|
|
|
225
|
-
The value is never
|
|
480
|
+
The value is never passed as an argument. A command line is visible to anyone
|
|
226
481
|
listing running processes, is saved in your shell history and is captured in CI
|
|
227
|
-
logs — so
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
482
|
+
logs — so the hidden prompt and piping it in are the only two ways.
|
|
483
|
+
|
|
484
|
+
Entries live in `~/.zenera/neo/keys.json`, in a file only you can read. They are
|
|
485
|
+
copied into the environment just before a run, which has two consequences worth
|
|
486
|
+
knowing: an environment variable you set yourself always wins, and a project
|
|
487
|
+
checked out on a machine without `zen` still runs.
|
|
231
488
|
|
|
232
489
|
| Provider | The value is | Exported as |
|
|
233
490
|
| ------------ | ------------------------------------- | -------------------------------- |
|
|
@@ -247,11 +504,13 @@ behind a proxy — is not a key that is wrong. `--no-check` skips the call.
|
|
|
247
504
|
A model reference is `[provider[/api]:]model`, and the first segment names a
|
|
248
505
|
**provider, not a vendor**. So `vertex:gemini-3.5-flash` and
|
|
249
506
|
`google:gemini-3.5-flash` are the same model reached through two different
|
|
250
|
-
services, needing two different credentials
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
507
|
+
services, needing two different credentials; a bare `gpt-5.4-mini` goes to the
|
|
508
|
+
default provider, `openai`.
|
|
509
|
+
|
|
510
|
+
`zen check` resolves every reference in a project against what is stored and
|
|
511
|
+
says which credential each one needs. It then spends a few tokens asking each
|
|
512
|
+
model to answer, which is the only way to catch a model id this account is not
|
|
513
|
+
served. `--no-models` stops before that.
|
|
255
514
|
|
|
256
515
|
### Which models you can use
|
|
257
516
|
|
|
@@ -267,15 +526,20 @@ zen models test vertex:gemini-embedding-001 # one real call, one verdict
|
|
|
267
526
|
zen models pick --embedding # the first ref that answers, on stdout
|
|
268
527
|
```
|
|
269
528
|
|
|
270
|
-
Listings are cached for a day in `~/.zenera/neo/cache`.
|
|
271
|
-
|
|
272
|
-
one does a short built-in list stand in.
|
|
529
|
+
Listings are cached for a day in `~/.zenera/neo/cache`. If a provider cannot be
|
|
530
|
+
reached, the last listing is used and marked stale; only when there has never
|
|
531
|
+
been one does a short built-in list stand in.
|
|
273
532
|
|
|
274
|
-
`test`
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
533
|
+
`zen models test` separates three kinds of failure, because each calls for a
|
|
534
|
+
different action:
|
|
535
|
+
|
|
536
|
+
- **refused** — the provider rejected the credential. Fix or replace the key.
|
|
537
|
+
- **blocked** — the credential was accepted and the account then said no: an API
|
|
538
|
+
switched off, an empty balance, a model this key was never granted. The key is
|
|
539
|
+
fine; the account needs changing, and the command that changes it is printed
|
|
540
|
+
with the error.
|
|
541
|
+
- **unknown** — the question never arrived (offline, proxy, timeout). Nothing is
|
|
542
|
+
wrong with the key; try again.
|
|
279
543
|
|
|
280
544
|
```
|
|
281
545
|
$ zen models test vertex:gemini-embedding-001
|
|
@@ -285,164 +549,192 @@ error 1 of 1 did not answer
|
|
|
285
549
|
find one that does: zen models pick --embedding
|
|
286
550
|
```
|
|
287
551
|
|
|
288
|
-
`pick` tries a short list of candidates one at a time
|
|
289
|
-
that works,
|
|
290
|
-
|
|
552
|
+
`zen models pick` tries a short list of candidates one at a time, stops at the
|
|
553
|
+
first that works, and prints the bare reference on stdout. So recovering from
|
|
554
|
+
the above is a single substitution, whether a person or an agent is doing it:
|
|
291
555
|
|
|
292
556
|
```sh
|
|
293
557
|
zen rag schema index --embedding "$(zen models pick --embedding)" ./specs/*.yaml
|
|
294
558
|
```
|
|
295
559
|
|
|
296
|
-
###
|
|
560
|
+
### Setting up each provider
|
|
297
561
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
otherwise it is treated as an API key. The name you choose for the entry has no
|
|
301
|
-
say in it — `vertex/express` is simply an entry called `express`, exactly as
|
|
302
|
-
`vertex/prod` is one called `prod`, and either name can hold either kind.
|
|
562
|
+
The shape is the same everywhere — `zen key add <provider>`, then answer the
|
|
563
|
+
prompt. Open the one you need for its specifics.
|
|
303
564
|
|
|
304
|
-
|
|
305
|
-
|
|
565
|
+
<details>
|
|
566
|
+
<summary><strong>OpenAI</strong> — one secret, exported as <code>OPENAI_API_KEY</code></summary>
|
|
306
567
|
|
|
307
568
|
```sh
|
|
308
|
-
zen key add
|
|
309
|
-
# Paste the key, or a path to the file: /Users/you/keys/vertex-sa.json
|
|
569
|
+
zen key add openai
|
|
310
570
|
```
|
|
311
571
|
|
|
312
|
-
|
|
313
|
-
|
|
572
|
+
`openai` is the default provider, so a reference with no prefix comes here:
|
|
573
|
+
`gpt-5.4-mini` and `openai:gpt-5.4-mini` are the same model.
|
|
574
|
+
|
|
575
|
+
</details>
|
|
576
|
+
|
|
577
|
+
<details>
|
|
578
|
+
<summary><strong>Anthropic</strong> — one secret, exported as <code>ANTHROPIC_API_KEY</code></summary>
|
|
314
579
|
|
|
315
580
|
```sh
|
|
316
|
-
|
|
581
|
+
zen key add anthropic
|
|
582
|
+
zen run --model anthropic:claude-sonnet-4-5 "summarise this repo"
|
|
317
583
|
```
|
|
318
584
|
|
|
319
|
-
|
|
320
|
-
moving or cleaning up the original later cannot break it.
|
|
585
|
+
</details>
|
|
321
586
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
keeping processing inside that territory, `global` takes whatever is free and
|
|
325
|
-
promises no residency. `global` pays about ten seconds of cold start on the
|
|
326
|
-
first request each process makes — a region answers in about two.
|
|
327
|
-
- Which models a location serves is per model, and not guessable. In one
|
|
328
|
-
project, `gemini-embedding-2` answered at `us` but 404'd at `us-central1`,
|
|
329
|
-
while `gemini-2.5-flash` did the opposite. New models often reach `global`,
|
|
330
|
-
`us` and `eu` first. `zen models test vertex:<model>` is what settles it —
|
|
331
|
-
`zen key add` only establishes that the credential itself works.
|
|
332
|
-
- `--gcp-project <id>` is only needed when the `project_id` inside the file is not
|
|
333
|
-
the project you want.
|
|
587
|
+
<details>
|
|
588
|
+
<summary><strong>OpenRouter</strong> — one secret, exported as <code>OPENROUTER_API_KEY</code></summary>
|
|
334
589
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
service account, and it needs neither a project nor a region, so `--gcp-project`
|
|
338
|
-
and `--gcp-location` mean nothing there and are not stored.
|
|
590
|
+
One credential for many vendors' models. The model id keeps its own vendor
|
|
591
|
+
prefix, after the `openrouter:` one:
|
|
339
592
|
|
|
340
|
-
|
|
593
|
+
```sh
|
|
594
|
+
zen key add openrouter
|
|
595
|
+
zen models search haiku --tools --free
|
|
596
|
+
zen run --model openrouter:anthropic/claude-haiku-4.5 "summarise this repo"
|
|
597
|
+
```
|
|
341
598
|
|
|
342
|
-
|
|
343
|
-
which one you hold decides the prefix a model reference needs.
|
|
599
|
+
</details>
|
|
344
600
|
|
|
345
|
-
|
|
346
|
-
|
|
601
|
+
<details>
|
|
602
|
+
<summary><strong>Google AI Studio</strong> — one secret, exported as <code>GEMINI_API_KEY</code></summary>
|
|
603
|
+
|
|
604
|
+
One key and nothing else to configure: the shortest way to a working Gemini
|
|
605
|
+
model.
|
|
347
606
|
|
|
348
607
|
```sh
|
|
349
|
-
zen key add google
|
|
608
|
+
zen key add google
|
|
350
609
|
zen check # google:gemini-3.5-flash now resolves
|
|
351
610
|
```
|
|
352
611
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
612
|
+
The same Gemini models are also served by Vertex AI, under a different
|
|
613
|
+
credential. Both can be configured at once — they are separate entries for
|
|
614
|
+
separate services, and the prefix on the model reference decides which is used:
|
|
356
615
|
|
|
357
616
|
```sh
|
|
358
|
-
|
|
617
|
+
zen run --model google:gemini-3.5-flash "summarise this repo"
|
|
618
|
+
zen run --model vertex:gemini-3.5-flash "summarise this repo"
|
|
359
619
|
```
|
|
360
620
|
|
|
361
|
-
|
|
362
|
-
|
|
621
|
+
</details>
|
|
622
|
+
|
|
623
|
+
<details>
|
|
624
|
+
<summary><strong>Vertex AI</strong> — a service-account file, or an express-mode key</summary>
|
|
625
|
+
|
|
626
|
+
Vertex accepts two kinds of credential, and you never declare which one you are
|
|
627
|
+
giving: if the value is a path to a file that exists it is treated as a
|
|
628
|
+
service-account key, otherwise as an API key. The name you give the entry has no
|
|
629
|
+
bearing on this — `vertex/express` is just an entry called `express`, exactly as
|
|
630
|
+
`vertex/prod` is one called `prod`, and either name can hold either kind.
|
|
631
|
+
|
|
632
|
+
**A service-account JSON file** is the usual one, and what production normally
|
|
633
|
+
runs on. Give its path, not its contents. Run the command with nothing piped and
|
|
634
|
+
it asks:
|
|
363
635
|
|
|
364
636
|
```sh
|
|
365
|
-
|
|
366
|
-
|
|
637
|
+
zen key add vertex --gcp-location us-central1
|
|
638
|
+
# Paste the key, or a path to the file: /Users/you/keys/vertex-sa.json
|
|
367
639
|
```
|
|
368
640
|
|
|
369
|
-
|
|
641
|
+
The prompt is read by `zen`, not by your shell, so give a full path there — `~`
|
|
642
|
+
is not expanded. In a script, pipe the path in instead:
|
|
370
643
|
|
|
371
644
|
```sh
|
|
372
|
-
zen key add vertex
|
|
645
|
+
echo ~/keys/vertex-sa.json | zen key add vertex --gcp-location us-central1
|
|
373
646
|
```
|
|
374
647
|
|
|
375
|
-
|
|
648
|
+
The file is copied into `~/.zenera/neo/keys/`, where only you can read it, so
|
|
649
|
+
moving or cleaning up the original later cannot break it.
|
|
376
650
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
651
|
+
- `--gcp-location <region>` is worth setting, because the file says which project
|
|
652
|
+
it belongs to but never which region to call. It takes a concrete region, or
|
|
653
|
+
one of the endpoints that route across regions: `us` and `eu` pool capacity
|
|
654
|
+
while keeping processing inside that territory, and `global` takes whatever is
|
|
655
|
+
free and promises no residency. `global` costs about ten seconds of cold start
|
|
656
|
+
on the first request each process makes; a named region answers in about two.
|
|
657
|
+
- Which models a location serves varies per model and is not guessable. In one
|
|
658
|
+
project, `gemini-embedding-2` answered at `us` but 404'd at `us-central1`,
|
|
659
|
+
while `gemini-2.5-flash` did the opposite. New models often reach `global`,
|
|
660
|
+
`us` and `eu` first. `zen models test vertex:<model>` is what settles it —
|
|
661
|
+
`zen key add` only establishes that the credential itself works.
|
|
662
|
+
- `--gcp-project <id>` is only needed when the `project_id` inside the file is
|
|
663
|
+
not the project you want to bill:
|
|
664
|
+
|
|
665
|
+
```sh
|
|
666
|
+
echo ~/keys/vertex-sa.json \
|
|
667
|
+
| zen key add vertex --gcp-project other-project --gcp-location europe-west4
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
**An express-mode API key** is the alternative: a single secret, stored under
|
|
671
|
+
`VERTEX_API_KEY`. It is the Vertex console's way of granting access without a
|
|
672
|
+
service account, and it needs neither a project nor a region, so `--gcp-project`
|
|
673
|
+
and `--gcp-location` mean nothing there and are not stored.
|
|
381
674
|
|
|
382
|
-
|
|
383
|
-
zen key
|
|
384
|
-
zen key show vertex/prod # masked; --reveal prints the path
|
|
675
|
+
```sh
|
|
676
|
+
zen key add vertex # paste the key at the prompt; no flags apply
|
|
385
677
|
```
|
|
386
678
|
|
|
387
|
-
|
|
388
|
-
|
|
679
|
+
**An existing `gcloud` login counts too.** If you have run
|
|
680
|
+
`gcloud auth application-default login`, Vertex works with nothing stored at
|
|
681
|
+
all: `zen key ls` shows that login as `adc`, marked `~` because it came from
|
|
682
|
+
outside the keyring. Anything already set in `GOOGLE_APPLICATION_CREDENTIALS`,
|
|
683
|
+
`VERTEX_API_KEY` or `GEMINI_API_KEY` is listed the same way and wins over the
|
|
684
|
+
keyring, so it is always visible which credential a run will actually use.
|
|
685
|
+
|
|
686
|
+
</details>
|
|
687
|
+
|
|
688
|
+
<details>
|
|
689
|
+
<summary><strong>Exa</strong> — one secret, exported as <code>EXA_API_KEY</code></summary>
|
|
690
|
+
|
|
691
|
+
Not a model provider: this is what backs the search tool an agent reaches for
|
|
692
|
+
when it has to look something up on the web.
|
|
389
693
|
|
|
390
694
|
```sh
|
|
391
|
-
zen
|
|
392
|
-
zen run --model vertex:gemini-3.5-flash "summarise this repo"
|
|
695
|
+
zen key add exa
|
|
393
696
|
```
|
|
394
697
|
|
|
395
|
-
|
|
396
|
-
itself a usable credential: `zen key ls` shows it as `adc`, marked `~` because
|
|
397
|
-
it came from outside the keyring, and Vertex works with nothing stored at all.
|
|
398
|
-
Anything already set in `GOOGLE_APPLICATION_CREDENTIALS`, `VERTEX_API_KEY` or
|
|
399
|
-
`GEMINI_API_KEY` is listed the same way and wins over the keyring, so it is
|
|
400
|
-
always visible which credential a run will actually use.
|
|
698
|
+
</details>
|
|
401
699
|
|
|
402
|
-
|
|
700
|
+
<details>
|
|
701
|
+
<summary><strong>More than one key for the same provider</strong></summary>
|
|
403
702
|
|
|
404
|
-
Entries are named, so a provider can hold several
|
|
703
|
+
Entries are named, so a provider can hold several, one of which is active:
|
|
405
704
|
|
|
406
705
|
```sh
|
|
407
706
|
zen key add openai/work # a second entry
|
|
408
|
-
zen key use openai/work #
|
|
707
|
+
zen key use openai/work # the one that runs will use
|
|
409
708
|
zen key ls --check # everything stored, and whether it still works
|
|
410
709
|
zen key show vertex/default # masked — --reveal prints the secret
|
|
411
710
|
zen key env openai # shell exports, for other tools
|
|
412
711
|
zen key rm openai/work
|
|
413
712
|
```
|
|
414
713
|
|
|
714
|
+
It is the same for Vertex, where holding several at once is the ordinary case:
|
|
715
|
+
|
|
716
|
+
```sh
|
|
717
|
+
echo ~/keys/prod-sa.json | zen key add vertex/prod --gcp-location us-central1
|
|
718
|
+
echo ~/keys/dev-sa.json | zen key add vertex/dev --gcp-location global
|
|
719
|
+
zen key add vertex/express # the express key, same provider
|
|
720
|
+
|
|
721
|
+
zen key use vertex/dev # which one the next run uses
|
|
722
|
+
```
|
|
723
|
+
|
|
415
724
|
`zen key ls` marks the active entry with `*`, and anything it found outside the
|
|
416
725
|
keyring — in your environment, or in a `gcloud` login — with `~`, so it is always
|
|
417
726
|
clear where a working provider actually comes from.
|
|
418
727
|
|
|
419
|
-
|
|
728
|
+
</details>
|
|
420
729
|
|
|
421
|
-
|
|
422
|
-
sessions that ran against it. Self-describing: `agents.yaml` is what makes it
|
|
423
|
-
one, so moving or cloning the directory loses nothing.
|
|
424
|
-
- **Session** — a context that persists: one workspace, one memory, one store
|
|
425
|
-
for large files, and a record of everything that happened, added to as it
|
|
426
|
-
goes. Resumable.
|
|
427
|
-
- **Run** — one prompt in, one answer out, inside a session. Recorded in full,
|
|
428
|
-
whether or not you were watching.
|
|
429
|
-
- **Workspace** — the directory the agents may read and write. A prompt given on
|
|
430
|
-
the command line uses the current directory; the TUI offers the session's own
|
|
431
|
-
empty folder and confirms anything outside it.
|
|
432
|
-
- **Keyring** — `~/.zenera/neo`, readable only by you. Keys are copied into the
|
|
433
|
-
environment just before a run, so an environment variable you set yourself
|
|
434
|
-
always wins and a project checked out on a machine without `zen` still runs.
|
|
435
|
-
- **Cache** — `~/.zenera/neo/cache`, one place for work already done: vectors,
|
|
436
|
-
parses, model listings, generated mocks. Shared by every project on the
|
|
437
|
-
machine, and never evicted by anything but you.
|
|
730
|
+
## Cache — work you have already paid for
|
|
438
731
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
is ever invalidated: it is simply never asked for again.
|
|
732
|
+
Embedding a paragraph, parsing a document, asking a provider what it serves: all
|
|
733
|
+
of these are slow or costly, and all of them give the same answer every time. So
|
|
734
|
+
the answers are kept in `~/.zenera/neo/cache/<kind>/`, under a key built from
|
|
735
|
+
every input that produced them. Change any input and the key changes, so you get
|
|
736
|
+
a fresh answer rather than a stale one. That is why entries are never
|
|
737
|
+
invalidated: one that no longer matches anything is simply never read again.
|
|
446
738
|
|
|
447
739
|
```
|
|
448
740
|
zen cache ls # what is kept, by kind
|
|
@@ -452,12 +744,14 @@ zen cache prune --max-size 2GB # or keep it under a ceiling
|
|
|
452
744
|
zen cache clear --kind docs-parse # throw one kind away
|
|
453
745
|
```
|
|
454
746
|
|
|
455
|
-
Age
|
|
456
|
-
weekly rebuild
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
747
|
+
Age means when an entry was last _read_, not when it was written, so a vector
|
|
748
|
+
that a weekly rebuild keeps using never counts as old.
|
|
749
|
+
|
|
750
|
+
Nothing is ever evicted automatically. A store that quietly deletes things is
|
|
751
|
+
only noticed once it has deleted the wrong one, so every removal is something
|
|
752
|
+
you ask for. Nothing in the cache is precious either: every entry is work that
|
|
753
|
+
can be done again, and the only cost of deleting one is paying for it a second
|
|
754
|
+
time.
|
|
461
755
|
|
|
462
756
|
## The library underneath
|
|
463
757
|
|
|
@@ -470,19 +764,18 @@ terminal:
|
|
|
470
764
|
|
|
471
765
|
## Documentation
|
|
472
766
|
|
|
473
|
-
[docs/specification.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/specification.md)
|
|
474
|
-
— writing the specification the project implements
|
|
475
|
-
[docs/projects.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/projects.md)
|
|
476
|
-
— the folder
|
|
477
|
-
[docs/agents-yaml.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/agents-yaml.md)
|
|
478
|
-
— every key in the configuration file.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
— what agents keep between sessions.
|
|
767
|
+
- [docs/specification.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/specification.md)
|
|
768
|
+
— writing the specification the project implements.
|
|
769
|
+
- [docs/projects.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/projects.md)
|
|
770
|
+
— what the project folder contains, file by file.
|
|
771
|
+
- [docs/agents-yaml.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/agents-yaml.md)
|
|
772
|
+
— every key in the configuration file.
|
|
773
|
+
- [docs/knowledge.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/knowledge.md)
|
|
774
|
+
— building a searchable document index.
|
|
775
|
+
- [docs/integrations.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/integrations.md)
|
|
776
|
+
— making an API description searchable and mockable.
|
|
777
|
+
- [docs/memory.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/memory.md)
|
|
778
|
+
— what agents keep between sessions.
|
|
486
779
|
|
|
487
780
|
## License
|
|
488
781
|
|
package/dist/commands/init.js
CHANGED
|
@@ -9,7 +9,7 @@ import { probeAll } from "../liveness.js";
|
|
|
9
9
|
import { ensurePodmanReady } from "../podman.js";
|
|
10
10
|
import { isProjectDir, Registry } from "../projects.js";
|
|
11
11
|
import { scaffold } from "../scaffold.js";
|
|
12
|
-
import { bold, cyan, dim, green,
|
|
12
|
+
import { bold, cyan, dim, green, json, note, progress, usageError, write, yellow, } from "../term.js";
|
|
13
13
|
const USAGE = 'zen init [dir] [--name <name>] [--model <ref>] [--force]';
|
|
14
14
|
/**
|
|
15
15
|
* The model each provider gets scaffolded with, when it is the one chosen.
|
|
@@ -101,6 +101,11 @@ export const init = {
|
|
|
101
101
|
'find it by name. Editor files (.vscode/settings.json and the .github/',
|
|
102
102
|
'tree) are written alongside, replacing any already there.',
|
|
103
103
|
'',
|
|
104
|
+
'A directory that is already a project is adopted rather than rebuilt:',
|
|
105
|
+
'it is registered under its own name, and nothing in it is touched.',
|
|
106
|
+
'That is how a project someone else wrote becomes a name on this',
|
|
107
|
+
'machine after a clone.',
|
|
108
|
+
'',
|
|
104
109
|
'SPECIFICATION.md states what the project is for and what it may do,',
|
|
105
110
|
'and describes the one that was just written. Say what you are building',
|
|
106
111
|
'there, then run /sync-with-spec in your editor to make the rest match.',
|
|
@@ -122,7 +127,8 @@ export const init = {
|
|
|
122
127
|
const dir = resolve(ctx.cwd, one(positionals, 'directory', USAGE) ?? '.');
|
|
123
128
|
const name = values.name ?? basename(dir);
|
|
124
129
|
if (isProjectDir(dir)) {
|
|
125
|
-
|
|
130
|
+
await adopt(dir, values, ctx.json);
|
|
131
|
+
return;
|
|
126
132
|
}
|
|
127
133
|
if (existsSync(dir) && readdirSync(dir).length > 0 && !values.force) {
|
|
128
134
|
throw usageError(`${dir} is not empty`, 'pass --force to write into it anyway');
|
|
@@ -177,6 +183,44 @@ export const init = {
|
|
|
177
183
|
write(dir);
|
|
178
184
|
},
|
|
179
185
|
};
|
|
186
|
+
/**
|
|
187
|
+
* Takes a directory that is already a project into the registry.
|
|
188
|
+
*
|
|
189
|
+
* The registry lives in `$HOME`, so a project that arrives by clone, copy or
|
|
190
|
+
* `mv` is a working project that no name resolves to — and the only thing
|
|
191
|
+
* missing is the entry. Scaffolding over it would be the wrong repair: the
|
|
192
|
+
* files are somebody's work, and this writes none of them.
|
|
193
|
+
*
|
|
194
|
+
* Idempotent, because the honest answer to `zen init` in a project that is
|
|
195
|
+
* already listed is "it is", not an error.
|
|
196
|
+
*/
|
|
197
|
+
async function adopt(dir, values, quiet) {
|
|
198
|
+
ensureHome();
|
|
199
|
+
const registry = await Registry.open();
|
|
200
|
+
const listed = registry.findPath(dir);
|
|
201
|
+
const name = values.name ?? listed?.name ?? basename(dir);
|
|
202
|
+
const renamed = listed !== undefined && listed.name !== name;
|
|
203
|
+
registry.add(name, dir);
|
|
204
|
+
registry.save();
|
|
205
|
+
const sandbox = await prepareSandbox(dir, quiet);
|
|
206
|
+
if (quiet) {
|
|
207
|
+
json({ name, path: dir, adopted: true, listed: listed !== undefined, sandbox });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const what = listed === undefined
|
|
211
|
+
? green('registered')
|
|
212
|
+
: renamed
|
|
213
|
+
? green('renamed')
|
|
214
|
+
: dim('already registered');
|
|
215
|
+
note(`${what} ${bold(name)} ${dim(dir)}`);
|
|
216
|
+
// Nothing here was scaffolded, so the flags that shape a scaffold did not apply.
|
|
217
|
+
if (values.model) {
|
|
218
|
+
note(`${yellow('--model ignored')} ${dim('— the model this project uses is in agents.yaml')}`);
|
|
219
|
+
}
|
|
220
|
+
note();
|
|
221
|
+
note(`Next: ${cyan(`cd ${dir}`)} then ${cyan('zen check')}`);
|
|
222
|
+
write(dir);
|
|
223
|
+
}
|
|
180
224
|
/**
|
|
181
225
|
* Builds the sandbox image the project was just given, here rather than on the
|
|
182
226
|
* first run.
|
package/dist/validate.js
CHANGED
|
@@ -88,7 +88,7 @@ export async function validateProject(opts) {
|
|
|
88
88
|
where: root,
|
|
89
89
|
message: 'this directory is not registered, so `zen open` and `zen list` will not ' +
|
|
90
90
|
'find it by name (naming it by path, as now, works either way)',
|
|
91
|
-
fix: `register it: zen init ${root}
|
|
91
|
+
fix: `register it: zen init ${root}`,
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
// -----------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenera/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.15",
|
|
4
4
|
"description": "Command-line front end for @zenera/neo: agentic projects you can run, share and commit.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@inkjs/ui": "^2.0.0",
|
|
52
52
|
"ink": "^7.1.1",
|
|
53
53
|
"react": "^19.2.8",
|
|
54
|
-
"@zenera/neo": "^1.1.
|
|
54
|
+
"@zenera/neo": "^1.1.15",
|
|
55
55
|
"@anthropic-ai/sdk": "^0.120.0",
|
|
56
56
|
"@google/genai": "^2.18.0",
|
|
57
57
|
"@openrouter/sdk": "^1.2.80",
|