@zeroxyz/cli 1.6.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 +785 -201
  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.6.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-RY3VFSIE.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
@@ -143081,6 +143081,76 @@ var ZeroConfigurationError = class extends ZeroError {
143081
143081
  this.name = "ZeroConfigurationError";
143082
143082
  }
143083
143083
  };
143084
+ var asSchemaNode = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : null;
143085
+ var extractInputEnvelope = (bodySchema, method) => {
143086
+ const root = asSchemaNode(bodySchema);
143087
+ if (!root) return {};
143088
+ const props = asSchemaNode(root.properties);
143089
+ if (!props) return {};
143090
+ const inputProps = asSchemaNode(asSchemaNode(props.input)?.properties);
143091
+ if (inputProps) {
143092
+ return {
143093
+ queryParams: asSchemaNode(inputProps.queryParams) ?? void 0,
143094
+ body: asSchemaNode(inputProps.body) ?? void 0
143095
+ };
143096
+ }
143097
+ const upperMethod = method.toUpperCase();
143098
+ const isGetLike = upperMethod === "GET" || upperMethod === "DELETE";
143099
+ return isGetLike ? { queryParams: root } : { body: root };
143100
+ };
143101
+ var unwrapTransportEnvelope = (request22) => {
143102
+ const input = asSchemaNode(asSchemaNode(request22)?.input);
143103
+ if (!input || input.type !== "http") return request22;
143104
+ const body = input.body;
143105
+ const hasBody = body !== void 0 && body !== null && !(typeof body === "object" && Object.keys(body).length === 0);
143106
+ if (hasBody) return body;
143107
+ const qp = asSchemaNode(input.queryParams);
143108
+ if (qp && Object.keys(qp).length > 0) return qp;
143109
+ return null;
143110
+ };
143111
+ var GET_LIKE = /* @__PURE__ */ new Set(["GET", "HEAD", "DELETE"]);
143112
+ var appendQueryParams = (url4, params) => {
143113
+ const entries = Object.entries(params).filter(([, v]) => v != null);
143114
+ if (entries.length === 0) return url4;
143115
+ try {
143116
+ const u2 = new URL(url4);
143117
+ for (const [k3, v] of entries) u2.searchParams.set(k3, String(v));
143118
+ return u2.toString();
143119
+ } catch {
143120
+ const qs = entries.map(
143121
+ ([k3, v]) => `${encodeURIComponent(k3)}=${encodeURIComponent(String(v))}`
143122
+ ).join("&");
143123
+ return url4.includes("?") ? `${url4}&${qs}` : `${url4}?${qs}`;
143124
+ }
143125
+ };
143126
+ var normalizeTransportEnvelopeRequest = (url4, opts) => {
143127
+ const { method, body } = opts;
143128
+ if (typeof body !== "string") return { url: url4, method, body };
143129
+ let parsed;
143130
+ try {
143131
+ parsed = JSON.parse(body);
143132
+ } catch {
143133
+ return { url: url4, method, body };
143134
+ }
143135
+ const input = asSchemaNode(asSchemaNode(parsed)?.input);
143136
+ if (!input || input.type !== "http") return { url: url4, method, body };
143137
+ const innerBody = input.body;
143138
+ const hasInnerBody = innerBody !== void 0 && innerBody !== null && !(typeof innerBody === "object" && Object.keys(innerBody).length === 0);
143139
+ const verb = (method ?? (typeof input.method === "string" ? input.method : void 0) ?? (hasInnerBody ? "POST" : "GET")).toUpperCase();
143140
+ if (GET_LIKE.has(verb)) {
143141
+ const qp = asSchemaNode(input.queryParams);
143142
+ return {
143143
+ url: qp ? appendQueryParams(url4, qp) : url4,
143144
+ method: verb,
143145
+ body: void 0
143146
+ };
143147
+ }
143148
+ return {
143149
+ url: url4,
143150
+ method: verb,
143151
+ body: hasInnerBody ? JSON.stringify(innerBody) : void 0
143152
+ };
143153
+ };
143084
143154
  var KNOWN_EIP712_DOMAINS = {
143085
143155
  // Base mainnet USDC. On-chain `name()` is "USD Coin" (NOT "USDC")
143086
143156
  // — verified via the contract's name() / version().
@@ -144092,7 +144162,7 @@ var Payments = class {
144092
144162
  };
144093
144163
  };
