@pushary/agent-hooks 0.62.0 → 0.62.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.62.1
4
+
5
+ ### `pushary clean --dry-run` was not a dry run
6
+
7
+ In 0.62.0 the flag ran four real operations while printing "would":
8
+
9
+ - uninstalled the global `@pushary/agent-hooks` package
10
+ - rewrote your shell profile to remove the `claude` alias
11
+ - stripped the Pushary block from `~/.codex/AGENTS.md`
12
+ - stripped it from `~/.gemini/GEMINI.md`
13
+
14
+ If you ran `pushary clean --dry-run` on 0.62.0 and the CLI then seemed to
15
+ disappear, that is why. Reinstall with `npm i -g @pushary/agent-hooks`, and check
16
+ your shell profile and those two files against version control if you keep them
17
+ there.
18
+
19
+ ### `pushary upgrade` could destroy `~/.claude.json`
20
+
21
+ `upgrade` treated an unparseable config as an empty one and wrote it back, so a
22
+ truncated `~/.claude.json` was replaced with nothing but the Pushary MCP entry,
23
+ losing every project, MCP server and permission rule in it. A half-written
24
+ `~/.claude.json` is what an interrupted write leaves behind, so this needed only
25
+ one earlier crash to line up.
26
+
27
+ A config that cannot be parsed is now left untouched, and the other agent files
28
+ are still refreshed around it.
29
+
30
+
3
31
  ## 0.62.0
4
32
 
5
33
  ### `pushary clean` could leave your API key on disk
@@ -91,6 +91,19 @@ var guardedExec = (command, options, describe) => {
91
91
  if (dryRun) return wouldNote("run ", describe);
92
92
  execSync(command, options);
93
93
  };
94
+ var guardedInstructionBlockRemoval = (path) => {
95
+ if (!dryRun) return removeInstructionBlock(path);
96
+ if (!existsSync(path)) return false;
97
+ const hasBlock = readFileSync(path, "utf-8").includes("pushary");
98
+ if (hasBlock) wouldNote("rewrite", path);
99
+ return hasBlock;
100
+ };
101
+ var guardedAliasRemoval = () => {
102
+ if (!dryRun) return removeClaudeAlias();
103
+ const shells = [".zshrc", ".bashrc", ".bash_profile", ".profile"].map((name) => join(homedir(), name)).filter((path) => existsSync(path) && readFileSync(path, "utf-8").includes("pushary claude"));
104
+ for (const path of shells) wouldNote("rewrite", path);
105
+ return shells;
106
+ };
94
107
  var writeJson = (path, data) => {
95
108
  guardedWrite(path, JSON.stringify(data, null, 2) + "\n");
96
109
  };
@@ -219,7 +232,7 @@ var main = async () => {
219
232
  console.log(` ${skip} Codex hooks ${dim("(not found)")}`);
220
233
  }
221
234
  const codexAgentsMd2 = codexAgentsMd();
