@0xgeorgemathew/zcode 0.4.1 → 0.4.4

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 CHANGED
@@ -30,13 +30,22 @@ The orchestrator keeps product decisions, design direction, and coordination. zc
30
30
  npm install -g @0xgeorgemathew/zcode
31
31
  echo "$ZAI_API_KEY" | zcode login # stores your Z.AI key with mode 600; zcode never prints it
32
32
  zcode doctor # checks the engine, isolation and key
33
+ zcode skills install # optional: teach Claude Code and Codex to delegate to zcode
33
34
 
34
35
  zcode task --check "npm test" "Fix the off-by-one in src/paginate.ts"
35
36
  ```
36
37
 
37
38
  The CLI needs node 20 or later. The engine child runs best on node 26. zcode also needs the ZCode engine, which today ships inside the ZCode desktop app; zcode finds it automatically.
38
39
 
39
- Installing the package also installs the Claude Code skill to `~/.claude/skills/zcode` and the Codex skill to `${CODEX_HOME:-~/.codex}/skills/zcode`. These skills teach the orchestrator how to delegate to zcode. Set `ZCODE_SKIP_SKILLS=1` to skip this step. Details are in [`skills/`](skills/README.md).
40
+ The package ships skills that teach Claude Code and Codex how to delegate to zcode. Install them with one command:
41
+
42
+ ```sh
43
+ zcode skills install # both; or: zcode skills install claude | codex
44
+ zcode skills # show what is installed and where
45
+ zcode skills uninstall # remove them
46
+ ```
47
+
48
+ The Claude Code skill goes to `~/.claude/skills/zcode` and the Codex skill to `${CODEX_HOME:-~/.codex}/skills/zcode`. The package runs no install scripts. Details are in [`skills/`](skills/README.md).
40
49
 
41
50
   
42
51
 
@@ -226,7 +235,8 @@ Inside `zcode task`, subagents are optional. The executor decides when to use th
226
235
  | `--timeout-ms` | all turns; wait | run 600000, task 1800000 | the whole invocation incl. checks and repairs |
227
236
  | `--session` | run | | 0.3 compat: continue by zcode id or engine `sess_` id |
228
237
  | `--profile` | agent | `general-purpose` | subagent profile |
229
- | `--mcp` / `--mcp-inherit` / `--mcp-list` | run, task, agent | hermetic | MCP servers (inline JSON or `@file`) |
238
+ | `--mcp` / `--mcp-inherit` / `--mcp-list` | run, task, agent | hermetic, plus the desktop's `playwright` | MCP servers (inline JSON or `@file`) |
239
+ | `--no-browser` | run, task, agent | off | Drop the desktop's `playwright` MCP server |
230
240
  | `--skills` / `--no-skills` | run, task, agent | desktop skills | add skills; or run with none |
231
241
  | `--json`, `--quiet` | all | | see Machine interface |
232
242
  | `--thinking` / `--no-thinking` | turns | run on, task off | mirror reasoning to stderr |
package/bin/zcode.js CHANGED
@@ -14,6 +14,8 @@ const USAGE = `usage:
14
14
  zcode cancel <id> [--json] stop a running session
15
15
  zcode login read API key from stdin → credentials
16
16
  zcode doctor [--json] engine + isolation + key report
17
+ zcode skills [install|uninstall] [claude|codex]
18
+ install the orchestrator skills (default: status)
17
19
 
18
20
  session:
19
21
  --id <id> name the session (letters, digits, . _ -); default zc-<random>.
@@ -43,6 +45,7 @@ execution:
43
45
  --profile <name> subagent profile (agent command; default general-purpose)
44
46
  --mcp <json|@f> add MCP servers (repeatable); hermetic by default
45
47
  --mcp-inherit also inherit the desktop's enabled MCP servers
48
+ --no-browser drop the desktop's playwright MCP server (on by default)
46
49
  --mcp-list print the effective MCP server set as JSON and exit
47
50
  --skills <dir> add skills (repeatable): one skill dir or a root of them
48
51
  --no-skills empty skill set
@@ -94,9 +97,9 @@ function parseMcpSpec(value) {
94
97
  return parsed;
95
98
  }
96
99
 
