@pushary/agent-hooks 0.89.4 → 0.89.6

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.
@@ -50,9 +50,19 @@ import {
50
50
  guardedArgv
51
51
  } from "../chunk-AIGDBRIJ.js";
52
52
  import {
53
+ daemonServiceManagerAvailable,
54
+ disableDaemonService,
53
55
  ensureDaemonRunning,
54
- replaceDaemonService
55
- } from "../chunk-NLMSSKES.js";
56
+ hasDaemonServiceDefinition,
57
+ readDaemonStatus,
58
+ replaceDaemonService,
59
+ stopDaemonService
60
+ } from "../chunk-NQNCSPEH.js";
61
+ import {
62
+ detectProviderLiveCapabilities,
63
+ hasSpawnProvider,
64
+ resolveControlPolicy
65
+ } from "../chunk-NKHEHEUM.js";
56
66
  import "../chunk-XHKBHWLX.js";
57
67
  import {
58
68
  installGlobally,
@@ -108,15 +118,16 @@ import {
108
118
  exportLine,
109
119
  keyPrefix,
110
120
  readConfigFileKey,
121
+ readControlMode,
111
122
  readKeySource,
112
123
  writeKey
113
- } from "../chunk-W6NPWHBJ.js";
124
+ } from "../chunk-L3YUHEB6.js";
114
125
  import {
115
126
  parseFlags
116
127
  } from "../chunk-GMXKITVA.js";
