@0xgeorgemathew/zcode 0.3.0
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/LICENSE +21 -0
- package/README.md +239 -0
- package/bin/zcode.js +239 -0
- package/lib/appserver.js +432 -0
- package/lib/bootstrap.js +89 -0
- package/lib/commands/agent.js +203 -0
- package/lib/commands/doctor.js +118 -0
- package/lib/commands/login.js +48 -0
- package/lib/commands/mcp-list.js +20 -0
- package/lib/commands/run.js +126 -0
- package/lib/engine.js +139 -0
- package/lib/isolation.js +313 -0
- package/lib/key.js +51 -0
- package/lib/models.js +53 -0
- package/lib/overlay.js +138 -0
- package/lib/ui.js +105 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The zai-code authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# zcode
|
|
2
|
+
|
|
3
|
+
Headless GLM-5.3 / GLM-5.3-Flash turns for agents, with the full agentic runtime underneath: subagents that do real work, MCP servers, skills, session memory. Zero dependencies. One binary, NDJSON out.
|
|
4
|
+
|
|
5
|
+
Built for agent callers, not humans: stable machine contract on stdout, everything a watcher needs on stderr, explicit exit codes, no surprises.
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
$ zcode "What is 17*23? Reason carefully, then reply with only the number."
|
|
9
|
+
│ The user wants 17 * 23. Simple math. 17*23 = 17*20 + 17*3 = 340 + 51 = 391. Reply with only the number.
|
|
10
|
+
391
|
|
11
|
+
GLM-5.3-Flash · effort max · 0 tool calls · 9.1s · sess_03bf3114-44
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
That is a real transcript. Thinking streams as a dimmed quote block on stderr, the answer lands alone on stdout, and a status line closes the turn. What you pipe is exactly what the model answered.
|
|
15
|
+
|
|
16
|
+
[](https://www.npmjs.com/package/@0xgeorgemathew/zcode)
|
|
17
|
+
[](LICENSE)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install -g @0xgeorgemathew/zcode
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Needs node 20 or newer for the CLI, 26 recommended for the engine child, plus the ZCode engine on the machine (today: the copy bundled with the ZCode desktop app, resolved automatically).
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## Start in three commands
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
echo "$ZAI_API_KEY" | zcode login # store your Z.AI key (mode 600, never printed)
|
|
35
|
+
zcode doctor # check engine, isolation, key
|
|
36
|
+
zcode "Explain this repo in two sentences"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Ask
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
zcode "Reply with exactly: OK" # GLM-5.3-Flash, effort max
|
|
45
|
+
zcode --model glm-5.3 "Reply with exactly: OK" # GLM-5.3
|
|
46
|
+
zcode --effort high "Draft a commit message" # low | medium | high | xhigh | max
|
|
47
|
+
echo "$PROMPT" | zcode # prompt via stdin
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Only `glm-5.3` and `glm-5.3-flash` (plus short aliases) are accepted. Anything else exits 2 before any engine spawn or API call. Default effort is max.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## Watch it work
|
|
55
|
+
|
|
56
|
+
Plain mode keeps two streams apart on purpose:
|
|
57
|
+
|
|
58
|
+
- **stdout** carries the answer, nothing else. Pipe it, capture it, trust it.
|
|
59
|
+
- **stderr** is the live feed, all dimmed so it visually recedes: thinking as `│ ` quote lines, tool calls as `→ started` / `✓ ok` / `✗ error`, then one status line with model, effort, tool count, duration, and the session id.
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
$ zcode "Create a file named note.txt containing exactly the word ready, then reply with just: DONE"
|
|
63
|
+
│ This is a trivial task: create note.txt containing exactly the word "ready", then reply with just "DONE".
|
|
64
|
+
│ …
|
|
65
|
+
→ Write started
|
|
66
|
+
DONE
|
|
67
|
+
GLM-5.3-Flash · effort max · 1 tool call · 11.1s · sess_e9fc3633-3b
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`--quiet` silences the stderr feed. `--include-reasoning` puts the full thinking text into the JSON result line.
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## Continue a conversation
|
|
75
|
+
|
|
76
|
+
Every result carries a `sessionId`. Sessions persist on disk, so a later invocation, in a new process, picks up the same thread:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
SID=$(zcode --json "Remember the secret word BANANA. Reply with exactly: OK" | tail -1 | jq -r .sessionId)
|
|
80
|
+
|
|
81
|
+
zcode --session "$SID" "Reply with only the secret word I told you." # -> BANANA
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The continued result line reports `"resumed": true` with the same `sessionId`. The status footer shows the short id so plain-mode callers can continue without a JSON round-trip.
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## Dispatch a subagent
|
|
89
|
+
|
|
90
|
+
`zcode agent` forces a dispatch through the Agent tool and verifies the outcome programmatically. It never trusts the model's prose: exit 0 only if the parent turn succeeded and at least one subagent ended with status `success`.
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
WORK=$(mktemp -d)
|
|
94
|
+
zcode agent --cwd "$WORK" \
|
|
95
|
+
"Use the Agent tool to create hello.txt containing exactly HELLO-SUBAGENT, then report the agent id."
|
|
96
|
+
cat "$WORK/hello.txt" # HELLO-SUBAGENT
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Use `--cwd` to declare where the subagent may work. `--profile` picks the subagent profile (default `general-purpose`; your own `~/.zcode/agents/*.md` profiles resolve too). With `--json`, the final line carries the engine-reported evidence:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
"subagents": [{
|
|
103
|
+
"agentId": "agent_8f5144b0-…",
|
|
104
|
+
"childSessionId": "sess_subagent_agent_8f5144b0-…",
|
|
105
|
+
"profileId": "general-purpose",
|
|
106
|
+
"status": "success",
|
|
107
|
+
"summary": "…the subagent's own report…",
|
|
108
|
+
"durationMs": 59765
|
|
109
|
+
}]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
## MCP servers
|
|
115
|
+
|
|
116
|
+
Hermetic by default: a run registers only the servers you pass. `--mcp` takes inline JSON or `@file`, repeatable; stdio, http, and sse are supported.
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
zcode --mcp '{"word-server":{"type":"stdio","command":"node","args":["srv.js"],"enabled":true}}' \
|
|
120
|
+
"Call get_magic_word and reply with exactly what it returns"
|
|
121
|
+
|
|
122
|
+
zcode --mcp '{"docs":{"type":"http","url":"https://mcp.example.com/mcp","headers":{"Authorization":"Bearer …"}}}' \
|
|
123
|
+
"Look up the MCP spec section on tools"
|
|
124
|
+
|
|
125
|
+
zcode --mcp @servers.json --mcp-list # print the effective set as JSON
|
|
126
|
+
zcode --mcp-inherit … # also load the desktop's enabled servers
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The CLI forces the connection and waits until your servers have settled before sending the prompt, so the first turn never misses a slow stdio spawn. Connection state streams to stderr (`◇ mcp word-server connected`) and into the JSON events. Inheritance is opt-in because desktop sets often include browser-OAuth servers that can never finish headless, and they stall the engine's connect pool.
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
## Skills
|
|
134
|
+
|
|
135
|
+
Two ways, both real: the model loads skills through its Skill tool.
|
|
136
|
+
|
|
137
|
+
- **Project skills, zero flags.** Put `<name>/SKILL.md` under `<workspace>/.zcode/skills/`. The engine walks your workspace; no copy, no flag.
|
|
138
|
+
|
|
139
|
+
- **Injected per run.** `--skills <dir>` where dir is one skill (has `SKILL.md`) or a root of `<name>/SKILL.md` dirs. Copied into the isolated scratch for this run, removed after.
|
|
140
|
+
|
|
141
|
+
```sh
|
|
142
|
+
zcode --skills ./my-skills "Load the word-vault skill, then reply with the magic word it defines."
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`--no-skills` runs with an empty skill set. Your desktop user skills (`~/.zcode/skills`) are inherited into the isolation automatically.
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
## Machine interface
|
|
150
|
+
|
|
151
|
+
`--json` emits NDJSON events on stdout with a stable final line:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{"type":"result","ok":true,"response":"…","model":"GLM-5.3-Flash","mode":"yolo",
|
|
155
|
+
"effort":"max","sessionId":"sess_…","resumed":false,
|
|
156
|
+
"usage":{"inputTokens":19608,"outputTokens":3},
|
|
157
|
+
"reasoningChars":2531,"toolCalls":2,"toolEvents":9,
|
|
158
|
+
"mcpServers":["word-server"],"skillsInjected":["word-vault"],
|
|
159
|
+
"durationMs":21972}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`model` and `effort` are engine-reported values cross-checked from the engine's own `model_request_started` event, not the wrapper's echo. A failed run still prints a final line with `"ok":false` plus the error kind, so a caller always gets one parseable frame.
|
|
163
|
+
|
|
164
|
+
Event types on the way in:
|
|
165
|
+
|
|
166
|
+
| event | meaning |
|
|
167
|
+
|---|---|
|
|
168
|
+
| `{"type":"stream","kind":"text_delta","delta":"…"}` | answer text, live |
|
|
169
|
+
| `{"type":"stream","kind":"reasoning_delta","delta":"…"}` | thinking, live |
|
|
170
|
+
| `{"type":"tool","name":"Bash","phase":"started"}` | tool lifecycle: scheduled, started, result, error |
|
|
171
|
+
| `{"type":"mcp","name":"word-server","status":"connected","toolCount":1}` | your MCP servers settling |
|
|
172
|
+
| `{"type":"model_request_started",…}` | engine-reported model and reasoning level |
|
|
173
|
+
| `{"type":"state","mode":"yolo"}` | session mode |
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
## Options
|
|
178
|
+
|
|
179
|
+
| option | takes | default | notes |
|
|
180
|
+
|---|---|---|---|
|
|
181
|
+
| `--model` | id | `glm-5.3-flash` | `glm-5.3` plus aliases: `5.3`, `53`, `flash`, `5.3-flash`, `53f` |
|
|
182
|
+
| `--effort` | level | `max` | `low`, `medium`, `high`, `xhigh`, `max` |
|
|
183
|
+
| `--cwd` | dir | current dir | the session workspace, and the blast radius |
|
|
184
|
+
| `--session` | id | new session | continue a previous conversation |
|
|
185
|
+
| `--mcp` | JSON or `@file` | none | repeatable; stdio, http, sse |
|
|
186
|
+
| `--mcp-inherit` | flag | off | also load desktop-enabled MCP servers |
|
|
187
|
+
| `--mcp-list` | flag | | print effective servers as JSON, exit |
|
|
188
|
+
| `--skills` | dir | none | repeatable; one skill or a root of skills |
|
|
189
|
+
| `--no-skills` | flag | off | empty skill set for this run |
|
|
190
|
+
| `--profile` | name | `general-purpose` | subagent profile for `agent` |
|
|
191
|
+
| `--include-reasoning` | flag | off | full thinking text in the result line |
|
|
192
|
+
| `--quiet` | flag | off | silence the stderr live feed |
|
|
193
|
+
| `--json` | flag | off | NDJSON events plus result line on stdout |
|
|
194
|
+
| `--timeout-ms` | number | `600000` | |
|
|
195
|
+
| `--fresh` | flag | off | rebuild the isolated scratch HOME first |
|
|
196
|
+
| `-p`, `--prompt` | text | | alias for the positional prompt |
|
|
197
|
+
|
|
198
|
+
## Exit codes
|
|
199
|
+
|
|
200
|
+
| code | meaning |
|
|
201
|
+
|---|---|
|
|
202
|
+
| 0 | success |
|
|
203
|
+
| 1 | unexpected internal error |
|
|
204
|
+
| 2 | usage error, unknown model, unknown effort, bad `--mcp` JSON |
|
|
205
|
+
| 3 | no API key (`zcode login` or `ZAI_API_KEY`) |
|
|
206
|
+
| 4 | engine or builtin provider config not found |
|
|
207
|
+
| 5 | turn failed (engine error, failed terminal, user-input request) |
|
|
208
|
+
| 6 | `agent`: no subagent dispatched or none ended `success` |
|
|
209
|
+
| 124 | `--timeout-ms` exceeded |
|
|
210
|
+
| 130 | interrupted (SIGINT) |
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
## Under the hood
|
|
215
|
+
|
|
216
|
+
**Key handling.** `ZAI_API_KEY` env var, or `zcode login` which stores it at `~/.zcode/credentials.json` (mode 600). The key is never printed or logged. The only file it ever lands in is the scratch provider config, which the engine requires to register the provider.
|
|
217
|
+
|
|
218
|
+
**Isolation.** The engine runs with `HOME` pointed at a scratch copy this package owns (`~/.zcode/home`, mode 700, mtime-cached). Your live `~/.zcode`, which the desktop app is using right now, is never written to. Your OAuth `credentials.json` is deliberately never copied. The scratch keeps exactly the `zai-api` provider rule with your key, your agents, and your skills.
|
|
219
|
+
|
|
220
|
+
**Yolo mode.** Sessions run with auto-approved tool side effects, matching the engine's own headless default; `--cwd` is the declared blast radius. A headless run that requests interactive user input fails fast with exit 5 rather than hanging.
|
|
221
|
+
|
|
222
|
+
**Engine.** Resolved from the ZCode desktop app bundle (`zcode doctor` prints path, version, sha256). `ZAI_CODE_ENGINE` points elsewhere if you need to pin one. macOS today; nothing else in the wrapper is macOS-specific.
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
## Development
|
|
227
|
+
|
|
228
|
+
Zero dependencies, plain CommonJS, no build step.
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
npm test # functional only, no mocks; live tests hit the real GLM API
|
|
232
|
+
npm run accept:flash # one-shot through GLM-5.3-Flash
|
|
233
|
+
npm run accept:subagent
|
|
234
|
+
npm run accept:53
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Tests load the key from the repo `.env` (never committed, never shipped). The design record and pinned wire facts live in `DESIGN.md` and `docs/BUILD-REPORT.md`.
|
|
238
|
+
|
|
239
|
+
MIT license.
|
package/bin/zcode.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// zcode — CLI entry: argv parsing + command dispatch (DESIGN §10).
|
|
4
|
+
const { normalizeModel, unknownModelMessage, normalizeEffort, EFFORT_LEVELS, DEFAULT_MODEL } = require('../lib/models.js');
|
|
5
|
+
|
|
6
|
+
const EXIT = {
|
|
7
|
+
OK: 0,
|
|
8
|
+
INTERNAL: 1,
|
|
9
|
+
USAGE: 2,
|
|
10
|
+
NO_KEY: 3,
|
|
11
|
+
NO_ENGINE: 4,
|
|
12
|
+
TURN_FAILED: 5,
|
|
13
|
+
NO_SUBAGENT: 6,
|
|
14
|
+
TIMEOUT: 124,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const USAGE = `usage: zcode [options] [prompt…] one-shot turn (prompt via stdin when piped)
|
|
18
|
+
zcode agent [--profile <name>] <instructions…>
|
|
19
|
+
turn that MUST dispatch a subagent
|
|
20
|
+
zcode login read API key from stdin → credentials
|
|
21
|
+
zcode doctor [--json] [--fresh] engine + isolation + key report
|
|
22
|
+
|
|
23
|
+
options:
|
|
24
|
+
--model <id> glm-5.3 | glm-5.3-flash (+aliases). default: glm-5.3-flash
|
|
25
|
+
--effort <level> low | medium | high | xhigh | max. default: max
|
|
26
|
+
--cwd <dir> workspace for the session (default: process.cwd())
|
|
27
|
+
--profile <name> subagent profile (agent command; default: general-purpose)
|
|
28
|
+
--session <id> continue a previous session (sessionId from a prior result)
|
|
29
|
+
--mcp <json|@f> add MCP servers (repeatable). JSON maps names to defs:
|
|
30
|
+
{"name":{"type":"stdio","command","args","env"}} or
|
|
31
|
+
{"name":{"type":"http","url","headers"}}; @file reads a file.
|
|
32
|
+
Hermetic default: only these run (deterministic, fast)
|
|
33
|
+
--mcp-inherit also inherit the desktop's enabled MCP servers
|
|
34
|
+
--mcp-list print the effective MCP server set as JSON and exit
|
|
35
|
+
--skills <dir> add skills (repeatable): one skill dir (has SKILL.md) or a
|
|
36
|
+
root of <name>/SKILL.md dirs. project <cwd>/.zcode/skills
|
|
37
|
+
is picked up by the engine without any flag
|
|
38
|
+
--no-skills run with an empty skill set (nothing inherited, nothing injected)
|
|
39
|
+
--include-reasoning put the full reasoning text in the final JSON result
|
|
40
|
+
--quiet do not mirror reasoning to stderr while running
|
|
41
|
+
--json NDJSON events on stdout; final line {"type":"result",…}
|
|
42
|
+
(stream events include reasoning_delta = thinking)
|
|
43
|
+
--timeout-ms <n> default 600000
|
|
44
|
+
--fresh rebuild the scratch HOME first
|
|
45
|
+
-p, --prompt <t> prompt text (alias for positional prompt)
|
|
46
|
+
--version, --help`;
|
|
47
|
+
|
|
48
|
+
function fail(code, message) {
|
|
49
|
+
process.stderr.write(`zcode: ${message}\n`);
|
|
50
|
+
process.exit(code);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isDirectory(p) {
|
|
54
|
+
try {
|
|
55
|
+
return require('node:fs').statSync(p).isDirectory();
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// --mcp accepts inline JSON or @file; the JSON must map server names to
|
|
62
|
+
// definition objects ({type:"stdio"|"http"|"sse", ...} per the engine's
|
|
63
|
+
// config schema). Fails (exit 2) before any engine spawn.
|
|
64
|
+
function parseMcpSpec(value) {
|
|
65
|
+
let text = value;
|
|
66
|
+
if (value.startsWith('@')) {
|
|
67
|
+
try {
|
|
68
|
+
text = require('node:fs').readFileSync(value.slice(1), 'utf8');
|
|
69
|
+
} catch {
|
|
70
|
+
fail(EXIT.USAGE, `--mcp file "${value.slice(1)}" could not be read.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
let parsed;
|
|
74
|
+
try {
|
|
75
|
+
parsed = JSON.parse(text);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
fail(EXIT.USAGE, `--mcp spec must be valid JSON mapping server names to definitions: ${err.message}`);
|
|
78
|
+
}
|
|
79
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
80
|
+
fail(EXIT.USAGE, `--mcp spec must be valid JSON mapping server names to definitions, got ${Array.isArray(parsed) ? 'an array' : `a ${typeof parsed}`}.`);
|
|
81
|
+
}
|
|
82
|
+
for (const [name, def] of Object.entries(parsed)) {
|
|
83
|
+
if (!def || typeof def !== 'object' || Array.isArray(def)) {
|
|
84
|
+
fail(EXIT.USAGE, `--mcp server "${name}" must be an object (e.g. {"type":"stdio","command":...,"args":[...]}).`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return parsed;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Parses argv (without node/script) into {command, prompts, flags}.
|
|
91
|
+
// Exits 2 on usage errors. Rejection happens here, before any engine spawn.
|
|
92
|
+
function parseArgv(argv) {
|
|
93
|
+
const out = {
|
|
94
|
+
command: 'run',
|
|
95
|
+
prompts: [],
|
|
96
|
+
flags: {
|
|
97
|
+
model: DEFAULT_MODEL,
|
|
98
|
+
effort: 'max',
|
|
99
|
+
cwd: process.cwd(),
|
|
100
|
+
json: false,
|
|
101
|
+
fresh: false,
|
|
102
|
+
timeoutMs: 600000,
|
|
103
|
+
profile: 'general-purpose',
|
|
104
|
+
prompt: undefined,
|
|
105
|
+
session: undefined,
|
|
106
|
+
mcpSpecs: [],
|
|
107
|
+
mcpInherit: false,
|
|
108
|
+
mcpList: false,
|
|
109
|
+
skillsDirs: [],
|
|
110
|
+
noSkills: false,
|
|
111
|
+
includeReasoning: false,
|
|
112
|
+
quiet: false,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
const valueFlags = new Map([
|
|
116
|
+
['--model', 'model'],
|
|
117
|
+
['--effort', 'effort'],
|
|
118
|
+
['--cwd', 'cwd'],
|
|
119
|
+
['--profile', 'profile'],
|
|
120
|
+
['--timeout-ms', 'timeoutMs'],
|
|
121
|
+
['-p', 'prompt'],
|
|
122
|
+
['--prompt', 'prompt'],
|
|
123
|
+
['--session', 'session'],
|
|
124
|
+
]);
|
|
125
|
+
const listFlags = new Map([
|
|
126
|
+
['--mcp', 'mcpSpecs'],
|
|
127
|
+
['--skills', 'skillsDirs'],
|
|
128
|
+
]);
|
|
129
|
+
const boolFlags = new Map([
|
|
130
|
+
['--json', 'json'],
|
|
131
|
+
['--fresh', 'fresh'],
|
|
132
|
+
['--mcp-inherit', 'mcpInherit'],
|
|
133
|
+
['--mcp-list', 'mcpList'],
|
|
134
|
+
['--no-skills', 'noSkills'],
|
|
135
|
+
['--include-reasoning', 'includeReasoning'],
|
|
136
|
+
['--quiet', 'quiet'],
|
|
137
|
+
]);
|
|
138
|
+
const commands = new Set(['run', 'agent', 'login', 'doctor']);
|
|
139
|
+
|
|
140
|
+
for (let i = 0; i < argv.length; i++) {
|
|
141
|
+
const arg = argv[i];
|
|
142
|
+
if (valueFlags.has(arg) || boolFlags.has(arg) || listFlags.has(arg)) {
|
|
143
|
+
let key;
|
|
144
|
+
if (valueFlags.has(arg)) {
|
|
145
|
+
key = valueFlags.get(arg);
|
|
146
|
+
const value = argv[++i];
|
|
147
|
+
if (value === undefined) fail(EXIT.USAGE, `option ${arg} requires a value.\n${USAGE}`);
|
|
148
|
+
if (key === 'timeoutMs') {
|
|
149
|
+
const n = Number(value);
|
|
150
|
+
if (!Number.isFinite(n) || n <= 0) fail(EXIT.USAGE, `--timeout-ms must be a positive number, got "${value}".`);
|
|
151
|
+
out.flags[key] = n;
|
|
152
|
+
} else if (key === 'session') {
|
|
153
|
+
if (!value.trim()) fail(EXIT.USAGE, '--session requires a session id.');
|
|
154
|
+
out.flags[key] = value.trim();
|
|
155
|
+
} else {
|
|
156
|
+
out.flags[key] = value;
|
|
157
|
+
}
|
|
158
|
+
} else if (listFlags.has(arg)) {
|
|
159
|
+
key = listFlags.get(arg);
|
|
160
|
+
const value = argv[++i];
|
|
161
|
+
if (value === undefined) fail(EXIT.USAGE, `option ${arg} requires a value.\n${USAGE}`);
|
|
162
|
+
if (key === 'mcpSpecs') {
|
|
163
|
+
out.flags.mcpSpecs.push(parseMcpSpec(value, arg));
|
|
164
|
+
} else {
|
|
165
|
+
if (!isDirectory(value)) fail(EXIT.USAGE, `--skills requires an existing directory, got "${value}".`);
|
|
166
|
+
out.flags.skillsDirs.push(value);
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
key = boolFlags.get(arg);
|
|
170
|
+
out.flags[key] = true;
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (arg === '--help' || arg === '-h') {
|
|
175
|
+
process.stdout.write(USAGE + '\n');
|
|
176
|
+
process.exit(EXIT.OK);
|
|
177
|
+
}
|
|
178
|
+
if (arg === '--version') {
|
|
179
|
+
process.stdout.write(require('../package.json').version + '\n');
|
|
180
|
+
process.exit(EXIT.OK);
|
|
181
|
+
}
|
|
182
|
+
if (arg === '--') {
|
|
183
|
+
out.prompts.push(...argv.slice(i + 1));
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
if (arg.startsWith('--') && arg.length > 2) {
|
|
187
|
+
fail(EXIT.USAGE, `unknown option "${arg}".\n${USAGE}`);
|
|
188
|
+
}
|
|
189
|
+
if (out.prompts.length === 0 && commands.has(arg)) {
|
|
190
|
+
out.command = arg;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
out.prompts.push(arg);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (out.command === 'run' && out.flags.prompt !== undefined) {
|
|
197
|
+
out.prompts.unshift(out.flags.prompt);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const model = normalizeModel(out.flags.model);
|
|
201
|
+
if (!model) fail(EXIT.USAGE, unknownModelMessage(out.flags.model));
|
|
202
|
+
out.flags.model = model;
|
|
203
|
+
|
|
204
|
+
const effort = normalizeEffort(out.flags.effort);
|
|
205
|
+
if (!effort) {
|
|
206
|
+
fail(
|
|
207
|
+
EXIT.USAGE,
|
|
208
|
+
`unknown effort "${out.flags.effort}". --effort accepts ${EFFORT_LEVELS.join(' | ')}.`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
out.flags.effort = effort;
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function main() {
|
|
216
|
+
const parsed = parseArgv(process.argv.slice(2));
|
|
217
|
+
const { command, flags } = parsed;
|
|
218
|
+
if (flags.mcpList) {
|
|
219
|
+
return require('../lib/commands/mcp-list.js')(parsed);
|
|
220
|
+
}
|
|
221
|
+
const run = require('../lib/commands/run.js');
|
|
222
|
+
switch (command) {
|
|
223
|
+
case 'run':
|
|
224
|
+
return run(parsed);
|
|
225
|
+
case 'agent':
|
|
226
|
+
return require('../lib/commands/agent.js')(parsed);
|
|
227
|
+
case 'login':
|
|
228
|
+
return require('../lib/commands/login.js')(parsed);
|
|
229
|
+
case 'doctor':
|
|
230
|
+
return require('../lib/commands/doctor.js')(parsed);
|
|
231
|
+
default:
|
|
232
|
+
fail(EXIT.USAGE, `unknown command "${command}".\n${USAGE}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
main().catch((err) => {
|
|
237
|
+
process.stderr.write(`zcode: internal error: ${err && err.stack ? err.stack : err}\n`);
|
|
238
|
+
process.exit(EXIT.INTERNAL);
|
|
239
|
+
});
|