@pushary/agent-hooks 1.6.4 → 1.7.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.
Files changed (102) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +3 -2
  3. package/dist/bin/pushary-bell-hook.js +23 -24
  4. package/dist/bin/pushary-bell.js +10 -11
  5. package/dist/bin/pushary-claude.js +27 -28
  6. package/dist/bin/pushary-clean.js +173 -71
  7. package/dist/bin/pushary-codex-bridge.js +17 -18
  8. package/dist/bin/pushary-codex-hook.js +30 -31
  9. package/dist/bin/pushary-codex.js +12 -13
  10. package/dist/bin/pushary-connect.js +14 -15
  11. package/dist/bin/pushary-cowork.js +5 -6
  12. package/dist/bin/pushary-daemon.js +27 -28
  13. package/dist/bin/pushary-disconnect.js +17 -18
  14. package/dist/bin/pushary-doctor.js +104 -41
  15. package/dist/bin/pushary-elicitation-hook.js +24 -25
  16. package/dist/bin/pushary-gemini-bridge.js +12 -13
  17. package/dist/bin/pushary-gemini-hook.js +27 -28
  18. package/dist/bin/pushary-hook.js +31 -32
  19. package/dist/bin/pushary-login.js +14 -16
  20. package/dist/bin/pushary-logout.js +8 -9
  21. package/dist/bin/pushary-mcp.d.ts +1 -0
  22. package/dist/bin/pushary-mcp.js +56 -0
  23. package/dist/bin/pushary-mode.js +10 -11
  24. package/dist/bin/pushary-notification-hook.js +27 -28
  25. package/dist/bin/pushary-opencode-hook.js +26 -27
  26. package/dist/bin/pushary-permission-denied-hook.js +31 -32
  27. package/dist/bin/pushary-permission-hook.js +30 -31
  28. package/dist/bin/pushary-post-hook.js +27 -28
  29. package/dist/bin/pushary-prompt-hook.js +27 -28
  30. package/dist/bin/pushary-session-end-hook.js +27 -28
  31. package/dist/bin/pushary-session-start-hook.js +31 -32
  32. package/dist/bin/pushary-setup.js +235 -100
  33. package/dist/bin/pushary-stats.js +5 -6
  34. package/dist/bin/pushary-status.js +16 -17
  35. package/dist/bin/pushary-stop-hook.js +27 -28
  36. package/dist/bin/pushary-stopfailure-hook.js +27 -28
  37. package/dist/bin/pushary-suggestions.js +5 -5
  38. package/dist/bin/pushary-transcript-register.js +19 -20
  39. package/dist/bin/pushary-transcripts.js +7 -8
  40. package/dist/bin/pushary-upgrade.js +20 -21
  41. package/dist/bin/pushary-wait.js +10 -11
  42. package/dist/bin/pushary.js +6 -7
  43. package/dist/chunk-2FU43B5Q.js +120 -0
  44. package/dist/{chunk-6NLWK6ZV.js → chunk-3QE3BHBW.js} +2 -2
  45. package/dist/{chunk-NYMPLNV3.js → chunk-3QORH5EJ.js} +3 -3
  46. package/dist/{chunk-LFMOYHPY.js → chunk-3T35OE7C.js} +2 -2
  47. package/dist/{chunk-5U3FJ6YF.js → chunk-42XR73I2.js} +8 -8
  48. package/dist/{chunk-UGKDBJJX.js → chunk-43SSQCWO.js} +3 -3
  49. package/dist/{chunk-K32C2RGK.js → chunk-454KMJHF.js} +17 -9
  50. package/dist/{chunk-YUGGNZSF.js → chunk-4C4NRDIC.js} +1 -1
  51. package/dist/{chunk-HUB4Q35G.js → chunk-55RO62FV.js} +1 -1
  52. package/dist/{chunk-YQMFGWBD.js → chunk-62TEPRWQ.js} +1 -1
  53. package/dist/{chunk-P4BNYJ5Q.js → chunk-6PV2JQ64.js} +2 -2
  54. package/dist/{chunk-53DN77WB.js → chunk-6SJQ442W.js} +2 -2
  55. package/dist/{chunk-UAP556WK.js → chunk-7GI6QC3M.js} +2 -2
  56. package/dist/{chunk-D3K2FPPJ.js → chunk-7GYGVQBU.js} +8 -8
  57. package/dist/{chunk-OZE2TQX5.js → chunk-AAGLDXH4.js} +5 -5
  58. package/dist/{chunk-XJG2FUWS.js → chunk-AHWJHLG5.js} +9 -30
  59. package/dist/{chunk-P4UYILQK.js → chunk-BYVB3RX3.js} +2 -2
  60. package/dist/{chunk-EA3IELHP.js → chunk-EIZW73P5.js} +6 -2
  61. package/dist/{chunk-BY2CRWBO.js → chunk-H6JV44TE.js} +1 -1
  62. package/dist/{chunk-3LU74H52.js → chunk-IGBDOZUO.js} +1 -1
  63. package/dist/{chunk-N62KAMMY.js → chunk-JMBG47WV.js} +30 -1
  64. package/dist/{chunk-DESAUCH2.js → chunk-JTQDP4ZL.js} +4 -6
  65. package/dist/{chunk-LJEL7IB5.js → chunk-KBLIVEFJ.js} +5 -5
  66. package/dist/{chunk-VOCZHOXV.js → chunk-KSNAAMU5.js} +4 -4
  67. package/dist/{chunk-5UWV5GHM.js → chunk-MCNS4725.js} +1 -1
  68. package/dist/{chunk-I3XJATMR.js → chunk-MWVCDP64.js} +1 -1
  69. package/dist/chunk-NAAZCNKI.js +757 -0
  70. package/dist/{chunk-3VZX4NSP.js → chunk-NID4UZDN.js} +1 -1
  71. package/dist/{chunk-BJENQBMH.js → chunk-NQFGVAT6.js} +8 -4
  72. package/dist/{chunk-XOMH53NB.js → chunk-O5JD7UI5.js} +1 -1
  73. package/dist/{chunk-5RUIFDTP.js → chunk-O5PRIYFZ.js} +30 -2
  74. package/dist/{chunk-OSJEMXSR.js → chunk-OAL44FM2.js} +1 -1
  75. package/dist/{chunk-66H2QQAK.js → chunk-OTZHT34O.js} +1 -1
  76. package/dist/{chunk-DZTKLE7Q.js → chunk-P5CBNSYL.js} +1 -1
  77. package/dist/{chunk-GBL4BTG3.js → chunk-Q7QRR6YL.js} +2 -2
  78. package/dist/{chunk-YPONELWS.js → chunk-QPR33SHS.js} +6 -6
  79. package/dist/{chunk-LDRGVXLR.js → chunk-SNWNA3MQ.js} +1 -1
  80. package/dist/{chunk-5SWQZ6HY.js → chunk-SVEMF2VO.js} +4 -4
  81. package/dist/{chunk-VHNDE3ZD.js → chunk-UPKP67TA.js} +2 -2
  82. package/dist/{chunk-PJF7L7AA.js → chunk-VCMFKZON.js} +2 -2
  83. package/dist/{chunk-H6XU22TN.js → chunk-WM3U2RUK.js} +8 -8
  84. package/dist/{chunk-ZA5UXRNE.js → chunk-WSC53O7I.js} +3 -3
  85. package/dist/{chunk-7CLTSZOL.js → chunk-WZWXBTRF.js} +3 -3
  86. package/dist/{chunk-YXPGFRRW.js → chunk-XDASEYRS.js} +3 -3
  87. package/dist/{chunk-CAS7GFWI.js → chunk-XFNVG3OV.js} +75 -5
  88. package/dist/{chunk-I2YUMZLE.js → chunk-XMN37YJH.js} +1 -1
  89. package/dist/{chunk-LQI7W4RG.js → chunk-XPMOCJHN.js} +1 -1
  90. package/dist/{chunk-VA2R64I7.js → chunk-XPTCPU6V.js} +1 -1
  91. package/dist/{chunk-PBKEYMAO.js → chunk-Y6GKOZBT.js} +1 -1
  92. package/dist/{chunk-JOGA3TL2.js → chunk-YQN7S57T.js} +6 -6
  93. package/dist/{chunk-R4HVHTNA.js → chunk-YZKS3ME3.js} +2 -2
  94. package/dist/chunk-ZEMSCGDD.js +87 -0
  95. package/dist/{chunk-EREBZHN2.js → chunk-ZIIYTQHC.js} +4 -4
  96. package/dist/{reapply-4DAJKNVP.js → reapply-66GQIBXN.js} +9 -10
  97. package/dist/src/index.js +19 -20
  98. package/package.json +2 -1
  99. package/dist/chunk-GU3EJCVV.js +0 -43
  100. package/dist/chunk-HWJW3CPO.js +0 -65
  101. package/dist/chunk-NFH73H5B.js +0 -360
  102. package/dist/chunk-P32LPT2L.js +0 -6
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  clientErrorKindOf,
3
3
  reportClientError