117
128
  import {
118
129
  renderCommandHelp
119
- } from "../chunk-GPNOHKFO.js";
130
+ } from "../chunk-3XTFX4CL.js";
120
131
  import {
121
132
  installClaudeAlias,
122
133
  resolveShellRc
@@ -487,6 +498,12 @@ var parseVerifyMode = (raw) => {
487
498
  if (VERIFY_MODES.includes(value)) return value;
488
499
  throw new UsageError(`--verify must be one of: ${VERIFY_MODES.join(", ")} (got ${raw})`);
489
500
  };
501
+ var CONTROL_MODES = ["auto", "on", "off"];
502
+ var parseControlMode = (raw) => {
503
+ const value = raw.trim().toLowerCase();
504
+ if (CONTROL_MODES.includes(value)) return value;
505
+ throw new UsageError(`--control must be one of: ${CONTROL_MODES.join(", ")} (got ${raw})`);
506
+ };
490
507
  var effectiveVerify = (options, context) => {
491
508
  if (options.verifyExplicit) return options.verify;
492
509
  if (options.verify === "roundtrip") return "roundtrip";
@@ -494,8 +511,9 @@ var effectiveVerify = (options, context) => {
494
511
  };
495
512
  var SETUP_FLAGS = {
496
513
  bools: ["key-stdin", "skip-phone", "yes", "y", "json", "dry-run", "help", "h"],
497
- values: ["key", "connect", "agents", "verify"]
514
+ values: ["key", "connect", "agents", "verify", "control"]
498
515
  };
516
+ var effectiveControlMode = (options, stored, configPresent) => options.controlExplicit ? options.control : stored ?? (configPresent ? null : options.control);
499
517
  var isAgentChoice = (value) => AGENT_CHOICES.includes(value);
500
518
  var parseAgentSelection = (raw, yes) => {
501
519
  if (raw === void 0) {
@@ -532,6 +550,7 @@ var parseSetupOptions = (input2) => {
532
550
  const connectRaw = flags.values.get("connect");
533
551
  const connectMode = connectRaw === void 0 ? "app" : parseConnectMode(connectRaw);
534
552
  const verifyRaw = flags.values.get("verify");
553
+ const controlRaw = flags.values.get("control");
535
554
  const nonInteractive = yes || agentsFlag !== void 0 || flags.bools.has("json") || !input2.stdinIsTty;
536
555
  const verify = verifyRaw !== void 0 ? parseVerifyMode(verifyRaw) : nonInteractive ? "push" : "roundtrip";
537
556
  const key = flags.values.get("key")?.trim() || null;
@@ -567,7 +586,9 @@ var parseSetupOptions = (input2) => {
567
586
  key,
568
587
  keyStdin,
569
588
  verify,
570
- verifyExplicit: verifyRaw !== void 0
589
+ verifyExplicit: verifyRaw !== void 0,
590
+ control: controlRaw === void 0 ? "auto" : parseControlMode(controlRaw),
591
+ controlExplicit: controlRaw !== void 0
571
592
  };
572
593
  };
573
594
 
@@ -719,7 +740,9 @@ var checkForUpdates = async (current) => {
719
740
  } catch {
720
741
  }
721
742
  };
743
+ var globalInstallReady = false;
722
744
  var installGlobally2 = async () => {
745
+ if (globalInstallReady) return;
723
746
  const version = getPackageVersion();
724
747
  await spinner(`Installing pushary-hook globally (${version})`, async () => {
725
748
  try {
@@ -728,6 +751,7 @@ var installGlobally2 = async () => {
728
751
  throw new Error(npmErrorMessage(err));
729
752
  }
730
753
  });
754
+ globalInstallReady = true;
731
755
  };
732
756
  var _cachedSkillContent = null;
733
757
  var fetchSkillContent = async () => {
@@ -1346,10 +1370,10 @@ ${pluginLocationSnippet(VSCODE_PLUGIN_DIR)}`
1346
1370
  console.log(` ${dim("\u2022")} Fully quit and reopen VS Code to load the plugin`);
1347
1371
  noteManual("Fully quit and reopen VS Code. A Reload Window may not pick up a new plugin location.");
1348
1372
  };
1349
- var saveApiKey = async (apiKey) => {
1373
+ var saveApiKey = async (apiKey, controlMode) => {
1350
1374
  let result;
1351
1375
  await spinner("Saving your API key", async () => {
1352
- result = writeKey(apiKey);
1376
+ result = writeKey(apiKey, controlMode === null ? {} : { controlMode });
1353
1377
  });
1354
1378
  if (!result) throw new Error("Could not save the API key");
1355
1379
  if (result.configBackupPath) {
@@ -1702,7 +1726,7 @@ var AGENT_TARGETS = {
1702
1726
  ],
1703
1727
  custom: ["nothing (prints connection details only)"]
1704
1728
  };
1705
- var reportDryRun = (apiKey, agents, keyCheck) => {
1729
+ var reportDryRun = (apiKey, agents, keyCheck, controlMode) => {
1706
1730
  console.log();
1707
1731
  console.log(` ${bold("Dry run")} ${dim("\u2014 nothing below has been written")}`);
1708
1732
  console.log();
@@ -1710,6 +1734,7 @@ var reportDryRun = (apiKey, agents, keyCheck) => {
1710
1734
  apiKey ? ` ${dim("Key:")} ${dim(`${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`)} ${dim(`(${keyCheck?.kind ?? "not verified"})`)}` : ` ${dim("Key:")} ${dim("none on this machine \u2014 a real run would acquire one")}`
1711
1735
  );
1712
1736
  console.log(` ${dim("Key store:")} ${PUSHARY_CONFIG_FILE}`);
1737
+ console.log(` ${dim("Control:")} ${controlMode ?? "unchanged (legacy install)"}`);
1713
1738
  const rc = resolveShellRc();
1714
1739
  console.log(` ${dim("Shell rc:")} ${rc ? rc.path : dim("none for this shell; the export line would be printed instead")}`);
1715
1740
  console.log();
@@ -1748,6 +1773,7 @@ var main = async () => {
1748
1773
  `);
1749
1774
  process.exit(EXIT.OK);
1750
1775
  }
1776
+ const controlMode = effectiveControlMode(options, readControlMode(), existsSync(PUSHARY_CONFIG_FILE));
1751
1777
  recordSetupOptions(options);
1752
1778
  if (options.json) {
1753
1779
  jsonMode = true;
@@ -1806,8 +1832,8 @@ var main = async () => {
1806
1832
  console.log();
1807
1833
  markStep("resolve-agents");
1808
1834
  const plannedAgents = await resolveAgents(options);
1809
- reportDryRun(null, plannedAgents, null);
1810
- emitJson(options, { ok: true, dryRun: true, agents: plannedAgents, keyCheck: "skipped", key: "none" });
1835
+ reportDryRun(null, plannedAgents, null, controlMode);
1836
+ emitJson(options, { ok: true, dryRun: true, agents: plannedAgents, control: controlMode, keyCheck: "skipped", key: "none" });
1811
1837
  process.exit(EXIT.OK);
1812
1838
  } else if (connectMode === "app" && canShowPairingQr() && !options.skipPhone) {
1813
1839
  console.log(` ${dim("Pairing with your Pushary app.")}`);
@@ -1894,12 +1920,12 @@ var main = async () => {
1894
1920
  markStep("resolve-agents");
1895
1921
  const agents = await resolveAgents(options);
1896
1922
  if (options.dryRun) {
1897
- reportDryRun(trimmedKey, agents, keyCheck);
1898
- emitJson(options, { ok: true, dryRun: true, agents, keyCheck: keyCheck?.kind ?? "skipped" });
1923
+ reportDryRun(trimmedKey, agents, keyCheck, controlMode);
1924
+ emitJson(options, { ok: true, dryRun: true, agents, control: controlMode, keyCheck: keyCheck?.kind ?? "skipped" });
1899
1925
  process.exit(EXIT.OK);
1900
1926
  }
1901
1927
  markStep("save-key");
1902
- const rc = await saveApiKey(trimmedKey);
1928
+ const rc = await saveApiKey(trimmedKey, controlMode);
1903
1929
  const context = { rc };
1904
1930
  if (envKey && envKey !== trimmedKey) {
1905
1931
  noteManual(
@@ -1909,6 +1935,7 @@ var main = async () => {
1909
1935
  const failed = [];
1910
1936
  const completed = [];
1911
1937
  const skipped = [];
1938
+ let controlLifecycleFailure = null;
1912
1939
  if (agents.length === 0) {
1913
1940
  console.log(`
1914
1941
  ${check} API key saved. No agents configured.`);
@@ -1947,6 +1974,16 @@ var main = async () => {
1947
1974
  await offerClaudeAlias(options);
1948
1975
  }
1949
1976
  }
1977
+ if (controlMode === "off") {
1978
+ markStep("control:disable");
1979
+ const disabled = await disableDaemonService();
1980
+ if (disabled.kind !== "ok") {
1981
+ controlLifecycleFailure = disabled.detail;
1982
+ console.log(` ${yellow("!")} Could not fully disable phone-start: ${controlLifecycleFailure}`);
1983
+ } else {
1984
+ console.log(` ${check} Phone-start background service disabled.`);
1985
+ }
1986
+ }
1950
1987
  markStep("connect-phone");
1951
1988
  const skipPhone = options.skipPhone;
1952
1989
  let phone = null;
@@ -2008,6 +2045,8 @@ var main = async () => {
2008
2045
  failed,
2009
2046
  keyCheck: "locked",
2010
2047
  rc: rc.kind,
2048
+ control: controlMode,
2049
+ daemon: controlLifecycleFailure ? "unavailable" : "skipped",
2011
2050
  exitCode: report.exitCode
2012
2051
  });
2013
2052
  await reportSetupOutcome({
@@ -2028,11 +2067,64 @@ var main = async () => {
2028
2067
  const reachesYou = phone?.reachesYou ?? true;
2029
2068
  markStep("closing");
2030
2069
  const keyIsTrusted = keyFromPairing || keyCheck?.kind === "ok";
2031
- const daemon = keyIsTrusted && completed.length > 0 ? await (async () => {
2032
- const service = await replaceDaemonService();
2033
- return service.kind === "ok" ? ensureDaemonRunning({ spawnDaemon: () => {
2034
- } }) : { kind: "unavailable", detail: service.detail };
2035
- })() : null;
2070
+ const managedControlAgents = ["claude_code", "codex", "gemini_cli"];
2071
+ const configuredControlAgents = managedControlAgents.filter((agent) => agentIsWired(agent));
2072
+ const controlCapabilities = detectProviderLiveCapabilities(configuredControlAgents.map(
2073
+ (agent) => agent === "claude_code" ? "claude" : agent === "gemini_cli" ? "gemini" : agent
2074
+ ));
2075
+ const providerAvailable = hasSpawnProvider(controlCapabilities);
2076
+ const managerAvailable = daemonServiceManagerAvailable();
2077
+ const servicePresent = readDaemonStatus().kind !== "absent" || hasDaemonServiceDefinition();
2078
+ let controlPolicy = resolveControlPolicy({
2079
+ mode: controlMode,
2080
+ providerAvailable,
2081
+ managerAvailable,
2082
+ servicePresent
2083
+ });
2084
+ if (controlPolicy === "legacy") {
2085
+ let legacyRuntimeFailure = null;
2086
+ try {
2087
+ await installGlobally2();
2088
+ const replaced = await replaceDaemonService();
2089
+ if (replaced.kind !== "ok") throw new Error(replaced.detail);
2090
+ } catch (err) {
2091
+ legacyRuntimeFailure = formatError(err);
2092
+ }
2093
+ let retired = legacyRuntimeFailure !== null;
2094
+ let stopped = retired ? await disableDaemonService() : await stopDaemonService();
2095
+ if (!retired && stopped.kind !== "ok") {
2096
+ legacyRuntimeFailure = stopped.detail;
2097
+ retired = true;
2098
+ stopped = await disableDaemonService();
2099
+ }
2100
+ if (stopped.kind !== "ok") {
2101
+ controlLifecycleFailure = [legacyRuntimeFailure, stopped.detail].filter(Boolean).join("; ");
2102
+ console.log(` ${yellow("!")} Could not make the legacy phone-start service safe: ${controlLifecycleFailure}`);
2103
+ } else if (retired) {
2104
+ controlPolicy = resolveControlPolicy({
2105
+ mode: controlMode,
2106
+ providerAvailable,
2107
+ managerAvailable,
2108
+ servicePresent: false
2109
+ });
2110
+ console.log(` ${yellow("!")} Retired the legacy phone-start service because it could not be safely repinned: ${legacyRuntimeFailure}`);
2111
+ }
2112
+ }
2113
+ if (controlMode === "on" && controlPolicy !== "enabled") {
2114
+ console.log(` ${yellow("!")} Phone-start was required, but this machine has no supported provider or native background-service manager.`);
2115
+ } else if (controlPolicy === "legacy") {
2116
+ console.log(` ${yellow("!")} A legacy phone-start service cannot be safely adopted in this session. Disable it with --control off or restore its provider and native service manager.`);
2117
+ }
2118
+ const daemon = keyIsTrusted && controlPolicy === "enabled" ? await (async () => {
2119
+ try {
2120
+ await installGlobally2();
2121
+ const service = await replaceDaemonService();
2122
+ return service.kind === "ok" ? ensureDaemonRunning({ spawnDaemon: () => {
2123
+ } }) : { kind: "unavailable", detail: service.detail };
2124
+ } catch (err) {
2125
+ return { kind: "unavailable", detail: formatError(err) };
2126
+ }
2127
+ })() : controlMode === "on" || controlPolicy === "legacy" ? { kind: "unavailable", detail: "Managed control is required but no usable provider/service-manager pair is available" } : null;
2036
2128
  const closing = describeClosing({
2037
2129
  agentsOk,
2038
2130
  configuredNothing,
@@ -2040,7 +2132,7 @@ var main = async () => {
2040
2132
  failed,
2041
2133
  reachChecked,
2042
2134
  reachesYou,
2043
- daemonReady: daemon === null ? null : daemon.kind === "running" || daemon.kind === "started",
2135
+ daemonReady: controlLifecycleFailure ? false : daemon === null ? null : daemon.kind === "running" || daemon.kind === "started",
2044
2136
  // null when the check did not run, so it changes nothing. Only an outright
2045
2137
  // `false` is allowed to hold back "Setup complete.".
2046
2138
  roundTripAnswered: roundTrip === null ? null : roundTrip.kind === "answered" ? true : roundTrip.kind === "unanswered" ? false : null
@@ -2085,7 +2177,10 @@ var main = async () => {
2085
2177
  roundTrip: roundTrip === null ? "skipped" : roundTrip.kind,
2086
2178
  rc: rc.kind,
2087
2179
  rcPath: rc.path,
2088
- daemon: daemon?.kind ?? "skipped",
2180
+ daemon: controlLifecycleFailure ? "unavailable" : daemon?.kind ?? "skipped",
2181
+ control: controlMode,
2182
+ controlPolicy,
2183
+ controlCapabilities,
2089
2184
  exitCode
2090
2185
  });
2091
2186
  if (exitCode !== EXIT.OK) {
@@ -4,8 +4,8 @@ import {
4
4
  } from "../chunk-VZKXBFUA.js";
5
5
  import {
6
6
  exitOnHelpFlag
7
- } from "../chunk-KJ57OPCC.js";
8
- import "../chunk-GPNOHKFO.js";
7
+ } from "../chunk-LURY4WNF.js";
8
+ import "../chunk-3XTFX4CL.js";
9
9
  import "../chunk-DINDL2CF.js";
10
10
  import "../chunk-KZERVKTD.js";
11
11
  import {
@@ -20,14 +20,14 @@ import {
20
20
  } from "../chunk-5RUIFDTP.js";
21
21
  import {
22
22
  readKeySource
23
- } from "../chunk-W6NPWHBJ.js";
23
+ } from "../chunk-L3YUHEB6.js";
24
24
  import {
25
25
  rejectUnknownFlags
26
26
  } from "../chunk-GMXKITVA.js";
27
27
  import {
28
28
  exitOnHelpFlag
29
- } from "../chunk-KJ57OPCC.js";
30
- import "../chunk-GPNOHKFO.js";
29
+ } from "../chunk-LURY4WNF.js";
30
+ import "../chunk-3XTFX4CL.js";
31
31
  import "../chunk-BC3VCZ3E.js";
32
32
  import "../chunk-DINDL2CF.js";
33
33
  import "../chunk-BSZYIAZL.js";
@@ -7,8 +7,8 @@ import {
7
7
  } from "../chunk-GMXKITVA.js";
8
8
  import {
9
9
  exitOnHelpFlag
10
- } from "../chunk-KJ57OPCC.js";
11
- import "../chunk-GPNOHKFO.js";
10
+ } from "../chunk-LURY4WNF.js";
11
+ import "../chunk-3XTFX4CL.js";
12
12
  import "../chunk-DINDL2CF.js";
13
13
  import {
14
14
  UsageError
@@ -1,253 +1,98 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- detectClaudeVersion
4
- } from "../chunk-475T3XIO.js";
5
- import {
6
- GEMINI_HOOK_BINARY,
7
- addClaudeMcpServer,
8
- addGeminiHooks,
9
- addGeminiMcpServer,
10
- addPusharyHooks,
11
- addPusharyToolPermissions,
12
- hasGeminiHooks
13
- } from "../chunk-7TS2G4QG.js";
3
+ reapplyAllAgents
4
+ } from "../chunk-5TASNKRX.js";
5
+ import "../chunk-475T3XIO.js";
6
+ import "../chunk-OBI4YMCT.js";
7
+ import "../chunk-7TS2G4QG.js";
14
8
  import "../chunk-ATBKJNWS.js";
9
+ import "../chunk-AIGDBRIJ.js";
15
10
  import {
16
- guardBinary
17
- } from "../chunk-AIGDBRIJ.js";
18
- import {
11
+ daemonServiceManagerAvailable,
12
+ disableDaemonService,
19
13
  ensureDaemonRunning,
20
- replaceDaemonService
21
- } from "../chunk-NLMSSKES.js";
14
+ hasDaemonServiceDefinition,
15
+ readDaemonStatus,
16
+ replaceDaemonService,
17
+ stopDaemonService
18
+ } from "../chunk-NQNCSPEH.js";
19
+ import {
20
+ detectProviderLiveCapabilities,
21
+ hasSpawnProvider,
22
+ resolveControlPolicy
23
+ } from "../chunk-NKHEHEUM.js";
22
24
  import {
23
25
  needsShell
24
26
  } from "../chunk-XHKBHWLX.js";
25
27
  import {
26
28
  execNpm,
27
29
  npmErrorMessage,
28
- resolveGlobalBinDir,
29
30
  resolveGlobalBinary
30
31
  } from "../chunk-PZA7G4CN.js";
31
32
  import "../chunk-2HYPZLYY.js";
32
33
  import {
33
- CODEX_HOOK_BINARY,
34
- addCodexHookTrust,
35
- addCodexHooks,
36
- addCodexMcpServer,
37
34
  claudeJson,
38
35
  codexConfigToml,
39
- codexHookPositions,
40
- codexHooksJson,
41
36
  cursorPluginDir,
42
37
  geminiSettings,
43
- hasCodexHooks,
44
- vscodePluginDir,
45
- vscodePluginMcp
38
+ vscodePluginDir
46
39
  } from "../chunk-V37ZXMH3.js";
40
+ import {
41
+ readControlMode
42
+ } from "../chunk-L3YUHEB6.js";
47
43
  import {
48
44
  exitOnHelpFlag
49
- } from "../chunk-KJ57OPCC.js";
50
- import "../chunk-GPNOHKFO.js";
45
+ } from "../chunk-LURY4WNF.js";
46
+ import "../chunk-3XTFX4CL.js";
47
+ import "../chunk-BC3VCZ3E.js";
51
48
  import {
52
49
  isNewerVersion
53
50
  } from "../chunk-DINDL2CF.js";
51
+ import "../chunk-OFRNZWC4.js";
54
52
  import "../chunk-KZERVKTD.js";
55
- import {
56
- readJsonSafe
57
- } from "../chunk-6MTNS63X.js";
53
+ import "../chunk-6MTNS63X.js";
58
54
  import {
59
55
  getApiKey
60
56
  } from "../chunk-2UMNXADU.js";
61
57
  import "../chunk-L4C2JXJS.js";
62
58
 
63
59
  // bin/pushary-upgrade.ts
64
- import { existsSync as existsSync2 } from "fs";
65
- import { homedir as homedir2 } from "os";
66
- import { join as join2 } from "path";
67
- import { execFileSync } from "child_process";
68
-
69
- // src/reapply.ts
70
- import { join, dirname } from "path";
60
+ import { existsSync } from "fs";
71
61
  import { homedir } from "os";
72
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
73
- import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
74
- var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
75
- var CLAUDE_JSON = join(homedir(), ".claude.json");
76
- var readJson = (filePath) => {
77
- const result = readJsonSafe(filePath);
78
- if (result.kind === "ok") return result.value;
79
- if (result.kind === "missing") return {};
80
- return null;
81
- };
82
- var writeJson = (filePath, data) => {
83
- const dir = dirname(filePath);
84
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
85
- writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
86
- };
87
- var hasPusharyClaudeHooks = (settings) => JSON.stringify(settings.hooks ?? {}).includes("pushary-hook");
88
- var hasPusharyMcp = (claudeJson2) => !!claudeJson2.mcpServers?.pushary;
89
- var reapplyClaudeSettings = (apiKey, paths = {}) => {
90
- const settingsPath = paths.settingsPath ?? CLAUDE_SETTINGS;
91
- const claudeJsonPath = paths.claudeJsonPath ?? CLAUDE_JSON;
92
- const settings = readJson(settingsPath);
93
- const claudeJson2 = readJson(claudeJsonPath);
94
- const configured = settings != null && hasPusharyClaudeHooks(settings) || claudeJson2 != null && hasPusharyMcp(claudeJson2);
95
- if (!configured) return { reapplied: false, hooks: false, mcp: false };
96
- const settingsWritable = settings != null;
97
- const claudeJsonWritable = claudeJson2 != null;
98
- let hooks = false;
99
- let mcp = false;
100
- try {
101
- if (!settingsWritable) throw new Error("settings.json could not be parsed");
102
- const s = settings;
103
- addPusharyToolPermissions(s);
104
- addPusharyHooks(s, resolveGlobalBinDir("pushary-hook"), detectClaudeVersion());
105
- writeJson(settingsPath, s);
106
- hooks = true;
107
- } catch {
108
- }
109
- if (apiKey) {
110
- try {
111
- if (!claudeJsonWritable) throw new Error(".claude.json could not be parsed");
112
- const cj = claudeJson2;
113
- addClaudeMcpServer(cj, apiKey);
114
- writeJson(claudeJsonPath, cj);
115
- mcp = true;
116
- } catch {
117
- }
118
- }
119
- return { reapplied: hooks || mcp, hooks, mcp };
120
- };
121
- var readToml = (filePath) => {
122
- try {
123
- const raw = readFileSync(filePath, "utf-8");
124
- return raw.trim() === "" ? {} : parseTOML(raw);
125
- } catch {
126
- return null;
127
- }
128
- };
129
- var reapplyCodexSettings = (apiKey, paths = {}) => {
130
- const configPath = paths.configPath ?? codexConfigToml();
131
- const hooksPath = paths.hooksPath ?? codexHooksJson();
132
- const config = readToml(configPath);
133
- const hooksConfig = readJson(hooksPath);
134
- const configured = config != null && !!config.mcp_servers?.pushary || hooksConfig != null && hasCodexHooks(hooksConfig);
135
- if (!configured) return { reapplied: false, hooks: false, mcp: false };
136
- const command = guardBinary(resolveGlobalBinary(CODEX_HOOK_BINARY) ?? CODEX_HOOK_BINARY);
137
- let hooks = false;
138
- let mcp = false;
139
- if (hooksConfig != null) {
140
- try {
141
- addCodexHooks(hooksConfig, command);
142
- writeJson(hooksPath, hooksConfig);
143
- hooks = true;
144
- } catch {
145
- }
146
- }
147
- if (config != null) {
148
- try {
149
- if (hooks) addCodexHookTrust(config, hooksPath, command, codexHookPositions(hooksConfig ?? void 0));
150
- if (apiKey) {
151
- addCodexMcpServer(config, apiKey);
152
- mcp = true;
153
- }
154
- if (hooks || apiKey) writeFileSync(configPath, stringifyTOML(config), "utf-8");
155
- } catch {
156
- }
157
- }
158
- return { reapplied: hooks || mcp, hooks, mcp };
159
- };
160
- var reapplyGeminiSettings = (apiKey, paths = {}) => {
161
- const settingsPath = paths.settingsPath ?? join(homedir(), ".gemini", "settings.json");
162
- const settings = readJson(settingsPath);
163
- if (settings == null) return { reapplied: false, hooks: false, mcp: false };
164
- const configured = hasGeminiHooks(settings) || !!settings.mcpServers?.pushary;
165
- if (!configured) return { reapplied: false, hooks: false, mcp: false };
166
- let hooks = false;
167
- let mcp = false;
168
- try {
169
- addGeminiHooks(settings, guardBinary(resolveGlobalBinary(GEMINI_HOOK_BINARY) ?? GEMINI_HOOK_BINARY));
170
- hooks = true;
171
- if (apiKey) {
172
- addGeminiMcpServer(settings, apiKey);
173
- mcp = true;
174
- }
175
- writeJson(settingsPath, settings);
176
- } catch {
177
- }
178
- return { reapplied: hooks || mcp, hooks, mcp };
179
- };
180
- var reapplyPluginMcp = (mcpPath, apiKey) => {
181
- if (!apiKey) return { reapplied: false, hooks: false, mcp: false };
182
- const mcpConfig = readJson(mcpPath);
183
- if (mcpConfig == null) return { reapplied: false, hooks: false, mcp: false };
184
- const servers = mcpConfig.mcpServers;
185
- if (!servers?.pushary) return { reapplied: false, hooks: false, mcp: false };
186
- try {
187
- addClaudeMcpServer(mcpConfig, apiKey);
188
- writeJson(mcpPath, mcpConfig);
189
- return { reapplied: true, hooks: false, mcp: true };
190
- } catch {
191
- return { reapplied: false, hooks: false, mcp: false };
192
- }
193
- };
194
- var reapplyCursorPlugin = (apiKey, paths = {}) => reapplyPluginMcp(
195
- paths.pluginMcpPath ?? join(homedir(), ".cursor", "plugins", "local", "pushary", "mcp.json"),
196
- apiKey
197
- );
198
- var reapplyVsCodePlugin = (apiKey, paths = {}) => reapplyPluginMcp(paths.pluginMcpPath ?? vscodePluginMcp(), apiKey);
199
- var reapplyAllAgents = (apiKey) => {
200
- const runners = [
201
- { label: "Claude Code", run: () => reapplyClaudeSettings(apiKey) },
202
- { label: "Codex", run: () => reapplyCodexSettings(apiKey) },
203
- { label: "Gemini CLI", run: () => reapplyGeminiSettings(apiKey) },
204
- { label: "Cursor", run: () => reapplyCursorPlugin(apiKey) },
205
- { label: "VS Code", run: () => reapplyVsCodePlugin(apiKey) }
206
- ];
207
- const done = [];
208
- for (const runner of runners) {
209
- try {
210
- const result = runner.run();
211
- if (result.reapplied) done.push({ label: runner.label, result });
212
- } catch {
213
- }
214
- }
215
- return done;
216
- };
217
-
218
- // bin/pushary-upgrade.ts
62
+ import { join } from "path";
63
+ import { execFileSync } from "child_process";
219
64
  exitOnHelpFlag("upgrade");
220
65
  var detectInstallModes = () => {
221
66
  const candidates = [
222
67
  {
223
68
  label: "Cursor plugin",
224
69
  upgrade: "updates itself through Cursor",
225
- present: existsSync2(cursorPluginDir())
70
+ present: existsSync(cursorPluginDir())
226
71
  },
227
72
  {
228
73
  label: "VS Code plugin",
229
74
  upgrade: "npx @pushary/agent-hooks@latest setup --agents vscode",
230
- present: existsSync2(vscodePluginDir())
75
+ present: existsSync(vscodePluginDir())
231
76
  },
232
77
  {
233
78
  label: "Codex config",
234
79
  upgrade: "npx @pushary/agent-hooks@latest setup --agents codex",
235
- present: existsSync2(codexConfigToml())
80
+ present: existsSync(codexConfigToml())
236
81
  },
237
82
  {
238
83
  label: "Claude Code config",
239
84
  upgrade: "npx @pushary/agent-hooks@latest setup --agents claude_code",
240
- present: existsSync2(claudeJson())
85
+ present: existsSync(claudeJson())
241
86
  },
242
87
  {
243
88
  label: "Gemini config",
244
89
  upgrade: "npx @pushary/agent-hooks@latest setup --agents gemini_cli",
245
- present: existsSync2(geminiSettings())
90
+ present: existsSync(geminiSettings())
246
91
  },
247
92
  {
248
93
  label: "Hermes plugin (pip)",
249
94
  upgrade: "npx @pushary/agent-hooks@latest setup --agents hermes",
250
- present: existsSync2(join2(homedir2(), ".hermes"))
95
+ present: existsSync(join(homedir(), ".hermes"))
251
96
  }
252
97
  ];
253
98
  return candidates.filter((candidate) => candidate.present).map(({ label, upgrade }) => ({ label, upgrade }));
@@ -278,13 +123,37 @@ var finishUpgrade = async () => {
278
123
  } catch {
279
124
  apiKey = void 0;
280
125
  }
281
- const refreshed = reapplyAllAgents(apiKey);
126
+ const inspectedAgents = reapplyAllAgents(apiKey);
127
+ const refreshed = inspectedAgents.filter((agent) => agent.result.reapplied);
128
+ const configuredControlProviders = inspectedAgents.filter((agent) => agent.result.configured && (agent.id === "claude" || agent.id === "codex" || agent.id === "gemini")).map((agent) => agent.id);
129
+ const controlMode = readControlMode();
130
+ const daemonStatus = readDaemonStatus();
131
+ const serviceWasPresent = daemonStatus.kind !== "absent" || hasDaemonServiceDefinition();
132
+ const controlPolicy = resolveControlPolicy({
133
+ mode: controlMode,
134
+ providerAvailable: hasSpawnProvider(detectProviderLiveCapabilities(configuredControlProviders)),
135
+ managerAvailable: daemonServiceManagerAvailable(),
136
+ servicePresent: serviceWasPresent
137
+ });
282
138
  let daemonReady = true;
283
- if (apiKey) {
284
- const service = await replaceDaemonService();
285
- const daemon = service.kind === "ok" ? await ensureDaemonRunning({ spawnDaemon: () => {
286
- } }) : null;
287
- daemonReady = service.kind === "ok" && (daemon?.kind === "running" || daemon?.kind === "started");
139
+ let legacyStopFailure = null;
140
+ if (controlPolicy === "disabled" && serviceWasPresent) {
141
+ daemonReady = (await disableDaemonService()).kind === "ok";
142
+ } else if (controlPolicy === "legacy") {
143
+ const stopped = await stopDaemonService();
144
+ legacyStopFailure = stopped.kind === "ok" ? null : stopped.detail;
145
+ daemonReady = false;
146
+ } else if (controlPolicy === "enabled") {
147
+ if (!apiKey) {
148
+ daemonReady = false;
149
+ } else {
150
+ const service = await replaceDaemonService();
151
+ const daemon = service.kind === "ok" ? await ensureDaemonRunning({ spawnDaemon: () => {
152
+ } }) : null;
153
+ daemonReady = service.kind === "ok" && (daemon?.kind === "running" || daemon?.kind === "started");
154
+ }
155
+ } else if (controlMode === "on" && controlPolicy === "unsupported") {
156
+ daemonReady = false;
288
157
  }
289
158
  console.log();
290
159
  if (refreshed.length > 0) {
@@ -302,11 +171,11 @@ var finishUpgrade = async () => {
302
171
  }
303
172
  console.log();
304
173
  if (!daemonReady) {
305
- console.error("Upgrade installed, but the background service did not start. Run: pushary daemon install");
174
+ console.error(controlPolicy === "disabled" ? "Upgrade installed, but the disabled background service could not be fully removed. Re-run setup with --control off." : controlPolicy === "legacy" ? legacyStopFailure ? `Upgrade installed, but the legacy background service could not be stopped: ${legacyStopFailure}. Run setup with --control off.` : "Upgrade installed and the legacy background service was stopped, but it cannot be safely adopted. Run setup with --control off, or restore its provider and native service manager." : controlPolicy === "enabled" && !apiKey ? "Upgrade installed, but phone-start could not be reconciled because no API key was readable. Re-run setup." : "Upgrade installed, but the background service did not start. Run: pushary daemon install");
306
175
  process.exitCode = 1;
307
176
  return;
308
177
  }
309
- console.log("Done. Your hooks and background service use the new global install.");
178
+ console.log(controlPolicy === "disabled" ? "Done. Your hooks use the new global install; phone-start remains disabled." : controlPolicy === "unsupported" || controlPolicy === "preserve-absent" ? "Done. Your hooks use the new global install; no background service was added." : "Done. Your hooks and background service use the new global install.");
310
179
  console.log("Verify with: pushary doctor");
311
180
  };
312
181
  var main = async () => {