@pushary/agent-hooks 0.89.5 → 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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ensureDaemonRunning
4
- } from "../chunk-TRQ46MXR.js";
4
+ } from "../chunk-NQNCSPEH.js";
5
5
  import {
6
6
  buildTranscriptRecords,
7
7
  decodePublicKey,
@@ -15,7 +15,7 @@ import "../chunk-ATBKJNWS.js";
15
15
  import "../chunk-AIGDBRIJ.js";
16
16
  import {
17
17
  disableDaemonService
18
- } from "../chunk-TRQ46MXR.js";
18
+ } from "../chunk-NQNCSPEH.js";
19
19
  import "../chunk-XHKBHWLX.js";
20
20
  import {
21
21
  execNpm
@@ -1,22 +1,33 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ inspectConfiguredManagedProviders
4
+ } from "../chunk-5TASNKRX.js";
5
+ import "../chunk-475T3XIO.js";
2
6
  import {
3
7
  HEARTBEAT_INTERVAL_MS,
4
8
  sendHeartbeat
5
9
  } from "../chunk-VP7SDXYG.js";
10
+ import "../chunk-OBI4YMCT.js";
11
+ import "../chunk-7TS2G4QG.js";
12
+ import "../chunk-ATBKJNWS.js";
13
+ import "../chunk-AIGDBRIJ.js";
6
14
  import {
7
15
  DAEMON_LEASE_RENEW_INTERVAL_MS,
8
16
  acquireDaemonLease,
17
+ daemonServiceManagerAvailable,
9
18
  ensureDaemonRunning,
19
+ hasDaemonServiceDefinition,
10
20
  manageDaemonService,
11
21
  parseDaemonAction,
12
22
  readDaemonStatus,
13
23
  replaceDaemonService,
14
24
  requestDaemonHandoff,
15
25
  stopDaemonRunning
16
- } from "../chunk-TRQ46MXR.js";
26
+ } from "../chunk-NQNCSPEH.js";
17
27
  import {
18
28
  detectProviderLiveCapabilities,
19
- hasSpawnProvider
29
+ hasSpawnProvider,
30
+ resolveControlPolicy
20
31
  } from "../chunk-NKHEHEUM.js";
21
32
  import {
22
33
  findLiveProcessBySpawnId,
@@ -32,11 +43,16 @@ import {
32
43
  getMachineId
33
44
  } from "../chunk-2HYPZLYY.js";
34
45
  import "../chunk-V37ZXMH3.js";
46
+ import {
47
+ readControlMode
48
+ } from "../chunk-L3YUHEB6.js";
35
49
  import {
36
50
  exitOnHelpFlag
37
51
  } from "../chunk-LURY4WNF.js";
38
52
  import "../chunk-3XTFX4CL.js";
53
+ import "../chunk-BC3VCZ3E.js";
39
54
  import "../chunk-DINDL2CF.js";
55
+ import "../chunk-OFRNZWC4.js";
40
56
  import {
41
57
  EXIT
42
58
  } from "../chunk-KZERVKTD.js";
@@ -174,9 +190,12 @@ var normalizeLaunchAgent = (requestedAgent) => {
174
190
  if (requestedAgent === "codex" || requestedAgent === "gemini") return requestedAgent;
175
191
  return null;
176
192
  };
177
- var providerLaunchError = (requestedAgent, binaries) => {
193
+ var providerLaunchError = (requestedAgent, binaries, configuredProviders) => {
178
194
  const agent = normalizeLaunchAgent(requestedAgent);
179
195
  if (!agent) return `Unsupported agent provider: ${requestedAgent}`;
196
+ if (configuredProviders && !configuredProviders.includes(agent)) {
197
+ return `${agent === "claude" ? "Claude Code" : agent === "codex" ? "Codex" : "Gemini"} is not configured for Pushary on this machine`;
198
+ }
180
199
  if (binaries[agent]) return null;
181
200
  return agent === "claude" ? "Claude Code is not installed on this machine" : `${agent === "codex" ? "Codex" : "Gemini"} CLI is not installed on this machine`;
182
201
  };
@@ -241,7 +260,8 @@ var runSpawnDaemon = async () => {
241
260
  const machineId = getMachineId();
242
261
  const baseUrl = getBaseUrl();
243
262
  const pusharyBin = resolveBinary("pushary") ?? "pushary";
244
- const providerCapabilities = detectProviderLiveCapabilities();
263
+ const configuredProviders = inspectConfiguredManagedProviders();
264
+ const providerCapabilities = detectProviderLiveCapabilities(configuredProviders);
245
265
  const siblingEntry = process.argv[1] ? join(dirname(process.argv[1]), "pushary.js") : "";
246
266
  const codexBridgeEntry = process.argv[1] ? join(dirname(process.argv[1]), "pushary-codex-bridge.js") : "pushary-codex-bridge";
247
267
  const geminiBridgeEntry = process.argv[1] ? join(dirname(process.argv[1]), "pushary-gemini-bridge.js") : "pushary-gemini-bridge";
@@ -298,7 +318,7 @@ var runSpawnDaemon = async () => {
298
318
  claude: agent === "claude" ? providerBin ?? null : null,
299
319
  codex: agent === "codex" ? providerBin ?? null : null,
300
320
  gemini: agent === "gemini" ? providerBin ?? null : null
301
- });
321
+ }, configuredProviders);
302
322
  if (unavailable) return Promise.reject(new Error(unavailable));
