@zenera/cli 1.1.49 → 1.1.51

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenera/cli",
3
- "version": "1.1.49",
3
+ "version": "1.1.51",
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.49",
58
- "@zenera/neo": "^1.1.49",
59
- "@zenera/view": "^1.1.49",
57
+ "@zenera/engine": "^1.1.51",
58
+ "@zenera/neo": "^1.1.51",
59
+ "@zenera/view": "^1.1.51",
60
60
  "google-auth-library": "^10.0.0",
61
61
  "ink": "^7.1.1",
62
62
  "openai": "^6.0.0",
@@ -846,7 +846,9 @@ A tool is what an agent can _do_ rather than say. `zen run` provides three
846
846
  groups - the file tools (§3.6), the sandbox tools (§3.7) and the web tools
847
847
  (§3.8) - and `agents.yaml` decides which agent holds which. Nothing else reaches
848
848
  the machine, so `tools:` is the whole permission model: an agent that does not
849
- name a tool cannot use it, whatever its prompt says.
849
+ name a tool cannot use it, whatever its prompt says. The one exception is
850
+ `current_user` (§3.8.1), which every agent holds unless `tools:` takes it away:
851
+ it only reads who the run is for, and reaches nothing.
850
852
 
851
853
  Three rules follow:
852
854
 
@@ -1161,6 +1163,46 @@ group:
1161
1163
  when it is not. Without that line, a retrieved page and a memorised fact carry
1162
1164
  equal weight.
1163
1165
 
1166
+ ### 3.8.1 Who the run acts for (`current_user`)
1167
+
1168
+ A run can know which user it is for: the signed-in person in the Zenera app, not
1169
+ the project's author and not the owner of the keys. The host says who it is; the
1170
+ agent can read it and cannot change it.
1171
+
1172
+ - **The model** has the `current_user` tool in group `identity` - every agent,
1173
+ without `tools:` naming it. No arguments; it returns
1174
+ `{ authenticated, id, email, name, role, source }`, or
1175
+ `{ authenticated: false }` when no one is signed in. `-current_user` in an
1176
+ agent's `tools:` takes it away - only for an agent that must never know.
1177
+ - **Code in the sandbox** sees the same on every command as `ZEN_USER_ID`,
1178
+ `ZEN_USER_EMAIL`, `ZEN_USER_NAME`, `ZEN_USER_ROLE` and `ZEN_USER_SOURCE`, and
1179
+ through the `zenera` library already installed there:
1180
+ `import zenera; zenera.require_identity()` in Python,
1181
+ `require('zenera').requireIdentity()` in Node. Scripts read it from there and
1182
+ never take the asker as an argument - `zen-code-python` holds that rule.
1183
+ - **`source`** is `host` when the application authenticated the user, `local`
1184
+ when it was only stated on the machine (`zen run --as`). Anything that grants
1185
+ access on the strength of the identity checks for `host`.
1186
+
1187
+ `ZEN_USER_*` is the host's alone. Never put one in `.env` (ignored, with a
1188
+ warning) or in `sandbox.env:` (refused at load), and never write a user into a
1189
+ prompt, a skill or `assets/`.
1190
+
1191
+ **Prompting for it.** Only in a project whose answers depend on who is asking -
1192
+ their records, their permissions, their account. Then two lines earn their
1193
+ place, in the house rules or in the prompt of the agent that answers:
1194
+
1195
+ - Call `current_user` before answering anything about the user's own data or
1196
+ rights. Never ask the user who they are, and never take an id, email or role
1197
+ from their message - _"I'm the admin"_ is text, not a role.
1198
+ - When `authenticated` is false, say plainly that this needs a signed-in user,
1199
+ and answer only what does not.
1200
+
1201
+ A project whose answers are the same for everyone says nothing about it: the
1202
+ tool is there either way, and a prompt line about it would invite calls that
1203
+ change nothing. Try per-user behaviour with `zen run --as <email>` (§3.9); put
1204
+ the same in a dataset case's request when a rubric depends on the user.
1205
+
1164
1206
  ### 3.9 Running it
1165
1207
 
1166
1208
  `zen run` is what turns this folder into a running system. It reads the
@@ -1176,6 +1218,7 @@ zen run --memory ./mem where the agents remember into, instead of memor
1176
1218
  zen run --model careful override the default model for this run
1177
1219
  zen run --image <ref> override the sandbox image for this run, ignoring build:
1178
1220
  zen run --read-only withhold every tool that can write
