@zeroxyz/cli 1.13.0 → 1.14.1

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 (2) hide show
  1. package/dist/index.js +199 -140
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -78576,7 +78576,7 @@ init_esm_shims();
78576
78576
 
78577
78577
  // package.json
78578
78578
  var package_default = {
78579
- version: "1.13.0"};
78579
+ version: "1.14.1"};
78580
78580
 
78581
78581
  // src/app.ts
78582
78582
  init_esm_shims();
@@ -79584,7 +79584,7 @@ init_esm_shims();
79584
79584
  // ../sdk/dist/index.js
79585
79585
  init_esm_shims();
79586
79586
 
79587
- // ../sdk/dist/chunk-T6JT5NV5.js
79587
+ // ../sdk/dist/chunk-AAAVBKT3.js
79588
79588
  init_esm_shims();
79589
79589
 
79590
79590
  // ../../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
@@ -144966,7 +144966,7 @@ config(en_default2());
144966
144966
  // ../../node_modules/.pnpm/zod@4.3.6/node_modules/zod/index.js
144967
144967
  var zod_default = external_exports3;
144968
144968
 
144969
- // ../sdk/dist/chunk-T6JT5NV5.js
144969
+ // ../sdk/dist/chunk-AAAVBKT3.js
144970
144970
  var createManagedAccount = (client, address2) => toAccount({
144971
144971
  address: address2,
144972
144972
  async signMessage({ message: message2 }) {
@@ -147884,6 +147884,10 @@ var searchResultSchema = external_exports3.object({
147884
147884
  url: external_exports3.string(),
147885
147885
  urlTemplate: external_exports3.string().nullable().optional(),
147886
147886
  cost: external_exports3.object({ amount: external_exports3.string(), asset: external_exports3.string() }),
147887
+ // ZERO-199 — the server-resolved price. Prefer `pricing.summary` over
147888
+ // deriving a label from `cost.amount` (which flattens usage-priced caps).
147889
+ // Optional: absent on older API deploys.
147890
+ pricing: external_exports3.object({ kind: external_exports3.string(), summary: external_exports3.string() }).optional(),
147887
147891
  // Payment protocol (x402 | mpp) for this result. The server emits it; the SDK
147888
147892
  // previously stripped it, leaving an agent unable to tell which handshake to
147889
147893
  // engage from a search row. Optional/nullable so older deploys still parse
@@ -149229,12 +149233,12 @@ var formatCost = (capability) => {
149229
149233
  const median = observed.medianCents ? centsToDollars(observed.medianCents) : null;
149230
149234
  const detail = median ? `median $${median}, n=${observed.sampleCount}` : `n=${observed.sampleCount}`;
149231
149235
  lines.push(` Cost: $${min}\u2013$${max}/call (${detail})`);
149232
- } else if (capability.priceDynamic && capability.priceHint) {
149236
+ } else if (capability.priceDynamic && capability.priceHint && /\$\d/.test(capability.priceHint)) {
149233
149237
  const ceiling = observed?.maxCents ? centsToDollars(observed.maxCents) : null;
149234
149238
  lines.push(
149235
149239
  ` Cost: ${capability.priceHint} (dynamic${ceiling ? `, up to ~$${ceiling}/call` : ""})`
149236
149240
  );
149237
- } else if (capability.priceStatus === "unknown_unparsed") {
149241
+ } else if (capability.priceStatus === "unknown_unparsed" && !(Number(capability.displayCostAmount) > 0) && !paidRailAmount(capability)) {
149238
149242
  lines.push(` Cost: price unknown`);
149239
149243
  } else if (capability.priceDynamic) {
149240
149244
  lines.push(
@@ -149652,16 +149656,20 @@ var agentStandaloneMcpSchema = zod_default.looseObject({
149652
149656
  var agentStandaloneHooksSchema = zod_default.looseObject({
149653
149657
  // Config file to merge the hook declarations into (relative to $HOME).
149654
149658
  file: zod_default.string(),
149655
- // Names the config schema. The CLI's generic JSON merger handles the shared
149656
- // `{ hooks: { Event: [...] } }` shape (claude-settings, codex-hooks); other
149657
- // formats (e.g. kiro-agent) need their own writer and are skipped until one
149658
- // exists.
149659
- format: zod_default.string(),
149659
+ // Informational name of the config schema. Registration is driven by `merge`
149660
+ // alone; `format` stays in the matrix because pre-1.14 CLIs require it.
149661
+ format: zod_default.string().optional(),
149660
149662
  sessionStart: zod_default.boolean().optional(),
149661
149663
  // Domains to ensure on this host's sandbox network allowlist
149662
149664
  // (sandbox.network.allowedDomains) after merging — e.g. ["*.zero.xyz"] for
149663
149665
  // Claude. Omitted hosts get no allowlist entry.
149664
- network: zod_default.array(zod_default.string()).optional()
149666
+ network: zod_default.array(zod_default.string()).optional(),
149667
+ // The literal hook config fragment to deep-merge into `file`, with
149668
+ // ${ZERO_HOOKS_DIR} standing in for the staged ~/.zero/hooks dir. The CLI
149669
+ // derives which hook scripts to fetch+stage by scanning this fragment for
149670
+ // ${ZERO_HOOKS_DIR}/<name> references. Hosts without a fragment get no hook
149671
+ // registration.
149672
+ merge: zod_default.record(zod_default.string(), zod_default.unknown()).optional()
149665
149673
  });
149666
149674
  var agentStandaloneSchema = zod_default.looseObject({
149667
149675
  // Skill install dirs (relative to $HOME) for the standalone fallback.
@@ -149746,6 +149754,57 @@ var loadAgents = async (webBase, cliVersion) => {
149746
149754
  return { entries: filterMatrix(matrix, cliVersion) };
149747
149755
  };
149748
149756
 
149757
+ // src/util/hook-fragment.ts
149758
+ init_esm_shims();
149759
+ var ZERO_HOOKS_DIR_PLACEHOLDER = "${ZERO_HOOKS_DIR}";
149760
+ var SCRIPT_REF_PATTERN = /\$\{ZERO_HOOKS_DIR\}\/([a-z0-9][a-z0-9-]*\.sh)/g;
149761
+ var isPlainObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
149762
+ var fragmentScriptNames = (fragment) => {
149763
+ const names = /* @__PURE__ */ new Set();
149764
+ for (const match of JSON.stringify(fragment).matchAll(SCRIPT_REF_PATTERN)) {
149765
+ if (match[1]) names.add(match[1]);
149766
+ }
149767
+ return [...names];
149768
+ };
149769
+ var substituteHooksDir = (value, hooksDir) => {
149770
+ if (typeof value === "string") {
149771
+ return value.replaceAll(ZERO_HOOKS_DIR_PLACEHOLDER, hooksDir);
149772
+ }
149773
+ if (Array.isArray(value)) {
149774
+ return value.map((item) => substituteHooksDir(item, hooksDir));
149775
+ }
149776
+ if (isPlainObject3(value)) {
149777
+ return Object.fromEntries(
149778
+ Object.entries(value).map(([key, item]) => [
149779
+ key,
149780
+ substituteHooksDir(item, hooksDir)
149781
+ ])
149782
+ );
149783
+ }
149784
+ return value;
149785
+ };
149786
+ var referencesAnyScript = (element, scriptNames) => {
149787
+ const json3 = JSON.stringify(element) ?? "";
149788
+ return scriptNames.some((name) => json3.includes(name));
149789
+ };
149790
+ var mergeHookFragment = (target, fragment, scriptNames) => {
149791
+ for (const [key, incoming] of Object.entries(fragment)) {
149792
+ const existing = target[key];
149793
+ if (Array.isArray(incoming)) {
149794
+ const kept = Array.isArray(existing) ? existing.filter(
149795
+ (element) => !referencesAnyScript(element, scriptNames)
149796
+ ) : [];
149797
+ target[key] = [...kept, ...incoming];
149798
+ } else if (isPlainObject3(incoming)) {
149799
+ const base2 = isPlainObject3(existing) ? existing : {};
149800
+ target[key] = base2;
149801
+ mergeHookFragment(base2, incoming, scriptNames);
149802
+ } else {
149803
+ target[key] = incoming;
149804
+ }
149805
+ }
149806
+ };
149807
+
149749
149808
  // src/util/install-banner.ts
149750
149809
  init_esm_shims();
149751
149810
  var colorsEnabled = () => {
@@ -149822,6 +149881,7 @@ var SKILL_DIRS = [".claude/skills", ".agents/skills"];
149822
149881
  var CLAUDE_SETTINGS_PATH = ".claude/settings.json";
149823
149882
  var CODEX_HOOKS_PATH = ".codex/hooks.json";
149824
149883
  var CLI_VERSION = package_default.version;
149884
+ var installStepTimeoutMs = () => Number(process.env.ZERO_INIT_STEP_TIMEOUT_MS ?? 12e4);
149825
149885
  var STANDALONE = "standalone";
149826
149886
  var unique = (items) => [...new Set(items)];
149827
149887
  var buildSelection = (standalone, matrixIds) => {
@@ -149844,7 +149904,13 @@ var commandOutput = (result) => {
149844
149904
  return chunks.join("\n").trim();
149845
149905
  };
149846
149906
  var commandErrorDetail = (result, fallback2) => {
149847
- if (result.error) return result.error.message;
149907
+ if (result.error) {
149908
+ const code = result.error.code;
149909
+ if (code === "ETIMEDOUT") {
149910
+ return `${fallback2} timed out after ${installStepTimeoutMs() / 1e3}s (is it waiting for interactive input?)`;
149911
+ }
149912
+ return result.error.message;
149913
+ }
149848
149914
  const output = commandOutput(result);
149849
149915
  if (output.length > 0) return truncateError(output);
149850
149916
  if (result.signal) return `${fallback2} exited via signal ${result.signal}`;
@@ -149886,7 +149952,8 @@ var installAgentPlugin = (entry, method, options) => {
149886
149952
  const result = spawnSync(step.command, step.args, {
149887
149953
  encoding: "utf8",
149888
149954
  stdio: "pipe",
149889
- env: process.env
149955
+ env: process.env,
149956
+ timeout: installStepTimeoutMs()
149890
149957
  });
149891
149958
  if (result.status === 0) continue;
149892
149959
  const detail = commandErrorDetail(result, step.label);
@@ -149913,8 +149980,6 @@ var fallbackReason = (selection, detections, results) => {
149913
149980
  }
149914
149981
  return "no plugin install succeeded";
149915
149982
  };
149916
- var PLUGIN_HOOKS_ROOT = "${CLAUDE_PLUGIN_ROOT}/hooks";
149917
- var SCRIPT_NAME_PATTERN = /\$\{CLAUDE_PLUGIN_ROOT\}\/hooks\/([a-z0-9][a-z0-9-]*\.sh)/;
149918
149983
  var fetchText = async (url4) => {
149919
149984
  const res = await fetch(url4);
149920
149985
  if (!res.ok) {
@@ -149922,15 +149987,6 @@ var fetchText = async (url4) => {
149922
149987
  }
149923
149988
  return await res.text();
149924
149989
  };
149925
- var entryScriptNames = (entry) => {
149926
- const names = [];
149927
- for (const hook of entry.hooks ?? []) {
149928
- if (typeof hook.command !== "string") continue;
149929
- const match = hook.command.match(SCRIPT_NAME_PATTERN);
149930
- if (match?.[1]) names.push(match[1]);
149931
- }
149932
- return names;
149933
- };
149934
149990
  var expandHome = (path3, home) => {
149935
149991
  if (path3 === "~") return home;
149936
149992
  if (path3.startsWith("~/")) return join(home, path3.slice(2));
@@ -149954,20 +150010,11 @@ var installSkill = (skillsDirs, skillMd, verbose) => {
149954
150010
  }
149955
150011
  return installed;
149956
150012
  };
149957
- var installHookScripts = async (home, hooksJson, webBase, verbose) => {
149958
- const names = /* @__PURE__ */ new Set();
149959
- for (const entries of Object.values(hooksJson.hooks)) {
149960
- for (const entry of entries ?? []) {
149961
- for (const name of entryScriptNames(entry)) names.add(name);
149962
- }
149963
- }
149964
- if (names.size === 0) {
149965
- throw new Error("hooks.json declared no recognizable hook scripts");
149966
- }
150013
+ var installHookScripts = async (home, names, webBase, verbose) => {
149967
150014
  const hooksDir = join(home, ".zero", "hooks");
149968
150015
  mkdirSync(hooksDir, { recursive: true });
149969
150016
  return await Promise.all(
149970
- [...names].map(async (name) => {
150017
+ names.map(async (name) => {
149971
150018
  const body = await fetchText(`${webBase}/hooks/${name}`);
149972
150019
  const dest = join(hooksDir, name);
149973
150020
  writeFileSync(dest, body);
@@ -149998,53 +150045,17 @@ var ensureSandboxDomains = (settings, domains, verbose) => {
149998
150045
  }
149999
150046
  }
150000
150047
  };
150001
- var registerHooksInJsonFile = (home, hooksJson, target, verbose) => {
150048
+ var registerHooksInJsonFile = (home, target, verbose) => {
150002
150049
  const hooksDir = join(home, ".zero", "hooks");
150003
150050
  const configPath = join(home, target.relativePath);
150051
+ const scriptNames = fragmentScriptNames(target.fragment);
150052
+ const fragment = substituteHooksDir(target.fragment, hooksDir);
150004
150053
  const result = updateJsonConfigFile(
150005
150054
  configPath,
150006
150055
  (config2) => {
150007
- if (!config2.hooks || typeof config2.hooks !== "object") {
150008
- config2.hooks = {};
150009
- }
150010
- const hooks = config2.hooks;
150011
- for (const [event, entries] of Object.entries(hooksJson.hooks)) {
150012
- for (const entry of entries ?? []) {
150013
- const scripts = entryScriptNames(entry);
150014
- if (scripts.length === 0) {
150015
- if (verbose) {
150016
- stepInfo(`${event}: entry has no plugin hook script \u2014 skipped`);
150017
- }
150018
- continue;
150019
- }
150020
- const rewritten = {
150021
- ...entry,
150022
- hooks: (entry.hooks ?? []).map((hook) => ({
150023
- ...hook,
150024
- command: typeof hook.command === "string" ? hook.command.replaceAll(PLUGIN_HOOKS_ROOT, hooksDir) : hook.command
150025
- }))
150026
- };
150027
- if (!Array.isArray(hooks[event])) hooks[event] = [];
150028
- const list2 = hooks[event];
150029
- const existingIdx = list2.findIndex((existing) => {
150030
- const existingHooks = existing?.hooks;
150031
- if (!Array.isArray(existingHooks)) return false;
150032
- return existingHooks.some(
150033
- (h2) => typeof h2.command === "string" && scripts.some((s) => h2.command.includes(s))
150034
- );
150035
- });
150036
- if (existingIdx >= 0) {
150037
- list2[existingIdx] = rewritten;
150038
- if (verbose) {
150039
- stepInfo(`${event}: ${scripts.join(", ")} entry replaced`);
150040
- }
150041
- } else {
150042
- list2.push(rewritten);
150043
- if (verbose) {
150044
- stepInfo(`${event}: ${scripts.join(", ")} entry appended`);
150045
- }
150046
- }
150047
- }
150056
+ mergeHookFragment(config2, fragment, scriptNames);
150057
+ if (verbose) {
150058
+ stepInfo(`merged hook fragment (${scriptNames.join(", ")})`);
150048
150059
  }
150049
150060
  ensureSandboxDomains(config2, target.network, verbose);
150050
150061
  return true;
@@ -150060,74 +150071,79 @@ var registerHooksInJsonFile = (home, hooksJson, target, verbose) => {
150060
150071
  }
150061
150072
  return true;
150062
150073
  };
150063
- var JSON_HOOK_FORMATS = /* @__PURE__ */ new Set(["claude-settings", "codex-hooks"]);
150064
150074
  var buildHookTargets = (entries) => {
150065
150075
  const byFile = /* @__PURE__ */ new Map();
150066
150076
  for (const entry of entries) {
150067
150077
  const hooks = entry.standalone?.hooks;
150068
- if (!hooks || !JSON_HOOK_FORMATS.has(hooks.format)) continue;
150078
+ if (!hooks?.merge || Object.keys(hooks.merge).length === 0) continue;
150069
150079
  const existing = byFile.get(hooks.file);
150070
150080
  const network = unique([
150071
150081
  ...existing?.network ?? [],
150072
150082
  ...hooks.network ?? []
150073
150083
  ]);
150074
- byFile.set(hooks.file, { relativePath: hooks.file, network });
150084
+ byFile.set(hooks.file, {
150085
+ relativePath: hooks.file,
150086
+ fragment: existing?.fragment ?? hooks.merge,
150087
+ network
150088
+ });
150075
150089
  }
150076
150090
  return [...byFile.values()];
150077
150091
  };
150078
150092
  var runStandaloneInit = async (appContext, options = {}) => {
150079
150093
  const verbose = options.verbose ?? false;
150094
+ const includeDefaultSkillDirs = options.includeDefaultSkillDirs ?? true;
150095
+ const requireHookScripts = options.requireHookScripts ?? true;
150080
150096
  options.onStep?.("fetch");
150081
150097
  console.log("");
150082
- console.log(` ${color.boldCyan("Installing standalone Zero fallback")}`);
150098
+ console.log(
150099
+ ` ${color.boldCyan(options.title ?? "Installing standalone Zero fallback")}`
150100
+ );
150083
150101
  console.log("");
150084
150102
  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"));
150085
150103
  console.log("");
150086
150104
  const home = homedir();
150087
150105
  const webBase = appContext.env.ZERO_WEB_URL;
150088
- const [skillMd, hooksJsonRaw] = await Promise.all([
150089
- fetchText(`${webBase}/SKILL.md`),
150090
- fetchText(`${webBase}/hooks.json`)
150091
- ]);
150092
- let hooksJson;
150093
- try {
150094
- hooksJson = JSON.parse(hooksJsonRaw);
150095
- } catch {
150096
- throw new Error(`${webBase}/hooks.json is not valid JSON`);
150097
- }
150098
- if (!hooksJson?.hooks || typeof hooksJson.hooks !== "object") {
150099
- throw new Error(`${webBase}/hooks.json has no "hooks" object`);
150100
- }
150101
- if (verbose) {
150102
- stepInfo(`fetched ${webBase}/SKILL.md and ${webBase}/hooks.json`);
150106
+ const hookTargets = options.hookTargets ?? [];
150107
+ const hookScriptNames = unique(
150108
+ hookTargets.flatMap((target) => fragmentScriptNames(target.fragment))
150109
+ );
150110
+ if (hookScriptNames.length === 0 && requireHookScripts) {
150111
+ throw new Error(
150112
+ "the agent matrix declared no standalone hook scripts to install"
150113
+ );
150103
150114
  }
150104
- options.onStep?.("skill");
150105
150115
  const skillsDirs = unique([
150106
- ...SKILL_DIRS.map((dir) => join(home, dir)),
150116
+ ...includeDefaultSkillDirs ? SKILL_DIRS.map((dir) => join(home, dir)) : [],
150107
150117
  ...(options.skillsDir ?? []).map((dir) => expandHome(dir, home))
150108
150118
  ]);
150109
- const skillsInstalled = installSkill(skillsDirs, skillMd, verbose);
150110
- if (skillsInstalled.length === 0) {
150111
- throw new Error("could not write the Zero skill to any skills directory");
150119
+ let skillsInstalled = [];
150120
+ if (skillsDirs.length > 0) {
150121
+ const skillMd = await fetchText(`${webBase}/SKILL.md`);
150122
+ if (verbose) {
150123
+ stepInfo(`fetched ${webBase}/SKILL.md`);
150124
+ }
150125
+ options.onStep?.("skill");
150126
+ skillsInstalled = installSkill(skillsDirs, skillMd, verbose);
150127
+ if (skillsInstalled.length === 0) {
150128
+ throw new Error("could not write the Zero skill to any skills directory");
150129
+ }
150130
+ stepSuccess("Skill installed", `${skillsInstalled.length} locations`);
150112
150131
  }
150113
- stepSuccess("Skill installed", `${skillsInstalled.length} locations`);
150114
150132
  options.onStep?.("hooks");
150115
- const hookScriptsInstalled = await installHookScripts(
150116
- home,
150117
- hooksJson,
150118
- webBase,
150119
- verbose
150120
- );
150121
- stepSuccess("Hook scripts installed", "~/.zero/hooks");
150122
- options.onStep?.("settings");
150123
- const registeredFiles = /* @__PURE__ */ new Set();
150124
- for (const target of options.hookTargets ?? []) {
150125
- const registered = registerHooksInJsonFile(
150133
+ let hookScriptsInstalled = [];
150134
+ if (hookScriptNames.length > 0) {
150135
+ hookScriptsInstalled = await installHookScripts(
150126
150136
  home,
150127
- hooksJson,
150128
- target,
150137
+ hookScriptNames,
150138
+ webBase,
150129
150139
  verbose
150130
150140
  );
150141
+ stepSuccess("Hook scripts installed", "~/.zero/hooks");
150142
+ }
150143
+ options.onStep?.("settings");
150144
+ const registeredFiles = /* @__PURE__ */ new Set();
150145
+ for (const target of hookTargets) {
150146
+ const registered = registerHooksInJsonFile(home, target, verbose);
150131
150147
  if (registered) {
150132
150148
  registeredFiles.add(target.relativePath);
150133
150149
  stepSuccess("Hooks registered", `~/${target.relativePath}`);
@@ -150185,7 +150201,6 @@ var runInit = async (appContext, options = {}) => {
150185
150201
  entries.map((entry) => entry.id)
150186
150202
  );
150187
150203
  const requestedAgents = selection.requestedAgents;
150188
- console.log(` Requested agents: ${requestedAgents.join(", ")}`);
150189
150204
  if (dryRun) stepInfo("dry run: no plugin commands or file writes will run");
150190
150205
  const detections = selection.kind === "standalone" ? [] : selection.pluginAgents.flatMap((id) => {
150191
150206
  const entry = entryById.get(id);
@@ -150200,7 +150215,10 @@ var runInit = async (appContext, options = {}) => {
150200
150215
  console.log("");
150201
150216
  const fallbackSkillDirs = [];
150202
150217
  const standaloneOnlyAgents = [];
150218
+ const standaloneNeedAgents = [];
150219
+ const hasStandaloneBits = (entry) => (entry.standalone?.skills ?? []).length > 0 || Boolean(entry.standalone?.hooks?.merge);
150203
150220
  const pluginResults = [];
150221
+ let undetectedCount = 0;
150204
150222
  for (const detection of detections) {
150205
150223
  const { entry, present, exeCallable, method } = detection;
150206
150224
  if (present && exeCallable && method) {
@@ -150212,12 +150230,14 @@ var runInit = async (appContext, options = {}) => {
150212
150230
  stepSkip(`${entry.name}: ${statusLabel(result2)}`, result2.detail);
150213
150231
  } else {
150214
150232
  stepWarn(`${entry.name}: ${statusLabel(result2)}`, result2.detail);
150233
+ if (hasStandaloneBits(entry)) standaloneNeedAgents.push(entry);
150215
150234
  }
150216
150235
  continue;
150217
150236
  }
150218
150237
  if (present && !method) {
150219
- if ((entry.standalone?.skills ?? []).length > 0) {
150238
+ if (hasStandaloneBits(entry)) {
150220
150239
  standaloneOnlyAgents.push(entry);
150240
+ standaloneNeedAgents.push(entry);
150221
150241
  } else {
150222
150242
  const result2 = {
150223
150243
  agent: entry.id,
@@ -150238,13 +150258,26 @@ var runInit = async (appContext, options = {}) => {
150238
150258
  detail
150239
150259
  };
150240
150260
  pluginResults.push(result);
150241
- stepSkip(`${entry.name}: ${statusLabel(result)}`, result.detail);
150261
+ if (present) {
150262
+ stepSkip(`${entry.name}: ${statusLabel(result)}`, result.detail);
150263
+ } else if (verbose) {
150264
+ stepSkip(`${entry.name}: not detected`, result.detail);
150265
+ } else {
150266
+ undetectedCount += 1;
150267
+ }
150242
150268
  if (present && !exeCallable) {
150243
150269
  for (const dir of entry.standalone?.skills ?? []) {
150244
150270
  fallbackSkillDirs.push(join(home, dir));
150245
150271
  }
150272
+ if (hasStandaloneBits(entry)) standaloneNeedAgents.push(entry);
150246
150273
  }
150247
150274
  }
150275
+ if (undetectedCount > 0) {
150276
+ stepSkip(
150277
+ `${undetectedCount} other supported ${undetectedCount === 1 ? "harness" : "harnesses"} not detected`,
150278
+ "run with --verbose to list them"
150279
+ );
150280
+ }
150248
150281
  const standaloneOnlyDirs = unique(
150249
150282
  standaloneOnlyAgents.flatMap(
150250
150283
  (entry) => (entry.standalone?.skills ?? []).map((dir) => join(home, dir))
@@ -150281,8 +150314,11 @@ var runInit = async (appContext, options = {}) => {
150281
150314
  if (dryRun) {
150282
150315
  stepInfo("would install/update ~/.claude/skills/zero");
150283
150316
  stepInfo("would install/update ~/.agents/skills/zero");
150317
+ const targetFiles = buildHookTargets(entries).map(
150318
+ (target) => `~/${target.relativePath}`
150319
+ );
150284
150320
  stepInfo(
150285
- "would register current standalone hooks in ~/.claude/settings.json and ~/.codex/hooks.json"
150321
+ `would register current standalone hooks in ${targetFiles.join(" and ")}`
150286
150322
  );
150287
150323
  } else {
150288
150324
  standaloneResult = await runStandaloneInit(appContext, {
@@ -150303,39 +150339,55 @@ var runInit = async (appContext, options = {}) => {
150303
150339
  console.log(
150304
150340
  " Standalone fallback not used: at least one harness plugin install succeeded."
150305
150341
  );
150306
- if (standaloneOnlyDirs.length > 0) {
150307
- currentStep = "skill";
150308
- if (dryRun) {
150309
- for (const dir of standaloneOnlyDirs) {
150342
+ if (standaloneNeedAgents.length > 0) {
150343
+ const needNames = standaloneNeedAgents.map((entry) => entry.id).join(", ");
150344
+ const needDirs = unique(
150345
+ standaloneNeedAgents.flatMap(
150346
+ (entry) => (entry.standalone?.skills ?? []).map((dir) => join(home, dir))
150347
+ )
150348
+ );
150349
+ const needTargets = buildHookTargets(standaloneNeedAgents);
150350
+ if (!fallbackAllowed) {
150351
+ stepSkip(
150352
+ `--no-fallback: skipping standalone install for ${needNames}`
150353
+ );
150354
+ } else if (dryRun) {
150355
+ for (const dir of needDirs) {
150310
150356
  stepInfo(`would install/update ${join(dir, "zero")}`);
150311
150357
  }
150358
+ for (const target of needTargets) {
150359
+ stepInfo(
150360
+ `would register standalone hooks in ~/${target.relativePath}`
150361
+ );
150362
+ }
150312
150363
  } else {
150313
- const skillMd = await fetchText(
150314
- `${appContext.env.ZERO_WEB_URL}/SKILL.md`
150315
- );
150316
- const installed = installSkill(standaloneOnlyDirs, skillMd, verbose);
150317
- standaloneResult = {
150318
- ...standaloneResult,
150319
- skillsInstalled: installed
150320
- };
150364
+ standaloneResult = await runStandaloneInit(appContext, {
150365
+ verbose,
150366
+ skillsDir: unique([...options.skillsDir ?? [], ...needDirs]),
150367
+ includeDefaultSkillDirs: false,
150368
+ hookTargets: needTargets,
150369
+ requireHookScripts: false,
150370
+ title: `Installing standalone Zero for: ${needNames}`,
150371
+ onStep: (step) => {
150372
+ currentStep = step;
150373
+ }
150374
+ });
150321
150375
  }
150322
150376
  }
150323
150377
  }
150324
150378
  for (const entry of standaloneOnlyAgents) {
150379
+ const what = entry.standalone?.hooks?.merge ? "standalone skill + hooks" : "standalone skill";
150325
150380
  const result = {
150326
150381
  agent: entry.id,
150327
150382
  displayName: entry.name,
150328
150383
  status: dryRun ? "would_install" : "installed",
150329
- detail: "standalone skill (no plugin for this harness)"
150384
+ detail: `${what} (no plugin for this harness)`
150330
150385
  };
150331
150386
  pluginResults.push(result);
150332
150387
  if (dryRun) {
150333
- stepSkip(
150334
- `${entry.name}: would install standalone skill`,
150335
- result.detail
150336
- );
150388
+ stepSkip(`${entry.name}: would install ${what}`, result.detail);
150337
150389
  } else {
150338
- stepSuccess(`${entry.name}: standalone skill installed`, result.detail);
150390
+ stepSuccess(`${entry.name}: ${what} installed`, result.detail);
150339
150391
  }
150340
150392
  }
150341
150393
  currentStep = "complete";
@@ -150368,6 +150420,13 @@ var runInit = async (appContext, options = {}) => {
150368
150420
  });
150369
150421
  sectionDivider();
150370
150422
  console.log(` ${color.boldGreen("Zero init complete.")}`);
150423
+ const skillPath = standaloneResult.skillsInstalled[0];
150424
+ if (skillPath) {
150425
+ console.log(` ${color.dim(`Skill installed at: ${skillPath}`)}`);
150426
+ console.log(
150427
+ ` ${color.dim("Agents: read that file now to use Zero in this session \u2014 future sessions load it automatically.")}`
150428
+ );
150429
+ }
150371
150430
  console.log(
150372
150431
  ` ${color.dim("Next: sign in with `zero auth login` \u2014 it prints a URL to approve in your browser.")}`
150373
150432
  );
@@ -150861,7 +150920,7 @@ var formatSearchResults = (results) => {
150861
150920
  const displayDescription = r2.whatItDoes ?? r2.description;
150862
150921
  const ratingBadge = formatRatingBadge(r2.rating);
150863
150922
  const statusBadge = formatStatusBadge(r2.availabilityStatus);
150864
- const baseCost = r2.cost.amount === "0" ? "Free" : r2.cost.amount === "unknown" ? "variable pricing" : `$${r2.cost.amount}/call`;
150923
+ const baseCost = r2.pricing?.summary ?? (r2.cost.amount === "0" ? "Free" : r2.cost.amount === "unknown" ? "variable pricing" : `$${r2.cost.amount}/call`);
150865
150924
  const costLabel = r2.protocol ? `${baseCost} \xB7 ${r2.protocol}` : baseCost;
150866
150925
  const tokenHint = r2.token ? ` [${r2.token}]` : "";
150867
150926
  return ` ${r2.position}.${tokenHint} ${displayName} \u2014 ${costLabel} \u2014 ${ratingBadge}${statusBadge}
@@ -152648,7 +152707,7 @@ function isErrorEvent(event) {
152648
152707
  function isEvent(candidate) {
152649
152708
  return "undefined" != typeof Event && isInstanceOf(candidate, Event);
152650
152709
  }
152651
- function isPlainObject3(candidate) {
152710
+ function isPlainObject4(candidate) {
152652
152711
  return isBuiltin(candidate, "Object");
152653
152712
  }
152654
152713
  function isInstanceOf(candidate, base2) {
@@ -155845,7 +155904,7 @@ var PostHogBackendClient = class extends PostHogCoreStateless {
155845
155904
  };
155846
155905
  const personProperties = {};
155847
155906
  const groupProperties = {};
155848
- for (const [key, value] of Object.entries(eventProperties)) if (isPlainObject3(value) && groups && key in groups) {
155907
+ for (const [key, value] of Object.entries(eventProperties)) if (isPlainObject4(value) && groups && key in groups) {
155849
155908
  const groupProps = {};
155850
155909
  for (const [groupKey, groupValue] of Object.entries(value)) groupProps[String(groupKey)] = String(groupValue);
155851
155910
  groupProperties[String(key)] = groupProps;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxyz/cli",
3
- "version": "1.13.0",
3
+ "version": "1.14.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "zero": "dist/index.js",