@pushary/agent-hooks 0.61.0 → 0.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/bin/pushary-claude.js +4 -4
  3. package/dist/bin/pushary-clean.js +62 -32
  4. package/dist/bin/pushary-codex-hook.js +4 -4
  5. package/dist/bin/pushary-codex.js +3 -3
  6. package/dist/bin/pushary-connect.js +6 -6
  7. package/dist/bin/pushary-daemon.js +37 -5
  8. package/dist/bin/pushary-doctor.js +113 -34
  9. package/dist/bin/pushary-gemini-hook.js +4 -4
  10. package/dist/bin/pushary-hook.js +5 -5
  11. package/dist/bin/pushary-login.js +4 -4
  12. package/dist/bin/pushary-logout.js +51 -9
  13. package/dist/bin/pushary-mode.js +4 -4
  14. package/dist/bin/pushary-notification-hook.js +2 -2
  15. package/dist/bin/pushary-permission-denied-hook.js +3 -3
  16. package/dist/bin/pushary-permission-hook.js +3 -3
  17. package/dist/bin/pushary-post-hook.js +2 -2
  18. package/dist/bin/pushary-prompt-hook.js +2 -2
  19. package/dist/bin/pushary-session-end-hook.js +2 -2
  20. package/dist/bin/pushary-session-start-hook.js +2 -2
  21. package/dist/bin/pushary-setup.js +92 -152
  22. package/dist/bin/pushary-stats.js +2 -2
  23. package/dist/bin/pushary-status.js +5 -5
  24. package/dist/bin/pushary-stop-hook.js +2 -2
  25. package/dist/bin/pushary-stopfailure-hook.js +2 -2
  26. package/dist/bin/pushary-suggestions.js +2 -2
  27. package/dist/bin/pushary-upgrade.js +176 -11
  28. package/dist/bin/pushary-wait.js +4 -4
  29. package/dist/bin/pushary.js +1 -1
  30. package/dist/{chunk-B26DNXCA.js → chunk-247C4RE5.js} +1 -0
  31. package/dist/{chunk-GX64YGU3.js → chunk-3O27ZSRE.js} +25 -0
  32. package/dist/{chunk-HI4AGGE6.js → chunk-5SO3CEGJ.js} +4 -4
  33. package/dist/chunk-E2U35RLD.js +108 -0
  34. package/dist/chunk-ER657KRJ.js +168 -0
  35. package/dist/{chunk-HUJQSP4F.js → chunk-IIZZYUWK.js} +1 -1
  36. package/dist/chunk-N7XJ4L2W.js +79 -0
  37. package/dist/{chunk-R6C4USIV.js → chunk-ONVEYEVR.js} +2 -2
  38. package/dist/{chunk-SML23YOT.js → chunk-SP7OZXCQ.js} +10 -2
  39. package/dist/chunk-UXXRRXUS.js +93 -0
  40. package/dist/{chunk-UKNAAVEE.js → chunk-YJ7YZRVV.js} +1 -1
  41. package/dist/src/index.js +3 -3
  42. package/package.json +1 -1
  43. package/dist/chunk-S4TFBJ5O.js +0 -339
  44. package/dist/{chunk-VFBYRR2N.js → chunk-HIIBSYFJ.js} +3 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.62.0