97
- const COMMANDS = new Set(['run', 'task', 'agent', 'resume', 'status', 'list', 'wait', 'cancel', 'login', 'doctor', '__exec']);
100
+ const COMMANDS = new Set(['run', 'task', 'agent', 'resume', 'status', 'list', 'wait', 'cancel', 'login', 'doctor', 'skills', '__exec']);
98
101
  const TASK_ONLY = new Set(['task', 'accept', 'check', 'file', 'context', 'maxRepairs', 'checkTimeoutMs']);
99
- const START_ONLY = new Set(['id', 'label', 'owns', 'allowOverlap', 'mcpSpecs', 'mcpInherit', 'skillsDirs', 'noSkills', 'profile', 'cwd']);
102
+ const START_ONLY = new Set(['id', 'label', 'owns', 'allowOverlap', 'mcpSpecs', 'mcpInherit', 'noBrowser', 'skillsDirs', 'noSkills', 'profile', 'cwd']);
100
103
 
101
104
  function parseArgv(argv) {
102
105
  const out = {
@@ -127,6 +130,7 @@ function parseArgv(argv) {
127
130
  checkTimeoutMs: undefined,
128
131
  mcpSpecs: [],
129
132
  mcpInherit: false,
133
+ noBrowser: false,
130
134
  mcpList: false,
131
135
  skillsDirs: [],
132
136
  noSkills: false,
@@ -148,7 +152,7 @@ function parseArgv(argv) {
148
152
  ]);
149
153
  const boolFlags = new Map([
150
154
  ['--json', ['json', true]], ['--fresh', ['fresh', true]], ['--mcp-inherit', ['mcpInherit', true]],
151
- ['--mcp-list', ['mcpList', true]], ['--no-skills', ['noSkills', true]],
155
+ ['--mcp-list', ['mcpList', true]], ['--no-skills', ['noSkills', true]], ['--no-browser', ['noBrowser', true]],
152
156
  ['--include-reasoning', ['includeReasoning', true]], ['--quiet', ['quiet', true]],
153
157
  ['--detach', ['detach', true]], ['--allow-overlap', ['allowOverlap', true]],
154
158
  ['--thinking', ['thinking', true]], ['--no-thinking', ['thinking', false]], ['--stream', ['stream', true]],
@@ -265,6 +269,8 @@ async function main() {
265
269
  return require('../lib/commands/login.js')(parsed);
266
270
  case 'doctor':
267
271
  return require('../lib/commands/doctor.js')(parsed);
272
+ case 'skills':
273
+ return require('../lib/commands/skills.js')(parsed);
268
274
  default:
269
275
  return fail(2, `unknown command "${parsed.command}".\n${USAGE}`);
270
276
  }
package/lib/appserver.js CHANGED
@@ -150,6 +150,17 @@ class AppServerClient {
150
150
  if (this.onUserInputRequest) this.onUserInputRequest(frame);
151
151
  return;
152
152
  }
153
+ if (method === 'interaction/browserList') {
154
+ // The desktop app answers with its built-in browser panes; the CLI has
155
+ // none. An empty list lets the agent fall back to the user's own tools
156
+ // (Playwright, MCP browsers) instead of failing on a policy error.
157
+ reply({ browsers: [] });
158
+ return;
159
+ }
160
+ if (method === 'interaction/browserExecute') {
161
+ replyError(-32000, 'zcode: no built-in browser in the CLI; use Playwright or a browser MCP server');
162
+ return;
163
+ }
153
164
  // Everything else — incl. interaction/requestOfficialMcpAuthHeaders,
154
165
  // proven non-fatal with -32601 — gets method-not-found and the run
155
166
  // continues.
@@ -220,8 +231,12 @@ function workspaceKeyFor(workspacePath) {
220
231
  // turn always sees the caller's --mcp servers. Inherited servers are
221
232
  // best-effort and never block the send.
222
233
  async function waitForMcp(client, { workspacePath, required, timeoutMs = 20000, onEvent = () => {} }) {
223
- if (!required || required.length === 0) return;
224
234
  const ws = { workspace: { workspacePath, workspaceKey: workspaceKeyFor(workspacePath) } };
235
+ if (!required || required.length === 0) {
236
+ // Still start inherited servers (the browser server); never block on them.
237
+ client.request('mcp/list', { ...ws, mode: 'connect' }, { timeoutMs: 15000 }).catch(() => {});
238
+ return;
239
+ }
225
240
  try {
226
241
  await client.request('mcp/list', { ...ws, mode: 'connect' }, { timeoutMs: 15000 });
227
242
  } catch {
@@ -77,6 +77,7 @@ function buildInvocation(parsed) {
77
77
  acceptanceDeclared: false,
78
78
  mcpSpecs: flags.mcpSpecs,
79
79
  mcpInherit: flags.mcpInherit,
80
+ noBrowser: flags.noBrowser,
80
81
  skillsDirs: flags.skillsDirs.map((d) => path.resolve(d)),
81
82
  noSkills: flags.noSkills,
82
83
  profile: flags.profile,
@@ -6,7 +6,7 @@ const { effectiveMcpServers } = require('../isolation.js');
6
6
 
7
7
  module.exports = async function mcpList(parsed) {
8
8
  const { flags } = parsed;
9
- const servers = effectiveMcpServers({ inherit: flags.mcpInherit, mcpSpecs: flags.mcpSpecs });
9
+ const servers = effectiveMcpServers({ inherit: flags.mcpInherit, browser: !flags.noBrowser, mcpSpecs: flags.mcpSpecs });
10
10
  process.stdout.write(JSON.stringify({ type: 'mcp', servers }) + '\n');
11
11
  process.exit(0);
12
12
  };
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+ // zcode skills [status|install|uninstall] [claude|codex]
3
+ // Copies the orchestrator skills shipped in this package into Claude Code
4
+ // and Codex. Nothing runs at npm install time; this command is the installer.
5
+ const { installSkills, uninstallSkills, skillStatus, PLATFORMS } = require('../skills.js');
6
+
7
+ const ACTIONS = new Set(['status', 'install', 'uninstall']);
8
+
9
+ module.exports = function skills({ prompts, flags }) {
10
+ const [action = 'status', ...platforms] = prompts;
11
+ const fail = (msg) => {
12
+ process.stderr.write(`zcode: ${msg}\nusage: zcode skills [status|install|uninstall] [claude|codex]\n`);
13
+ process.exit(2);
14
+ };
15
+ if (!ACTIONS.has(action)) fail(`unknown skills action "${action}".`);
16
+ for (const p of platforms) if (!PLATFORMS.includes(p)) fail(`unknown platform "${p}" (claude | codex).`);
17
+
18
+ const view = (t) => ({ platform: t.platform, name: t.name, path: t.to, ...(t.installed !== undefined && { installed: t.installed }) });
19
+ let rows;
20
+ if (action === 'install') rows = installSkills({ platforms });
21
+ else if (action === 'uninstall') rows = uninstallSkills({ platforms });
22
+ else rows = skillStatus().filter((t) => !platforms.length || platforms.includes(t.platform));
23
+
24
+ if (flags.json) {
25
+ process.stdout.write(JSON.stringify({ action, skills: rows.map(view) }, null, 2) + '\n');
26
+ return;
27
+ }
28
+ if (rows.length === 0) {
29
+ process.stdout.write('zcode: nothing to uninstall\n');
30
+ return;
31
+ }
32
+ for (const t of rows) {
33
+ const what = action === 'install' ? 'installed' : action === 'uninstall' ? 'removed' : t.installed ? 'installed' : 'not installed';
34
+ process.stdout.write(`${t.name.padEnd(12)} ${what.padEnd(14)} ${t.to}\n`);
35
+ }
36
+ if (action === 'status' && rows.some((t) => !t.installed)) {
37
+ process.stdout.write('\nrun `zcode skills install` to install them\n');
38
+ }
39
+ };
package/lib/executor.js CHANGED
@@ -52,7 +52,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
52
52
  // Settings a session keeps across invocations (stored as invocation.json).
53
53
  const SETTING_KEYS = [
54
54
  'kind', 'label', 'cwd', 'model', 'effort', 'timeoutMs', 'maxRepairs', 'checkTimeoutMs',
55
- 'checks', 'owns', 'ownsDisplay', 'acceptanceDeclared', 'mcpSpecs', 'mcpInherit',
55
+ 'checks', 'owns', 'ownsDisplay', 'acceptanceDeclared', 'mcpSpecs', 'mcpInherit', 'noBrowser',
56
56
  'skillsDirs', 'noSkills', 'profile', 'includeReasoning', 'allowOverlap', 'stream',
57
57
  ];
58
58
 
@@ -338,6 +338,7 @@ async function execute(inv, opts = {}) {
338
338
  live: liveDir(env),
339
339
  mcpSpecs: inv.mcpSpecs || [],
340
340
  mcpInherit: inv.mcpInherit,
341
+ browser: !inv.noBrowser,
341
342
  skillsDirs: inv.skillsDirs || [],
342
343
  noSkills: inv.noSkills,
343
344
  });
package/lib/isolation.js CHANGED
@@ -142,8 +142,18 @@ function liveMcpServers(live = LIVE_ZCODE) {
142
142
  // base (specs still win by name). Hermetic default is deliberate: the
143
143
  // desktop set includes OAuth servers that can never complete headless and
144
144
  // stall the engine's connect pool.
145
- function effectiveMcpServers({ inherit = false, mcpSpecs = [], live = LIVE_ZCODE } = {}) {
146
- const servers = inherit ? liveMcpServers(live) : {};
145
+ // Exception: the desktop's local browser server (stdio, no OAuth) is always
146
+ // inherited, so browser prompts open the same Playwright window the desktop
147
+ // opens. --no-browser drops it.
148
+ const BROWSER_SERVERS = ['playwright'];
149
+
150
+ function effectiveMcpServers({ inherit = false, browser = true, mcpSpecs = [], live = LIVE_ZCODE } = {}) {
151
+ const liveSet = liveMcpServers(live);
152
+ const servers = inherit ? liveSet : {};
153
+ for (const name of BROWSER_SERVERS) {
154
+ if (!browser) delete servers[name];
155
+ else if (liveSet[name]) servers[name] = liveSet[name];
156
+ }
147
157
  for (const spec of mcpSpecs) {
148
158
  for (const [name, def] of Object.entries(spec)) servers[name] = def;
149
159
  }
@@ -200,6 +210,11 @@ function isDanglingLink(p) {
200
210
  // sources + this invocation's flags; engine state under the HOME (cli/db,
201
211
  // cli/agents, cli/rollout, logs, memories) is never touched, so a session
202
212
  // stays resumable across invocations. Returns facts for reporting.
213
+ // Desktop skills that drive the app's own UI (the built-in browser pane).
214
+ // The CLI cannot serve them, and loading one steers the agent away from
215
+ // Playwright into a dead end.
216
+ const HOST_ONLY_SKILLS = new Set(['control-browser']);
217
+
203
218
  function buildEngineHome({
204
219
  home,
205
220
  root,
@@ -207,6 +222,7 @@ function buildEngineHome({
207
222
  live = LIVE_ZCODE,
208
223
  mcpSpecs = [],
209
224
  mcpInherit = false,
225
+ browser = true,
210
226
  skillsDirs = [],
211
227
  noSkills = false,
212
228
  }) {
@@ -237,7 +253,7 @@ function buildEngineHome({
237
253
  zai.options = zai.options || {};
238
254
  zai.options.apiKey = key;
239
255
  }
240
- const servers = effectiveMcpServers({ inherit: mcpInherit, mcpSpecs, live });
256
+ const servers = effectiveMcpServers({ inherit: mcpInherit, browser, mcpSpecs, live });
241
257
  cfg.mcp = { ...(cfg.mcp || {}), servers };
242
258
  writeJson600(path.join(cliDir, 'config.json'), cfg);
243
259
 
@@ -268,6 +284,7 @@ function buildEngineHome({
268
284
  for (const entry of listDir(liveSkills)) {
269
285
  if (!entry.isDirectory()) continue;
270
286
  if (!fs.existsSync(path.join(liveSkills, entry.name, 'SKILL.md'))) continue;
287
+ if (HOST_ONLY_SKILLS.has(entry.name)) continue;
271
288
  fs.cpSync(path.join(liveSkills, entry.name), path.join(skillsDir, entry.name), {
272
289
  recursive: true,
273
290
  });
@@ -345,6 +362,18 @@ function migrateRoot(env = process.env) {
345
362
  // config, and the uncontrolled overrides deleted. cwd is set by the caller.
346
363
  function engineEnv({ home, builtinProviderConfig, parentEnv = process.env }) {
347
364
  const childEnv = { ...parentEnv };
365
+ const realHome = parentEnv.HOME || os.homedir();
366
+ // The scratch HOME hides the user's tool caches. Point Playwright and npm
367
+ // back at the real ones so installed browsers and packages are found.
368
+ if (!childEnv.PLAYWRIGHT_BROWSERS_PATH) {
369
+ const cache = process.platform === 'darwin'
370
+ ? path.join(realHome, 'Library', 'Caches', 'ms-playwright')
371
+ : path.join(realHome, '.cache', 'ms-playwright');
372
+ if (fs.existsSync(cache)) childEnv.PLAYWRIGHT_BROWSERS_PATH = cache;
373
+ }
374
+ if (!childEnv.npm_config_cache && !childEnv.NPM_CONFIG_CACHE) {
375
+ childEnv.npm_config_cache = path.join(realHome, '.npm');
376
+ }
348
377
  childEnv.HOME = home;
349
378
  childEnv.ZCODE_BUILTIN_PROVIDER_CONFIG_FILE = builtinProviderConfig;
350
379
  for (const name of [
package/lib/skills.js ADDED
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+ // The orchestrator skills shipped in the package, and where `zcode skills`
3
+ // installs them:
4
+ //
5
+ // skills/claude-code/zcode -> ~/.claude/skills/zcode
6
+ // skills/codex/zcode -> ${CODEX_HOME:-~/.codex}/skills/zcode
7
+ //
8
+ // Only the zcode skill directory is ever replaced or removed.
9
+ const fs = require('node:fs');
10
+ const os = require('node:os');
11
+ const path = require('node:path');
12
+
13
+ const pkgRoot = path.join(__dirname, '..');
14
+ const PLATFORMS = ['claude', 'codex'];
15
+
16
+ function targets(env = process.env) {
17
+ const home = env.HOME || os.homedir();
18
+ return [
19
+ {
20
+ platform: 'claude',
21
+ name: 'Claude Code',
22
+ from: path.join(pkgRoot, 'skills', 'claude-code', 'zcode'),
23
+ to: path.join(home, '.claude', 'skills', 'zcode'),
24
+ },
25
+ {
26
+ platform: 'codex',
27
+ name: 'Codex',
28
+ from: path.join(pkgRoot, 'skills', 'codex', 'zcode'),
29
+ to: path.join(env.CODEX_HOME || path.join(home, '.codex'), 'skills', 'zcode'),
30
+ },
31
+ ];
32
+ }
33
+
34
+ function select(platforms, env) {
35
+ const want = platforms && platforms.length ? new Set(platforms) : new Set(PLATFORMS);
36
+ return targets(env).filter((t) => want.has(t.platform));
37
+ }
38
+
39
+ function installSkills({ platforms, env = process.env } = {}) {
40
+ const done = [];
41
+ for (const t of select(platforms, env)) {
42
+ fs.mkdirSync(path.dirname(t.to), { recursive: true });
43
+ fs.rmSync(t.to, { recursive: true, force: true });
44
+ fs.cpSync(t.from, t.to, { recursive: true });
45
+ done.push(t);
46
+ }
47
+ return done;
48
+ }
49
+
50
+ function uninstallSkills({ platforms, env = process.env } = {}) {
51
+ const done = [];
52
+ for (const t of select(platforms, env)) {
53
+ if (!fs.existsSync(t.to)) continue;
54
+ fs.rmSync(t.to, { recursive: true, force: true });
55
+ done.push(t);
56
+ }
57
+ return done;
58
+ }
59
+
60
+ function skillStatus({ env = process.env } = {}) {
61
+ return targets(env).map((t) => ({ ...t, installed: fs.existsSync(path.join(t.to, 'SKILL.md')) }));
62
+ }
63
+
64
+ module.exports = { installSkills, uninstallSkills, skillStatus, targets, PLATFORMS };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xgeorgemathew/zcode",
3
- "version": "0.4.1",
3
+ "version": "0.4.4",
4
4
  "description": "Lean GLM-5.3 execution engine for orchestrator agents: task contracts, independent sessions, zcode-run verification, structured handbacks, subagents, MCP, skills. Wraps the ZCode engine. Zero dependencies.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -21,7 +21,6 @@
21
21
  "LICENSE"
22
22
  ],
23
23
  "scripts": {
24
- "postinstall": "node lib/install-skills.js",
25
24
  "test": "node --test --test-concurrency=1 \"test/unit/*.test.js\"",
26
25
  "test:live": "node --test --test-concurrency=1 \"test/live/*.test.js\"",
27
26
  "prepublishOnly": "npm test && npm run test:live",
package/skills/README.md CHANGED
@@ -9,9 +9,15 @@ These skills teach an orchestrating agent how to delegate work to zcode, run ses
9
9
 
10
10
  ## Install
11
11
 
12
- `npm install -g @0xgeorgemathew/zcode` installs both skills for you. Its postinstall step copies the Claude Code skill to `~/.claude/skills/zcode` and the Codex skill to `${CODEX_HOME:-~/.codex}/skills/zcode`, replacing any earlier copy of the zcode skill only. Set `ZCODE_SKIP_SKILLS=1` to skip it. If npm runs with `--ignore-scripts`, nothing is copied; use the manual steps below.
12
+ After `npm install -g @0xgeorgemathew/zcode`, install both skills with:
13
13
 
14
- Run these from a checkout of this repo, or from the installed package (`$(npm root -g)/@0xgeorgemathew/zcode`), which ships `skills/`.
14
+ ```sh
15
+ zcode skills install # or: zcode skills install claude | codex
16
+ ```
17
+
18
+ This copies the Claude Code skill to `~/.claude/skills/zcode` and the Codex skill to `${CODEX_HOME:-~/.codex}/skills/zcode`, replacing only an earlier copy of the zcode skill. Run it again after upgrading zcode to refresh them. `zcode skills` shows what is installed, and `zcode skills uninstall` removes them. The package runs no install scripts, so npm never asks to allow one.
19
+
20
+ To copy them by hand instead, run these from a checkout of this repo, or from the installed package (`$(npm root -g)/@0xgeorgemathew/zcode`), which ships `skills/`.
15
21
 
16
22
  **Claude Code.** Personal skills, available in every project:
17
23
 
@@ -1,54 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
- // postinstall: copy the orchestrator skills into Claude Code and Codex.
4
- //
5
- // skills/claude-code/zcode -> ~/.claude/skills/zcode
6
- // skills/codex/zcode -> ${CODEX_HOME:-~/.codex}/skills/zcode
7
- //
8
- // Only the zcode skill directory is replaced; nothing else is touched.
9
- // Skipped inside the source checkout (a .git next to package.json) and when
10
- // ZCODE_SKIP_SKILLS=1. Never fails the install: errors become one warning.
11
- const fs = require('node:fs');
12
- const os = require('node:os');
13
- const path = require('node:path');
14
-
15
- const pkgRoot = path.join(__dirname, '..');
16
-
17
- function targets(env = process.env) {
18
- const home = os.homedir();
19
- return [
20
- {
21
- name: 'Claude Code',
22
- from: path.join(pkgRoot, 'skills', 'claude-code', 'zcode'),
23
- to: path.join(home, '.claude', 'skills', 'zcode'),
24
- },
25
- {
26
- name: 'Codex',
27
- from: path.join(pkgRoot, 'skills', 'codex', 'zcode'),
28
- to: path.join(env.CODEX_HOME || path.join(home, '.codex'), 'skills', 'zcode'),
29
- },
30
- ];
31
- }
32
-
33
- function installSkills(env = process.env) {
34
- const done = [];
35
- for (const t of targets(env)) {
36
- if (!fs.existsSync(t.from)) continue;
37
- fs.mkdirSync(path.dirname(t.to), { recursive: true });
38
- fs.rmSync(t.to, { recursive: true, force: true });
39
- fs.cpSync(t.from, t.to, { recursive: true });
40
- done.push(t);
41
- }
42
- return done;
43
- }
44
-
45
- if (require.main === module) {
46
- if (process.env.ZCODE_SKIP_SKILLS === '1' || fs.existsSync(path.join(pkgRoot, '.git'))) process.exit(0);
47
- try {
48
- for (const t of installSkills()) console.log(`zcode: installed ${t.name} skill -> ${t.to}`);
49
- } catch (err) {
50
- console.warn(`zcode: could not install skills (${err.message}). Copy them from ${path.join(pkgRoot, 'skills')}.`);
51
- }
52
- }
53
-
54
- module.exports = { installSkills, targets };