4
- } from "./chunk-VOCZHOXV.js";
4
+ } from "./chunk-KSNAAMU5.js";
5
5
  import {
6
6
  callMcpTool
7
- } from "./chunk-BJENQBMH.js";
7
+ } from "./chunk-NQFGVAT6.js";
8
8
  import {
9
9
  installNetworkDefaults
10
- } from "./chunk-DESAUCH2.js";
10
+ } from "./chunk-JTQDP4ZL.js";
11
11
  import {
12
12
  getBaseUrl
13
- } from "./chunk-OSJEMXSR.js";
13
+ } from "./chunk-OAL44FM2.js";
14
14
 
15
15
  // src/api.ts
16
16
  import { randomUUID } from "crypto";
@@ -9,18 +9,17 @@ import {
9
9
  reapplyGeminiSettings,
10
10
  reapplyVsCodePlugin,
11
11
  repairCodexHolds
12
- } from "./chunk-YPONELWS.js";
13
- import "./chunk-PBKEYMAO.js";
14
- import "./chunk-53DN77WB.js";
15
- import "./chunk-ZA5UXRNE.js";
16
- import "./chunk-YXPGFRRW.js";
17
- import "./chunk-7CLTSZOL.js";
12
+ } from "./chunk-QPR33SHS.js";
13
+ import "./chunk-Y6GKOZBT.js";
14
+ import "./chunk-6SJQ442W.js";
15
+ import "./chunk-WSC53O7I.js";
16
+ import "./chunk-XDASEYRS.js";
17
+ import "./chunk-WZWXBTRF.js";
18
18
  import "./chunk-XHKBHWLX.js";
