@pushary/agent-hooks 0.60.0 → 0.61.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 (53) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/README.md +52 -9
  3. package/dist/bin/pushary-claude.js +3 -3
  4. package/dist/bin/pushary-clean.js +26 -11
  5. package/dist/bin/pushary-codex-hook.js +3 -3
  6. package/dist/bin/pushary-codex.js +3 -3
  7. package/dist/bin/pushary-connect.d.ts +1 -0
  8. package/dist/bin/pushary-connect.js +92 -0
  9. package/dist/bin/pushary-daemon.js +7 -2
  10. package/dist/bin/pushary-doctor.js +142 -46
  11. package/dist/bin/pushary-gemini-hook.js +3 -3
  12. package/dist/bin/pushary-hook.js +9 -4
  13. package/dist/bin/pushary-login.d.ts +1 -0
  14. package/dist/bin/pushary-login.js +156 -0
  15. package/dist/bin/pushary-logout.d.ts +1 -0
  16. package/dist/bin/pushary-logout.js +102 -0
  17. package/dist/bin/pushary-mode.js +24 -13
  18. package/dist/bin/pushary-notification-hook.js +3 -3
  19. package/dist/bin/pushary-permission-denied-hook.js +4 -4
  20. package/dist/bin/pushary-permission-hook.js +4 -4
  21. package/dist/bin/pushary-post-hook.js +3 -3
  22. package/dist/bin/pushary-prompt-hook.js +3 -3
  23. package/dist/bin/pushary-session-end-hook.js +3 -3
  24. package/dist/bin/pushary-session-start-hook.js +3 -3
  25. package/dist/bin/pushary-setup.js +858 -547
  26. package/dist/bin/pushary-stats.js +6 -1
  27. package/dist/bin/pushary-status.d.ts +1 -0
  28. package/dist/bin/pushary-status.js +206 -0
  29. package/dist/bin/pushary-stop-hook.js +3 -3
  30. package/dist/bin/pushary-stopfailure-hook.js +3 -3
  31. package/dist/bin/pushary-suggestions.js +10 -5
  32. package/dist/bin/pushary-upgrade.js +11 -6
  33. package/dist/bin/pushary-wait.js +21 -12
  34. package/dist/bin/pushary.js +42 -60
  35. package/dist/{chunk-NKXSILEW.js → chunk-2UMNXADU.js} +18 -2
  36. package/dist/chunk-3EGEA4KH.js +44 -0
  37. package/dist/chunk-7QLSKOSU.js +19 -0
  38. package/dist/chunk-B26DNXCA.js +235 -0
  39. package/dist/chunk-GX64YGU3.js +544 -0
  40. package/dist/{chunk-BE5X3WXL.js → chunk-HI4AGGE6.js} +2 -2
  41. package/dist/{chunk-DWED7BS3.js → chunk-HNTKTK5B.js} +1 -1
  42. package/dist/chunk-HUJQSP4F.js +18 -0
  43. package/dist/{chunk-J7JWI3KU.js → chunk-MUEW424A.js} +19 -1
  44. package/dist/chunk-N4DA4CJB.js +57 -0
  45. package/dist/chunk-QHOVJXOT.js +255 -0
  46. package/dist/chunk-R6C4USIV.js +44 -0
  47. package/dist/{chunk-BQLCELYC.js → chunk-S4TFBJ5O.js} +7 -0
  48. package/dist/chunk-SML23YOT.js +216 -0
  49. package/dist/chunk-TX7KBKT7.js +79 -0
  50. package/dist/chunk-UKNAAVEE.js +217 -0
  51. package/dist/{chunk-WNRYRN2R.js → chunk-VFBYRR2N.js} +2 -2
  52. package/dist/src/index.js +4 -4
  53. package/package.json +11 -4