4
+
5
+ ### `pushary clean` could leave your API key on disk
6
+
7
+ If `CODEX_HOME` was set, `clean` looked for Codex's `config.toml` in `~/.codex`
8
+ and never found the real one. It printed `Codex config (not found)`, exited `0`,
9
+ and left the key in the file. A user who ran `clean` to remove their key was told
10
+ it had worked.
11
+
12
+ The same fault ran through the rest of the Codex path. With `CODEX_HOME` set:
13
+
14
+ - `setup` wrote the MCP server, the trust hashes and the notify entry to
15
+ `~/.codex/config.toml` while writing `hooks.json`, `AGENTS.md` and the skill to
16
+ the real `CODEX_HOME`, splitting the install across two directories
17
+ - `setup --dry-run` named the relocated path, so the preview and the real run
18
+ disagreed about where the key would land
19
+ - `doctor` read the relocated config and reported the key missing
20
+ - `logout` treated an exported-but-empty `CODEX_HOME` as a path relative to the
21
+ current directory
22
+
23
+ If you use `CODEX_HOME`, run `pushary clean --dry-run` to see what is still
24
+ there, and check `$CODEX_HOME/config.toml` for a key you thought was gone.
25
+
26
+ ### New
27
+
28
+ - `pushary clean --dry-run` lists everything it would remove and changes nothing
29
+ - `pushary doctor --bundle` writes a redacted diagnostics file for a support
30
+ thread. Keys, your home directory and the machine name are stripped.
31
+ - `pushary logout --revoke` kills the key server-side as well as locally, so it
32
+ stops working everywhere rather than only on this machine
33
+
34
+ ### Changed
35
+
36
+ - `pushary upgrade` now refreshes Codex, Gemini CLI and Cursor, not only Claude
37
+ Code, and names the agents it actually touched. It matters most for Codex,
38
+ whose hooks are trusted by hash: a refreshed hook command without a refreshed
39
+ trust entry is a hook Codex silently declines to run.
40
+ - `pushary upgrade` with no global install now names the install modes it can
41
+ see and the command that updates each, instead of one generic line and exit 0
42
+ - `pushary daemon` stops on a rejected key or a closed plan and exits `4`
43
+ (`UNAUTHENTICATED`). It used to treat that like a network blip, back off to a
44
+ two-minute poll and sit there under an `online` banner, so a revoked key left a
45
+ daemon that looked healthy and would never launch anything again.
46
+ - Ctrl-C during app pairing now cancels the pairing on the server, so a QR still
47
+ on your screen stops being claimable immediately instead of staying live for
48
+ the rest of its five minutes
49
+ - `setup` prints the command surface when it finishes
50
+
3
51
  ## 0.61.0
4
52
 
5
53
  The largest release since the CLI shipped. Setup no longer asks you to paste an
@@ -17,14 +17,14 @@ import {
17
17
  reportEvent,
18
18
  resolvePolicy,
19
19
  waitForAnswer
20
- } from "../chunk-VFBYRR2N.js";
21
- import {
22
- getMachineId
23
- } from "../chunk-RN3NOEJF.js";
20
+ } from "../chunk-HIIBSYFJ.js";
24
21
  import "../chunk-HNTKTK5B.js";
25
22
  import {
26
23
  redactSecrets
27
24
  } from "../chunk-DQAN3JQP.js";
25
+ import {
26
+ getMachineId
27
+ } from "../chunk-RN3NOEJF.js";
28
28
  import {
29
29
  getApiKey,
30
30
  getBaseUrl
@@ -4,10 +4,18 @@ import {
4
4
  removePusharySettings
5
5
  } from "../chunk-CAJZAFVS.js";
6
6
  import {
7
- removeCodexHooks,
8
- removeGeminiSettings,
9
7
  removeInstructionBlock
10
- } from "../chunk-S4TFBJ5O.js";
8
+ } from "../chunk-N7XJ4L2W.js";
9
+ import {
10
+ removeGeminiSettings
11
+ } from "../chunk-E2U35RLD.js";
12
+ import {
13
+ codexAgentsMd,
14
+ codexConfigToml,
15
+ codexHooksJson,
16
+ codexSkillDir,
17
+ removeCodexHooks
18
+ } from "../chunk-ER657KRJ.js";
11
19
  import {
12
20
  removeClaudeAlias
13
21
  } from "../chunk-BC3VCZ3E.js";
@@ -16,8 +24,8 @@ import {
16
24
  } from "../chunk-MUEW424A.js";
17
25
  import {
18
26
  exitOnHelpFlag
19
- } from "../chunk-HUJQSP4F.js";
20
- import "../chunk-SML23YOT.js";
27
+ } from "../chunk-IIZZYUWK.js";
28
+ import "../chunk-SP7OZXCQ.js";
21
29
  import "../chunk-DQAN3JQP.js";