222
- if (removeInstructionBlock(codexAgentsMd2)) {
235
+ if (guardedInstructionBlockRemoval(codexAgentsMd2)) {
223
236
  console.log(` ${check} Codex AGENTS.md ${dim("(removed Pushary block)")}`);
224
237
  } else {
225
238
  console.log(` ${skip} Codex AGENTS.md ${dim("(no pushary block)")}`);
@@ -244,7 +257,7 @@ var main = async () => {
244
257
  console.log(` ${skip} Gemini CLI settings ${dim("(not found)")}`);
245
258
  }
246
259
  const geminiMd = join(homedir(), ".gemini", "GEMINI.md");
247
- if (removeInstructionBlock(geminiMd)) {
260
+ if (guardedInstructionBlockRemoval(geminiMd)) {
248
261
  console.log(` ${check} Gemini GEMINI.md ${dim("(removed Pushary block)")}`);
249
262
  } else {
250
263
  console.log(` ${skip} Gemini GEMINI.md ${dim("(no pushary block)")}`);
@@ -288,7 +301,7 @@ var main = async () => {
288
301
  } catch {
289
302
  }
290
303
  }
291
- const aliasRemovedFrom = removeClaudeAlias();
304
+ const aliasRemovedFrom = guardedAliasRemoval();
292
305
  if (aliasRemovedFrom.length > 0) {
293
306
  console.log(` ${check} claude alias ${dim(`(removed from ${aliasRemovedFrom.map((p) => p.split("/").pop()).join(", ")})`)}`);
294
307
  } else {
@@ -314,7 +327,11 @@ var main = async () => {
314
327
  } catch {
315
328
  }
316
329
  try {
317
- execNpm("uninstall -g --no-workspaces @pushary/agent-hooks", { stdio: "ignore", timeout: 3e4 });
330
+ if (dryRun) {
331
+ wouldNote("run ", "npm uninstall -g @pushary/agent-hooks");
332
+ } else {
333
+ execNpm("uninstall -g --no-workspaces @pushary/agent-hooks", { stdio: "ignore", timeout: 3e4 });
334
+ }
318
335
  console.log(` ${check} Global package ${dim(did("uninstalled"))}`);
319
336
  } catch {
320
337
  console.log(` ${skip} Global package ${dim("(not installed)")}`);
@@ -13,7 +13,8 @@ import {
13
13
  } from "../chunk-N4DA4CJB.js";
14
14
  import {
15
15
  readKeySource
16
- } from "../chunk-247C4RE5.js";
16
+ } from "../chunk-COC6NPWG.js";
17
+ import "../chunk-6MTNS63X.js";
17
18
  import {
18
19
  checkApiKey
19
20
  } from "../chunk-YJ7YZRVV.js";
@@ -35,7 +35,8 @@ import {
35
35
  import {
36
36
  keyPrefix,
37
37
  readKeySource
38
- } from "../chunk-247C4RE5.js";
38
+ } from "../chunk-COC6NPWG.js";
39
+ import "../chunk-6MTNS63X.js";
39
40
  import {
40
41
  checkApiKey,
41
42
  describeKeyCheck
@@ -9,7 +9,8 @@ import {
9
9
  import {
10
10
  readKeySource,
11
11
  writeKey
12
- } from "../chunk-247C4RE5.js";
12
+ } from "../chunk-COC6NPWG.js";
13
+ import "../chunk-6MTNS63X.js";
13
14
  import {
14
15
  checkApiKey,
15
16
  describeKeyCheck
@@ -8,7 +8,8 @@ import {
8
8
  import {
9
9
  clearKey,
10
10
  readKeySource
11
- } from "../chunk-247C4RE5.js";
11
+ } from "../chunk-COC6NPWG.js";
12
+ import "../chunk-6MTNS63X.js";
12
13
  import {
13
14
  createIo
14
15
  } from "../chunk-TX7KBKT7.js";
@@ -42,13 +42,15 @@ import {
42
42
  } from "../chunk-N4DA4CJB.js";
43
43
  import {
44
44
  KEY_FILE_MODE,
45
- backupFile,
46
45
  exportLine,
46
+ writeKey
47
+ } from "../chunk-COC6NPWG.js";
48
+ import {
49
+ backupFile,
47
50
  readJsonSafe,
48
51
  writeFileAtomic,
49
- writeJsonAtomic,
50
- writeKey
51
- } from "../chunk-247C4RE5.js";
52
+ writeJsonAtomic
53
+ } from "../chunk-6MTNS63X.js";
52
54
  import {
53
55
  checkApiKey,
54
56
  describeKeyCheck
@@ -7,7 +7,8 @@ import {
7
7
  import "../chunk-3EGEA4KH.js";
8
8
  import {
9
9
  readKeySource
10
- } from "../chunk-247C4RE5.js";
10
+ } from "../chunk-COC6NPWG.js";
11
+ import "../chunk-6MTNS63X.js";
11
12
  import {
12
13
  checkApiKey,
13
14
  describeKeyCheck
@@ -19,6 +19,9 @@ import {
19
19
  codexHooksJson,
20
20
  hasCodexHooks
21
21
  } from "../chunk-ER657KRJ.js";
22
+ import {
23
+ readJsonSafe
24
+ } from "../chunk-6MTNS63X.js";
22
25
  import {
23
26
  execNpm,
24
27
  npmErrorMessage,
@@ -48,11 +51,10 @@ import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
48
51
  var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
49
52
  var CLAUDE_JSON = join(homedir(), ".claude.json");
50
53
  var readJson = (filePath) => {
51
- try {
52
- return JSON.parse(readFileSync(filePath, "utf-8"));
53
- } catch {
54
- return null;
55
- }
54
+ const result = readJsonSafe(filePath);
55
+ if (result.kind === "ok") return result.value;
56
+ if (result.kind === "missing") return {};
57
+ return null;
56
58
  };
57
59
  var writeJson = (filePath, data) => {
58
60
  const dir = dirname(filePath);
@@ -68,10 +70,13 @@ var reapplyClaudeSettings = (apiKey, paths = {}) => {
68
70
  const claudeJson = readJson(claudeJsonPath);
69
71
  const configured = settings != null && hasPusharyClaudeHooks(settings) || claudeJson != null && hasPusharyMcp(claudeJson);
70
72
  if (!configured) return { reapplied: false, hooks: false, mcp: false };
73
+ const settingsWritable = settings != null;
74
+ const claudeJsonWritable = claudeJson != null;
71
75
  let hooks = false;
72
76
  let mcp = false;
73
77
  try {
74
- const s = settings ?? {};
78
+ if (!settingsWritable) throw new Error("settings.json could not be parsed");
79
+ const s = settings;
75
80
  addPusharyToolPermissions(s);
76
81
  addPusharyHooks(s, resolveGlobalBinDir("pushary-hook"));
77
82
  writeJson(settingsPath, s);
@@ -80,7 +85,8 @@ var reapplyClaudeSettings = (apiKey, paths = {}) => {
80
85
  }
81
86
  if (apiKey) {
82
87
  try {
83
- const cj = claudeJson ?? {};
88
+ if (!claudeJsonWritable) throw new Error(".claude.json could not be parsed");
89
+ const cj = claudeJson;
84
90
  addClaudeMcpServer(cj, apiKey);
85
91
  writeJson(claudeJsonPath, cj);
86
92
  mcp = true;
@@ -0,0 +1,55 @@
1
+ // src/cli/fsx.ts
2
+ import { copyFileSync, existsSync, fsyncSync, mkdirSync, openSync, closeSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
3
+ import { dirname, join } from "path";
4
+ var readJsonSafe = (filePath) => {
5
+ let raw;
6
+ try {
7
+ raw = readFileSync(filePath, "utf-8");
8
+ } catch (err) {
9
+ const error = err;
10
+ if (error.code === "ENOENT") return { kind: "missing" };
11
+ return { kind: "unreadable", error };
12
+ }
13
+ if (raw.trim() === "") return { kind: "ok", value: {} };
14
+ try {
15
+ return { kind: "ok", value: JSON.parse(raw) };
16
+ } catch (err) {
17
+ return { kind: "unparseable", error: err, raw };
18
+ }
19
+ };
20
+ var stamp = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
21
+ var backupFile = (filePath) => {
22
+ const target = `${filePath}.pushary-backup-${stamp()}`;
23
+ copyFileSync(filePath, target);
24
+ return target;
25
+ };
26
+ var writeFileAtomic = (filePath, contents, mode) => {
27
+ const dir = dirname(filePath);
28
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
29
+ const temp = join(dir, `.${process.pid}-${stamp()}.pushary-tmp`);
30
+ try {
31
+ writeFileSync(temp, contents, mode === void 0 ? "utf-8" : { encoding: "utf-8", mode });
32
+ const handle = openSync(temp, "r+");
33
+ try {
34
+ fsyncSync(handle);
35
+ } finally {
36
+ closeSync(handle);
37
+ }
38
+ renameSync(temp, filePath);
39
+ } catch (err) {
40
+ try {
41
+ if (existsSync(temp)) unlinkSync(temp);
42
+ } catch {
43
+ }
44
+ throw err;
45
+ }
46
+ };
47
+ var writeJsonAtomic = (filePath, data, mode) => writeFileAtomic(filePath, `${JSON.stringify(data, null, 2)}
48
+ `, mode);
49
+
50
+ export {
51
+ readJsonSafe,
52
+ backupFile,
53
+ writeFileAtomic,
54
+ writeJsonAtomic
55
+ };
@@ -1,3 +1,9 @@
1
+ import {
2
+ backupFile,
3
+ readJsonSafe,
4
+ writeFileAtomic,
5
+ writeJsonAtomic
6
+ } from "./chunk-6MTNS63X.js";
1
7
  import {
2
8
  resolveShellRc
3
9
  } from "./chunk-BC3VCZ3E.js";
@@ -5,59 +11,10 @@ import {
5
11
  configFilePath
6
12
  } from "./chunk-2UMNXADU.js";
7
13
 
8
- // src/cli/fsx.ts
9
- import { copyFileSync, existsSync, fsyncSync, mkdirSync, openSync, closeSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
10
- import { dirname, join } from "path";
11
- var readJsonSafe = (filePath) => {
12
- let raw;
13
- try {
14
- raw = readFileSync(filePath, "utf-8");
15
- } catch (err) {
16
- const error = err;
17
- if (error.code === "ENOENT") return { kind: "missing" };
18
- return { kind: "unreadable", error };
19
- }
20
- if (raw.trim() === "") return { kind: "ok", value: {} };
21
- try {
22
- return { kind: "ok", value: JSON.parse(raw) };
23
- } catch (err) {
24
- return { kind: "unparseable", error: err, raw };
25
- }
26
- };
27
- var stamp = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
28
- var backupFile = (filePath) => {
29
- const target = `${filePath}.pushary-backup-${stamp()}`;
30
- copyFileSync(filePath, target);
31
- return target;
32
- };
33
- var writeFileAtomic = (filePath, contents, mode) => {
34
- const dir = dirname(filePath);
35
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
36
- const temp = join(dir, `.${process.pid}-${stamp()}.pushary-tmp`);
37
- try {
38
- writeFileSync(temp, contents, mode === void 0 ? "utf-8" : { encoding: "utf-8", mode });
39
- const handle = openSync(temp, "r+");
40
- try {
41
- fsyncSync(handle);
42
- } finally {
43
- closeSync(handle);
44
- }
45
- renameSync(temp, filePath);
46
- } catch (err) {
47
- try {
48
- if (existsSync(temp)) unlinkSync(temp);
49
- } catch {
50
- }
51
- throw err;
52
- }
53
- };
54
- var writeJsonAtomic = (filePath, data, mode) => writeFileAtomic(filePath, `${JSON.stringify(data, null, 2)}
55
- `, mode);
56
-
57
14
  // src/keystore.ts
58
- import { chmodSync, existsSync as existsSync2, readFileSync as readFileSync2, realpathSync, writeFileSync as writeFileSync2 } from "fs";
15
+ import { chmodSync, existsSync, readFileSync, realpathSync, writeFileSync } from "fs";
59
16
  import { homedir } from "os";
60
- import { join as join2 } from "path";
17
+ import { join } from "path";
61
18
  var KEY_FILE_MODE = 384;
62
19
  var FALLBACK_RCS = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"];
63
20
  var OUR_EXPORT = /^[ \t]*export[ \t]+PUSHARY_API_KEY=(['"]?)(pk_[a-f0-9]+\.[a-f0-9]+)\1[ \t]*$/;
@@ -86,7 +43,7 @@ var rewriteRcExport = (rcPath, apiKey, shell = null) => {
86
43
  const target = realPathOf(rcPath);
87
44
  let content = "";
88
45
  try {
89
- content = readFileSync2(target, "utf-8");
46
+ content = readFileSync(target, "utf-8");
90
47
  } catch (err) {
91
48
  const error = err;
92
49
  if (error.code !== "ENOENT") {
@@ -141,7 +98,7 @@ var rewriteRcExport = (rcPath, apiKey, shell = null) => {
141
98
  var resolveRcTarget = () => {
142
99
  const resolved = resolveShellRc();
143
100
  if (resolved) return { path: resolved.path, shell: resolved.shell };
144
- const fallback = FALLBACK_RCS.map((name) => join2(homedir(), name)).find(existsSync2);
101
+ const fallback = FALLBACK_RCS.map((name) => join(homedir(), name)).find(existsSync);
145
102
  return fallback ? { path: fallback, shell: null } : null;
146
103
  };
147
104
  var writeKey = (apiKey, options = {}) => {
@@ -181,13 +138,13 @@ var clearKey = (options = {}) => {
181
138
  if (target) {
182
139
  try {
183
140
  const real = realpathSync(target.path);
184
- const lines = readFileSync2(real, "utf8").split("\n");
141
+ const lines = readFileSync(real, "utf8").split("\n");
185
142
  const next = lines.map((line) => {
186
143
  if (!OUR_EXPORT.test(line)) return line;
187
144
  rcLinesDisabled += 1;
188
145
  return `# ${line.trim()} # removed by pushary logout`;
189
146
  });
190
- if (rcLinesDisabled > 0) writeFileSync2(real, next.join("\n"));
147
+ if (rcLinesDisabled > 0) writeFileSync(real, next.join("\n"));
191
148
  } catch {
192
149
  rcLinesDisabled = 0;
193
150
  }
@@ -223,10 +180,6 @@ var readKeySource = () => {
223
180
  };
224
181
 
225
182
  export {
226
- readJsonSafe,
227
- backupFile,
228
- writeFileAtomic,
229
- writeJsonAtomic,
230
183
  KEY_FILE_MODE,
231
184
  exportLine,
232
185
  keyPrefix,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.62.0",
3
+ "version": "0.62.1",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",