19
- import "./chunk-YUGGNZSF.js";
20
- import "./chunk-K32C2RGK.js";
19
+ import "./chunk-4C4NRDIC.js";
20
+ import "./chunk-454KMJHF.js";
21
21
  import "./chunk-T7E75EKA.js";
22
- import "./chunk-CAS7GFWI.js";
23
- import "./chunk-HWJW3CPO.js";
22
+ import "./chunk-XFNVG3OV.js";
24
23
  export {
25
24
  ensureCodexTrustAfterReapply,
26
25
  inspectConfiguredManagedProviders,
package/dist/src/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-5U3FJ6YF.js";
4
- import "../chunk-LJEL7IB5.js";
5
- import "../chunk-ZA5UXRNE.js";
6
- import "../chunk-NYMPLNV3.js";
7
- import "../chunk-5UWV5GHM.js";
3
+ } from "../chunk-42XR73I2.js";
4
+ import "../chunk-KBLIVEFJ.js";
5
+ import "../chunk-WSC53O7I.js";
6
+ import "../chunk-3QORH5EJ.js";
7
+ import "../chunk-MCNS4725.js";
8
8
  import "../chunk-YHG74UFF.js";
9
- import "../chunk-7CLTSZOL.js";
9
+ import "../chunk-WZWXBTRF.js";
10
10
  import {
11
11
  fetchAccountPublicKey
12
- } from "../chunk-66H2QQAK.js";
12
+ } from "../chunk-OTZHT34O.js";
13
13
  import {
14
14
  fetchModeOverride,
15
15
  fetchModeState,
@@ -18,32 +18,31 @@ import {
18
18
  handlePostToolUse,
19
19
  handleStop,
20
20
  reportEvent
21
- } from "../chunk-H6XU22TN.js";
22
- import "../chunk-YUGGNZSF.js";
21
+ } from "../chunk-WM3U2RUK.js";
22
+ import "../chunk-4C4NRDIC.js";
23
23
  import {
24
24
  askUser,
25
25
  cancelQuestion,
26
26
  waitForAnswer
27
- } from "../chunk-EREBZHN2.js";
28
- import "../chunk-VOCZHOXV.js";
27
+ } from "../chunk-ZIIYTQHC.js";
28
+ import "../chunk-KSNAAMU5.js";
29
29
  import "../chunk-POFBE6ZY.js";
30
- import "../chunk-BJENQBMH.js";
31
- import "../chunk-DESAUCH2.js";
32
- import "../chunk-DZTKLE7Q.js";
30
+ import "../chunk-NQFGVAT6.js";
31
+ import "../chunk-JTQDP4ZL.js";
32
+ import "../chunk-P5CBNSYL.js";
33
33
  import "../chunk-7QLSKOSU.js";
34
34
  import "../chunk-KZERVKTD.js";
35
- import "../chunk-VHNDE3ZD.js";
36
- import "../chunk-K32C2RGK.js";
35
+ import "../chunk-UPKP67TA.js";
36
+ import "../chunk-454KMJHF.js";
37
37
  import "../chunk-T7E75EKA.js";
