@zenera/cli 1.1.47 → 1.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/batch.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { RunIdentity } from '@zenera/neo';
1
2
  import type { BatchItem, BatchRequest } from './request.ts';
2
3
  /**
3
4
  * Many questions, one project, at once.
@@ -34,6 +35,8 @@ export interface BatchOptions {
34
35
  readOnly?: boolean;
35
36
  keys?: boolean;
36
37
  yes?: boolean;
38
+ /** `--as`: who every item acts for */
39
+ user?: RunIdentity;
37
40
  /** a second home for the combined envelope */
38
41
  out?: string;
39
42
  json: boolean;
package/dist/batch.js CHANGED
@@ -228,6 +228,7 @@ async function runItem(item, ctx) {
228
228
  memoryDir: ctx.memory,
229
229
  memoryReadOnly: ctx.memoryReadOnly,
230
230
  keys: ctx.opts.keys,
231
+ user: ctx.opts.user,
231
232
  yes: true,
232
233
  // The project's own problems are the batch's, not each item's.
233
234
  quiet: true,
@@ -1,6 +1,6 @@
1
1
  import * as Projects from '@zenera/engine';
2
2
  import { display } from '@zenera/engine';
3
- import { usageByModel } from '@zenera/neo';
3
+ import { identityFromEnv, usageByModel } from '@zenera/neo';
4
4
  import { writeFile } from 'node:fs/promises';
5
5
  import { resolve } from 'node:path';
6
6
  import { parse } from "../args.js";
@@ -41,6 +41,9 @@ export const run = {
41
41
  ' --image <ref> Use this container image to run commands in.',
42
42
  ' --read-only Take away every tool that can write.',
43
43
  ' --no-keys Do not pass API keys into the container.',
44
+ ' --as <user> Act for this user: what `current_user` answers and $ZEN_USER_ID',
45
+ ' holds. An email also sets $ZEN_USER_EMAIL. For trying',
46
+ ' a project; the Zenera app passes the signed-in user.',
44
47
  ' --plain Never open the TUI. Needs a prompt.',
45
48
  ' --theme <dark|light> Colours for the TUI. Default: auto.',
46
49
  ' --out <file> Put the answer in this file instead of on screen.',
@@ -129,6 +132,7 @@ export const run = {
129
132
  model: { type: 'string' },
130
133
  image: { type: 'string' },
131
134
  'no-keys': { type: 'boolean' },
135
+ as: { type: 'string' },
132
136
  'read-only': { type: 'boolean' },
133
137
  yes: { type: 'boolean' },
134
138
  plain: { type: 'boolean' },
@@ -201,6 +205,7 @@ export const run = {
201
205
  memoryDir: request?.memory ?? (values.memory ? resolve(ctx.cwd, values.memory) : undefined),
202
206
  memoryReadOnly: values['memory-read-only'],
203
207
  keys: values['no-keys'] ? false : undefined,
208
+ user: asUser(values.as),
204
209
  yes: values.yes || ctx.json,
205
210
  disposable: values.disposable,
206
211
  });
@@ -236,6 +241,31 @@ export const run = {
236
241
  // ---------------------------------------------------------------------------
237
242
  // Many shots
238
243
  // ---------------------------------------------------------------------------
244
+ /**
245
+ * `--as`, as the identity a run acts for. Always `local` — saying who you are
246
+ * on your own machine is a test, not a login. The other fields still come from
247
+ * `ZEN_USER_*` when exported, so `ZEN_USER_ROLE=viewer zen run --as ann` tries
248
+ * a role too. Unset, the engine reads the environment by itself.
249
+ */
250
+ function asUser(as) {
251
+ if (as === undefined) {
252
+ return undefined;
253
+ }
254
+ const id = as.trim();
255
+ if (!id) {
256
+ throw usageError('--as needs a user', 'an id or an email, e.g. --as ann@example.com');
257
+ }
258
+ const env = identityFromEnv({ ...process.env, ZEN_USER_ID: id });
259
+ return {
260
+ ...env,
261
+ id,
262
+ email: env?.email ?? (id.includes('@') ? id : undefined),
263
+ source: 'local',
264
+ };
265
+ }
266
+ // ---------------------------------------------------------------------------
267
+ // Many shots
268
+ // ---------------------------------------------------------------------------
239
269
  /**
240
270
  * Flags that mean something to one run and nothing to a hundred. Refused by
241
271
  * name rather than ignored: a `--workspace` that was quietly dropped is a batch
@@ -276,6 +306,7 @@ async function batch(ctx, values, rest, piped) {
276
306
  image: values.image,
277
307
  readOnly: values['read-only'],
278
308
  keys: values['no-keys'] ? false : undefined,
309
+ user: asUser(values.as),
279
310
  yes: values.yes,
280
311
  out: values.out,
281
312
  json: ctx.json,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenera/cli",
3
- "version": "1.1.47",
3
+ "version": "1.1.49",
4
4
  "description": "Command-line front end for @zenera/neo: agentic projects you can run, share and commit.",
5
5
  "keywords": [
6
6
  "agents",
@@ -54,9 +54,9 @@
54
54
  "@google/genai": "^2.30.0",
55
55
  "@inkjs/ui": "^2.0.0",
56
56
  "@openrouter/sdk": "^1.4.25",
57
- "@zenera/engine": "^1.1.47",
58
- "@zenera/neo": "^1.1.47",
59
- "@zenera/view": "^1.1.47",
57
+ "@zenera/engine": "^1.1.49",
58
+ "@zenera/neo": "^1.1.49",
59
+ "@zenera/view": "^1.1.49",
60
60
  "google-auth-library": "^10.0.0",
61
61
  "ink": "^7.1.1",
62
62
  "openai": "^6.0.0",
@@ -180,7 +180,13 @@ nothing it did not already owe when the last pass verified it.
180
180
  path, every changed top-level key in `agents.yaml`, every `SPECIFICATION.md`
181
181
  hunk, every answered feedback question and every `Still open` item. A hunk
182
182
  counts by what it asks for, not by the section it sits in: a _Scope_ line
183
- that introduces writing scripts reaches **code**.
183
+ that introduces writing scripts reaches **code**. A hunk that only
184
+ describes what is already built asks for nothing: when the implementation
185
+ diff is empty and reading the files the hunk names shows they already do
186
+ exactly what it says, it counts as **specification**. A `✅` entry whose
187
+ **Still to implement** says nothing is left is the usual source; confirm
188
+ it by that reading rather than trusting it. One line that differs makes the
189
+ hunk count by what it asks for.
184
190
  2. **Take the union** of the skills and checks those areas name. Load those
185
191
  skills and no others. Name each skill and expensive check left out, with
186
192
  the reason: `zen-sandbox - no sandbox/, sandbox: or pin change`. Reading
@@ -199,8 +205,9 @@ A full or tampered pass reaches every area.
199
205
  ### Areas
200
206
 
201
207
  - **specification** - wording in `SPECIFICATION.md` or
202
- `SPECIFICATION-FEEDBACK.md` that asks for nothing in the areas below. Skill:
203
- none. Checks: baseline.
208
+ `SPECIFICATION-FEEDBACK.md` that asks for nothing in the areas below,
209
+ including a hunk that only describes what is already built. Skill: none.
210
+ Checks: baseline.
204
211
  - **prompts and grants** - `agents/prompts/**`, `agents/skills/**`, and the
205
212
  `tools:` and `skills:` lists of an agent. Skill: none of its own; the content
206
213
  may reach another area. A `sandbox:*` or `files:*` grant is a tool grant, not
@@ -341,7 +348,9 @@ feedback or `Still open` item requires implementation work:
341
348
  - the pass edits nothing except `SPECIFICATION.md`, `SPECIFICATION-FEEDBACK.md`
342
349
  and the `@zenera/*` pin in `sandbox/Dockerfile`
343
350
  - no changed specification hunk names an agent, tool, model, embedding, skill,
344
- house rule, handoff, memory, index, asset or setup step
351
+ house rule, handoff, memory, index, asset or setup step - except a hunk that
352
+ only describes what is already built, confirmed as the plan says, such as a
353
+ `➕` keep answer folded into the specification
345
354
 
346
355
  Generated copies and the editor tree repaired by `--fix` are maintenance, not
347
356
  project implementation edits for this test.