@zeroxyz/cli 1.7.0 → 1.8.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.
Files changed (3) hide show
  1. package/README.md +24 -2
  2. package/dist/index.js +623 -165
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,17 +4,26 @@ The `zero` CLI lets AI agents discover, call, and review capabilities indexed by
4
4
 
5
5
  ## Install
6
6
 
7
- The recommended way to install and initialize Zero skill and hooks is via Agent Plugins — see the Zero agent plugin at [github.com/officialzeroxyz/zero-plugins](https://github.com/officialzeroxyz/zero-plugins). The npm package installs just the CLI:
7
+ The npm package is the bootstrap and manual CLI. Agent sessions should run through
8
+ the Zero plugin's managed runner, which auto-updates at `~/.zero/runtime/bin/zero`.
8
9
 
9
10
  ```bash
10
11
  npm install -g @zeroxyz/cli
12
+ zero init
13
+ zero auth login
11
14
  ```
12
15
 
16
+ `zero init` installs the Zero plugin through supported harness CLIs when they are
17
+ callable from the current shell (`codex`, `claude`, `gemini`, `openclaw`). If no
18
+ plugin install succeeds, it falls back to the standalone skill/hook install under
19
+ `~/.claude` and `~/.agents`, with hooks registered in `~/.claude/settings.json`
20
+ and `~/.codex/hooks.json`.
21
+
13
22
  ## Breaking changes
14
23
 
15
24
  ### 1.0.0
16
25
 
17
- - **`zero init` no longer does anything.** It used to generate a local wallet and copy skills/hooks into your agent tools; it now just prints a pointer to the [Zero agent plugin](https://github.com/officialzeroxyz/zero-plugins). The bundled skill is no longer shipped in this package — fetch it from [zero.xyz/SKILL.md](https://zero.xyz/SKILL.md) for a manual install. `zero uninstall` still removes anything a pre-1.0 `zero init` left behind.
26
+ - **`zero init` no longer creates a local wallet.** It installs the Zero plugin when possible and otherwise writes the standalone skill/hooks. `zero uninstall` removes the standalone files; host plugin installs are managed by their harnesses.
18
27
  - **`zero welcome` was removed.** The welcome bonus is claimed by signing in (`zero auth login`).
19
28
  - **The legacy `privateKey` field in `~/.zero/config.json` is no longer an identity and never pays.** Wallets created by a pre-1.0 `zero init` stop signing requests and payments. Pick one of:
20
29
  - **Recommended:** sign in and sweep the old wallet's funds into your managed Zero wallet:
@@ -53,6 +62,19 @@ zero review <runId> --success --accuracy 5 --value 4 --reliability 5
53
62
 
54
63
  ## Commands
55
64
 
65
+ ### `zero init`
66
+
67
+ Bootstrap Zero for agent harnesses.
68
+
69
+ ```bash
70
+ zero init # target every supported agent
71
+ zero init --agent codex # only Codex
72
+ zero init --agent claude,codex # comma-separated or repeat --agent
73
+ zero init --agent standalone # only standalone skills + Claude/Codex hooks
74
+ zero init --no-fallback # fail instead of writing standalone fallback
75
+ zero init --dry-run # preview actions
76
+ ```
77
+
56
78
  ### `zero auth`
57
79
 
58
80
  Sign in and out of Zero. Signing in provisions a managed Zero wallet that signs your payments.
package/dist/index.js CHANGED
@@ -52631,12 +52631,12 @@ var require_array = __commonJS({
52631
52631
  if (Array.isArray(values)) {
52632
52632
  arrayValues = values;
52633
52633
  } else if (values && typeof values === "object") {
52634
- let unique = {};
52634
+ let unique2 = {};
52635
52635
  arrayValues = coders.map((coder) => {
52636
52636
  const name = coder.localName;
52637
52637
  (0, index_js_1.assert)(name, "cannot encode object for signature with missing names", "INVALID_ARGUMENT", { argument: "values", info: { coder }, value: values });
52638
- (0, index_js_1.assert)(!unique[name], "cannot encode object for signature with duplicate names", "INVALID_ARGUMENT", { argument: "values", info: { coder }, value: values });
52639
- unique[name] = true;
52638
+ (0, index_js_1.assert)(!unique2[name], "cannot encode object for signature with duplicate names", "INVALID_ARGUMENT", { argument: "values", info: { coder }, value: values });
52639
+ unique2[name] = true;
52640
52640
  return values[name];
52641
52641
  });
52642
52642
  } else {
@@ -55096,10 +55096,10 @@ var require_dist = __commonJS({
55096
55096
  }
55097
55097
  }
55098
55098
  check_fenced(norm);
55099
- let unique = Array_from(new Set(chars));
55100
- let [g2] = determine_group(unique);
55099
+ let unique2 = Array_from(new Set(chars));
55100
+ let [g2] = determine_group(unique2);
55101
55101
  check_group(g2, chars);
55102
- check_whole(g2, unique);
55102
+ check_whole(g2, unique2);
55103
55103
  type2 = g2.N;
55104
55104
  }
55105
55105
  }
@@ -55110,10 +55110,10 @@ var require_dist = __commonJS({
55110
55110
  return info;
55111
55111
  });
55112
55112
  }
55113
- function check_whole(group, unique) {
55113
+ function check_whole(group, unique2) {
55114
55114
  let maker;
55115
55115
  let shared = [];
55116
- for (let cp of unique) {
55116
+ for (let cp of unique2) {
55117
55117
  let whole = WHOLE_MAP.get(cp);
55118
55118
  if (whole === UNIQUE_PH) return;
55119
55119
  if (whole) {
@@ -55132,9 +55132,9 @@ var require_dist = __commonJS({
55132
55132
  }
55133
55133
  }
55134
55134
  }
55135
- function determine_group(unique) {
55135
+ function determine_group(unique2) {
55136
55136
  let groups = GROUPS;
55137
- for (let cp of unique) {
55137
+ for (let cp of unique2) {
55138
55138
  let gs = groups.filter((g2) => group_has_cp(g2, cp));
55139
55139
  if (!gs.length) {
55140
55140
  if (!GROUPS.some((g2) => group_has_cp(g2, cp))) {
@@ -76582,7 +76582,7 @@ init_esm_shims();
76582
76582
 
76583
76583
  // package.json
76584
76584
  var package_default = {
76585
- version: "1.7.0"};
76585
+ version: "1.8.0"};
76586
76586
 
76587
76587
  // src/app.ts
76588
76588
  init_esm_shims();
@@ -77590,7 +77590,7 @@ init_esm_shims();
77590
77590
  // ../sdk/dist/index.js
77591
77591
  init_esm_shims();
77592
77592
 
77593
- // ../sdk/dist/chunk-3YKWO4B2.js
77593
+ // ../sdk/dist/chunk-W7JBWVWW.js
77594
77594
  init_esm_shims();
77595
77595
 
77596
77596
  // ../../node_modules/.pnpm/viem@2.52.2_bufferutil@4.1.0_typescript@5.9.3_utf-8-validate@6.0.6_zod@4.3.6/node_modules/viem/_esm/index.js
@@ -145833,6 +145833,9 @@ var searchResultSchema = external_exports3.object({
145833
145833
  cost: external_exports3.object({ amount: external_exports3.string(), asset: external_exports3.string() }),
145834
145834
  reviewCount: external_exports3.number().optional(),
145835
145835
  rating: ratingSchema,
145836
+ // All-time activation count (non-deleted runs), mirroring capabilities.ts.
145837
+ // Optional so a response from an older API deploy still parses (ZERO-385).
145838
+ activationCount: external_exports3.number().optional(),
145836
145839
  availabilityStatus: external_exports3.enum(["healthy", "unknown", "down"]).nullable().optional(),
145837
145840
  /**
145838
145841
  * @deprecated The API no longer emits `displayStatus`; it has been replaced
@@ -147591,9 +147594,251 @@ var sectionDivider = () => {
147591
147594
  console.log("");
147592
147595
  };
147593
147596
 
147597
+ // src/util/json-config-file.ts
147598
+ init_esm_shims();
147599
+ var updateJsonConfigFile = (path3, update2, options = {}) => {
147600
+ let config2 = {};
147601
+ if (existsSync(path3)) {
147602
+ try {
147603
+ config2 = JSON.parse(readFileSync(path3, "utf-8"));
147604
+ } catch {
147605
+ return { status: "unparseable", path: path3 };
147606
+ }
147607
+ } else if (!options.createIfMissing) {
147608
+ return { status: "missing", path: path3 };
147609
+ }
147610
+ const changed = update2(config2);
147611
+ if (!changed) return { status: "unchanged", path: path3 };
147612
+ mkdirSync(dirname(path3), { recursive: true });
147613
+ writeFileSync(path3, `${JSON.stringify(config2, null, 2)}
147614
+ `);
147615
+ return { status: "updated", path: path3 };
147616
+ };
147617
+
147594
147618
  // src/commands/init-command.ts
147595
147619
  var SKILL_DIRS = [".claude/skills", ".agents/skills"];
147596
147620
  var ZERO_SANDBOX_DOMAIN = "*.zero.xyz";
147621
+ var CLAUDE_SETTINGS_PATH = ".claude/settings.json";
147622
+ var CODEX_HOOKS_PATH = ".codex/hooks.json";
147623
+ var PLUGIN_AGENTS = ["codex", "claude", "gemini", "openclaw"];
147624
+ var INIT_AGENTS = [...PLUGIN_AGENTS, "standalone"];
147625
+ var initAgentSchema = zod_default.enum(INIT_AGENTS);
147626
+ var HARNESS_SPECS = {
147627
+ claude: {
147628
+ agent: "claude",
147629
+ displayName: "Claude Code",
147630
+ executable: "claude",
147631
+ installCommands: [
147632
+ {
147633
+ label: "add marketplace",
147634
+ command: "claude",
147635
+ args: ["plugin", "marketplace", "add", "officialzeroxyz/zero-plugins"],
147636
+ required: false
147637
+ },
147638
+ {
147639
+ label: "install plugin",
147640
+ command: "claude",
147641
+ args: ["plugin", "install", "zero@zero-plugins"]
147642
+ }
147643
+ ],
147644
+ manualInstructions: [
147645
+ "Claude Code Desktop: open the Code tab, choose Customize, then add a Personal plugin marketplace from officialzeroxyz/zero-plugins.",
147646
+ "After it syncs, click the + icon on the Zero plugin, then start a new session and ask Claude to help set up and test Zero."
147647
+ ]
147648
+ },
147649
+ codex: {
147650
+ agent: "codex",
147651
+ displayName: "Codex",
147652
+ executable: "codex",
147653
+ installCommands: [
147654
+ {
147655
+ label: "add marketplace",
147656
+ command: "codex",
147657
+ args: ["plugin", "marketplace", "add", "officialzeroxyz/zero-plugins"],
147658
+ required: false
147659
+ },
147660
+ {
147661
+ label: "install plugin",
147662
+ command: "codex",
147663
+ args: ["plugin", "add", "zero@zero-plugins"]
147664
+ }
147665
+ ],
147666
+ manualInstructions: [
147667
+ "Codex app: open Plugins in the sidebar, click +, then add marketplace officialzeroxyz/zero-plugins.",
147668
+ "Select the Zero filter, click Add plugin on Zero, then start a new chat and ask Codex to help set up and test Zero."
147669
+ ]
147670
+ },
147671
+ gemini: {
147672
+ agent: "gemini",
147673
+ displayName: "Gemini CLI",
147674
+ executable: "gemini",
147675
+ installCommands: [
147676
+ {
147677
+ label: "install extension",
147678
+ command: "gemini",
147679
+ args: [
147680
+ "extensions",
147681
+ "install",
147682
+ "https://github.com/officialzeroxyz/zero-plugins",
147683
+ "--auto-update"
147684
+ ]
147685
+ }
147686
+ ],
147687
+ manualInstructions: [
147688
+ "Gemini CLI: run this from a shell where `gemini` is on PATH: gemini extensions install https://github.com/officialzeroxyz/zero-plugins --auto-update"
147689
+ ]
147690
+ },
147691
+ openclaw: {
147692
+ agent: "openclaw",
147693
+ displayName: "OpenClaw",
147694
+ executable: "openclaw",
147695
+ installCommands: [
147696
+ {
147697
+ label: "install plugin",
147698
+ command: "openclaw",
147699
+ args: [
147700
+ "plugins",
147701
+ "install",
147702
+ "zero",
147703
+ "--marketplace",
147704
+ "officialzeroxyz/zero-plugins"
147705
+ ]
147706
+ }
147707
+ ],
147708
+ manualInstructions: [
147709
+ "OpenClaw: run this from a shell where `openclaw` is on PATH: openclaw plugins install zero --marketplace officialzeroxyz/zero-plugins"
147710
+ ]
147711
+ }
147712
+ };
147713
+ var unique = (items) => [...new Set(items)];
147714
+ var parseAgentValues = (values) => {
147715
+ if (!values || values.length === 0) {
147716
+ return {
147717
+ kind: "default",
147718
+ requestedAgents: [...PLUGIN_AGENTS],
147719
+ pluginAgents: [...PLUGIN_AGENTS]
147720
+ };
147721
+ }
147722
+ const parsed = unique(
147723
+ values.flatMap(
147724
+ (value) => value.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean)
147725
+ )
147726
+ );
147727
+ if (parsed.length === 0) {
147728
+ throw new Error("Pass at least one agent name to --agent");
147729
+ }
147730
+ const agents = parsed.map((value) => {
147731
+ const result = initAgentSchema.safeParse(value);
147732
+ if (!result.success) {
147733
+ throw new Error(
147734
+ `Unknown agent "${value}". Expected one of: ${INIT_AGENTS.join(", ")}`
147735
+ );
147736
+ }
147737
+ return result.data;
147738
+ });
147739
+ if (agents.includes("standalone") && agents.length > 1) {
147740
+ throw new Error(
147741
+ "`standalone` cannot be combined with other --agent values; run it separately."
147742
+ );
147743
+ }
147744
+ if (agents[0] === "standalone") {
147745
+ return { kind: "standalone", requestedAgents: agents, pluginAgents: [] };
147746
+ }
147747
+ return {
147748
+ kind: "specific",
147749
+ requestedAgents: agents,
147750
+ pluginAgents: agents
147751
+ };
147752
+ };
147753
+ var collectAgentOption = (value, previous = []) => [
147754
+ ...previous,
147755
+ value
147756
+ ];
147757
+ var commandSummary = (command, args) => [command, ...args].join(" ");
147758
+ var commandOutput = (result) => {
147759
+ const chunks = [result.stderr, result.stdout].map((chunk) => typeof chunk === "string" ? chunk.trim() : "").filter(Boolean);
147760
+ return chunks.join("\n").trim();
147761
+ };
147762
+ var commandErrorDetail = (result, fallback2) => {
147763
+ if (result.error) return result.error.message;
147764
+ const output = commandOutput(result);
147765
+ if (output.length > 0) return truncateError(output);
147766
+ if (result.signal) return `${fallback2} exited via signal ${result.signal}`;
147767
+ return `${fallback2} exited with status ${result.status ?? "unknown"}`;
147768
+ };
147769
+ var isCommandCallable = (command) => {
147770
+ if (process.platform === "win32") {
147771
+ const result2 = spawnSync("where.exe", [command], {
147772
+ encoding: "utf8",
147773
+ stdio: "ignore"
147774
+ });
147775
+ return result2.status === 0;
147776
+ }
147777
+ const result = spawnSync("/bin/sh", ["-c", `command -v ${command}`], {
147778
+ encoding: "utf8",
147779
+ stdio: "ignore"
147780
+ });
147781
+ return result.status === 0;
147782
+ };
147783
+ var installHarnessPlugin = (spec, options) => {
147784
+ if (options.dryRun) {
147785
+ for (const step of spec.installCommands) {
147786
+ stepInfo(`would run: ${commandSummary(step.command, step.args)}`);
147787
+ }
147788
+ return {
147789
+ agent: spec.agent,
147790
+ displayName: spec.displayName,
147791
+ status: "would_install",
147792
+ detail: "dry run"
147793
+ };
147794
+ }
147795
+ let optionalFailure;
147796
+ for (const step of spec.installCommands) {
147797
+ if (options.verbose) {
147798
+ stepInfo(`running: ${commandSummary(step.command, step.args)}`);
147799
+ }
147800
+ const result = spawnSync(step.command, step.args, {
147801
+ encoding: "utf8",
147802
+ stdio: "pipe",
147803
+ env: process.env
147804
+ });
147805
+ if (result.status === 0) continue;
147806
+ const detail = commandErrorDetail(result, step.label);
147807
+ if (step.required === false) {
147808
+ optionalFailure = `${step.label}: ${detail}`;
147809
+ if (options.verbose) stepInfo(`${optionalFailure}; continuing`);
147810
+ continue;
147811
+ }
147812
+ return {
147813
+ agent: spec.agent,
147814
+ displayName: spec.displayName,
147815
+ status: "failed",
147816
+ detail
147817
+ };
147818
+ }
147819
+ return {
147820
+ agent: spec.agent,
147821
+ displayName: spec.displayName,
147822
+ status: "installed",
147823
+ detail: optionalFailure ? `installed; ignored ${optionalFailure}` : void 0
147824
+ };
147825
+ };
147826
+ var printManualInstructions = (spec) => {
147827
+ for (const line of spec.manualInstructions) {
147828
+ console.log(` ${color.gray("\xB7")} ${color.dim(line)}`);
147829
+ }
147830
+ };
147831
+ var fallbackReason = (selection, detectedAgents, results) => {
147832
+ if (selection.kind === "standalone")
147833
+ return "--agent standalone was requested";
147834
+ if (selection.kind === "default" && detectedAgents.length === 0) {
147835
+ return "no supported harness CLIs were callable in this shell";
147836
+ }
147837
+ if (results.every((result) => result.status === "skipped_unavailable")) {
147838
+ return "no requested harness CLIs were callable in this shell";
147839
+ }
147840
+ return "no plugin install succeeded";
147841
+ };
147597
147842
  var PLUGIN_HOOKS_ROOT = "${CLAUDE_PLUGIN_ROOT}/hooks";
147598
147843
  var SCRIPT_NAME_PATTERN = /\$\{CLAUDE_PLUGIN_ROOT\}\/hooks\/([a-z0-9][a-z0-9-]*\.sh)/;
147599
147844
  var fetchText = async (url4) => {
@@ -147658,59 +147903,7 @@ var installHookScripts = async (home, hooksJson, webBase, verbose) => {
147658
147903
  })
147659
147904
  );
147660
147905
  };
147661
- var registerHooksInClaudeSettings = (home, hooksJson, verbose) => {
147662
- const hooksDir = join(home, ".zero", "hooks");
147663
- const settingsPath = join(home, ".claude", "settings.json");
147664
- let settings = {};
147665
- if (existsSync(settingsPath)) {
147666
- try {
147667
- settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
147668
- } catch {
147669
- stepWarn(
147670
- "Hooks not registered",
147671
- `${settingsPath} is not valid JSON \u2014 fix it and re-run \`zero init\``
147672
- );
147673
- return false;
147674
- }
147675
- }
147676
- if (!settings.hooks || typeof settings.hooks !== "object") {
147677
- settings.hooks = {};
147678
- }
147679
- const hooks = settings.hooks;
147680
- for (const [event, entries] of Object.entries(hooksJson.hooks)) {
147681
- for (const entry of entries ?? []) {
147682
- const scripts = entryScriptNames(entry);
147683
- if (scripts.length === 0) {
147684
- if (verbose) {
147685
- stepInfo(`${event}: entry has no plugin hook script \u2014 skipped`);
147686
- }
147687
- continue;
147688
- }
147689
- const rewritten = {
147690
- ...entry,
147691
- hooks: (entry.hooks ?? []).map((hook) => ({
147692
- ...hook,
147693
- command: typeof hook.command === "string" ? hook.command.replaceAll(PLUGIN_HOOKS_ROOT, hooksDir) : hook.command
147694
- }))
147695
- };
147696
- if (!Array.isArray(hooks[event])) hooks[event] = [];
147697
- const list2 = hooks[event];
147698
- const existingIdx = list2.findIndex((existing) => {
147699
- const existingHooks = existing?.hooks;
147700
- if (!Array.isArray(existingHooks)) return false;
147701
- return existingHooks.some(
147702
- (h2) => typeof h2.command === "string" && scripts.some((s) => h2.command.includes(s))
147703
- );
147704
- });
147705
- if (existingIdx >= 0) {
147706
- list2[existingIdx] = rewritten;
147707
- if (verbose) stepInfo(`${event}: ${scripts.join(", ")} entry replaced`);
147708
- } else {
147709
- list2.push(rewritten);
147710
- if (verbose) stepInfo(`${event}: ${scripts.join(", ")} entry appended`);
147711
- }
147712
- }
147713
- }
147906
+ var ensureZeroSandboxAllowlist = (settings, verbose) => {
147714
147907
  if (!settings.sandbox || typeof settings.sandbox !== "object") {
147715
147908
  settings.sandbox = {};
147716
147909
  }
@@ -147729,86 +147922,326 @@ var registerHooksInClaudeSettings = (home, hooksJson, verbose) => {
147729
147922
  stepInfo(`sandbox.network.allowedDomains += ${ZERO_SANDBOX_DOMAIN}`);
147730
147923
  }
147731
147924
  }
147732
- mkdirSync(join(home, ".claude"), { recursive: true });
147733
- writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
147734
- `);
147925
+ };
147926
+ var registerHooksInJsonFile = (home, hooksJson, target, verbose) => {
147927
+ const hooksDir = join(home, ".zero", "hooks");
147928
+ const configPath = join(home, target.relativePath);
147929
+ const result = updateJsonConfigFile(
147930
+ configPath,
147931
+ (config2) => {
147932
+ if (!config2.hooks || typeof config2.hooks !== "object") {
147933
+ config2.hooks = {};
147934
+ }
147935
+ const hooks = config2.hooks;
147936
+ for (const [event, entries] of Object.entries(hooksJson.hooks)) {
147937
+ for (const entry of entries ?? []) {
147938
+ const scripts = entryScriptNames(entry);
147939
+ if (scripts.length === 0) {
147940
+ if (verbose) {
147941
+ stepInfo(`${event}: entry has no plugin hook script \u2014 skipped`);
147942
+ }
147943
+ continue;
147944
+ }
147945
+ const rewritten = {
147946
+ ...entry,
147947
+ hooks: (entry.hooks ?? []).map((hook) => ({
147948
+ ...hook,
147949
+ command: typeof hook.command === "string" ? hook.command.replaceAll(PLUGIN_HOOKS_ROOT, hooksDir) : hook.command
147950
+ }))
147951
+ };
147952
+ if (!Array.isArray(hooks[event])) hooks[event] = [];
147953
+ const list2 = hooks[event];
147954
+ const existingIdx = list2.findIndex((existing) => {
147955
+ const existingHooks = existing?.hooks;
147956
+ if (!Array.isArray(existingHooks)) return false;
147957
+ return existingHooks.some(
147958
+ (h2) => typeof h2.command === "string" && scripts.some((s) => h2.command.includes(s))
147959
+ );
147960
+ });
147961
+ if (existingIdx >= 0) {
147962
+ list2[existingIdx] = rewritten;
147963
+ if (verbose) {
147964
+ stepInfo(`${event}: ${scripts.join(", ")} entry replaced`);
147965
+ }
147966
+ } else {
147967
+ list2.push(rewritten);
147968
+ if (verbose) {
147969
+ stepInfo(`${event}: ${scripts.join(", ")} entry appended`);
147970
+ }
147971
+ }
147972
+ }
147973
+ }
147974
+ if (target.includeSandboxAllowlist) {
147975
+ ensureZeroSandboxAllowlist(config2, verbose);
147976
+ }
147977
+ return true;
147978
+ },
147979
+ { createIfMissing: true }
147980
+ );
147981
+ if (result.status === "unparseable") {
147982
+ stepWarn(
147983
+ "Hooks not registered",
147984
+ `${configPath} is not valid JSON \u2014 fix it and re-run \`zero init\``
147985
+ );
147986
+ return false;
147987
+ }
147735
147988
  return true;
147736
147989
  };
147990
+ var runStandaloneInit = async (appContext, options = {}) => {
147991
+ const verbose = options.verbose ?? false;
147992
+ options.onStep?.("fetch");
147993
+ console.log("");
147994
+ console.log(` ${color.boldCyan("Installing standalone Zero fallback")}`);
147995
+ console.log("");
147996
+ console.log(color.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
147997
+ console.log("");
147998
+ const home = homedir();
147999
+ const webBase = appContext.env.ZERO_WEB_URL;
148000
+ const [skillMd, hooksJsonRaw] = await Promise.all([
148001
+ fetchText(`${webBase}/SKILL.md`),
148002
+ fetchText(`${webBase}/hooks.json`)
148003
+ ]);
148004
+ let hooksJson;
148005
+ try {
148006
+ hooksJson = JSON.parse(hooksJsonRaw);
148007
+ } catch {
148008
+ throw new Error(`${webBase}/hooks.json is not valid JSON`);
148009
+ }
148010
+ if (!hooksJson?.hooks || typeof hooksJson.hooks !== "object") {
148011
+ throw new Error(`${webBase}/hooks.json has no "hooks" object`);
148012
+ }
148013
+ if (verbose) {
148014
+ stepInfo(`fetched ${webBase}/SKILL.md and ${webBase}/hooks.json`);
148015
+ }
148016
+ options.onStep?.("skill");
148017
+ const skillsDirs = unique([
148018
+ ...SKILL_DIRS.map((dir) => join(home, dir)),
148019
+ ...(options.skillsDir ?? []).map((dir) => expandHome(dir, home))
148020
+ ]);
148021
+ const skillsInstalled = installSkill(skillsDirs, skillMd, verbose);
148022
+ if (skillsInstalled.length === 0) {
148023
+ throw new Error("could not write the Zero skill to any skills directory");
148024
+ }
148025
+ stepSuccess("Skill installed", `${skillsInstalled.length} locations`);
148026
+ options.onStep?.("hooks");
148027
+ const hookScriptsInstalled = await installHookScripts(
148028
+ home,
148029
+ hooksJson,
148030
+ webBase,
148031
+ verbose
148032
+ );
148033
+ stepSuccess("Hook scripts installed", "~/.zero/hooks");
148034
+ options.onStep?.("settings");
148035
+ const settingsRegistered = registerHooksInJsonFile(
148036
+ home,
148037
+ hooksJson,
148038
+ {
148039
+ relativePath: CLAUDE_SETTINGS_PATH,
148040
+ includeSandboxAllowlist: true
148041
+ },
148042
+ verbose
148043
+ );
148044
+ if (settingsRegistered) {
148045
+ stepSuccess("Hooks registered", "~/.claude/settings.json");
148046
+ }
148047
+ const codexHooksRegistered = registerHooksInJsonFile(
148048
+ home,
148049
+ hooksJson,
148050
+ {
148051
+ relativePath: CODEX_HOOKS_PATH,
148052
+ includeSandboxAllowlist: false
148053
+ },
148054
+ verbose
148055
+ );
148056
+ if (codexHooksRegistered) {
148057
+ stepSuccess("Codex hooks registered", "~/.codex/hooks.json");
148058
+ }
148059
+ return {
148060
+ skillsInstalled,
148061
+ hookScriptsInstalled,
148062
+ settingsRegistered,
148063
+ codexHooksRegistered
148064
+ };
148065
+ };
148066
+ var statusLabel = (result) => {
148067
+ switch (result.status) {
148068
+ case "installed":
148069
+ return "plugin installed";
148070
+ case "skipped_unavailable":
148071
+ return "skipped unavailable";
148072
+ case "failed":
148073
+ return "failed";
148074
+ case "would_install":
148075
+ return "would install";
148076
+ }
148077
+ };
148078
+ var printRunnerModelNote = () => {
148079
+ console.log(
148080
+ ` ${color.dim("Agent sessions use the auto-updated managed runner at ~/.zero/runtime/bin/zero.")}`
148081
+ );
148082
+ console.log(
148083
+ ` ${color.dim("The global npm `zero` is the bootstrap and manual CLI; it does not replace the managed runner.")}`
148084
+ );
148085
+ };
147737
148086
  var runInit = async (appContext, options = {}) => {
147738
148087
  const verbose = options.verbose ?? false;
148088
+ const dryRun = options.dryRun ?? false;
148089
+ const fallbackAllowed = options.fallback ?? true;
148090
+ const selection = parseAgentValues(options.agent);
148091
+ const requestedAgents = selection.requestedAgents;
148092
+ let currentStep = "plugins";
147739
148093
  appContext.services.analyticsService.capture("init_started", {});
147740
- let currentStep = "fetch";
147741
148094
  try {
147742
148095
  console.log("");
147743
- console.log(` ${color.boldCyan("Installing Zero")}`);
148096
+ console.log(` ${color.boldCyan("Initializing Zero")}`);
147744
148097
  console.log("");
147745
148098
  console.log(color.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
147746
148099
  console.log("");
147747
- const home = homedir();
147748
- const webBase = appContext.env.ZERO_WEB_URL;
147749
- const [skillMd, hooksJsonRaw] = await Promise.all([
147750
- fetchText(`${webBase}/SKILL.md`),
147751
- fetchText(`${webBase}/hooks.json`)
147752
- ]);
147753
- let hooksJson;
147754
- try {
147755
- hooksJson = JSON.parse(hooksJsonRaw);
147756
- } catch {
147757
- throw new Error(`${webBase}/hooks.json is not valid JSON`);
147758
- }
147759
- if (!hooksJson?.hooks || typeof hooksJson.hooks !== "object") {
147760
- throw new Error(`${webBase}/hooks.json has no "hooks" object`);
147761
- }
147762
- if (verbose) {
147763
- stepInfo(`fetched ${webBase}/SKILL.md and ${webBase}/hooks.json`);
148100
+ console.log(` Requested agents: ${requestedAgents.join(", ")}`);
148101
+ if (dryRun) stepInfo("dry run: no plugin commands or file writes will run");
148102
+ const detectedAgents = selection.kind === "standalone" ? [] : selection.pluginAgents.filter(
148103
+ (agent) => isCommandCallable(HARNESS_SPECS[agent].executable)
148104
+ );
148105
+ if (selection.kind === "default") {
148106
+ console.log(
148107
+ ` Detected callable harnesses: ${detectedAgents.length > 0 ? detectedAgents.join(", ") : "none"}`
148108
+ );
147764
148109
  }
147765
- currentStep = "skill";
147766
- const skillsDirs = [
147767
- .../* @__PURE__ */ new Set([
147768
- ...SKILL_DIRS.map((dir) => join(home, dir)),
147769
- ...(options.skillsDir ?? []).map((dir) => expandHome(dir, home))
147770
- ])
147771
- ];
147772
- const skillsInstalled = installSkill(skillsDirs, skillMd, verbose);
147773
- if (skillsInstalled.length === 0) {
147774
- throw new Error("could not write the Zero skill to any skills directory");
148110
+ console.log("");
148111
+ const pluginResults = [];
148112
+ if (selection.kind !== "standalone") {
148113
+ for (const agent of selection.pluginAgents) {
148114
+ const spec = HARNESS_SPECS[agent];
148115
+ if (!detectedAgents.includes(agent)) {
148116
+ const result2 = {
148117
+ agent,
148118
+ displayName: spec.displayName,
148119
+ status: "skipped_unavailable",
148120
+ detail: `\`${spec.executable}\` is not callable in this shell`
148121
+ };
148122
+ pluginResults.push(result2);
148123
+ stepSkip(
148124
+ `${spec.displayName}: ${statusLabel(result2)}`,
148125
+ result2.detail
148126
+ );
148127
+ if (selection.kind === "specific") {
148128
+ printManualInstructions(spec);
148129
+ }
148130
+ continue;
148131
+ }
148132
+ const result = installHarnessPlugin(spec, { dryRun, verbose });
148133
+ pluginResults.push(result);
148134
+ if (result.status === "installed") {
148135
+ stepSuccess(
148136
+ `${spec.displayName}: ${statusLabel(result)}`,
148137
+ result.detail
148138
+ );
148139
+ } else if (result.status === "would_install") {
148140
+ stepSkip(
148141
+ `${spec.displayName}: ${statusLabel(result)}`,
148142
+ result.detail
148143
+ );
148144
+ } else {
148145
+ stepWarn(
148146
+ `${spec.displayName}: ${statusLabel(result)}`,
148147
+ result.detail
148148
+ );
148149
+ }
148150
+ }
147775
148151
  }
147776
- stepSuccess("Skill installed", `${skillsInstalled.length} locations`);
147777
- currentStep = "hooks";
147778
- const hookScriptsInstalled = await installHookScripts(
147779
- home,
147780
- hooksJson,
147781
- webBase,
147782
- verbose
148152
+ const pluginSucceeded = pluginResults.some(
148153
+ (result) => result.status === "installed" || result.status === "would_install"
147783
148154
  );
147784
- stepSuccess("Hook scripts installed", "~/.zero/hooks");
147785
- currentStep = "settings";
147786
- const settingsRegistered = registerHooksInClaudeSettings(
147787
- home,
147788
- hooksJson,
147789
- verbose
147790
- );
147791
- if (settingsRegistered) {
147792
- stepSuccess("Hooks registered", "~/.claude/settings.json");
148155
+ let standaloneFallbackReason;
148156
+ let standaloneFallbackUsed = false;
148157
+ let standaloneResult = {
148158
+ skillsInstalled: [],
148159
+ hookScriptsInstalled: [],
148160
+ settingsRegistered: false,
148161
+ codexHooksRegistered: false
148162
+ };
148163
+ if (selection.kind === "standalone" || !pluginSucceeded) {
148164
+ standaloneFallbackReason = fallbackReason(
148165
+ selection,
148166
+ detectedAgents,
148167
+ pluginResults
148168
+ );
148169
+ if (!fallbackAllowed && selection.kind !== "standalone") {
148170
+ throw new Error(
148171
+ `No Zero plugin install succeeded and --no-fallback was set (${standaloneFallbackReason}).`
148172
+ );
148173
+ }
148174
+ standaloneFallbackUsed = !dryRun;
148175
+ sectionDivider();
148176
+ const fallbackVerb = dryRun ? "would be used" : "used";
148177
+ console.log(
148178
+ ` Standalone fallback ${fallbackVerb}: ${standaloneFallbackReason}.`
148179
+ );
148180
+ if (dryRun) {
148181
+ stepInfo("would install/update ~/.claude/skills/zero");
148182
+ stepInfo("would install/update ~/.agents/skills/zero");
148183
+ stepInfo(
148184
+ "would register current standalone hooks in ~/.claude/settings.json and ~/.codex/hooks.json"
148185
+ );
148186
+ } else {
148187
+ standaloneResult = await runStandaloneInit(appContext, {
148188
+ verbose,
148189
+ skillsDir: options.skillsDir,
148190
+ onStep: (step) => {
148191
+ currentStep = step;
148192
+ }
148193
+ });
148194
+ }
148195
+ } else {
148196
+ sectionDivider();
148197
+ console.log(
148198
+ " Standalone fallback not used: at least one harness plugin install succeeded."
148199
+ );
147793
148200
  }
147794
148201
  currentStep = "complete";
147795
148202
  appContext.services.analyticsService.capture("init_completed", {
147796
148203
  // biome-ignore lint/style/useNamingConvention: snake_case for analytics
147797
- skills_installed: skillsInstalled,
148204
+ requested_agents: requestedAgents,
147798
148205
  // biome-ignore lint/style/useNamingConvention: snake_case for analytics
147799
- skills_installed_count: skillsInstalled.length,
148206
+ detected_harnesses: detectedAgents,
147800
148207
  // biome-ignore lint/style/useNamingConvention: snake_case for analytics
147801
- hook_scripts_installed: hookScriptsInstalled,
148208
+ plugin_installs: pluginResults.map((result) => ({
148209
+ agent: result.agent,
148210
+ status: result.status
148211
+ })),
148212
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
148213
+ standalone_fallback_used: standaloneFallbackUsed,
148214
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
148215
+ standalone_fallback_reason: standaloneFallbackReason,
148216
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
148217
+ dry_run: dryRun,
148218
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
148219
+ skills_installed: standaloneResult.skillsInstalled,
148220
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
148221
+ skills_installed_count: standaloneResult.skillsInstalled.length,
148222
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
148223
+ hook_scripts_installed: standaloneResult.hookScriptsInstalled,
148224
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
148225
+ settings_registered: standaloneResult.settingsRegistered,
147802
148226
  // biome-ignore lint/style/useNamingConvention: snake_case for analytics
147803
- settings_registered: settingsRegistered
148227
+ codex_hooks_registered: standaloneResult.codexHooksRegistered
147804
148228
  });
147805
148229
  sectionDivider();
147806
- console.log(` ${color.boldGreen("Zero is set up.")}`);
148230
+ console.log(` ${color.boldGreen("Zero init complete.")}`);
147807
148231
  console.log(
147808
148232
  ` ${color.dim("Next: sign in with `zero auth login` \u2014 it prints a URL to approve in your browser.")}`
147809
148233
  );
148234
+ printRunnerModelNote();
147810
148235
  console.log("");
147811
- return { skillsInstalled, hookScriptsInstalled, settingsRegistered };
148236
+ return {
148237
+ ...standaloneResult,
148238
+ requestedAgents,
148239
+ detectedAgents,
148240
+ pluginResults,
148241
+ standaloneFallbackUsed,
148242
+ standaloneFallbackReason,
148243
+ dryRun
148244
+ };
147812
148245
  } catch (err) {
147813
148246
  appContext.services.analyticsService.capture("init_failed", {
147814
148247
  step: currentStep,
@@ -147818,7 +148251,18 @@ var runInit = async (appContext, options = {}) => {
147818
148251
  }
147819
148252
  };
147820
148253
  var initCommand = (appContext) => new Command("init").description(
147821
- "Install the Zero skill and hooks for the agent harnesses on this machine"
148254
+ "Install the Zero plugin for supported agents, with a standalone skill/hook fallback"
148255
+ ).option(
148256
+ "--agent <agent>",
148257
+ "Agent target: codex, claude, gemini, openclaw, or standalone. Repeat or comma-separate values. Omit to target every supported agent.",
148258
+ collectAgentOption,
148259
+ []
148260
+ ).option(
148261
+ "--no-fallback",
148262
+ "Do not install standalone skills/hooks if plugin install does not succeed"
148263
+ ).option(
148264
+ "--dry-run",
148265
+ "Show what would be installed without running commands or writing files"
147822
148266
  ).option(
147823
148267
  "-v, --verbose",
147824
148268
  "Explain why each install step was taken or skipped"
@@ -148166,11 +148610,12 @@ var formatDetail = (r2) => {
148166
148610
  }
148167
148611
  return lines.join("\n");
148168
148612
  };
148169
- var runsDetailCommand = (appContext) => new Command("detail").description("Show full metadata for a single run").argument("<runId>", "Run ID (run_\u2026)").option("--json", "Output raw JSON to stdout").action(async (runId, options) => {
148613
+ var runsDetailCommand = (appContext) => new Command("detail").description("Show full metadata for a single run").argument("<runId>", "Run ID (run_\u2026)").option("--json", "Output raw JSON to stdout").action(async (runId, _opts, cmd) => {
148614
+ const { json: json3 } = cmd.opts();
148170
148615
  try {
148171
148616
  const { client } = appContext.services;
148172
148617
  const run = await client.runs.get(runId);
148173
- if (options.json) {
148618
+ if (json3) {
148174
148619
  console.log(JSON.stringify(run, null, 2));
148175
148620
  return;
148176
148621
  }
@@ -148434,6 +148879,8 @@ var HOOK_FILES = [
148434
148879
  "ensure-runner.sh"
148435
148880
  ];
148436
148881
  var ZERO_SANDBOX_DOMAIN2 = "*.zero.xyz";
148882
+ var CLAUDE_SETTINGS_PATH2 = ".claude/settings.json";
148883
+ var CODEX_HOOKS_PATH2 = ".codex/hooks.json";
148437
148884
  var LEGACY_SKILL_NAMES = ["zero"];
148438
148885
  var removeSkills = (home, verbose = false) => {
148439
148886
  const skillNames = LEGACY_SKILL_NAMES;
@@ -148459,30 +148906,17 @@ var removeSkills = (home, verbose = false) => {
148459
148906
  return removed;
148460
148907
  };
148461
148908
  var removeHooks = (home, verbose = false) => {
148462
- let settingsChanged = false;
148463
- const settingsPath = join(home, ".claude", "settings.json");
148464
- if (!existsSync(settingsPath)) {
148465
- if (verbose) stepInfo(`${settingsPath} not found \u2014 no settings to clean`);
148466
- } else {
148467
- let settings = null;
148468
- try {
148469
- settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
148470
- } catch {
148471
- if (verbose) {
148472
- stepInfo(`${settingsPath} was unparseable JSON \u2014 leaving it untouched`);
148473
- }
148474
- }
148475
- if (settings) {
148476
- settingsChanged = stripZeroFromSettings(settings, verbose);
148477
- if (settingsChanged) {
148478
- writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
148479
- `);
148480
- if (verbose) stepInfo(`rewrote ${settingsPath} without Zero entries`);
148481
- } else if (verbose) {
148482
- stepInfo(`no Zero entries found in ${settingsPath}`);
148483
- }
148484
- }
148485
- }
148909
+ const claudeSettingsChanged = stripZeroFromJsonFile(
148910
+ join(home, CLAUDE_SETTINGS_PATH2),
148911
+ verbose,
148912
+ true
148913
+ );
148914
+ const codexHooksChanged = stripZeroFromJsonFile(
148915
+ join(home, CODEX_HOOKS_PATH2),
148916
+ verbose,
148917
+ false
148918
+ );
148919
+ const settingsChanged = claudeSettingsChanged || codexHooksChanged;
148486
148920
  let scriptsRemoved = 0;
148487
148921
  const zeroHooksDir = join(home, ".zero", "hooks");
148488
148922
  if (existsSync(zeroHooksDir)) {
@@ -148502,7 +148936,29 @@ var removeHooks = (home, verbose = false) => {
148502
148936
  }
148503
148937
  return { settingsChanged, scriptsRemoved };
148504
148938
  };
148505
- var stripZeroFromSettings = (settings, verbose) => {
148939
+ var stripZeroFromJsonFile = (settingsPath, verbose, removeSandboxAllowlist) => {
148940
+ const result = updateJsonConfigFile(
148941
+ settingsPath,
148942
+ (settings) => stripZeroFromSettings(settings, verbose, removeSandboxAllowlist)
148943
+ );
148944
+ if (result.status === "missing") {
148945
+ if (verbose) stepInfo(`${settingsPath} not found \u2014 no settings to clean`);
148946
+ return false;
148947
+ }
148948
+ if (result.status === "unparseable") {
148949
+ if (verbose) {
148950
+ stepInfo(`${settingsPath} was unparseable JSON \u2014 leaving it untouched`);
148951
+ }
148952
+ return false;
148953
+ }
148954
+ if (result.status === "updated") {
148955
+ if (verbose) stepInfo(`rewrote ${settingsPath} without Zero entries`);
148956
+ return true;
148957
+ }
148958
+ if (verbose) stepInfo(`no Zero entries found in ${settingsPath}`);
148959
+ return false;
148960
+ };
148961
+ var stripZeroFromSettings = (settings, verbose, removeSandboxAllowlist) => {
148506
148962
  let changed = false;
148507
148963
  const hooks = settings.hooks && typeof settings.hooks === "object" ? settings.hooks : null;
148508
148964
  if (hooks) {
@@ -148515,24 +148971,26 @@ var stripZeroFromSettings = (settings, verbose) => {
148515
148971
  }
148516
148972
  if (Object.keys(hooks).length === 0) delete settings.hooks;
148517
148973
  }
148518
- const sandbox = settings.sandbox && typeof settings.sandbox === "object" ? settings.sandbox : null;
148519
- const network = sandbox?.network && typeof sandbox.network === "object" ? sandbox.network : null;
148520
- if (network && Array.isArray(network.allowedDomains)) {
148521
- const domains = network.allowedDomains;
148522
- const next = domains.filter((d3) => d3 !== ZERO_SANDBOX_DOMAIN2);
148523
- if (next.length !== domains.length) {
148524
- changed = true;
148525
- if (verbose) {
148526
- stepInfo(`sandbox.network.allowedDomains -= ${ZERO_SANDBOX_DOMAIN2}`);
148527
- }
148528
- if (next.length === 0) {
148529
- delete network.allowedDomains;
148530
- if (sandbox && Object.keys(network).length === 0)
148531
- delete sandbox.network;
148532
- if (sandbox && Object.keys(sandbox).length === 0)
148533
- delete settings.sandbox;
148534
- } else {
148535
- network.allowedDomains = next;
148974
+ if (removeSandboxAllowlist) {
148975
+ const sandbox = settings.sandbox && typeof settings.sandbox === "object" ? settings.sandbox : null;
148976
+ const network = sandbox?.network && typeof sandbox.network === "object" ? sandbox.network : null;
148977
+ if (network && Array.isArray(network.allowedDomains)) {
148978
+ const domains = network.allowedDomains;
148979
+ const next = domains.filter((d3) => d3 !== ZERO_SANDBOX_DOMAIN2);
148980
+ if (next.length !== domains.length) {
148981
+ changed = true;
148982
+ if (verbose) {
148983
+ stepInfo(`sandbox.network.allowedDomains -= ${ZERO_SANDBOX_DOMAIN2}`);
148984
+ }
148985
+ if (next.length === 0) {
148986
+ delete network.allowedDomains;
148987
+ if (sandbox && Object.keys(network).length === 0)
148988
+ delete sandbox.network;
148989
+ if (sandbox && Object.keys(sandbox).length === 0)
148990
+ delete settings.sandbox;
148991
+ } else {
148992
+ network.allowedDomains = next;
148993
+ }
148536
148994
  }
148537
148995
  }
148538
148996
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxyz/cli",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "zero": "dist/index.js",