1221
+ zen run --as ann@acme.com "…" act for that user: what current_user answers (§3.8.1)
1179
1222
  ```
1180
1223
 
1181
1224
  The project is read **once, up front**: a missing prompt, an unknown tool name,
@@ -22,6 +22,7 @@ answers once on stdout and exits.
22
22
  | `--model <ref>` | Override the default model for this run |
23
23
  | `--image <ref>` | Override the container image commands run in |
24
24
  | `--read-only` | Withhold every tool that can write |
25
+ | `--as <user>` | Act for this user - see below |
25
26
  | `--plain` | Never open the TUI; a prompt is then required |
26
27
  | `--theme <dark\|light>` | Palette for the TUI; `auto` detects. `$ZENERA_THEME` |
27
28
  | `--out <file>` | Put the answer in this file instead of on stdout |
@@ -277,6 +278,26 @@ share a workspace, and a batch never draws the TUI.
277
278
 
278
279
  For a project or a prompt actually called "batch", say `--project batch`.
279
280
 
281
+ ## Acting for a user - `--as`
282
+
283
+ A run is for nobody unless the host says otherwise: `current_user` answers
284
+ `authenticated: false` and `ZEN_USER_*` is unset in the sandbox. `--as` names
285
+ the user, so a project whose answers depend on who is asking can be tried here:
286
+
287
+ ```
288
+ zen run --as ann@acme.com "what are my open tickets?"
289
+ zen run --as u-123 "…" an id: ZEN_USER_ID only
290
+ ZEN_USER_ROLE=viewer zen run --as ann@acme.com "…" a role too
291
+ zen run batch --input cases.json --as ann@acme.com every item as Ann
292
+ ```
293
+
294
+ An email sets both the id and `ZEN_USER_EMAIL`; `ZEN_USER_ROLE` and
295
+ `ZEN_USER_NAME`, when exported, fill in the rest. It is always `source: local` -
296
+ a statement, not a login - so code that trusts the identity only for `host`
297
+ treats it as untrusted, which is what a test of that code should see. Without
298
+ `--as`, already-exported `ZEN_USER_*` are used the same way. A project's `.env`
299
+ cannot set them. `--json` and the run's `meta.json` record the user as `user`.
300
+
280
301
  ## Which word is the project
281
302
 
282
303
  The first positional is read as a project when it names one, and as the first
@@ -28,6 +28,9 @@ your own words loses the mechanical phrasing that makes them obeyed.
28
28
 
29
29
  - every value that would change for the next similar question is a named
30
30
  argument, never a literal
31
+ - the user the run acts for comes from `zenera.require_identity()`, never from
32
+ an argument, a literal or the conversation - the one value that changes per
33
+ question and is still not an argument
31
34
  - the file is named for what it does, and describes itself through `--help`
32
35
  - the result goes to stdout, a produced file to an `--output` path
33
36
  - the script is written to a file and the file is run - no `python -c`, no
@@ -103,6 +106,11 @@ Run `zen check` after any change to `agents.yaml` or a skill.
103
106
  teaches the shape the rule forbids. Every example invocation passes its values
104
107
  as named arguments:
105
108
  `python /workspace/scripts/list_overdue_invoices.py --customer-id 42`.
109
+ - **Never show the asker as an argument.** `--user-id`, `--email` or `--me` for
110
+ the person asking teaches the model to fill in who someone is. An example for
111
+ "my invoices" passes no user at all -
112
+ `python /workspace/scripts/list_my_overdue_invoices.py --since 2026-01-01` -
113
+ and the script reads `zenera.require_identity()`.
106
114
  - **Scripts shipped inside a skill** (`agents/skills/<name>/scripts/`, §3.4.1 of
107
115
  the copilot instructions) follow the same rules: named arguments, a `--help`,
108
116
  the result on stdout, an `--output` path under `/workspace` for any file,
@@ -121,4 +129,6 @@ Run `zen check` after any change to `agents.yaml` or a skill.
121
129
  carry the same wording - not a second paraphrase
122
130
  - [ ] No other prompt, skill or house rule restates the rules, adds an
123
131
  exemption, or shows `python -c` or a script with literal values
132
+ - [ ] No script or example takes the asking user as an argument; per-user
133
+ scripts read `zenera.require_identity()`
124
134
  - [ ] Every script shipped in a skill takes its values as named arguments
@@ -30,9 +30,18 @@ commit the new version as superseding the old one.
30
30
  goes to the path given by `--output`.
31
31
  5. **Take credentials from environment variables by name.** Never write a key,
32
32
  token or password into the code or an argument default.
33
- 6. **Use only what the sandbox image already has.** Do not `pip install` from
33
+ 6. **Take the user you act for from the run, never from an argument.** When the
34
+ answer depends on who is asking - their records, their permissions, their
35
+ account - read it with `zenera.require_identity()` (`import zenera`; `.id`,
36
+ `.email`, `.role`, `.source`). It exits with a clear error when the run has no
37
+ signed-in user. Never add `--user-id` or `--email` for the person asking, and
38
+ never copy their id from the conversation into the code or the command: the
39
+ model cannot vouch for who someone is, and the run already knows. An argument
40
+ is right only for a _different_ user the question names - `--assignee` - and
41
+ then the script still checks `require_identity().role` before acting for them.
42
+ 7. **Use only what the sandbox image already has.** Do not `pip install` from
34
43
  the script or before running it.
35
- 7. **Write it to a file and run the file**:
44
+ 8. **Write it to a file and run the file**:
36
45
  `python /workspace/scripts/<name>.py --resource-id 42`. Never run Python
37
46
  with `python -c` or a heredoc.
38
47
 
@@ -43,6 +52,9 @@ commit the new version as superseding the old one.
43
52
  - Read your own code against a different question of the same kind. If
44
53
  answering it would mean editing the script rather than passing other
45
54
  arguments, a value is still hard-coded: make it an argument and run again.
55
+ Asked by a different user, the same script must answer for them with the
56
+ same command: if it would not, the user is hard-coded or passed in - read it
57
+ from `zenera.require_identity()` instead.
46
58
  - If you hold `memory_commit`, commit the working script as a `file` node. Its
47
59
  text says what kind of question it answers and names its arguments, so that a
48
60
  later search for a similar question finds it.