303
323
  spawnTimestamps.push(Date.now());
304
324
  const cwd = directory.cwd;
@@ -598,6 +618,24 @@ var main = async () => {
598
618
  `);
599
619
  return status.kind === "running" ? EXIT.OK : EXIT.UNREACHABLE;
600
620
  }
621
+ if (action === "install" || action === "start" || action === "restart") {
622
+ if (!getApiKey()) {
623
+ process.stderr.write("[pushary] daemon service has no readable API key; re-run setup.\n");
624
+ return EXIT.FAILED;
625
+ }
626
+ const status = readDaemonStatus();
627
+ const controlPolicy = resolveControlPolicy({
628
+ mode: readControlMode() ?? (action === "install" ? "on" : null),
629
+ providerAvailable: hasSpawnProvider(detectProviderLiveCapabilities(inspectConfiguredManagedProviders())),
630
+ managerAvailable: daemonServiceManagerAvailable(),
631
+ servicePresent: status.kind !== "absent" || hasDaemonServiceDefinition()
632
+ });
633
+ if (controlPolicy !== "enabled") {
634
+ process.stderr.write(`[pushary] daemon service is ${controlPolicy}; run setup with --control on to configure phone-start safely.
635
+ `);
636
+ return EXIT.FAILED;
637
+ }
638
+ }
601
639
  if (action === "install") {
602
640
  const installed = await replaceDaemonService();
603
641
  if (installed.kind !== "ok") {
@@ -34,7 +34,7 @@ import {
34
34
  daemonServiceManagerAvailable,
35
35
  hasDaemonServiceDefinition,
36
36
  readDaemonStatus
37
- } from "../chunk-TRQ46MXR.js";
37
+ } from "../chunk-NQNCSPEH.js";
38
38
  import {
39
39
  detectProviderLiveCapabilities,
40
40
  hasSpawnProvider,
@@ -55,8 +55,9 @@ import {
55
55
  ensureDaemonRunning,
56
56
  hasDaemonServiceDefinition,
57
57
  readDaemonStatus,
58
- replaceDaemonService
59
- } from "../chunk-TRQ46MXR.js";
58
+ replaceDaemonService,
59
+ stopDaemonService
60
+ } from "../chunk-NQNCSPEH.js";
60
61
  import {
61
62
  detectProviderLiveCapabilities,
62
63
  hasSpawnProvider,
@@ -1934,7 +1935,7 @@ var main = async () => {
1934
1935
  const failed = [];
1935
1936
  const completed = [];
1936
1937
  const skipped = [];
1937
- let controlDisableFailure = null;
1938
+ let controlLifecycleFailure = null;
1938
1939
  if (agents.length === 0) {
1939
1940
  console.log(`
1940
1941
  ${check} API key saved. No agents configured.`);
@@ -1977,8 +1978,8 @@ var main = async () => {
1977
1978
  markStep("control:disable");
1978
1979
  const disabled = await disableDaemonService();
1979
1980
  if (disabled.kind !== "ok") {
1980
- controlDisableFailure = disabled.detail;
1981
- console.log(` ${yellow("!")} Could not fully disable phone-start: ${controlDisableFailure}`);
1981
+ controlLifecycleFailure = disabled.detail;
1982
+ console.log(` ${yellow("!")} Could not fully disable phone-start: ${controlLifecycleFailure}`);
1982
1983
  } else {
1983
1984
  console.log(` ${check} Phone-start background service disabled.`);
1984
1985
  }
@@ -2045,7 +2046,7 @@ var main = async () => {
2045
2046
  keyCheck: "locked",
2046
2047
  rc: rc.kind,
2047
2048
  control: controlMode,
2048
- daemon: controlMode === "off" ? controlDisableFailure ? "unavailable" : "disabled" : "skipped",
2049
+ daemon: controlLifecycleFailure ? "unavailable" : "skipped",
2049
2050
  exitCode: report.exitCode
2050
2051
  });
2051
2052
  await reportSetupOutcome({
@@ -2074,12 +2075,41 @@ var main = async () => {
2074
2075
  const providerAvailable = hasSpawnProvider(controlCapabilities);
2075
2076
  const managerAvailable = daemonServiceManagerAvailable();
2076
2077
  const servicePresent = readDaemonStatus().kind !== "absent" || hasDaemonServiceDefinition();
2077
- const controlPolicy = resolveControlPolicy({
2078
+ let controlPolicy = resolveControlPolicy({
2078
2079
  mode: controlMode,
2079
2080
  providerAvailable,
2080
2081
  managerAvailable,
2081
2082
  servicePresent
2082
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
+ }
2083
2113
  if (controlMode === "on" && controlPolicy !== "enabled") {
2084
2114
  console.log(` ${yellow("!")} Phone-start was required, but this machine has no supported provider or native background-service manager.`);
2085
2115
  } else if (controlPolicy === "legacy") {
@@ -2102,7 +2132,7 @@ var main = async () => {
2102
2132
  failed,
2103
2133
  reachChecked,
2104
2134
  reachesYou,
2105
- daemonReady: controlDisableFailure ? false : daemon === null ? null : daemon.kind === "running" || daemon.kind === "started",
2135
+ daemonReady: controlLifecycleFailure ? false : daemon === null ? null : daemon.kind === "running" || daemon.kind === "started",
2106
2136
  // null when the check did not run, so it changes nothing. Only an outright
2107
2137
  // `false` is allowed to hold back "Setup complete.".
2108
2138
  roundTripAnswered: roundTrip === null ? null : roundTrip.kind === "answered" ? true : roundTrip.kind === "unanswered" ? false : null
@@ -2147,7 +2177,7 @@ var main = async () => {
2147
2177
  roundTrip: roundTrip === null ? "skipped" : roundTrip.kind,
2148
2178
  rc: rc.kind,
2149
2179
  rcPath: rc.path,
2150
- daemon: controlMode === "off" ? controlDisableFailure ? "unavailable" : "disabled" : controlPolicy === "unsupported" || controlPolicy === "preserve-absent" ? "unsupported" : daemon?.kind ?? "skipped",
2180
+ daemon: controlLifecycleFailure ? "unavailable" : daemon?.kind ?? "skipped",
2151
2181
  control: controlMode,
2152
2182
  controlPolicy,
2153
2183
  controlCapabilities,
@@ -1,28 +1,21 @@
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";
15
- import {
16
- guardBinary
17
- } from "../chunk-AIGDBRIJ.js";
9
+ import "../chunk-AIGDBRIJ.js";
18
10
  import {
19
11
  daemonServiceManagerAvailable,
20
12
  disableDaemonService,
21
13
  ensureDaemonRunning,
22
14
  hasDaemonServiceDefinition,
23
15
  readDaemonStatus,
24
- replaceDaemonService
25
- } from "../chunk-TRQ46MXR.js";
16
+ replaceDaemonService,
17
+ stopDaemonService
18
+ } from "../chunk-NQNCSPEH.js";
26
19
  import {
27
20
  detectProviderLiveCapabilities,
28
21
  hasSpawnProvider,
@@ -34,24 +27,15 @@ import {
34
27
  import {
35
28
  execNpm,
36
29
  npmErrorMessage,
37
- resolveGlobalBinDir,
38
30
  resolveGlobalBinary
39
31
  } from "../chunk-PZA7G4CN.js";
40
32
  import "../chunk-2HYPZLYY.js";
41
33
  import {
42
- CODEX_HOOK_BINARY,
43
- addCodexHookTrust,
44
- addCodexHooks,
45
- addCodexMcpServer,
46
34
  claudeJson,
47
35
  codexConfigToml,
48
- codexHookPositions,
49
- codexHooksJson,
50
36
  cursorPluginDir,
51
37
  geminiSettings,
52
- hasCodexHooks,
53
- vscodePluginDir,
54
- vscodePluginMcp
38
+ vscodePluginDir
55
39
  } from "../chunk-V37ZXMH3.js";
56
40
  import {
57
41
  readControlMode
@@ -66,202 +50,49 @@ import {
66
50
  } from "../chunk-DINDL2CF.js";
67
51
  import "../chunk-OFRNZWC4.js";
68
52
  import "../chunk-KZERVKTD.js";
69
- import {
70
- readJsonSafe
71
- } from "../chunk-6MTNS63X.js";
53
+ import "../chunk-6MTNS63X.js";
72
54
  import {
73
55
  getApiKey
74
56
  } from "../chunk-2UMNXADU.js";
75
57
  import "../chunk-L4C2JXJS.js";
76
58
 
77
59
  // bin/pushary-upgrade.ts
78
- import { existsSync as existsSync2 } from "fs";
79
- import { homedir as homedir2 } from "os";
80
- import { join as join2 } from "path";
81
- import { execFileSync } from "child_process";
82
-
83
- // src/reapply.ts
84
- import { join, dirname } from "path";
60
+ import { existsSync } from "fs";
85
61
  import { homedir } from "os";
86
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
87
- import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
88
- var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
89
- var CLAUDE_JSON = join(homedir(), ".claude.json");
90
- var readJson = (filePath) => {
91
- const result = readJsonSafe(filePath);
92
- if (result.kind === "ok") return result.value;
93
- if (result.kind === "missing") return {};
94
- return null;
95
- };
96
- var writeJson = (filePath, data) => {
97
- const dir = dirname(filePath);
98
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
99
- writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
100
- };
101
- var hasPusharyClaudeHooks = (settings) => JSON.stringify(settings.hooks ?? {}).includes("pushary-hook");
102
- var hasPusharyMcp = (claudeJson2) => !!claudeJson2.mcpServers?.pushary;
103
- var reapplyClaudeSettings = (apiKey, paths = {}) => {
104
- const settingsPath = paths.settingsPath ?? CLAUDE_SETTINGS;
105
- const claudeJsonPath = paths.claudeJsonPath ?? CLAUDE_JSON;
106
- const settings = readJson(settingsPath);
107
- const claudeJson2 = readJson(claudeJsonPath);
108
- const configured = settings != null && hasPusharyClaudeHooks(settings) || claudeJson2 != null && hasPusharyMcp(claudeJson2);
109
- if (!configured) return { configured: false, reapplied: false, hooks: false, mcp: false };
110
- const settingsWritable = settings != null;
111
- const claudeJsonWritable = claudeJson2 != null;
112
- let hooks = false;
113
- let mcp = false;
114
- try {
115
- if (!settingsWritable) throw new Error("settings.json could not be parsed");
116
- const s = settings;
117
- addPusharyToolPermissions(s);
118
- addPusharyHooks(s, resolveGlobalBinDir("pushary-hook"), detectClaudeVersion());
119
- writeJson(settingsPath, s);
120
- hooks = true;
121
- } catch {
122
- }
123
- if (apiKey) {
124
- try {
125
- if (!claudeJsonWritable) throw new Error(".claude.json could not be parsed");
126
- const cj = claudeJson2;
127
- addClaudeMcpServer(cj, apiKey);
128
- writeJson(claudeJsonPath, cj);
129
- mcp = true;
130
- } catch {
131
- }
132
- }
133
- return { configured: true, reapplied: hooks || mcp, hooks, mcp };
134
- };
135
- var readToml = (filePath) => {
136
- try {
137
- const raw = readFileSync(filePath, "utf-8");
138
- return raw.trim() === "" ? {} : parseTOML(raw);
139
- } catch {
140
- return null;
141
- }
142
- };
143
- var reapplyCodexSettings = (apiKey, paths = {}) => {
144
- const configPath = paths.configPath ?? codexConfigToml();
145
- const hooksPath = paths.hooksPath ?? codexHooksJson();
146
- const config = readToml(configPath);
147
- const hooksConfig = readJson(hooksPath);
148
- const configured = config != null && !!config.mcp_servers?.pushary || hooksConfig != null && hasCodexHooks(hooksConfig);
149
- if (!configured) return { configured: false, reapplied: false, hooks: false, mcp: false };
150
- const command = guardBinary(resolveGlobalBinary(CODEX_HOOK_BINARY) ?? CODEX_HOOK_BINARY);
151
- let hooks = false;
152
- let mcp = false;
153
- if (hooksConfig != null) {
154
- try {
155
- addCodexHooks(hooksConfig, command);
156
- writeJson(hooksPath, hooksConfig);
157
- hooks = true;
158
- } catch {
159
- }
160
- }
161
- if (config != null) {
162
- try {
163
- if (hooks) addCodexHookTrust(config, hooksPath, command, codexHookPositions(hooksConfig ?? void 0));
164
- if (apiKey) {
165
- addCodexMcpServer(config, apiKey);
166
- mcp = true;
167
- }
168
- if (hooks || apiKey) writeFileSync(configPath, stringifyTOML(config), "utf-8");
169
- } catch {
170
- }
171
- }
172
- return { configured: true, reapplied: hooks || mcp, hooks, mcp };
173
- };
174
- var reapplyGeminiSettings = (apiKey, paths = {}) => {
175
- const settingsPath = paths.settingsPath ?? join(homedir(), ".gemini", "settings.json");
176
- const settings = readJson(settingsPath);
177
- if (settings == null) return { configured: false, reapplied: false, hooks: false, mcp: false };
178
- const configured = hasGeminiHooks(settings) || !!settings.mcpServers?.pushary;
179
- if (!configured) return { configured: false, reapplied: false, hooks: false, mcp: false };
180
- let hooks = false;
181
- let mcp = false;
182
- try {
183
- addGeminiHooks(settings, guardBinary(resolveGlobalBinary(GEMINI_HOOK_BINARY) ?? GEMINI_HOOK_BINARY));
184
- hooks = true;
185
- if (apiKey) {
186
- addGeminiMcpServer(settings, apiKey);
187
- mcp = true;
188
- }
189
- writeJson(settingsPath, settings);
190
- } catch {
191
- }
192
- return { configured: true, reapplied: hooks || mcp, hooks, mcp };
193
- };
194
- var reapplyPluginMcp = (mcpPath, apiKey) => {
195
- const mcpConfig = readJson(mcpPath);
196
- if (mcpConfig == null) return { configured: false, reapplied: false, hooks: false, mcp: false };
197
- const servers = mcpConfig.mcpServers;
198
- if (!servers?.pushary) return { configured: false, reapplied: false, hooks: false, mcp: false };
199
- if (!apiKey) return { configured: true, reapplied: false, hooks: false, mcp: false };
200
- try {
201
- addClaudeMcpServer(mcpConfig, apiKey);
202
- writeJson(mcpPath, mcpConfig);
203
- return { configured: true, reapplied: true, hooks: false, mcp: true };
204
- } catch {
205
- return { configured: true, reapplied: false, hooks: false, mcp: false };
206
- }
207
- };
208
- var reapplyCursorPlugin = (apiKey, paths = {}) => reapplyPluginMcp(
209
- paths.pluginMcpPath ?? join(homedir(), ".cursor", "plugins", "local", "pushary", "mcp.json"),
210
- apiKey
211
- );
212
- var reapplyVsCodePlugin = (apiKey, paths = {}) => reapplyPluginMcp(paths.pluginMcpPath ?? vscodePluginMcp(), apiKey);
213
- var reapplyAllAgents = (apiKey) => {
214
- const runners = [
215
- { id: "claude", label: "Claude Code", run: () => reapplyClaudeSettings(apiKey) },
216
- { id: "codex", label: "Codex", run: () => reapplyCodexSettings(apiKey) },
217
- { id: "gemini", label: "Gemini CLI", run: () => reapplyGeminiSettings(apiKey) },
218
- { id: "cursor", label: "Cursor", run: () => reapplyCursorPlugin(apiKey) },
219
- { id: "vscode", label: "VS Code", run: () => reapplyVsCodePlugin(apiKey) }
220
- ];
221
- const done = [];
222
- for (const runner of runners) {
223
- try {
224
- const result = runner.run();
225
- if (result.configured) done.push({ id: runner.id, label: runner.label, result });
226
- } catch {
227
- }
228
- }
229
- return done;
230
- };
231
-
232
- // bin/pushary-upgrade.ts
62
+ import { join } from "path";
63
+ import { execFileSync } from "child_process";
233
64
  exitOnHelpFlag("upgrade");
234
65
  var detectInstallModes = () => {
235
66
  const candidates = [
236
67
  {
237
68
  label: "Cursor plugin",
238
69
  upgrade: "updates itself through Cursor",
239
- present: existsSync2(cursorPluginDir())
70
+ present: existsSync(cursorPluginDir())
240
71
  },
241
72
  {
242
73
  label: "VS Code plugin",
243
74
  upgrade: "npx @pushary/agent-hooks@latest setup --agents vscode",
244
- present: existsSync2(vscodePluginDir())
75
+ present: existsSync(vscodePluginDir())
245
76
  },
246
77
  {
247
78
  label: "Codex config",
248
79
  upgrade: "npx @pushary/agent-hooks@latest setup --agents codex",
249
- present: existsSync2(codexConfigToml())
80
+ present: existsSync(codexConfigToml())
250
81
  },
251
82
  {
252
83
  label: "Claude Code config",
253
84
  upgrade: "npx @pushary/agent-hooks@latest setup --agents claude_code",
254
- present: existsSync2(claudeJson())
85
+ present: existsSync(claudeJson())
255
86
  },
256
87
  {
257
88
  label: "Gemini config",
258
89
  upgrade: "npx @pushary/agent-hooks@latest setup --agents gemini_cli",
259
- present: existsSync2(geminiSettings())
90
+ present: existsSync(geminiSettings())
260
91
  },
261
92
  {
262
93
  label: "Hermes plugin (pip)",
263
94
  upgrade: "npx @pushary/agent-hooks@latest setup --agents hermes",
264
- present: existsSync2(join2(homedir2(), ".hermes"))
95
+ present: existsSync(join(homedir(), ".hermes"))
265
96
  }
266
97
  ];
267
98
  return candidates.filter((candidate) => candidate.present).map(({ label, upgrade }) => ({ label, upgrade }));
@@ -305,14 +136,23 @@ var finishUpgrade = async () => {
305
136
  servicePresent: serviceWasPresent
306
137
  });
307
138
  let daemonReady = true;
139
+ let legacyStopFailure = null;
308
140
  if (controlPolicy === "disabled" && serviceWasPresent) {
309
141
  daemonReady = (await disableDaemonService()).kind === "ok";
310
- } else if (apiKey && controlPolicy === "enabled") {
311
- const service = await replaceDaemonService();
312
- const daemon = service.kind === "ok" ? await ensureDaemonRunning({ spawnDaemon: () => {
313
- } }) : null;
314
- daemonReady = service.kind === "ok" && (daemon?.kind === "running" || daemon?.kind === "started");
315
- } else if (controlPolicy === "legacy" || controlMode === "on" && controlPolicy === "unsupported") {
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") {
316
156
  daemonReady = false;
317
157
  }
318
158
  console.log();
@@ -331,7 +171,7 @@ var finishUpgrade = async () => {
331
171
  }
332
172
  console.log();
333
173
  if (!daemonReady) {
334
- console.error(controlPolicy === "disabled" ? "Upgrade installed, but the disabled background service could not be fully removed. Re-run setup with --control off." : controlPolicy === "legacy" ? "Upgrade installed, but a legacy background service cannot be safely adopted in this session. Run setup with --control off, or restore its provider and native service manager." : "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");
335
175
  process.exitCode = 1;
336
176
  return;
337
177
  }
@@ -0,0 +1,210 @@
1
+ import {
2
+ detectClaudeVersion
3
+ } from "./chunk-475T3XIO.js";
4
+ import {
5
+ claudeWired,
6
+ codexWired,
7
+ geminiWired
8
+ } from "./chunk-OBI4YMCT.js";
9
+ import {
10
+ GEMINI_HOOK_BINARY,
11
+ addClaudeMcpServer,
12
+ addGeminiHooks,
13
+ addGeminiMcpServer,
14
+ addPusharyHooks,
15
+ addPusharyToolPermissions,
16
+ hasGeminiHooks
17
+ } from "./chunk-7TS2G4QG.js";
18
+ import {
19
+ guardBinary
20
+ } from "./chunk-AIGDBRIJ.js";
21
+ import {
22
+ resolveGlobalBinDir,
23
+ resolveGlobalBinary
24
+ } from "./chunk-PZA7G4CN.js";
25
+ import {
26
+ CODEX_HOOK_BINARY,
27
+ addCodexHookTrust,
28
+ addCodexHooks,
29
+ addCodexMcpServer,
30
+ codexConfigToml,
31
+ codexHookPositions,
32
+ codexHooksJson,
33
+ hasCodexHooks,
34
+ vscodePluginMcp
35
+ } from "./chunk-V37ZXMH3.js";
36
+ import {
37
+ readJsonSafe
38
+ } from "./chunk-6MTNS63X.js";
39
+
40
+ // src/reapply.ts
41
+ import { join, dirname } from "path";
42
+ import { homedir } from "os";
43
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
44
+ import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
45
+ var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
46
+ var CLAUDE_JSON = join(homedir(), ".claude.json");
47
+ var readJson = (filePath) => {
48
+ const result = readJsonSafe(filePath);
49
+ if (result.kind === "ok") return result.value;
50
+ if (result.kind === "missing") return {};
51
+ return null;
52
+ };
53
+ var writeJson = (filePath, data) => {
54
+ const dir = dirname(filePath);
55
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
56
+ writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
57
+ };
58
+ var hasPusharyClaudeHooks = (settings) => JSON.stringify(settings.hooks ?? {}).includes("pushary-hook");
59
+ var hasPusharyMcp = (claudeJson) => !!claudeJson.mcpServers?.pushary;
60
+ var reapplyClaudeSettings = (apiKey, paths = {}) => {
61
+ const settingsPath = paths.settingsPath ?? CLAUDE_SETTINGS;
62
+ const claudeJsonPath = paths.claudeJsonPath ?? CLAUDE_JSON;
63
+ const settings = readJson(settingsPath);
64
+ const claudeJson = readJson(claudeJsonPath);
65
+ const configured = settings != null && hasPusharyClaudeHooks(settings) || claudeJson != null && hasPusharyMcp(claudeJson);
66
+ if (!configured) return { configured: false, reapplied: false, hooks: false, mcp: false };
67
+ const settingsWritable = settings != null;
68
+ const claudeJsonWritable = claudeJson != null;
69
+ let hooks = false;
70
+ let mcp = false;
71
+ try {
72
+ if (!settingsWritable) throw new Error("settings.json could not be parsed");
73
+ const s = settings;
74
+ addPusharyToolPermissions(s);
75
+ addPusharyHooks(s, resolveGlobalBinDir("pushary-hook"), detectClaudeVersion());
76
+ writeJson(settingsPath, s);
77
+ hooks = true;
78
+ } catch {
79
+ }
80
+ if (apiKey) {
81
+ try {
82
+ if (!claudeJsonWritable) throw new Error(".claude.json could not be parsed");
83
+ const cj = claudeJson;
84
+ addClaudeMcpServer(cj, apiKey);
85
+ writeJson(claudeJsonPath, cj);
86
+ mcp = true;
87
+ } catch {
88
+ }
89
+ }
90
+ return { configured: true, reapplied: hooks || mcp, hooks, mcp };
91
+ };
92
+ var readToml = (filePath) => {
93
+ try {
94
+ const raw = readFileSync(filePath, "utf-8");
95
+ return raw.trim() === "" ? {} : parseTOML(raw);
96
+ } catch {
97
+ return null;
98
+ }
99
+ };
100
+ var inspectConfiguredManagedProviders = (paths = {}) => {
101
+ const providers = [];
102
+ const claudeSettings = readJson(paths.claudeSettingsPath ?? CLAUDE_SETTINGS);
103
+ const claudeJson = readJson(paths.claudeJsonPath ?? CLAUDE_JSON);
104
+ if (claudeWired({ claudeJson, settings: claudeSettings, skillExists: false })) {
105
+ providers.push("claude");
106
+ }
107
+ const codexConfig = readToml(paths.codexConfigPath ?? codexConfigToml());
108
+ const codexHooks = readJson(paths.codexHooksPath ?? codexHooksJson());
109
+ if (codexWired({ config: codexConfig, hooks: codexHooks, skillExists: false })) {
110
+ providers.push("codex");
111
+ }
112
+ const gemini = readJson(paths.geminiSettingsPath ?? join(homedir(), ".gemini", "settings.json"));
113
+ if (geminiWired({ settings: gemini })) {
114
+ providers.push("gemini");
115
+ }
116
+ return providers;
117
+ };
118
+ var reapplyCodexSettings = (apiKey, paths = {}) => {
119
+ const configPath = paths.configPath ?? codexConfigToml();
120
+ const hooksPath = paths.hooksPath ?? codexHooksJson();
121
+ const config = readToml(configPath);
122
+ const hooksConfig = readJson(hooksPath);
123
+ const configured = config != null && !!config.mcp_servers?.pushary || hooksConfig != null && hasCodexHooks(hooksConfig);
124
+ if (!configured) return { configured: false, reapplied: false, hooks: false, mcp: false };
125
+ const command = guardBinary(resolveGlobalBinary(CODEX_HOOK_BINARY) ?? CODEX_HOOK_BINARY);
126
+ let hooks = false;
127
+ let mcp = false;
128
+ if (hooksConfig != null) {
129
+ try {
130
+ addCodexHooks(hooksConfig, command);
131
+ writeJson(hooksPath, hooksConfig);
132
+ hooks = true;
133
+ } catch {
134
+ }
135
+ }
136
+ if (config != null) {
137
+ try {
138
+ if (hooks) addCodexHookTrust(config, hooksPath, command, codexHookPositions(hooksConfig ?? void 0));
139
+ if (apiKey) {
140
+ addCodexMcpServer(config, apiKey);
141
+ mcp = true;
142
+ }
143
+ if (hooks || apiKey) writeFileSync(configPath, stringifyTOML(config), "utf-8");
144
+ } catch {
145
+ }
146
+ }
147
+ return { configured: true, reapplied: hooks || mcp, hooks, mcp };
148
+ };
149
+ var reapplyGeminiSettings = (apiKey, paths = {}) => {
150
+ const settingsPath = paths.settingsPath ?? join(homedir(), ".gemini", "settings.json");
151
+ const settings = readJson(settingsPath);
152
+ if (settings == null) return { configured: false, reapplied: false, hooks: false, mcp: false };
153
+ const configured = hasGeminiHooks(settings) || !!settings.mcpServers?.pushary;
154
+ if (!configured) return { configured: false, reapplied: false, hooks: false, mcp: false };
155
+ let hooks = false;
156
+ let mcp = false;
157
+ try {
158
+ addGeminiHooks(settings, guardBinary(resolveGlobalBinary(GEMINI_HOOK_BINARY) ?? GEMINI_HOOK_BINARY));
159
+ hooks = true;
160
+ if (apiKey) {
161
+ addGeminiMcpServer(settings, apiKey);
162
+ mcp = true;
163
+ }
164
+ writeJson(settingsPath, settings);
165
+ } catch {
166
+ }
167
+ return { configured: true, reapplied: hooks || mcp, hooks, mcp };
168
+ };
169
+ var reapplyPluginMcp = (mcpPath, apiKey) => {
170
+ const mcpConfig = readJson(mcpPath);
171
+ if (mcpConfig == null) return { configured: false, reapplied: false, hooks: false, mcp: false };
172
+ const servers = mcpConfig.mcpServers;
173
+ if (!servers?.pushary) return { configured: false, reapplied: false, hooks: false, mcp: false };
174
+ if (!apiKey) return { configured: true, reapplied: false, hooks: false, mcp: false };
175
+ try {
176
+ addClaudeMcpServer(mcpConfig, apiKey);
177
+ writeJson(mcpPath, mcpConfig);
178
+ return { configured: true, reapplied: true, hooks: false, mcp: true };
179
+ } catch {
180
+ return { configured: true, reapplied: false, hooks: false, mcp: false };
181
+ }
182
+ };
183
+ var reapplyCursorPlugin = (apiKey, paths = {}) => reapplyPluginMcp(
184
+ paths.pluginMcpPath ?? join(homedir(), ".cursor", "plugins", "local", "pushary", "mcp.json"),
185
+ apiKey
186
+ );
187
+ var reapplyVsCodePlugin = (apiKey, paths = {}) => reapplyPluginMcp(paths.pluginMcpPath ?? vscodePluginMcp(), apiKey);
188
+ var reapplyAllAgents = (apiKey) => {
189
+ const runners = [
190
+ { id: "claude", label: "Claude Code", run: () => reapplyClaudeSettings(apiKey) },
191
+ { id: "codex", label: "Codex", run: () => reapplyCodexSettings(apiKey) },
192
+ { id: "gemini", label: "Gemini CLI", run: () => reapplyGeminiSettings(apiKey) },
193
+ { id: "cursor", label: "Cursor", run: () => reapplyCursorPlugin(apiKey) },
194
+ { id: "vscode", label: "VS Code", run: () => reapplyVsCodePlugin(apiKey) }
195
+ ];
196
+ const done = [];
197
+ for (const runner of runners) {
198
+ try {
199
+ const result = runner.run();
200
+ if (result.configured) done.push({ id: runner.id, label: runner.label, result });
201
+ } catch {
202
+ }
203
+ }
204
+ return done;
205
+ };
206
+
207
+ export {
208
+ inspectConfiguredManagedProviders,
209
+ reapplyAllAgents
210
+ };
@@ -265,7 +265,7 @@ var parseDaemonAction = (argv) => {
265
265
  return DAEMON_ACTIONS.has(action) ? action : null;
266
266
  };
267
267
  var manageDaemonService = (action, options = {}) => {
268
- const definition = action === "uninstall" ? buildDaemonService({
268
+ const definition = action === "uninstall" || action === "stop" ? buildDaemonService({
269
269
  platform: options.platform ?? process.platform,
270
270
  homeDir: options.homeDir ?? homedir(),
271
271
  stateDir: options.stateDir ?? pusharyDir(),
@@ -559,12 +559,12 @@ var stopDaemonRunning = async (options = {}) => {
559
559
  safeUnlink(lockPath(dir));
560
560
  return { kind: "stopped" };
561
561
  };
562
- var disableDaemonService = async (options = {}) => {
562
+ var deactivateDaemonService = async (action, options) => {
563
563
  const platform = options.platform ?? process.platform;
564
564
  const supported = ["darwin", "linux", "win32"].includes(platform);
565
565
  const managerAvailable = supported && daemonServiceManagerAvailable(options);
566
566
  const definitionPresent = supported && hasDaemonServiceDefinition(options);
567
- const service = definitionPresent && !managerAvailable ? { kind: "failed", detail: "Native background-service manager is unavailable" } : managerAvailable ? manageDaemonService("uninstall", options) : { kind: "ok" };
567
+ const service = definitionPresent && !managerAvailable ? { kind: "failed", detail: "Native background-service manager is unavailable" } : managerAvailable ? manageDaemonService(action, options) : { kind: "ok" };
568
568
  const stopped = await stopDaemonRunning(options);
569
569
  const failures = [
570
570
  service.kind === "ok" ? null : service.detail,
@@ -572,6 +572,8 @@ var disableDaemonService = async (options = {}) => {
572
572
  ].filter((detail) => detail !== null);
573
573
  return failures.length > 0 ? { kind: "failed", detail: failures.join("; ") } : { kind: "ok" };
574
574
  };
575
+ var stopDaemonService = async (options = {}) => deactivateDaemonService("stop", options);
576
+ var disableDaemonService = async (options = {}) => deactivateDaemonService("uninstall", options);
575
577
  var replaceDaemonService = async (options = {}) => {
576
578
  const stopped = await stopDaemonRunning(options);
577
579
  return stopped.kind === "unavailable" ? stopped : manageDaemonService("install", options);
@@ -642,6 +644,7 @@ export {
642
644
  acquireDaemonLease,
643
645
  requestDaemonHandoff,
644
646
  stopDaemonRunning,
647
+ stopDaemonService,
645
648
  disableDaemonService,
646
649
  replaceDaemonService,
647
650
  ensureDaemonRunning
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.89.5",
3
+ "version": "0.89.6",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",