22
30
 
23
31
  // bin/pushary-clean.ts
@@ -66,8 +74,25 @@ var readJson = (path) => {
66
74
  return null;
67
75
  }
68
76
  };
77
+ var dryRun = false;
78
+ var did = (past) => dryRun ? `(would ${past === "cleaned" ? "clean" : past === "removed" ? "remove" : past === "uninstalled" ? "uninstall" : past})` : `(${past})`;
79
+ var wouldNote = (action, path) => {
80
+ console.log(` ${dim("would")} ${action} ${path}`);
81
+ };
82
+ var guardedWrite = (path, contents) => {
83
+ if (dryRun) return wouldNote("rewrite", path);
84
+ writeFileSync(path, contents, "utf-8");
85
+ };
86
+ var guardedRemove = (path, options = {}) => {
87
+ if (dryRun) return wouldNote("remove ", path);
88
+ rmSync(path, { recursive: true, force: options.force });
89
+ };
90
+ var guardedExec = (command, options, describe) => {
91
+ if (dryRun) return wouldNote("run ", describe);
92
+ execSync(command, options);
93
+ };
69
94
  var writeJson = (path, data) => {
70
- writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
95
+ guardedWrite(path, JSON.stringify(data, null, 2) + "\n");
71
96
  };
72
97
  var cleanSettingsFile = (path, label) => {
73
98
  const data = readJson(path);
@@ -78,7 +103,7 @@ var cleanSettingsFile = (path, label) => {
78
103
  const changed = removePusharySettings(data);
79
104
  if (changed) {
80
105
  writeJson(path, data);
81
- console.log(` ${check} ${label} ${dim("(cleaned)")}`);
106
+ console.log(` ${check} ${label} ${dim(did("cleaned"))}`);
82
107
  } else {
83
108
  console.log(` ${skip} ${label} ${dim("(no pushary entries)")}`);
84
109
  }
@@ -88,7 +113,12 @@ var main = async () => {
88
113
  console.log(` ${bold("Pushary Clean")}`);
89
114
  console.log(` ${dim("Removes all Pushary configuration")}`);
90
115
  console.log();
91
- const assumeYes = process.argv.includes("--yes") || process.argv.includes("-y");
116
+ dryRun = process.argv.includes("--dry-run");
117
+ if (dryRun) {
118
+ console.log(` ${bold("Dry run")} ${dim("- nothing will be changed")}`);
119
+ console.log();
120
+ }
121
+ const assumeYes = dryRun || process.argv.includes("--yes") || process.argv.includes("-y");
92
122
  if (!assumeYes && !process.stdin.isTTY) {
93
123
  console.log(` ${yellow("!")} Non-interactive shell. Re-run with --yes to confirm removal.`);
94
124
  process.exit(1);
@@ -118,7 +148,7 @@ var main = async () => {
118
148
  if (mcpServers?.pushary) {
119
149
  delete mcpServers.pushary;
120
150
  writeJson(CURSOR_MCP, cursorData);
121
- console.log(` ${check} Cursor MCP config ${dim("(cleaned)")}`);
151
+ console.log(` ${check} Cursor MCP config ${dim(did("cleaned"))}`);
122
152
  } else {
123
153
  console.log(` ${skip} Cursor MCP config ${dim("(no pushary entries)")}`);
124
154
  }
@@ -126,7 +156,7 @@ var main = async () => {
126
156
  console.log(` ${skip} Cursor MCP config ${dim("(not found)")}`);
127
157
  }
128
158
  if (existsSync(CURSOR_PLUGIN_DIR)) {
129
- rmSync(CURSOR_PLUGIN_DIR, { recursive: true });
159
+ guardedRemove(CURSOR_PLUGIN_DIR);
130
160
  console.log(` ${check} Cursor plugin ${dim("(removed from ~/.cursor/plugins/local)")}`);
131
161
  } else {
132
162
  console.log(` ${skip} Cursor plugin ${dim("(not installed)")}`);
@@ -149,12 +179,12 @@ var main = async () => {
149
179
  console.log(` ${skip} Cursor gate ${dim("(no hooks.json)")}`);
150
180
  }
151
181
  if (existsSync(SKILL_DIR)) {
152
- rmSync(SKILL_DIR, { recursive: true });
153
- console.log(` ${check} Skill directory ${dim("(removed)")}`);
182
+ guardedRemove(SKILL_DIR);
183
+ console.log(` ${check} Skill directory ${dim(did("removed"))}`);
154
184
  } else {
155
185
  console.log(` ${skip} Skill directory ${dim("(not found)")}`);
156
186
  }
157
- const codexConfig = join(homedir(), ".codex", "config.toml");
187
+ const codexConfig = codexConfigToml();
158
188
  try {
159
189
  const config = readFileSync(codexConfig, "utf-8");
160
190
  const hadPushary = config.includes("pushary");
@@ -168,19 +198,19 @@ var main = async () => {
168
198
  parsed.notify = parsed.notify.filter((n) => typeof n !== "string" || !n.includes("pushary-codex"));
169
199
  if (parsed.notify.length === 0) delete parsed.notify;
170
200
  }
171
- writeFileSync(codexConfig, stringifyTOML(parsed), "utf-8");
172
- console.log(` ${check} Codex config ${dim("(cleaned)")}`);
201
+ guardedWrite(codexConfig, stringifyTOML(parsed));
202
+ console.log(` ${check} Codex config ${dim(did("cleaned"))}`);
173
203
  } else {
174
204
  console.log(` ${skip} Codex config ${dim("(no pushary entries)")}`);
175
205
  }
176
206
  } catch {
177
207
  console.log(` ${skip} Codex config ${dim("(not found)")}`);
178
208
  }
179
- const codexHooksJson = join(homedir(), ".codex", "hooks.json");
180
- const codexHooksData = readJson(codexHooksJson);
209
+ const codexHooksJson2 = codexHooksJson();
210
+ const codexHooksData = readJson(codexHooksJson2);
181
211
  if (codexHooksData) {
182
212
  if (removeCodexHooks(codexHooksData)) {
183
- writeJson(codexHooksJson, codexHooksData);
213
+ writeJson(codexHooksJson2, codexHooksData);
184
214
  console.log(` ${check} Codex hooks ${dim("(removed from ~/.codex/hooks.json)")}`);
185
215
  } else {
186
216
  console.log(` ${skip} Codex hooks ${dim("(no pushary entries)")}`);
@@ -188,16 +218,16 @@ var main = async () => {
188
218
  } else {
189
219
  console.log(` ${skip} Codex hooks ${dim("(not found)")}`);
190
220
  }
191
- const codexAgentsMd = join(homedir(), ".codex", "AGENTS.md");
192
- if (removeInstructionBlock(codexAgentsMd)) {
221
+ const codexAgentsMd2 = codexAgentsMd();
222
+ if (removeInstructionBlock(codexAgentsMd2)) {
193
223
  console.log(` ${check} Codex AGENTS.md ${dim("(removed Pushary block)")}`);
194
224
  } else {
195
225
  console.log(` ${skip} Codex AGENTS.md ${dim("(no pushary block)")}`);
196
226
  }
197
- const codexSkillDir = join(homedir(), ".codex", "skills", "pushary");
198
- if (existsSync(codexSkillDir)) {
199
- rmSync(codexSkillDir, { recursive: true });
200
- console.log(` ${check} Codex skill directory ${dim("(removed)")}`);
227
+ const codexSkillDir2 = codexSkillDir();
228
+ if (existsSync(codexSkillDir2)) {
229
+ guardedRemove(codexSkillDir2);
230
+ console.log(` ${check} Codex skill directory ${dim(did("removed"))}`);
201
231
  } else {
202
232
  console.log(` ${skip} Codex skill directory ${dim("(not found)")}`);
203
233
  }
@@ -206,7 +236,7 @@ var main = async () => {
206
236
  if (geminiSettings) {
207
237
  if (removeGeminiSettings(geminiSettings)) {
208
238
  writeJson(geminiSettingsPath, geminiSettings);
209
- console.log(` ${check} Gemini CLI settings ${dim("(cleaned)")}`);
239
+ console.log(` ${check} Gemini CLI settings ${dim(did("cleaned"))}`);
210
240
  } else {
211
241
  console.log(` ${skip} Gemini CLI settings ${dim("(no pushary entries)")}`);
212
242
  }
@@ -223,13 +253,13 @@ var main = async () => {
223
253
  if (hermesPython) {
224
254
  const snippet = 'from hermes_cli.config import load_config, save_config; c = load_config(); p = c.get("plugins") if isinstance(c.get("plugins"), dict) else {}; e = p.get("enabled") if isinstance(p.get("enabled"), list) else []; p["enabled"] = [x for x in e if x != "pushary"]; c["plugins"] = p; a = c.get("agent") if isinstance(c.get("agent"), dict) else {}; d = a.get("disabled_toolsets") if isinstance(a.get("disabled_toolsets"), list) else []; a["disabled_toolsets"] = [x for x in d if x != "clarify"]; c["agent"] = a; save_config(c)';
225
255
  try {
226
- execSync(`"${hermesPython}" -c '${snippet}'`, { stdio: "pipe", timeout: 15e3 });
256
+ guardedExec(`"${hermesPython}" -c '${snippet}'`, { stdio: "pipe", timeout: 15e3 }, "hermes plugin config update");
227
257
  console.log(` ${check} Hermes config ${dim("(plugin disabled, clarify toolset restored)")}`);
228
258
  } catch {
229
259
  console.log(` ${skip} Hermes config ${dim("(could not update config.yaml)")}`);
230
260
  }
231
261
  try {
232
- execSync(`"${hermesPython}" -m pip uninstall -y hermes-plugin-pushary`, { stdio: "pipe", timeout: 6e4 });
262
+ guardedExec(`"${hermesPython}" -m pip uninstall -y hermes-plugin-pushary`, { stdio: "pipe", timeout: 6e4 }, "pip uninstall hermes-plugin-pushary");
233
263
  console.log(` ${check} Hermes plugin ${dim("(pip uninstalled)")}`);
234
264
  } catch {
235
265
  console.log(` ${skip} Hermes plugin ${dim("(not installed)")}`);
@@ -247,8 +277,8 @@ var main = async () => {
247
277
  const cleaned = lines.filter((l) => !isOurExport(l)).join("\n");
248
278
  if (cleaned !== content) {
249
279
  const backup = `${shellFile}.pushary-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
250
- copyFileSync(shellFile, backup);
251
- writeFileSync(shellFile, cleaned, "utf-8");
280
+ if (!dryRun) copyFileSync(shellFile, backup);
281
+ guardedWrite(shellFile, cleaned);
252
282
  console.log(` ${check} ${shellFile.split("/").pop()} ${dim(`(removed the export, backup at ${backup})`)}`);
253
283
  }
254
284
  for (const line of declined) {
@@ -265,7 +295,7 @@ var main = async () => {
265
295
  console.log(` ${skip} claude alias ${dim("(not set)")}`);
266
296
  }
267
297
  if (existsSync(PUSHARY_DIR)) {
268
- rmSync(PUSHARY_DIR, { recursive: true });
298
+ guardedRemove(PUSHARY_DIR);
269
299
  console.log(` ${check} Local state ${dim("(~/.pushary removed: stored API key, ledger)")}`);
270
300
  } else {
271
301
  console.log(` ${skip} Local state ${dim("(~/.pushary not found)")}`);
@@ -275,7 +305,7 @@ var main = async () => {
275
305
  for (const f of readdirSync(tmpdir())) {
276
306
  if (!f.startsWith("pushary-")) continue;
277
307
  try {
278
- rmSync(join(tmpdir(), f), { recursive: true, force: true });
308
+ guardedRemove(join(tmpdir(), f), { force: true });
279
309
  swept++;
280
310
  } catch {
281
311
  }
@@ -285,7 +315,7 @@ var main = async () => {
285
315
  }
286
316
  try {
287
317
  execNpm("uninstall -g --no-workspaces @pushary/agent-hooks", { stdio: "ignore", timeout: 3e4 });
288
- console.log(` ${check} Global package ${dim("(uninstalled)")}`);
318
+ console.log(` ${check} Global package ${dim(did("uninstalled"))}`);
289
319
  } catch {
290
320
  console.log(` ${skip} Global package ${dim("(not installed)")}`);
291
321
  }
@@ -31,10 +31,7 @@ import {
31
31
  toCodexWire,
32
32
  toPolicyLookup,
33
33
  waitForAnswer
34
- } from "../chunk-VFBYRR2N.js";
35
- import {
36
- getMachineId
37
- } from "../chunk-RN3NOEJF.js";
34
+ } from "../chunk-HIIBSYFJ.js";
38
35
  import {
39
36
  isGatingMoment,
40
37
  recordKeylessMoment
@@ -46,6 +43,9 @@ import {
46
43
  hookWaitClamped,
47
44
  hookWaitDeadline
48
45
  } from "../chunk-DQAN3JQP.js";
46
+ import {
47
+ getMachineId
48
+ } from "../chunk-RN3NOEJF.js";
49
49
  import {
50
50
  getApiKey
51
51
  } from "../chunk-2UMNXADU.js";
@@ -3,12 +3,12 @@ import {
3
3
  askUser,
4
4
  reportEvent,
5
5
  waitForAnswer
6
- } from "../chunk-VFBYRR2N.js";
6
+ } from "../chunk-HIIBSYFJ.js";
7
+ import "../chunk-HNTKTK5B.js";
8
+ import "../chunk-DQAN3JQP.js";
7
9
  import {
8
10
  getMachineId
9
11
  } from "../chunk-RN3NOEJF.js";
10
- import "../chunk-HNTKTK5B.js";
11
- import "../chunk-DQAN3JQP.js";
12
12
  import {
13
13
  getApiKey
14
14
  } from "../chunk-2UMNXADU.js";
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  printKeyCheck
4
- } from "../chunk-R6C4USIV.js";
4
+ } from "../chunk-ONVEYEVR.js";
5
5
  import {
6
6
  confirmAppConnection,
7
7
  connectDevice,
8
8
  printConnectInstructions
9
- } from "../chunk-GX64YGU3.js";
9
+ } from "../chunk-3O27ZSRE.js";
10
10
  import "../chunk-3EGEA4KH.js";
11
11
  import {
12
12
  parseFlags
13
13
  } from "../chunk-N4DA4CJB.js";
14
14
  import {
15
15
  readKeySource
16
- } from "../chunk-B26DNXCA.js";
16
+ } from "../chunk-247C4RE5.js";
17
17
  import {
18
18
  checkApiKey
19
- } from "../chunk-UKNAAVEE.js";
19
+ } from "../chunk-YJ7YZRVV.js";
20
20
  import {
21
21
  createIo
22
22
  } from "../chunk-TX7KBKT7.js";
@@ -26,10 +26,10 @@ import {
26
26
  import "../chunk-BC3VCZ3E.js";
27
27
  import {
28
28
  exitOnHelpFlag
29
- } from "../chunk-HUJQSP4F.js";
29
+ } from "../chunk-IIZZYUWK.js";
30
30
  import {
31
31
  EXIT
32
- } from "../chunk-SML23YOT.js";
32
+ } from "../chunk-SP7OZXCQ.js";
33
33
  import "../chunk-HNTKTK5B.js";
34
34
  import "../chunk-DQAN3JQP.js";
35
35
  import "../chunk-2UMNXADU.js";
@@ -7,8 +7,10 @@ import {
7
7
  } from "../chunk-MUEW424A.js";
8
8
  import {
9
9
  exitOnHelpFlag
10
- } from "../chunk-HUJQSP4F.js";
11
- import "../chunk-SML23YOT.js";
10
+ } from "../chunk-IIZZYUWK.js";
11
+ import {
12
+ EXIT
13
+ } from "../chunk-SP7OZXCQ.js";
12
14
  import {
13
15
  getMachineId
14
16
  } from "../chunk-RN3NOEJF.js";
@@ -30,6 +32,19 @@ var MAX_ERROR_STREAK = 6;
30
32
  var REQUEST_TIMEOUT_MS = 1e4;
31
33
  var MAX_SPAWNS_PER_WINDOW = 5;
32
34
  var SPAWN_WINDOW_MS = 6e4;
35
+ var DrainError = class extends Error {
36
+ constructor(status) {
37
+ super(`drain ${status}`);
38
+ this.status = status;
39
+ this.name = "DrainError";
40
+ }
41
+ };
42
+ var isAuthFailure = (err) => {
43
+ if (typeof err !== "object" || err === null) return false;
44
+ const candidate = err;
45
+ if (candidate.name !== "DrainError") return false;
46
+ return candidate.status === 401 || candidate.status === 403;
47
+ };
33
48
  var NATIVE_AGENT_BINARIES = { codex: "codex", gemini: "gemini" };
34
49
  var buildSpawnLaunch = (req, opts) => {
35
50
  const agent = req.agent === "codex" || req.agent === "gemini" ? req.agent : "claude";
@@ -68,6 +83,9 @@ var runSpawnDaemon = async () => {
68
83
  const spawnTimestamps = [];
69
84
  let timer;
70
85
  let inflight;
86
+ let fatalCode = null;
87
+ let finish = () => {
88
+ };
71
89
  const launch = (req) => {
72
90
  spawnTimestamps.push(Date.now());
73
91
  const cwd = req.cwd && existsSync(req.cwd) ? req.cwd : process.cwd();
@@ -98,7 +116,7 @@ var runSpawnDaemon = async () => {
98
116
  body: JSON.stringify({ machineId }),
99
117
  signal
100
118
  });
101
- if (!res.ok) throw new Error(`drain ${res.status}`);
119
+ if (!res.ok) throw new DrainError(res.status);
102
120
  const data = await res.json();
103
121
  const spawn2 = data.spawn;
104
122
  return spawn2 && typeof spawn2.prompt === "string" && typeof spawn2.id === "string" ? spawn2 : null;
@@ -126,7 +144,16 @@ var runSpawnDaemon = async () => {
126
144
  } else {
127
145
  idleTicks++;
128
146
  }
129
- } catch {
147
+ } catch (err) {
148
+ if (isAuthFailure(err)) {
149
+ const status = err.status;
150
+ fatalCode = EXIT.UNAUTHENTICATED;
151
+ stderr(
152
+ status === 401 ? "[pushary] daemon stopping: this machine's key was rejected. Run `pushary login` to sign in again.\n" : "[pushary] daemon stopping: this workspace has no active plan, so the queue is closed. Pick one at pushary.com/dashboard/settings?tab=billing\n"
153
+ );
154
+ finish();
155
+ return;
156
+ }
130
157
  errorStreak = Math.min(errorStreak + 1, MAX_ERROR_STREAK);
131
158
  } finally {
132
159
  clearTimeout(timeout);
@@ -152,8 +179,13 @@ var runSpawnDaemon = async () => {
152
179
  resolve();
153
180
  };
154
181
  for (const signal of signals) process.on(signal, onSignal);
182
+ finish = () => {
183
+ teardown();
184
+ for (const signal of signals) process.off(signal, onSignal);
185
+ resolve();
186
+ };
155
187
  });
156
- return 0;
188
+ return fatalCode ?? 0;
157
189
  };
158
190
 
159
191
  // bin/pushary-daemon.ts