38
38
  import {
39
39
  getApiKey,
40
40
  getBaseUrl
41
- } from "../chunk-OSJEMXSR.js";
41
+ } from "../chunk-OAL44FM2.js";
42
+ import "../chunk-EIZW73P5.js";
42
43
  import {
43
44
  resolvePolicy
44
- } from "../chunk-CAS7GFWI.js";
45
- import "../chunk-EA3IELHP.js";
46
- import "../chunk-HWJW3CPO.js";
45
+ } from "../chunk-XFNVG3OV.js";
47
46
  export {
48
47
  askUser,
49
48
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "1.6.4",
3
+ "version": "1.7.1",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -67,6 +67,7 @@
67
67
  "pushary-connect": "./dist/bin/pushary-connect.js",
68
68
  "pushary-doctor": "./dist/bin/pushary-doctor.js",
69
69
  "pushary-mode": "./dist/bin/pushary-mode.js",
70
+ "pushary-mcp": "./dist/bin/pushary-mcp.js",
70
71
  "pushary-wait": "./dist/bin/pushary-wait.js",
71
72
  "pushary-stats": "./dist/bin/pushary-stats.js",
72
73
  "pushary-suggestions": "./dist/bin/pushary-suggestions.js",
@@ -1,43 +0,0 @@
1
- // src/diagnostics/service-runtime.ts
2
- var unescapeXml = (value) => value.replaceAll("&quot;", '"').replaceAll("&apos;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
3
- var unquoteSystemd = (value) => value.replaceAll('\\"', '"').replaceAll("\\\\", "\\").replaceAll("%%", "%");
4
- var readServiceExecPath = (contents, platform) => {
5
- if (platform === "darwin") {
6
- const program = contents.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]*)<\/string>/)?.[1];
7
- return program ? unescapeXml(program) : null;
8
- }
9
- if (platform === "linux") {
10
- const exec = contents.match(/^ExecStart="((?:[^"\\]|\\.)*)"/m)?.[1];
11
- return exec ? unquoteSystemd(exec) : null;
12
- }
13
- if (platform === "win32") {
14
- const command = contents.match(/<Command>([^<]*)<\/Command>/)?.[1];
15
- return command ? unescapeXml(command) : null;
16
- }
17
- return null;
18
- };
19
- var MANAGER_SEGMENTS = [
20
- [".nvm", "nvm"],
21
- ["nvm", "nvm"],
22
- ["fnm", "fnm"],
23
- [".fnm", "fnm"],
24
- [".volta", "volta"],
25
- ["volta", "volta"],
26
- [".asdf", "asdf"],
27
- ["asdf", "asdf"]
28
- ];
29
- var versionManagerOf = (execPath) => {
30
- const segments = execPath.split(/[/\\]/);
31
- for (const segment of segments) {
32
- const hit = MANAGER_SEGMENTS.find(([name]) => name === segment);
33
- if (hit) return hit[1];
34
- }
35
- return null;
36
- };
37
- var describeVersionManagerPin = (execPath, manager) => `The background service is pinned to ${execPath}, which ${manager} manages. Switching Node versions strands it; run pushary daemon install afterwards.`;
38
-
39
- export {
40
- readServiceExecPath,
41
- versionManagerOf,
42
- describeVersionManagerPin
43
- };
@@ -1,65 +0,0 @@
1
- // src/cli/version.ts
2
- import { readFileSync } from "fs";
3
- import { dirname, join, parse } from "path";
4
- import { fileURLToPath } from "url";
5
- var PACKAGE_NAME = "@pushary/agent-hooks";
6
- var UNKNOWN_VERSION = "0.0.0";
7
- var findOwnPackageJson = (start) => {
8
- const { root } = parse(start);
9
- let dir = start;
10
- while (true) {
11
- const candidate = join(dir, "package.json");
12
- try {
13
- const pkg = JSON.parse(readFileSync(candidate, "utf-8"));
14
- if (pkg.name === PACKAGE_NAME) return candidate;
15
- } catch {
16
- }
17
- if (dir === root) return void 0;
18
- const parent = dirname(dir);
19
- if (parent === dir) return void 0;
20
- dir = parent;
21
- }
22
- };
23
- var getPackageVersion = () => {
24
- const root = ownPackageRoot();
25
- return (root === void 0 ? void 0 : packageVersionAt(root)) ?? UNKNOWN_VERSION;
26
- };
27
- var ownPackageRoot = () => {
28
- try {
29
- const found = findOwnPackageJson(dirname(fileURLToPath(import.meta.url)));
30
- return found ? dirname(found) : void 0;
31
- } catch {
32
- return void 0;
33
- }
34
- };
35
- var packageVersionAt = (packageRoot) => {
36
- try {
37
- const manifest = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf-8"));
38
- const version = manifest && typeof manifest === "object" && "version" in manifest ? manifest.version : void 0;
39
- return typeof version === "string" ? version : void 0;
40
- } catch {
41
- return void 0;
42
- }
43
- };
44
- var isNewerVersion = (candidate, current) => {
45
- const a = candidate.split(".").map((part) => parseInt(part, 10) || 0);
46
- const b = current.split(".").map((part) => parseInt(part, 10) || 0);
47
- for (let i = 0; i < 3; i++) {
48
- if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
49
- if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
50
- }
51
- return false;
52
- };
53
- var RELEASE_VERSION = /^\d+\.\d+\.\d+$/;
54
- var isReleaseVersion = (value) => RELEASE_VERSION.test(value);
55
- var majorOf = (version) => parseInt(version.split(".")[0] ?? "", 10) || 0;
56
- var isSameMajorVersion = (candidate, current) => majorOf(candidate) === majorOf(current);
57
-
58
- export {
59
- getPackageVersion,
60
- ownPackageRoot,
61
- packageVersionAt,
62
- isNewerVersion,
63
- isReleaseVersion,
64
- isSameMajorVersion
65
- };
@@ -1,360 +0,0 @@
1
- import {
2
- openCodeConfigDirFrom
3
- } from "./chunk-3VZX4NSP.js";
4
- import {
5
- codexHomeFrom
6
- } from "./chunk-K32C2RGK.js";
7
- import {
8
- HOOK_COMPATIBLE_PROFILES
9
- } from "./chunk-CAS7GFWI.js";
10
-
11
- // src/vscode-config.ts
12
- import { homedir } from "os";
13
- import { join } from "path";
14
- var PLUGIN_LOCATIONS_KEY = "chat.pluginLocations";
15
- var blankJsonComments = (text) => {
16
- let out = "";
17
- let i = 0;
18
- let inString = false;
19
- let escaped = false;
20
- while (i < text.length) {
21
- const ch = text[i];
22
- if (inString) {
23
- out += ch;
24
- if (escaped) escaped = false;
25
- else if (ch === "\\") escaped = true;
26
- else if (ch === '"') inString = false;
27
- i += 1;
28
- continue;
29
- }
30
- if (ch === '"') {
31
- inString = true;
32
- out += ch;
33
- i += 1;
34
- continue;
35
- }
36
- if (ch === "/" && text[i + 1] === "/") {
37
- while (i < text.length && text[i] !== "\n") {
38
- out += " ";
39
- i += 1;
40
- }
41
- continue;
42
- }
43
- if (ch === "/" && text[i + 1] === "*") {
44
- const end = text.indexOf("*/", i + 2);
45
- const stop = end === -1 ? text.length : end + 2;
46
- while (i < stop) {
47
- out += text[i] === "\n" ? "\n" : " ";
48
- i += 1;
49
- }
50
- continue;
51
- }
52
- out += ch;
53
- i += 1;
54
- }
55
- return out;
56
- };
57
- var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
58
- var parseJsonc = (text) => {
59
- const blanked = blankJsonComments(text);
60
- const withoutTrailingCommas = blanked.replace(/,(\s*[}\]])/g, "$1");
61
- try {
62
- return asRecord(JSON.parse(withoutTrailingCommas));
63
- } catch {
64
- return void 0;
65
- }
66
- };
67
- var rootBraceIndex = (text) => blankJsonComments(text).indexOf("{");
68
- var detectIndent = (text) => {
69
- const match = /\n([ \t]+)"/.exec(text);
70
- return match?.[1] ?? " ";
71
- };
72
- var freshDocument = (pluginDir, indent = " ") => `${JSON.stringify({ [PLUGIN_LOCATIONS_KEY]: { [pluginDir]: true } }, null, indent)}
73
- `;
74
- var registerPluginLocation = (existing, pluginDir) => {
75
- if (existing === null || existing.trim() === "") {
76
- return { kind: "created", content: freshDocument(pluginDir) };
77
- }
78
- const indent = detectIndent(existing);
79
- try {
80
- const parsed2 = asRecord(JSON.parse(existing));
81
- if (parsed2) {
82
- const locations2 = asRecord(parsed2[PLUGIN_LOCATIONS_KEY]) ?? {};
83
- if (locations2[pluginDir] === true) return { kind: "already" };
84
- parsed2[PLUGIN_LOCATIONS_KEY] = { ...locations2, [pluginDir]: true };
85
- return { kind: "merged", content: `${JSON.stringify(parsed2, null, indent)}
86
- ` };
87
- }
88
- } catch {
89
- }
90
- const parsed = parseJsonc(existing);
91
- if (!parsed) return { kind: "manual" };
92
- const locations = asRecord(parsed[PLUGIN_LOCATIONS_KEY]);
93
- if (locations?.[pluginDir] === true) return { kind: "already" };
94
- if (parsed[PLUGIN_LOCATIONS_KEY] !== void 0) return { kind: "manual" };
95
- const brace = rootBraceIndex(existing);
96
- if (brace === -1) return { kind: "manual" };
97
- const needsComma = Object.keys(parsed).length > 0;
98
- const line = `
99
- ${indent}${JSON.stringify(PLUGIN_LOCATIONS_KEY)}: { ${JSON.stringify(pluginDir)}: true }${needsComma ? "," : ""}`;
100
- return {
101
- kind: "inserted",
102
- content: `${existing.slice(0, brace + 1)}${line}${existing.slice(brace + 1)}`
103
- };
104
- };
105
- var unregisterPluginLocation = (existing, pluginDir) => {
106
- if (existing === null || existing.trim() === "") return { kind: "absent" };
107
- const indent = detectIndent(existing);
108
- try {
109
- const parsed2 = asRecord(JSON.parse(existing));
110
- if (parsed2) {
111
- const locations2 = asRecord(parsed2[PLUGIN_LOCATIONS_KEY]);
112
- if (!locations2 || !(pluginDir in locations2)) return { kind: "absent" };
113
- const remaining = { ...locations2 };
114
- delete remaining[pluginDir];
115
- if (Object.keys(remaining).length === 0) delete parsed2[PLUGIN_LOCATIONS_KEY];
116
- else parsed2[PLUGIN_LOCATIONS_KEY] = remaining;
117
- return { kind: "removed", content: `${JSON.stringify(parsed2, null, indent)}
118
- ` };
119
- }
120
- } catch {
121
- }
122
- const parsed = parseJsonc(existing);
123
- if (!parsed) return { kind: "manual" };
124
- const locations = asRecord(parsed[PLUGIN_LOCATIONS_KEY]);
125
- if (!locations || !(pluginDir in locations)) return { kind: "absent" };
126
- const line = new RegExp(
127
- `\\n[ \\t]*${escapeRegExp(JSON.stringify(PLUGIN_LOCATIONS_KEY))}\\s*:\\s*\\{\\s*${escapeRegExp(JSON.stringify(pluginDir))}\\s*:\\s*true\\s*\\},?`
128
- );
129
- if (!line.test(existing)) return { kind: "manual" };
130
- return { kind: "removed", content: existing.replace(line, "") };
131
- };
132
- var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
133
- var isPluginRegistered = (existing, pluginDir) => {
134
- if (!existing) return false;
135
- const parsed = parseJsonc(existing);
136
- return asRecord(parsed?.[PLUGIN_LOCATIONS_KEY])?.[pluginDir] === true;
137
- };
138
- var pluginLocationSnippet = (pluginDir) => `"${PLUGIN_LOCATIONS_KEY}": {
139
- ${JSON.stringify(pluginDir)}: true
140
- }`;
141
- var VSCODE_PRODUCTS = ["Code", "Code - Insiders"];
142
- var settingsRoot = (home, platform) => {
143
- if (platform === "darwin") return join(home, "Library", "Application Support");
144
- if (platform === "win32") return process.env.APPDATA?.trim() || join(home, "AppData", "Roaming");
145
- return process.env.XDG_CONFIG_HOME?.trim() || join(home, ".config");
146
- };
147
- var vscodeSettingsCandidates = (home = homedir(), platform = process.platform) => {
148
- const root = settingsRoot(home, platform);
149
- return VSCODE_PRODUCTS.map((product) => join(root, product, "User", "settings.json"));
150
- };
151
- var vscodeSettingsTargets = (exists, home, platform) => {
152
- const candidates = vscodeSettingsCandidates(home, platform);
153
- const present = candidates.filter((path) => exists(path));
154
- return present.length > 0 ? present : candidates.slice(0, 1);
155
- };
156
-
157
- // src/setup/detect.ts
158
- import { execSync } from "child_process";
159
- import { existsSync } from "fs";
160
- import { homedir as homedir2 } from "os";
161
- import { dirname, join as join2 } from "path";
162
- var whichCommand = () => process.platform === "win32" ? "where" : "which";
163
- var binaryOnPath = (binary) => {
164
- try {
165
- execSync(`${whichCommand()} ${binary}`, { stdio: "ignore", timeout: 5e3 });
166
- return true;
167
- } catch {
168
- return false;
169
- }
170
- };
171
- var defaultDeps = { onPath: binaryOnPath, exists: existsSync };
172
- var detectAgent = (probe, deps = defaultDeps) => {
173
- const checked = [];
174
- if (probe.binary) {
175
- checked.push(`${whichCommand()} ${probe.binary}`);
176
- if (deps.onPath(probe.binary)) {
177
- return { kind: "installed", evidence: `${probe.binary} on PATH`, strong: true };
178
- }
179
- }
180
- for (const path of probe.paths) {
181
- checked.push(path);
182
- if (deps.exists(path)) return { kind: "installed", evidence: path, strong: true };
183
- }
184
- for (const marker of probe.configMarkers) {
185
- checked.push(marker);
186
- if (deps.exists(marker)) return { kind: "installed", evidence: marker, strong: false };
187
- }
188
- if (probe.binary === null && probe.paths.length === 0 && probe.configMarkers.length === 0) {
189
- return { kind: "unknown" };
190
- }
191
- return { kind: "not-found", checked };
192
- };
193
- var agentProbes = (home = homedir2()) => ({
194
- claude_code: {
195
- binary: "claude",
196
- // The native installer puts it here, outside a default non-login PATH.
197
- paths: [join2(home, ".local", "bin", "claude")],
198
- configMarkers: [join2(home, ".claude")]
199
- },
200
- codex: {
201
- binary: "codex",
202
- paths: [],
203
- configMarkers: [codexHomeFrom(home)]
204
- },
205
- gemini_cli: {
206
- binary: "gemini",
207
- paths: [],
208
- configMarkers: [join2(home, ".gemini")]
209
- },
210
- hermes: {
211
- binary: "hermes",
212
- paths: [],
213
- configMarkers: [join2(home, ".hermes")]
214
- },
215
- cursor: {
216
- // GUI editor. `cursor` on PATH only exists if the user ran "Install 'cursor'
217
- // command in PATH" from the command palette, which most never do.
218
- binary: "cursor",
219
- paths: process.platform === "darwin" ? ["/Applications/Cursor.app"] : process.platform === "win32" ? [join2(process.env.LOCALAPPDATA ?? join2(home, "AppData", "Local"), "Programs", "cursor")] : ["/usr/share/cursor", join2(home, ".local", "share", "cursor")],
220
- configMarkers: [join2(home, ".cursor")]
221
- },
222
- vscode: {
223
- // Like Cursor, a GUI editor: `code` on PATH only exists if the user ran
224
- // "Shell Command: Install 'code' command in PATH", which on macOS is opt-in.
225
- binary: "code",
226
- paths: process.platform === "darwin" ? ["/Applications/Visual Studio Code.app"] : process.platform === "win32" ? [join2(process.env.LOCALAPPDATA ?? join2(home, "AppData", "Local"), "Programs", "Microsoft VS Code")] : ["/usr/share/code", join2(home, ".local", "share", "code")],
227
- // The settings directory rather than ~/.vscode, because the settings
228
- // directory is what this installer actually writes to. Insiders counts:
229
- // someone running only Insiders still has VS Code.
230
- configMarkers: vscodeSettingsCandidates(home).map((path) => dirname(path))
231
- },
232
- opencode: {
233
- binary: "opencode",
234
- paths: [],
235
- // Resolved rather than spelled: OpenCode honours OPENCODE_CONFIG_DIR and then
236
- // XDG_CONFIG_HOME, and `~/.opencode` — the obvious guess — is a path it has
237
- // never used.
238
- configMarkers: [openCodeConfigDirFrom(home)]
239
- },
240
- ...compatibleProbes(),
241
- // "Other" is a set of printed instructions for any MCP or HTTP client. There is
242
- // nothing to detect, and it must never be auto-selected.
243
- custom: { binary: null, paths: [], configMarkers: [] }
244
- });
245
- var compatibleProbes = () => HOOK_COMPATIBLE_PROFILES.reduce((probes, profile) => ({
246
- ...probes,
247
- [profile.agentType]: {
248
- binary: profile.binary,
249
- paths: [],
250
- configMarkers: []
251
- }
252
- }), {});
253
- var detectAllAgents = (deps = defaultDeps, home) => {
254
- const probes = agentProbes(home);
255
- return Object.keys(probes).map((agent) => ({
256
- agent,
257
- result: detectAgent(probes[agent], deps)
258
- }));
259
- };
260
- var isDetected = (result) => result.kind === "installed";
261
- var shortenHome = (path, home = homedir2()) => home && path.startsWith(home) ? `~${path.slice(home.length)}` : path;
262
- var describeDetection = (result, home) => {
263
- if (result.kind !== "installed") return null;
264
- const evidence = shortenHome(result.evidence, home);
265
- return result.strong ? evidence : `${evidence}, config only`;
266
- };
267
-
268
- // src/instruction-file.ts
269
- import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync, rmSync } from "fs";
270
- import { dirname as dirname2 } from "path";
271
- var PUSHARY_BEGIN = "<!-- BEGIN PUSHARY (managed by @pushary/agent-hooks; do not edit this block) -->";
272
- var PUSHARY_END = "<!-- END PUSHARY -->";
273
- var instructionBody = (label) => `## Pushary notifications and approvals
274
-
275
- You have Pushary MCP tools that reach the user on their phone. Use them proactively. Do not wait for the user to ask.
276
-
277
- - When you need the user to make a decision or answer a clarifying question, call \`ask_user\` instead of guessing or stalling. Use type "confirm" for yes/no, "select" for a fixed set of options, and "input" for free text. Read \`answered\`, \`status\` and the returned handoff instead of assuming the tool blocked. Silence is not consent; choice/text answers do not authorize a separate action.
278
- - Ask once, on one channel. If you also have a built-in way to prompt the user, use that or \`ask_user\` for a given question, never both. \`ask_user\` is the one that reaches the user when they are away from this machine, so prefer it. Raising both strands the user in whichever prompt you are not waiting on.
279
- - Honor authorization already granted in this session. Ask before a risky or irreversible action outside that authorization, or when an enforced host policy requires it. Do not ask again for an already authorized step, and never bypass an enforced gate. When a Pushary hook result says the user approved a specific command after a denial, rerunning that exact command is the approved path and not a bypass of that gate.
280
- - Call \`propose_scope\` only for an unresolved or requested file boundary. A ratified scope narrows what may proceed; it does not bypass other approval rules. Do not add a plan approval merely because work spans several files, and do not propose a new scope mid-run just to widen it. Let the installed gate request any scope expansion before the edit executes. Only file paths are checked, and only on tool calls that carry one, so put a boundary that is not a path (recipients, channels, spend, systems you will not open) in \`promises\` and never in \`allowedPaths\`. Read the \`enforces\` field that comes back: an empty array means nothing in that contract is checked automatically, and you must say so rather than report that a scope is in force. \`hookSeen: false\` means no hook has reported that sessionId, so the gate will never find the contract: fix the session id rather than proceeding as if a scope were in force.
281
- - Before a task of more than two steps, break it into steps and find the forks. Settle by yourself every fork that a file, a command or a tool call can answer, then ask the rest as one numbered round with your recommended answer against each. A question in the terminal is free; a push is not. When the user is at the terminal, ask the whole round there. When the user is away, send only the fork that stops the run, and decide the rest on your recommendation.
282
- - When you are blocked, stuck, or hit an error you cannot resolve on your own, call \`send_notification\` so the user knows, and call \`ask_user\` if you need a decision to continue.
283
- - When meaningful work finishes while the user is away, or they requested an alert, call \`send_notification\` so the user knows it is done. Skip trivial completions and duplicate reports to someone already reading. Always pass \`context\` with a \`type\` of "task_complete", "error" or "info": it is what tells Pushary this is a task update, so the user's own setting for where updates land can route it.
284
- - Confirm notifications can offer lock-screen actions; arbitrary choices and text open the app. The Mac notch handles personal account questions, not Partner customer enrollment.
285
- - Use \`cancel_question\` to retract a pending question once it is no longer needed.
286
- - Respect the user's Pushary delivery mode: it decides where an approval is surfaced (phone, terminal, or awareness only), not whether it is required. In Terminal mode do not expect a phone answer; the approval waits in the terminal. If the Pushary hook already gated an action, a single approval is enough, do not ask again.
287
-
288
- Pass \`agentName\` as "${label} - <project folder name>" so the user knows which session is asking. If a question times out, follow \`handoffAction\` when present, otherwise \`nextAction\`: poll once, then cancel a live phone question before asking in the current client. If cancellation returns \`handoffAction: "stop"\`, stop. Otherwise, if cancellation returns false, poll once for 1 second and honor the answer that won the race.`;
289
- var renderAgentInstructions = (label) => instructionBody(label);
290
- var KEYLESS_FALLBACK_LINE = "This repo uses Pushary phone approvals. Your teammates get agent questions on their lock screens; you do not have a key yet, so questions will fall back to the terminal. Get your own key (3-day trial): https://pushary.com/sign-up?utm_source=agents-md&utm_medium=teammate";
291
- var renderProjectAgentInstructions = (label) => `${instructionBody(label)}
292
-
293
- If this environment has no Pushary API key (no PUSHARY_API_KEY and no key in ~/.pushary/config.json), the tools above are unavailable in this session. In that case, treat the following as a statement of fact about this session and continue in the terminal: ${KEYLESS_FALLBACK_LINE}`;
294
- var wrap = (body) => `${PUSHARY_BEGIN}
295
- ${body}
296
- ${PUSHARY_END}`;
297
- var writeInstructionBlock = (filePath, body) => {
298
- const dir = dirname2(filePath);
299
- if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
300
- let existing = "";
301
- try {
302
- existing = readFileSync(filePath, "utf-8");
303
- } catch {
304
- }
305
- const block = wrap(body);
306
- const start = existing.indexOf(PUSHARY_BEGIN);
307
- const end = existing.indexOf(PUSHARY_END);
308
- let next;
309
- if (start !== -1 && end !== -1 && end > start) {
310
- next = existing.slice(0, start) + block + existing.slice(end + PUSHARY_END.length);
311
- } else {
312
- const prefix = existing.trim() ? existing.replace(/\s*$/, "") + "\n\n" : "";
313
- next = prefix + block + "\n";
314
- }
315
- writeFileSync(filePath, next, "utf-8");
316
- };
317
- var removeInstructionBlock = (filePath) => {
318
- let existing = "";
319
- try {
320
- existing = readFileSync(filePath, "utf-8");
321
- } catch {
322
- return false;
323
- }
324
- const start = existing.indexOf(PUSHARY_BEGIN);
325
- const end = existing.indexOf(PUSHARY_END);
326
- if (start === -1 || end === -1 || end < start) return false;
327
- const remaining = (existing.slice(0, start) + existing.slice(end + PUSHARY_END.length)).trim();
328
- if (remaining === "") {
329
- rmSync(filePath, { force: true });
330
- } else {
331
- writeFileSync(filePath, remaining + "\n", "utf-8");
332
- }
333
- return true;
334
- };
335
- var hasInstructionBlock = (filePath) => {
336
- try {
337
- return readFileSync(filePath, "utf-8").includes(PUSHARY_BEGIN);
338
- } catch {
339
- return false;
340
- }
341
- };
342
-
343
- export {
344
- registerPluginLocation,
345
- unregisterPluginLocation,
346
- isPluginRegistered,
347
- pluginLocationSnippet,
348
- vscodeSettingsTargets,
349
- detectAgent,
350
- agentProbes,
351
- detectAllAgents,
352
- isDetected,
353
- shortenHome,
354
- describeDetection,
355
- renderAgentInstructions,
356
- renderProjectAgentInstructions,
357
- writeInstructionBlock,
358
- removeInstructionBlock,
359
- hasInstructionBlock
360
- };
@@ -1,6 +0,0 @@
1
- // src/hooks-spec.ts
2
- var CURSOR_GATE_SCRIPT = "pushary-gate.mjs";
3
-
4
- export {
5
- CURSOR_GATE_SCRIPT
6
- };