144094
144164
  var package_default2 = {
144095
- version: "0.5.0"
144165
+ version: "0.6.0"
144096
144166
  };
144097
144167
  var SDK_VERSION2 = package_default2.version;
144098
144168
  var userWalletDtoSchema = external_exports3.object({
@@ -144807,6 +144877,30 @@ var listRunsResponseSchema = external_exports3.object({
144807
144877
  runs: external_exports3.array(runListItemSchema),
144808
144878
  nextCursor: external_exports3.string().nullable()
144809
144879
  });
144880
+ var runDetailSchema = external_exports3.object({
144881
+ runId: external_exports3.string(),
144882
+ status: external_exports3.number().nullable(),
144883
+ latencyMs: external_exports3.number().nullable(),
144884
+ errorClass: external_exports3.string().nullable(),
144885
+ createdAt: external_exports3.coerce.date(),
144886
+ cost: external_exports3.object({ amount: external_exports3.string(), asset: external_exports3.string().nullable() }).nullable(),
144887
+ payment: external_exports3.object({
144888
+ protocol: external_exports3.string(),
144889
+ chain: external_exports3.string().nullable(),
144890
+ txHash: external_exports3.string().nullable(),
144891
+ mode: external_exports3.string().nullable()
144892
+ }).nullable(),
144893
+ capability: external_exports3.object({
144894
+ uid: external_exports3.string(),
144895
+ slug: external_exports3.string(),
144896
+ name: external_exports3.string(),
144897
+ url: external_exports3.string(),
144898
+ method: external_exports3.string(),
144899
+ whatItDoes: external_exports3.string().nullable(),
144900
+ tags: external_exports3.array(external_exports3.string())
144901
+ }),
144902
+ reviewed: external_exports3.boolean()
144903
+ });
144810
144904
  var createReviewResponseSchema = external_exports3.object({
144811
144905
  reviewId: external_exports3.string(),
144812
144906
  recorded: external_exports3.boolean(),
@@ -144886,6 +144980,15 @@ var Runs = class {
144886
144980
  listRunsResponseSchema
144887
144981
  );
144888
144982
  };
144983
+ get = (runId, opts = {}) => request2(
144984
+ this.client,
144985
+ {
144986
+ method: "GET",
144987
+ path: `/v1/runs/${runId}`,
144988
+ signal: opts.signal
144989
+ },
144990
+ runDetailSchema
144991
+ );
144889
144992
  review = (input, opts = {}) => request2(
144890
144993
  this.client,
144891
144994
  {
@@ -145410,17 +145513,23 @@ var withDefaultContentType = (headers, body) => {
145410
145513
  var clientFetch = async (client, url4, opts = {}) => {
145411
145514
  const startedAt = Date.now();
145412
145515
  const capabilityIdRequested = opts.capabilityId !== void 0;
145413
- const method = opts.method ?? (opts.body !== void 0 ? "POST" : "GET");
145516
+ const normalized = normalizeTransportEnvelopeRequest(url4, {
145517
+ method: opts.method,
145518
+ body: opts.body
145519
+ });
145520
+ const requestUrl = normalized.url;
145521
+ const requestBody = normalized.body;
145522
+ const method = normalized.method ?? (requestBody !== void 0 ? "POST" : "GET");
145414
145523
  const { canRecord: canRecordForResolve } = resolveRecordingClient(
145415
145524
  client,
145416
145525
  opts
145417
145526
  );
145418
- const resolvePromise = !capabilityIdRequested && canRecordForResolve ? client.capabilities.resolveByUrl(redactUrl2(url4), method).catch(() => null) : Promise.resolve(null);
145419
- const probeHeaders = withDefaultContentType(opts.headers, opts.body);
145527
+ const resolvePromise = !capabilityIdRequested && canRecordForResolve ? client.capabilities.resolveByUrl(redactUrl2(requestUrl), method).catch(() => null) : Promise.resolve(null);
145528
+ const probeHeaders = withDefaultContentType(opts.headers, requestBody);
145420
145529
  const requestInit = {
145421
145530
  method,
145422
145531
  headers: probeHeaders,
145423
- body: opts.body,
145532
+ body: requestBody,
145424
145533
  signal: opts.signal
145425
145534
  };
145426
145535
  const configuredFetch = buildConfiguredFetch(client, {
@@ -145431,7 +145540,7 @@ var clientFetch = async (client, url4, opts = {}) => {
145431
145540
  const skipReasons = [];
145432
145541
  const warnings = [];
145433
145542
  try {
145434
- response = await configuredFetch(url4, requestInit);
145543
+ response = await configuredFetch(requestUrl, requestInit);
145435
145544
  } catch (err) {
145436
145545
  if (err instanceof ZeroTimeoutError) {
145437
145546
  await recordTimeoutRun(
@@ -145461,13 +145570,13 @@ var clientFetch = async (client, url4, opts = {}) => {
145461
145570
  const payCall = requirement.protocol === "x402" ? client.payments.pay : client.payments.payMpp;
145462
145571
  try {
145463
145572
  const paid = await payCall({
145464
- url: url4,
145573
+ url: requestUrl,
145465
145574
  method,
145466
145575
  // Fresh clone for the paid retry — same rationale as
145467
145576
  // the probe-side clone above. Re-derives the default
145468
145577
  // Content-Type so the paid leg matches the probe.
145469
- headers: withDefaultContentType(opts.headers, opts.body),
145470
- body: opts.body,
145578
+ headers: withDefaultContentType(opts.headers, requestBody),
145579
+ body: requestBody,
145471
145580
  maxPay: opts.maxPay,
145472
145581
  account: opts.account,
145473
145582
  signal: opts.signal,
@@ -145724,6 +145833,9 @@ var searchResultSchema = external_exports3.object({
145724
145833
  cost: external_exports3.object({ amount: external_exports3.string(), asset: external_exports3.string() }),
145725
145834
  reviewCount: external_exports3.number().optional(),
145726
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(),
145727
145839
  availabilityStatus: external_exports3.enum(["healthy", "unknown", "down"]).nullable().optional(),
145728
145840
  /**
145729
145841
  * @deprecated The API no longer emits `displayStatus`; it has been replaced
@@ -146645,7 +146757,7 @@ var fetchCommand = (appContext) => new Command("fetch").description(
146645
146757
  headers["content-type"] = Buffer.isBuffer(resolvedBody) ? sniffJsonShape(resolvedBody) ? "application/json" : "application/octet-stream" : "application/json";
146646
146758
  }
146647
146759
  const log2 = (msg) => console.error(` ${msg}`);
146648
- const method = options.method ? options.method.toUpperCase() : resolvedMethodFromCapability && !resolvedBody ? resolvedMethodFromCapability.toUpperCase() : resolvedBody ? "POST" : "GET";
146760
+ const method = options.method ? options.method.toUpperCase() : resolvedMethodFromCapability ? resolvedMethodFromCapability.toUpperCase() : resolvedBody ? "POST" : "GET";
146649
146761
  const explicitCapabilityCtx = options.capability ? stateService.findSearchContextByCapability(options.capability) : null;
146650
146762
  const urlCtx = stateService.findSearchContextByUrl(resolvedUrl, {
146651
146763
  matchTemplate: (template) => {
@@ -147137,22 +147249,6 @@ var sampleValueFor = (fieldName, propSchema, depth = 0) => {
147137
147249
  }
147138
147250
  };
147139
147251
  var placeholderFor = (fieldName, propSchema) => String(sampleValueFor(fieldName, propSchema));
147140
- var extractInputEnvelope = (bodySchema, method) => {
147141
- const root = asNode(bodySchema);
147142
- if (!root) return {};
147143
- const props = asNode(root.properties);
147144
- if (!props) return {};
147145
- const inputProps = asNode(asNode(props.input)?.properties);
147146
- if (inputProps) {
147147
- return {
147148
- queryParams: asNode(inputProps.queryParams) ?? void 0,
147149
- body: asNode(inputProps.body) ?? void 0
147150
- };
147151
- }
147152
- const upperMethod = method.toUpperCase();
147153
- const isGetLike = upperMethod === "GET" || upperMethod === "DELETE";
147154
- return isGetLike ? { queryParams: root } : { body: root };
147155
- };
147156
147252
  var schemaTypeLabel = (node) => {
147157
147253
  const t = node.type;
147158
147254
  if (typeof t === "string") return t;
@@ -147291,16 +147387,6 @@ var buildResponseSection = (capability) => {
147291
147387
  }
147292
147388
  return [];
147293
147389
  };
147294
- var unwrapTransportEnvelope = (req) => {
147295
- const input = asNode(asNode(req)?.input);
147296
- if (!input || input.type !== "http") return req;
147297
- const body = input.body;
147298
- const hasBody = body !== void 0 && body !== null && !(typeof body === "object" && Object.keys(body).length === 0);
147299
- if (hasBody) return body;
147300
- const qp = asNode(input.queryParams);
147301
- if (qp && Object.keys(qp).length > 0) return qp;
147302
- return null;
147303
- };
147304
147390
  var buildVerifiedExampleSection = (capability) => {
147305
147391
  const req = capability.example?.request;
147306
147392
  if (req === void 0 || req === null) return [];
@@ -147508,9 +147594,251 @@ var sectionDivider = () => {
147508
147594
  console.log("");
147509
147595
  };
147510
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
+
147511
147618
  // src/commands/init-command.ts
147512
147619
  var SKILL_DIRS = [".claude/skills", ".agents/skills"];
147513
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
+ };
147514
147842
  var PLUGIN_HOOKS_ROOT = "${CLAUDE_PLUGIN_ROOT}/hooks";
147515
147843
  var SCRIPT_NAME_PATTERN = /\$\{CLAUDE_PLUGIN_ROOT\}\/hooks\/([a-z0-9][a-z0-9-]*\.sh)/;
147516
147844
  var fetchText = async (url4) => {
@@ -147575,59 +147903,7 @@ var installHookScripts = async (home, hooksJson, webBase, verbose) => {
147575
147903
  })
147576
147904
  );
147577
147905
  };
147578
- var registerHooksInClaudeSettings = (home, hooksJson, verbose) => {
147579
- const hooksDir = join(home, ".zero", "hooks");
147580
- const settingsPath = join(home, ".claude", "settings.json");
147581
- let settings = {};
147582
- if (existsSync(settingsPath)) {
147583
- try {
147584
- settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
147585
- } catch {
147586
- stepWarn(
147587
- "Hooks not registered",
147588
- `${settingsPath} is not valid JSON \u2014 fix it and re-run \`zero init\``
147589
- );
147590
- return false;
147591
- }
147592
- }
147593
- if (!settings.hooks || typeof settings.hooks !== "object") {
147594
- settings.hooks = {};
147595
- }
147596
- const hooks = settings.hooks;
147597
- for (const [event, entries] of Object.entries(hooksJson.hooks)) {
147598
- for (const entry of entries ?? []) {
147599
- const scripts = entryScriptNames(entry);
147600
- if (scripts.length === 0) {
147601
- if (verbose) {
147602
- stepInfo(`${event}: entry has no plugin hook script \u2014 skipped`);
147603
- }
147604
- continue;
147605
- }
147606
- const rewritten = {
147607
- ...entry,
147608
- hooks: (entry.hooks ?? []).map((hook) => ({
147609
- ...hook,
147610
- command: typeof hook.command === "string" ? hook.command.replaceAll(PLUGIN_HOOKS_ROOT, hooksDir) : hook.command
147611
- }))
147612
- };
147613
- if (!Array.isArray(hooks[event])) hooks[event] = [];
147614
- const list2 = hooks[event];
147615
- const existingIdx = list2.findIndex((existing) => {
147616
- const existingHooks = existing?.hooks;
147617
- if (!Array.isArray(existingHooks)) return false;
147618
- return existingHooks.some(
147619
- (h2) => typeof h2.command === "string" && scripts.some((s) => h2.command.includes(s))
147620
- );
147621
- });
147622
- if (existingIdx >= 0) {
147623
- list2[existingIdx] = rewritten;
147624
- if (verbose) stepInfo(`${event}: ${scripts.join(", ")} entry replaced`);
147625
- } else {
147626
- list2.push(rewritten);
147627
- if (verbose) stepInfo(`${event}: ${scripts.join(", ")} entry appended`);
147628
- }
147629
- }
147630
- }
147906
+ var ensureZeroSandboxAllowlist = (settings, verbose) => {
147631
147907
  if (!settings.sandbox || typeof settings.sandbox !== "object") {
147632
147908
  settings.sandbox = {};
147633
147909
  }
@@ -147646,86 +147922,326 @@ var registerHooksInClaudeSettings = (home, hooksJson, verbose) => {
147646
147922
  stepInfo(`sandbox.network.allowedDomains += ${ZERO_SANDBOX_DOMAIN}`);
147647
147923
  }
147648
147924
  }
147649
- mkdirSync(join(home, ".claude"), { recursive: true });
147650
- writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
147651
- `);
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
+ }
147652
147988
  return true;
147653
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
+ };
147654
148086
  var runInit = async (appContext, options = {}) => {
147655
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";
147656
148093
  appContext.services.analyticsService.capture("init_started", {});
147657
- let currentStep = "fetch";
147658
148094
  try {
147659
148095
  console.log("");
147660
- console.log(` ${color.boldCyan("Installing Zero")}`);
148096
+ console.log(` ${color.boldCyan("Initializing Zero")}`);
147661
148097
  console.log("");
147662
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"));
147663
148099
  console.log("");
147664
- const home = homedir();
147665
- const webBase = appContext.env.ZERO_WEB_URL;
147666
- const [skillMd, hooksJsonRaw] = await Promise.all([
147667
- fetchText(`${webBase}/SKILL.md`),
147668
- fetchText(`${webBase}/hooks.json`)
147669
- ]);
147670
- let hooksJson;
147671
- try {
147672
- hooksJson = JSON.parse(hooksJsonRaw);
147673
- } catch {
147674
- throw new Error(`${webBase}/hooks.json is not valid JSON`);
147675
- }
147676
- if (!hooksJson?.hooks || typeof hooksJson.hooks !== "object") {
147677
- throw new Error(`${webBase}/hooks.json has no "hooks" object`);
147678
- }
147679
- if (verbose) {
147680
- 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
+ );
147681
148109
  }
147682
- currentStep = "skill";
147683
- const skillsDirs = [
147684
- .../* @__PURE__ */ new Set([
147685
- ...SKILL_DIRS.map((dir) => join(home, dir)),
147686
- ...(options.skillsDir ?? []).map((dir) => expandHome(dir, home))
147687
- ])
147688
- ];
147689
- const skillsInstalled = installSkill(skillsDirs, skillMd, verbose);
147690
- if (skillsInstalled.length === 0) {
147691
- 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
+ }
147692
148151
  }
147693
- stepSuccess("Skill installed", `${skillsInstalled.length} locations`);
147694
- currentStep = "hooks";
147695
- const hookScriptsInstalled = await installHookScripts(
147696
- home,
147697
- hooksJson,
147698
- webBase,
147699
- verbose
147700
- );
147701
- stepSuccess("Hook scripts installed", "~/.zero/hooks");
147702
- currentStep = "settings";
147703
- const settingsRegistered = registerHooksInClaudeSettings(
147704
- home,
147705
- hooksJson,
147706
- verbose
148152
+ const pluginSucceeded = pluginResults.some(
148153
+ (result) => result.status === "installed" || result.status === "would_install"
147707
148154
  );
147708
- if (settingsRegistered) {
147709
- 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
+ );
147710
148200
  }
147711
148201
  currentStep = "complete";
147712
148202
  appContext.services.analyticsService.capture("init_completed", {
147713
148203
  // biome-ignore lint/style/useNamingConvention: snake_case for analytics
147714
- skills_installed: skillsInstalled,
148204
+ requested_agents: requestedAgents,
148205
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
148206
+ detected_harnesses: detectedAgents,
148207
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
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,
147715
148216
  // biome-ignore lint/style/useNamingConvention: snake_case for analytics
147716
- skills_installed_count: skillsInstalled.length,
148217
+ dry_run: dryRun,
147717
148218
  // biome-ignore lint/style/useNamingConvention: snake_case for analytics
147718
- hook_scripts_installed: hookScriptsInstalled,
148219
+ skills_installed: standaloneResult.skillsInstalled,
147719
148220
  // biome-ignore lint/style/useNamingConvention: snake_case for analytics
147720
- settings_registered: settingsRegistered
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,
148226
+ // biome-ignore lint/style/useNamingConvention: snake_case for analytics
148227
+ codex_hooks_registered: standaloneResult.codexHooksRegistered
147721
148228
  });
147722
148229
  sectionDivider();
147723
- console.log(` ${color.boldGreen("Zero is set up.")}`);
148230
+ console.log(` ${color.boldGreen("Zero init complete.")}`);
147724
148231
  console.log(
147725
148232
  ` ${color.dim("Next: sign in with `zero auth login` \u2014 it prints a URL to approve in your browser.")}`
147726
148233
  );
148234
+ printRunnerModelNote();
147727
148235
  console.log("");
147728
- return { skillsInstalled, hookScriptsInstalled, settingsRegistered };
148236
+ return {
148237
+ ...standaloneResult,
148238
+ requestedAgents,
148239
+ detectedAgents,
148240
+ pluginResults,
148241
+ standaloneFallbackUsed,
148242
+ standaloneFallbackReason,
148243
+ dryRun
148244
+ };
147729
148245
  } catch (err) {
147730
148246
  appContext.services.analyticsService.capture("init_failed", {
147731
148247
  step: currentStep,
@@ -147735,7 +148251,18 @@ var runInit = async (appContext, options = {}) => {
147735
148251
  }
147736
148252
  };
147737
148253
  var initCommand = (appContext) => new Command("init").description(
147738
- "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"
147739
148266
  ).option(
147740
148267
  "-v, --verbose",
147741
148268
  "Explain why each install step was taken or skipped"
@@ -148055,6 +148582,49 @@ var formatPayment = (payment) => {
148055
148582
  const mode = payment.mode ? ` (${payment.mode})` : "";
148056
148583
  return `${payment.protocol}${chain2}${mode}`;
148057
148584
  };
148585
+ var formatDetail = (r2) => {
148586
+ const lines = [];
148587
+ const when = r2.createdAt.toISOString().replace("T", " ").slice(0, 19);
148588
+ lines.push(`Run: ${r2.runId}`);
148589
+ lines.push(`When: ${when}`);
148590
+ lines.push(`Status: ${r2.status ?? "\u2014"}`);
148591
+ lines.push(`Latency: ${r2.latencyMs != null ? `${r2.latencyMs}ms` : "\u2014"}`);
148592
+ lines.push(`Result: ${r2.errorClass ?? "\u2014"}`);
148593
+ lines.push(`Reviewed: ${r2.reviewed ? "yes" : "no"}`);
148594
+ lines.push("");
148595
+ lines.push(`Capability: ${r2.capability.name} (${r2.capability.slug})`);
148596
+ lines.push(`URL: ${r2.capability.method} ${r2.capability.url}`);
148597
+ if (r2.capability.whatItDoes)
148598
+ lines.push(`What: ${r2.capability.whatItDoes}`);
148599
+ if (r2.capability.tags.length > 0)
148600
+ lines.push(`Tags: ${r2.capability.tags.join(", ")}`);
148601
+ if (r2.cost) {
148602
+ const { amount: amount2, asset } = r2.cost;
148603
+ lines.push(`Cost: ${asset ? `${amount2} ${asset}` : amount2}`);
148604
+ }
148605
+ if (r2.payment) {
148606
+ const chain2 = r2.payment.chain ? `:${r2.payment.chain}` : "";
148607
+ const mode = r2.payment.mode ? ` (${r2.payment.mode})` : "";
148608
+ const tx = r2.payment.txHash ? ` tx=${r2.payment.txHash}` : "";
148609
+ lines.push(`Payment: ${r2.payment.protocol}${chain2}${mode}${tx}`);
148610
+ }
148611
+ return lines.join("\n");
148612
+ };
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();
148615
+ try {
148616
+ const { client } = appContext.services;
148617
+ const run = await client.runs.get(runId);
148618
+ if (json3) {
148619
+ console.log(JSON.stringify(run, null, 2));
148620
+ return;
148621
+ }
148622
+ console.log(formatDetail(run));
148623
+ } catch (err) {
148624
+ console.error(err instanceof Error ? err.message : "Failed");
148625
+ process.exitCode = 1;
148626
+ }
148627
+ });
148058
148628
  var runsCommand = (appContext) => new Command("runs").description("List your recent capability runs").addHelpText(
148059
148629
  "after",
148060
148630
  `
@@ -148065,8 +148635,9 @@ Examples:
148065
148635
  zero runs # most recent runs
148066
148636
  zero runs --unreviewed # filter to runs without a review
148067
148637
  zero runs --capability translate-en # filter to one capability (uid or slug)
148068
- zero runs --json # full structured output for scripts`
148069
- ).option("--capability <id>", "Filter by capability uid or slug").option("--unreviewed", "Only show runs without a review").option(
148638
+ zero runs --json # full structured output for scripts
148639
+ zero runs detail <runId> # full metadata for a single run`
148640
+ ).addCommand(runsDetailCommand(appContext)).option("--capability <id>", "Filter by capability uid or slug").option("--unreviewed", "Only show runs without a review").option(
148070
148641
  "--limit <n>",
148071
148642
  "Max rows (1-100, default 25)",
148072
148643
  (v) => Number.parseInt(v, 10)
@@ -148308,6 +148879,8 @@ var HOOK_FILES = [
148308
148879
  "ensure-runner.sh"
148309
148880
  ];
148310
148881
  var ZERO_SANDBOX_DOMAIN2 = "*.zero.xyz";
148882
+ var CLAUDE_SETTINGS_PATH2 = ".claude/settings.json";
148883
+ var CODEX_HOOKS_PATH2 = ".codex/hooks.json";
148311
148884
  var LEGACY_SKILL_NAMES = ["zero"];
148312
148885
  var removeSkills = (home, verbose = false) => {
148313
148886
  const skillNames = LEGACY_SKILL_NAMES;
@@ -148333,30 +148906,17 @@ var removeSkills = (home, verbose = false) => {
148333
148906
  return removed;
148334
148907
  };
148335
148908
  var removeHooks = (home, verbose = false) => {
148336
- let settingsChanged = false;
148337
- const settingsPath = join(home, ".claude", "settings.json");
148338
- if (!existsSync(settingsPath)) {
148339
- if (verbose) stepInfo(`${settingsPath} not found \u2014 no settings to clean`);
148340
- } else {
148341
- let settings = null;
148342
- try {
148343
- settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
148344
- } catch {
148345
- if (verbose) {
148346
- stepInfo(`${settingsPath} was unparseable JSON \u2014 leaving it untouched`);
148347
- }
148348
- }
148349
- if (settings) {
148350
- settingsChanged = stripZeroFromSettings(settings, verbose);
148351
- if (settingsChanged) {
148352
- writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
148353
- `);
148354
- if (verbose) stepInfo(`rewrote ${settingsPath} without Zero entries`);
148355
- } else if (verbose) {
148356
- stepInfo(`no Zero entries found in ${settingsPath}`);
148357
- }
148358
- }
148359
- }
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;
148360
148920
  let scriptsRemoved = 0;
148361
148921
  const zeroHooksDir = join(home, ".zero", "hooks");
148362
148922
  if (existsSync(zeroHooksDir)) {
@@ -148376,7 +148936,29 @@ var removeHooks = (home, verbose = false) => {
148376
148936
  }
148377
148937
  return { settingsChanged, scriptsRemoved };
148378
148938
  };
148379
- 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) => {
148380
148962
  let changed = false;
148381
148963
  const hooks = settings.hooks && typeof settings.hooks === "object" ? settings.hooks : null;
148382
148964
  if (hooks) {
@@ -148389,24 +148971,26 @@ var stripZeroFromSettings = (settings, verbose) => {
148389
148971
  }
148390
148972
  if (Object.keys(hooks).length === 0) delete settings.hooks;
148391
148973
  }
148392
- const sandbox = settings.sandbox && typeof settings.sandbox === "object" ? settings.sandbox : null;
148393
- const network = sandbox?.network && typeof sandbox.network === "object" ? sandbox.network : null;
148394
- if (network && Array.isArray(network.allowedDomains)) {
148395
- const domains = network.allowedDomains;
148396
- const next = domains.filter((d3) => d3 !== ZERO_SANDBOX_DOMAIN2);
148397
- if (next.length !== domains.length) {
148398
- changed = true;
148399
- if (verbose) {
148400
- stepInfo(`sandbox.network.allowedDomains -= ${ZERO_SANDBOX_DOMAIN2}`);
148401
- }
148402
- if (next.length === 0) {
148403
- delete network.allowedDomains;
148404
- if (sandbox && Object.keys(network).length === 0)
148405
- delete sandbox.network;
148406
- if (sandbox && Object.keys(sandbox).length === 0)
148407
- delete settings.sandbox;
148408
- } else {
148409
- 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
+ }
148410
148994
  }
148411
148995
  }
148412
148996
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxyz/cli",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "zero": "dist/index.js",