@@ -0,0 +1,235 @@
1
+ import {
2
+ resolveShellRc
3
+ } from "./chunk-BC3VCZ3E.js";
4
+ import {
5
+ configFilePath
6
+ } from "./chunk-2UMNXADU.js";
7
+
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
+ // src/keystore.ts
58
+ import { chmodSync, existsSync as existsSync2, readFileSync as readFileSync2, realpathSync, writeFileSync as writeFileSync2 } from "fs";
59
+ import { homedir } from "os";
60
+ import { join as join2 } from "path";
61
+ var KEY_FILE_MODE = 384;
62
+ var FALLBACK_RCS = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"];
63
+ var OUR_EXPORT = /^[ \t]*export[ \t]+PUSHARY_API_KEY=(['"]?)(pk_[a-f0-9]+\.[a-f0-9]+)\1[ \t]*$/;
64
+ var MENTIONS_KEY = /PUSHARY_API_KEY/;
65
+ var isComment = (line) => line.trimStart().startsWith("#");
66
+ var exportLine = (apiKey) => `export PUSHARY_API_KEY='${apiKey}'`;
67
+ var keyPrefix = (key) => key.split(".")[0] ?? key;
68
+ var rcResult = (kind, over = {}) => ({
69
+ kind,
70
+ path: null,
71
+ shell: null,
72
+ previousKeyPrefix: null,
73
+ declinedLine: null,
74
+ backupPath: null,
75
+ detail: null,
76
+ ...over
77
+ });
78
+ var realPathOf = (filePath) => {
79
+ try {
80
+ return realpathSync(filePath);
81
+ } catch {
82
+ return filePath;
83
+ }
84
+ };
85
+ var rewriteRcExport = (rcPath, apiKey, shell = null) => {
86
+ const target = realPathOf(rcPath);
87
+ let content = "";
88
+ try {
89
+ content = readFileSync2(target, "utf-8");
90
+ } catch (err) {
91
+ const error = err;
92
+ if (error.code !== "ENOENT") {
93
+ return rcResult("unavailable", { path: target, shell, detail: error.message });
94
+ }
95
+ }
96
+ const lines = content.split("\n");
97
+ const foreign = lines.findIndex((line) => !isComment(line) && MENTIONS_KEY.test(line) && !OUR_EXPORT.test(line));
98
+ if (foreign >= 0) {
99
+ const match = /pk_[a-f0-9]+/.exec(lines[foreign] ?? "");
100
+ return rcResult("declined", {
101
+ path: target,
102
+ shell,
103
+ declinedLine: foreign + 1,
104
+ previousKeyPrefix: match?.[0] ?? null
105
+ });
106
+ }
107
+ const ours = lines.map((line, index) => ({ line, index })).filter((entry) => OUR_EXPORT.test(entry.line));
108
+ if (ours.length > 0) {
109
+ const previous = OUR_EXPORT.exec(ours[ours.length - 1].line)?.[2] ?? null;
110
+ if (ours.every((entry) => OUR_EXPORT.exec(entry.line)?.[2] === apiKey)) {
111
+ return rcResult("unchanged", {
112
+ path: target,
113
+ shell,
114
+ previousKeyPrefix: previous ? keyPrefix(previous) : null
115
+ });
116
+ }
117
+ const next = [...lines];
118
+ for (const entry of ours) next[entry.index] = exportLine(apiKey);
119
+ try {
120
+ const backupPath = backupFile(target);
121
+ writeFileAtomic(target, next.join("\n"), KEY_FILE_MODE);
122
+ return rcResult("updated", {
123
+ path: target,
124
+ shell,
125
+ previousKeyPrefix: previous ? keyPrefix(previous) : null,
126
+ backupPath
127
+ });
128
+ } catch (err) {
129
+ return rcResult("unavailable", { path: target, shell, detail: err.message });
130
+ }
131
+ }
132
+ const gap = content.length === 0 ? "" : content.endsWith("\n") ? "" : "\n";
133
+ try {
134
+ writeFileAtomic(target, `${content}${gap}${exportLine(apiKey)}
135
+ `, KEY_FILE_MODE);
136
+ } catch (err) {
137
+ return rcResult("unavailable", { path: target, shell, detail: err.message });
138
+ }
139
+ return rcResult("written", { path: target, shell });
140
+ };
141
+ var resolveRcTarget = () => {
142
+ const resolved = resolveShellRc();
143
+ if (resolved) return { path: resolved.path, shell: resolved.shell };
144
+ const fallback = FALLBACK_RCS.map((name) => join2(homedir(), name)).find(existsSync2);
145
+ return fallback ? { path: fallback, shell: null } : null;
146
+ };
147
+ var writeKey = (apiKey, options = {}) => {
148
+ const configPath = configFilePath();
149
+ const existing = readJsonSafe(configPath);
150
+ let current = {};
151
+ let configBackupPath = null;
152
+ if (existing.kind === "unreadable") {
153
+ throw new Error(
154
+ `${configPath} exists but could not be read (${existing.error.message}). Fix its permissions or remove it, then re-run setup. Nothing was changed.`
155
+ );
156
+ }
157
+ if (existing.kind === "ok") {
158
+ current = existing.value;
159
+ } else if (existing.kind === "unparseable") {
160
+ configBackupPath = backupFile(configPath);
161
+ }
162
+ writeJsonAtomic(configPath, { ...current, apiKey }, KEY_FILE_MODE);
163
+ restrictFile(configPath);
164
+ const target = (options.resolveRc ?? resolveRcTarget)();
165
+ const rc = target ? rewriteRcExport(target.path, apiKey, target.shell) : rcResult("no-rc");
166
+ process.env.PUSHARY_API_KEY = apiKey;
167
+ return { configPath, rc, configBackupPath };
168
+ };
169
+ var clearKey = (options = {}) => {
170
+ const configPath = configFilePath();
171
+ const existing = readJsonSafe(configPath);
172
+ let removedFromConfig = false;
173
+ if (existing.kind === "ok" && typeof existing.value.apiKey === "string") {
174
+ const { apiKey: _dropped, ...rest } = existing.value;
175
+ writeJsonAtomic(configPath, rest, KEY_FILE_MODE);
176
+ restrictFile(configPath);
177
+ removedFromConfig = true;
178
+ }
179
+ const target = (options.resolveRc ?? resolveRcTarget)();
180
+ let rcLinesDisabled = 0;
181
+ if (target) {
182
+ try {
183
+ const real = realpathSync(target.path);
184
+ const lines = readFileSync2(real, "utf8").split("\n");
185
+ const next = lines.map((line) => {
186
+ if (!OUR_EXPORT.test(line)) return line;
187
+ rcLinesDisabled += 1;
188
+ return `# ${line.trim()} # removed by pushary logout`;
189
+ });
190
+ if (rcLinesDisabled > 0) writeFileSync2(real, next.join("\n"));
191
+ } catch {
192
+ rcLinesDisabled = 0;
193
+ }
194
+ }
195
+ delete process.env.PUSHARY_API_KEY;
196
+ return {
197
+ removedFromConfig,
198
+ configPath,
199
+ rcPath: target?.path ?? null,
200
+ rcLinesDisabled
201
+ };
202
+ };
203
+ var restrictFile = (filePath) => {
204
+ try {
205
+ chmodSync(filePath, KEY_FILE_MODE);
206
+ } catch {
207
+ }
208
+ };
209
+ var readConfigKey = (configPath) => {
210
+ const result = readJsonSafe(configPath);
211
+ if (result.kind !== "ok") return null;
212
+ const key = typeof result.value.apiKey === "string" ? result.value.apiKey.trim() : "";
213
+ return key || null;
214
+ };
215
+ var readKeySource = () => {
216
+ const configPath = configFilePath();
217
+ const envKey = process.env.PUSHARY_API_KEY?.trim() || null;
218
+ const fileKey = readConfigKey(configPath);
219
+ const drift = envKey && fileKey && envKey !== fileKey ? { envPrefix: keyPrefix(envKey), configPrefix: keyPrefix(fileKey), configPath } : null;
220
+ if (envKey) return { kind: "env", key: envKey, origin: "PUSHARY_API_KEY", drift };
221
+ if (fileKey) return { kind: "config", key: fileKey, origin: configPath, drift: null };
222
+ return { kind: "none", key: null, origin: null, drift: null };
223
+ };
224
+
225
+ export {
226
+ readJsonSafe,
227
+ backupFile,
228
+ writeFileAtomic,
229
+ writeJsonAtomic,
230
+ KEY_FILE_MODE,
231
+ exportLine,
232
+ writeKey,
233
+ clearKey,
234
+ readKeySource
235
+ };