@pushary/agent-hooks 0.71.0 → 0.72.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.
- package/CHANGELOG.md +29 -3
- package/dist/bin/pushary-setup.js +158 -85
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.72.0
|
|
4
|
+
|
|
5
|
+
### Setup tells you when Codex was already quarantined
|
|
6
|
+
|
|
7
|
+
0.71.0 stopped setup from getting your Codex binary deleted. This handles the
|
|
8
|
+
machines where it already happened. What macOS leaves behind passes every check
|
|
9
|
+
setup made: the `codex` on your PATH is a small JavaScript launcher and it
|
|
10
|
+
survives, `which codex` still answers, only the binary it launches is gone. So
|
|
11
|
+
setup wired up an agent that could not start and reported success.
|
|
12
|
+
|
|
13
|
+
It now says so before writing anything, and only when it can prove it: a vendor
|
|
14
|
+
directory that exists and holds nothing the size of a native binary. A layout it
|
|
15
|
+
does not recognise stays quiet, because telling somebody their working install is
|
|
16
|
+
broken is the worse mistake. Reinstall with `npm install -g @openai/codex` or
|
|
17
|
+
`brew install --cask codex`.
|
|
18
|
+
|
|
19
|
+
### Setup no longer runs the Hermes binary either
|
|
20
|
+
|
|
21
|
+
The same hazard, one agent over. When the config edit that enables the plugin
|
|
22
|
+
failed, setup fell back to running `hermes plugins enable pushary` — executing a
|
|
23
|
+
third-party agent binary, which is exactly what cost people their Codex install.
|
|
24
|
+
The fallback also did not work, because `hermes plugins enable` does not see a
|
|
25
|
+
pip install. It is gone, and a failed config edit now tells you the one line to
|
|
26
|
+
add by hand.
|
|
27
|
+
|
|
28
|
+
Executing an agent binary is now a build failure rather than a habit: every
|
|
29
|
+
source file in this package is scanned on every CI run, and the check knows the
|
|
30
|
+
difference between running `codex` and asking `which` where it is.
|
|
31
|
+
|
|
3
32
|
## 0.71.0
|
|
4
33
|
|
|
5
34
|
### Setting up Codex no longer gets Codex deleted by macOS
|
|
@@ -15,9 +44,6 @@ it. The version now comes from the package manifest next to the binary, which
|
|
|
15
44
|
covers npm, bun, pnpm and yarn installs, and from `brew list` for the Homebrew
|
|
16
45
|
cask, which ships no manifest. Same result, nothing launched.
|
|
17
46
|
|
|
18
|
-
If your Codex was already quarantined, reinstall it: `npm install -g @openai/codex`
|
|
19
|
-
or `brew install --cask codex`.
|
|
20
|
-
|
|
21
47
|
## 0.70.0
|
|
22
48
|
|
|
23
49
|
### A key you name is the key you get
|
|
@@ -110,8 +110,8 @@ import {
|
|
|
110
110
|
} from "../chunk-KZERVKTD.js";
|
|
111
111
|
|
|
112
112
|
// bin/pushary-setup.ts
|
|
113
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, cpSync, rmSync, renameSync, realpathSync } from "fs";
|
|
114
|
-
import { join, dirname, basename } from "path";
|
|
113
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, cpSync, rmSync, renameSync, realpathSync, readdirSync, statSync } from "fs";
|
|
114
|
+
import { join as join2, dirname as dirname2, basename } from "path";
|
|
115
115
|
import { homedir, tmpdir } from "os";
|
|
116
116
|
import { execSync as execSync2 } from "child_process";
|
|
117
117
|
import { checkbox, input, confirm } from "@inquirer/prompts";
|
|
@@ -199,6 +199,7 @@ var describeClosing = (facts) => {
|
|
|
199
199
|
};
|
|
200
200
|
|
|
201
201
|
// src/setup/codex-version.ts
|
|
202
|
+
import { dirname, join } from "path";
|
|
202
203
|
var parseCodexVersion = (raw) => {
|
|
203
204
|
const match = raw.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
204
205
|
if (!match) return null;
|
|
@@ -210,21 +211,21 @@ var compareCodexVersion = (a, b) => {
|
|
|
210
211
|
}
|
|
211
212
|
return 0;
|
|
212
213
|
};
|
|
213
|
-
var
|
|
214
|
-
let dir =
|
|
214
|
+
var versionFromManifest = (probe, binPath) => {
|
|
215
|
+
let dir = dirname(binPath);
|
|
215
216
|
for (let depth = 0; depth < 4; depth++) {
|
|
216
|
-
const raw = probe.readFile(
|
|
217
|
+
const raw = probe.readFile(join(dir, "package.json"));
|
|
217
218
|
if (raw) {
|
|
218
219
|
try {
|
|
219
|
-
const
|
|
220
|
-
if (typeof
|
|
221
|
-
const parsed = parseCodexVersion(
|
|
220
|
+
const { version } = JSON.parse(raw);
|
|
221
|
+
if (typeof version === "string") {
|
|
222
|
+
const parsed = parseCodexVersion(version);
|
|
222
223
|
if (parsed) return parsed;
|
|
223
224
|
}
|
|
224
225
|
} catch {
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
|
-
const parent =
|
|
228
|
+
const parent = dirname(dir);
|
|
228
229
|
if (parent === dir) break;
|
|
229
230
|
dir = parent;
|
|
230
231
|
}
|
|
@@ -233,16 +234,51 @@ var versionFromPackageManifest = (probe, binPath) => {
|
|
|
233
234
|
var readCodexVersion = (probe) => {
|
|
234
235
|
const onPath = probe.resolveOnPath();
|
|
235
236
|
if (onPath) {
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
if (fromManifest) return { kind: "known", version: fromManifest, source: "package manifest" };
|
|
237
|
+
const fromManifest = versionFromManifest(probe, probe.realpath(onPath) ?? onPath);
|
|
238
|
+
if (fromManifest) return fromManifest;
|
|
239
239
|
}
|
|
240
240
|
const brew = probe.brewVersions();
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
241
|
+
return brew ? parseCodexVersion(brew) : null;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// src/setup/codex-install.ts
|
|
245
|
+
var NATIVE_BINARY_MIN_BYTES = 20 * 1024 * 1024;
|
|
246
|
+
var packageRootFor = (probe, binPath) => {
|
|
247
|
+
let dir = probe.dirname(binPath);
|
|
248
|
+
for (let depth = 0; depth < 4; depth++) {
|
|
249
|
+
if (probe.exists(probe.join(dir, "package.json"))) return dir;
|
|
250
|
+
const parent = probe.dirname(dir);
|
|
251
|
+
if (parent === dir) return null;
|
|
252
|
+
dir = parent;
|
|
244
253
|
}
|
|
245
|
-
return
|
|
254
|
+
return null;
|
|
255
|
+
};
|
|
256
|
+
var holdsNativeBinary = (probe, dir, depth = 0) => {
|
|
257
|
+
if (depth > 4) return false;
|
|
258
|
+
const entries = probe.listDir(dir);
|
|
259
|
+
if (!entries) return false;
|
|
260
|
+
return entries.some((entry) => {
|
|
261
|
+
const path = probe.join(dir, entry);
|
|
262
|
+
const size = probe.fileSize(path);
|
|
263
|
+
if (size !== null) return size >= NATIVE_BINARY_MIN_BYTES;
|
|
264
|
+
return holdsNativeBinary(probe, path, depth + 1);
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
var platformPackages = (probe, packageRoot) => {
|
|
268
|
+
const scopes = [probe.join(packageRoot, "node_modules", "@openai"), probe.dirname(packageRoot)];
|
|
269
|
+
return scopes.flatMap(
|
|
270
|
+
(scope) => (probe.listDir(scope) ?? []).filter((entry) => entry.startsWith("codex-")).map((entry) => probe.join(scope, entry))
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
var checkCodexInstall = (probe) => {
|
|
274
|
+
const onPath = probe.resolveOnPath();
|
|
275
|
+
if (!onPath) return { kind: "unknown" };
|
|
276
|
+
const packageRoot = packageRootFor(probe, probe.realpath(onPath) ?? onPath);
|
|
277
|
+
if (!packageRoot) return { kind: "unknown" };
|
|
278
|
+
const vendors = platformPackages(probe, packageRoot).map((pkg) => probe.join(pkg, "vendor")).filter((vendor) => probe.exists(vendor));
|
|
279
|
+
if (vendors.length === 0) return { kind: "unknown" };
|
|
280
|
+
if (vendors.some((vendor) => holdsNativeBinary(probe, vendor))) return { kind: "ok" };
|
|
281
|
+
return { kind: "binary-missing", vendorDir: vendors[0] };
|
|
246
282
|
};
|
|
247
283
|
|
|
248
284
|
// src/skills-cli.ts
|
|
@@ -482,7 +518,7 @@ var readAgentJson = (filePath) => {
|
|
|
482
518
|
);
|
|
483
519
|
};
|
|
484
520
|
var writeJson = (filePath, data) => {
|
|
485
|
-
const dir =
|
|
521
|
+
const dir = dirname2(filePath);
|
|
486
522
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
487
523
|
writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
488
524
|
};
|
|
@@ -561,23 +597,23 @@ var installGlobally2 = async () => {
|
|
|
561
597
|
var _cachedSkillContent = null;
|
|
562
598
|
var fetchSkillContent = async () => {
|
|
563
599
|
if (_cachedSkillContent) return _cachedSkillContent;
|
|
564
|
-
const __dirname =
|
|
600
|
+
const __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
565
601
|
const candidates = [
|
|
566
|
-
|
|
567
|
-
|
|
602
|
+
join2(__dirname, "..", "..", "data", "SKILL.md"),
|
|
603
|
+
join2(__dirname, "..", "data", "SKILL.md")
|
|
568
604
|
];
|
|
569
605
|
const source = candidates.find((path) => existsSync(path));
|
|
570
606
|
if (!source) throw new Error("packaged skill not found");
|
|
571
607
|
_cachedSkillContent = readFileSync(source, "utf-8");
|
|
572
608
|
return _cachedSkillContent;
|
|
573
609
|
};
|
|
574
|
-
var skillsCliInUse = () => isInstalled("skills") || existsSync(
|
|
610
|
+
var skillsCliInUse = () => isInstalled("skills") || existsSync(join2(homedir(), ".agents", "skills"));
|
|
575
611
|
var installSkill = async (agent, fallbackDir) => {
|
|
576
612
|
await spinner("Installing Pushary skill", async () => {
|
|
577
613
|
if (skillsCliInUse() && installSkillViaSkillsCli(agent)) return;
|
|
578
614
|
const content = await fetchSkillContent();
|
|
579
615
|
if (!existsSync(fallbackDir)) mkdirSync(fallbackDir, { recursive: true });
|
|
580
|
-
writeFileSync(
|
|
616
|
+
writeFileSync(join2(fallbackDir, "SKILL.md"), content, "utf-8");
|
|
581
617
|
});
|
|
582
618
|
};
|
|
583
619
|
var setupClaudeCode = async (apiKey) => {
|
|
@@ -624,8 +660,8 @@ var resolveHermesPython = () => {
|
|
|
624
660
|
}
|
|
625
661
|
} catch {
|
|
626
662
|
}
|
|
627
|
-
const venvRoot =
|
|
628
|
-
const candidates = IS_WINDOWS ? [
|
|
663
|
+
const venvRoot = join2(homedir(), ".hermes", "hermes-agent", "venv");
|
|
664
|
+
const candidates = IS_WINDOWS ? [join2(venvRoot, "Scripts", "python.exe"), join2(venvRoot, "Scripts", "python3.exe")] : [join2(venvRoot, "bin", "python3"), join2(venvRoot, "bin", "python")];
|
|
629
665
|
return candidates.find(existsSync) ?? null;
|
|
630
666
|
};
|
|
631
667
|
var ensurePip = (python) => {
|
|
@@ -641,13 +677,13 @@ var ensurePip = (python) => {
|
|
|
641
677
|
};
|
|
642
678
|
var enablePusharyPlugin = (python) => {
|
|
643
679
|
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"] = (e + ["pushary"]) if "pushary" not in e else e; 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"] = (d + ["clarify"]) if "clarify" not in d else d; c["agent"] = a; save_config(c)';
|
|
644
|
-
const scriptPath =
|
|
680
|
+
const scriptPath = join2(tmpdir(), `pushary-hermes-${process.pid}.py`);
|
|
645
681
|
try {
|
|
646
682
|
writeFileSync(scriptPath, snippet.split("; ").join("\n"), "utf-8");
|
|
647
683
|
execSync2(`"${python}" "${scriptPath}"`, { stdio: "pipe", timeout: 15e3 });
|
|
648
|
-
return;
|
|
684
|
+
return true;
|
|
649
685
|
} catch {
|
|
650
|
-
|
|
686
|
+
return false;
|
|
651
687
|
} finally {
|
|
652
688
|
try {
|
|
653
689
|
rmSync(scriptPath, { force: true });
|
|
@@ -674,9 +710,14 @@ var setupHermes = async (_apiKey) => {
|
|
|
674
710
|
ensurePip(python);
|
|
675
711
|
execSync2(`"${python}" -m pip install --upgrade hermes-plugin-pushary`, { stdio: "pipe", timeout: 18e4 });
|
|
676
712
|
});
|
|
713
|
+
let pluginEnabled = false;
|
|
677
714
|
await spinner("Enabling plugin + routing questions to push", async () => {
|
|
678
|
-
enablePusharyPlugin(python);
|
|
715
|
+
pluginEnabled = enablePusharyPlugin(python);
|
|
679
716
|
});
|
|
717
|
+
if (!pluginEnabled) {
|
|
718
|
+
console.log(` ${yellow("!")} Installed the plugin, but could not edit Hermes' config.`);
|
|
719
|
+
noteManual('Add "pushary" to plugins.enabled in ~/.hermes/config.yaml, and "clarify" to agent.disabled_toolsets');
|
|
720
|
+
}
|
|
680
721
|
console.log();
|
|
681
722
|
console.log(` ${dim("What this configured:")}`);
|
|
682
723
|
console.log(` ${dim("\u2022")} Native tools: pushary_notify, pushary_ask, pushary_wait, pushary_cancel`);
|
|
@@ -685,49 +726,71 @@ var setupHermes = async (_apiKey) => {
|
|
|
685
726
|
console.log(` ${dim("\u2022")} Permission gating: set ${bold("PUSHARY_GATE_TOOLS")} to require lock-screen approval for risky tools`);
|
|
686
727
|
console.log(` ${dim("To re-enable terminal prompts:")} remove ${bold("clarify")} from ${dim("agent.disabled_toolsets")} in ~/.hermes/config.yaml`);
|
|
687
728
|
};
|
|
688
|
-
var CODEX_HOOKS_JSON =
|
|
729
|
+
var CODEX_HOOKS_JSON = join2(CODEX_HOME, "hooks.json");
|
|
689
730
|
var CODEX_HOOKS_MIN_VERSION = [0, 122, 0];
|
|
690
731
|
var CODEX_TRUST_VERIFIED_MAX = [0, 142, 2];
|
|
732
|
+
var codexOnPath = () => {
|
|
733
|
+
const whichCmd = IS_WINDOWS ? "where" : "which";
|
|
734
|
+
try {
|
|
735
|
+
const found = execSync2(`${whichCmd} codex`, { encoding: "utf-8", stdio: "pipe", timeout: 5e3 }).split("\n")[0].trim();
|
|
736
|
+
return found || null;
|
|
737
|
+
} catch {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
var codexRealpath = (path) => {
|
|
742
|
+
try {
|
|
743
|
+
return realpathSync(path);
|
|
744
|
+
} catch {
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
};
|
|
691
748
|
var detectCodexVersion = () => readCodexVersion({
|
|
692
|
-
resolveOnPath:
|
|
693
|
-
|
|
749
|
+
resolveOnPath: codexOnPath,
|
|
750
|
+
realpath: codexRealpath,
|
|
751
|
+
readFile: (path) => {
|
|
694
752
|
try {
|
|
695
|
-
|
|
696
|
-
return found || null;
|
|
753
|
+
return readFileSync(path, "utf-8");
|
|
697
754
|
} catch {
|
|
698
755
|
return null;
|
|
699
756
|
}
|
|
700
757
|
},
|
|
701
|
-
|
|
758
|
+
brewVersions: () => {
|
|
702
759
|
try {
|
|
703
|
-
return
|
|
760
|
+
return execSync2("brew list --versions codex", {
|
|
761
|
+
encoding: "utf-8",
|
|
762
|
+
stdio: "pipe",
|
|
763
|
+
timeout: 1e4
|
|
764
|
+
}).trim() || null;
|
|
704
765
|
} catch {
|
|
705
766
|
return null;
|
|
706
767
|
}
|
|
707
|
-
}
|
|
708
|
-
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
var detectCodexInstall = () => checkCodexInstall({
|
|
771
|
+
resolveOnPath: codexOnPath,
|
|
772
|
+
realpath: codexRealpath,
|
|
773
|
+
exists: existsSync,
|
|
774
|
+
listDir: (path) => {
|
|
709
775
|
try {
|
|
710
|
-
return
|
|
776
|
+
return readdirSync(path);
|
|
711
777
|
} catch {
|
|
712
778
|
return null;
|
|
713
779
|
}
|
|
714
780
|
},
|
|
715
|
-
|
|
781
|
+
fileSize: (path) => {
|
|
716
782
|
try {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
stdio: "pipe",
|
|
720
|
-
timeout: 1e4
|
|
721
|
-
}).trim() || null;
|
|
783
|
+
const stat = statSync(path);
|
|
784
|
+
return stat.isFile() ? stat.size : null;
|
|
722
785
|
} catch {
|
|
723
786
|
return null;
|
|
724
787
|
}
|
|
725
788
|
},
|
|
726
|
-
join,
|
|
727
|
-
dirname
|
|
789
|
+
join: join2,
|
|
790
|
+
dirname: dirname2
|
|
728
791
|
});
|
|
729
|
-
var codexSupportsHooks = (
|
|
730
|
-
var codexTrustAutoSupported = (
|
|
792
|
+
var codexSupportsHooks = (version) => version !== null && compareCodexVersion(version, CODEX_HOOKS_MIN_VERSION) >= 0;
|
|
793
|
+
var codexTrustAutoSupported = (version) => version !== null && codexSupportsHooks(version) && compareCodexVersion(version, CODEX_TRUST_VERIFIED_MAX) <= 0;
|
|
731
794
|
var removeCodexNotifyEntry = (codexConfig) => {
|
|
732
795
|
let raw = "";
|
|
733
796
|
try {
|
|
@@ -771,6 +834,16 @@ var setupCodex = async (apiKey) => {
|
|
|
771
834
|
console.log(` ${dim("Install Codex and re-run setup to configure.")}`);
|
|
772
835
|
return "skipped";
|
|
773
836
|
}
|
|
837
|
+
if (detectCodexInstall().kind === "binary-missing") {
|
|
838
|
+
console.log(` ${yellow("!")} Your Codex install is missing the binary it runs.`);
|
|
839
|
+
console.log(` ${dim("A Pushary setup before 0.71.0 asked Codex its version. On macOS that hands")}`);
|
|
840
|
+
console.log(` ${dim("the binary to XProtect, which quarantines the Codex CLI and moves it to the")}`);
|
|
841
|
+
console.log(` ${dim("Trash. Setup no longer runs it. Reinstall Codex to get it back:")}`);
|
|
842
|
+
console.log(` ${cyan("npm install -g @openai/codex")}`);
|
|
843
|
+
console.log(` ${dim("Configuring anyway, so it is ready when Codex is.")}`);
|
|
844
|
+
console.log();
|
|
845
|
+
noteManual("Reinstall Codex: a Pushary setup before 0.71.0 got its binary quarantined by macOS");
|
|
846
|
+
}
|
|
774
847
|
await installGlobally2();
|
|
775
848
|
const codexConfig = codexConfigToml();
|
|
776
849
|
await spinner("Adding Pushary MCP server (key embedded, auto-allowed)", async () => {
|
|
@@ -806,7 +879,7 @@ var setupCodex = async (apiKey) => {
|
|
|
806
879
|
removeCodexNotifyEntry(codexConfig);
|
|
807
880
|
});
|
|
808
881
|
} else {
|
|
809
|
-
if (codexVersion
|
|
882
|
+
if (codexVersion !== null) {
|
|
810
883
|
console.log(` ${yellow("!")} This Codex version predates native hooks (needs ${CODEX_HOOKS_MIN_VERSION.join(".")}+).`);
|
|
811
884
|
console.log(` ${dim("Installing the deprecated notify handler instead. Upgrade Codex and re-run setup")}`);
|
|
812
885
|
console.log(` ${dim("to get policy enforcement, phone approvals, and session tracking.")}`);
|
|
@@ -853,17 +926,17 @@ var setupCodex = async (apiKey) => {
|
|
|
853
926
|
}
|
|
854
927
|
};
|
|
855
928
|
var resolveBundledPlugin = () => {
|
|
856
|
-
const dir =
|
|
929
|
+
const dir = dirname2(fileURLToPath(import.meta.url));
|
|
857
930
|
const candidates = [
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
931
|
+
join2(dir, "..", "..", "data", "cursor-plugin"),
|
|
932
|
+
join2(dir, "..", "data", "cursor-plugin"),
|
|
933
|
+
join2(dir, "..", "..", "..", "cursor-plugin"),
|
|
934
|
+
join2(dir, "..", "..", "cursor-plugin")
|
|
862
935
|
];
|
|
863
|
-
return candidates.find((p) => existsSync(
|
|
936
|
+
return candidates.find((p) => existsSync(join2(p, ".cursor-plugin", "plugin.json"))) ?? null;
|
|
864
937
|
};
|
|
865
938
|
var installCursorUserHooks = (gateScript) => {
|
|
866
|
-
const template = readJson(
|
|
939
|
+
const template = readJson(join2(CURSOR_PLUGIN_DIR, "hooks", "hooks.json")).hooks?.beforeShellExecution?.[0];
|
|
867
940
|
if (!template) throw new Error("bundled Cursor hooks.json missing a beforeShellExecution entry");
|
|
868
941
|
const entry = { ...template, command: `node "${gateScript}"` };
|
|
869
942
|
let userHooks = {};
|
|
@@ -885,7 +958,7 @@ var installCursorUserHooks = (gateScript) => {
|
|
|
885
958
|
writeJson(CURSOR_USER_HOOKS, { ...userHooks, version: userHooks.version ?? 1, hooks });
|
|
886
959
|
};
|
|
887
960
|
var neutralizePluginGate = () => {
|
|
888
|
-
const path =
|
|
961
|
+
const path = join2(CURSOR_PLUGIN_DIR, "hooks", "hooks.json");
|
|
889
962
|
if (!existsSync(path)) return;
|
|
890
963
|
const data = readJson(path);
|
|
891
964
|
if (data.hooks && "beforeShellExecution" in data.hooks) {
|
|
@@ -900,7 +973,7 @@ var setupCursor = async (apiKey) => {
|
|
|
900
973
|
const source = resolveBundledPlugin();
|
|
901
974
|
if (!source) throw new Error("bundled Cursor plugin not found in this package");
|
|
902
975
|
await spinner("Installing Pushary plugin", async () => {
|
|
903
|
-
const staging =
|
|
976
|
+
const staging = join2(dirname2(CURSOR_PLUGIN_DIR), `.pushary-staging-${process.pid}`);
|
|
904
977
|
const backup = `${CURSOR_PLUGIN_DIR}.pushary-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
905
978
|
rmSync(staging, { recursive: true, force: true });
|
|
906
979
|
try {
|
|
@@ -908,7 +981,7 @@ var setupCursor = async (apiKey) => {
|
|
|
908
981
|
recursive: true,
|
|
909
982
|
filter: (p) => !["tools", "node_modules", ".git", ".DS_Store"].includes(basename(p))
|
|
910
983
|
});
|
|
911
|
-
const staged = readJsonSafe(
|
|
984
|
+
const staged = readJsonSafe(join2(staging, ".cursor-plugin", "plugin.json"));
|
|
912
985
|
if (staged.kind !== "ok") {
|
|
913
986
|
throw new Error("staged Cursor plugin is missing or has an unreadable plugin.json");
|
|
914
987
|
}
|
|
@@ -926,7 +999,7 @@ var setupCursor = async (apiKey) => {
|
|
|
926
999
|
}
|
|
927
1000
|
});
|
|
928
1001
|
await spinner("Linking your API key", async () => {
|
|
929
|
-
const mcpPath =
|
|
1002
|
+
const mcpPath = join2(CURSOR_PLUGIN_DIR, "mcp.json");
|
|
930
1003
|
const mcp = readAgentJson(mcpPath);
|
|
931
1004
|
const servers = mcp.mcpServers ?? {};
|
|
932
1005
|
if (servers.pushary) {
|
|
@@ -936,7 +1009,7 @@ var setupCursor = async (apiKey) => {
|
|
|
936
1009
|
}
|
|
937
1010
|
});
|
|
938
1011
|
await spinner("Registering permission gate (~/.cursor/hooks.json)", async () => {
|
|
939
|
-
installCursorUserHooks(
|
|
1012
|
+
installCursorUserHooks(join2(CURSOR_PLUGIN_DIR, "scripts", "pushary-gate.mjs"));
|
|
940
1013
|
neutralizePluginGate();
|
|
941
1014
|
});
|
|
942
1015
|
console.log();
|
|
@@ -948,23 +1021,23 @@ var setupCursor = async (apiKey) => {
|
|
|
948
1021
|
noteManual("Fully quit and reopen Cursor. A Reload Window may not be enough.");
|
|
949
1022
|
};
|
|
950
1023
|
var resolveBundledVsCodePlugin = () => {
|
|
951
|
-
const dir =
|
|
1024
|
+
const dir = dirname2(fileURLToPath(import.meta.url));
|
|
952
1025
|
const candidates = [
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1026
|
+
join2(dir, "..", "..", "data", "vscode-plugin"),
|
|
1027
|
+
join2(dir, "..", "data", "vscode-plugin"),
|
|
1028
|
+
join2(dir, "..", "..", "..", "vscode-plugin"),
|
|
1029
|
+
join2(dir, "..", "..", "vscode-plugin")
|
|
957
1030
|
];
|
|
958
|
-
return candidates.find((p) => existsSync(
|
|
1031
|
+
return candidates.find((p) => existsSync(join2(p, ".claude-plugin", "plugin.json"))) ?? null;
|
|
959
1032
|
};
|
|
960
1033
|
var pinVsCodeGatePath = (pluginDir) => {
|
|
961
|
-
const hooksPath =
|
|
1034
|
+
const hooksPath = join2(pluginDir, "hooks", "hooks.json");
|
|
962
1035
|
const data = readJson(hooksPath);
|
|
963
1036
|
const entries = data.hooks?.PreToolUse;
|
|
964
1037
|
if (!Array.isArray(entries) || entries.length === 0) {
|
|
965
1038
|
throw new Error("bundled VS Code hooks.json is missing a PreToolUse entry");
|
|
966
1039
|
}
|
|
967
|
-
const gate =
|
|
1040
|
+
const gate = join2(pluginDir, "scripts", "pushary-gate.mjs");
|
|
968
1041
|
data.hooks.PreToolUse = entries.map((entry) => ({ ...entry, command: `node "${gate}"` }));
|
|
969
1042
|
writeJson(hooksPath, data);
|
|
970
1043
|
};
|
|
@@ -980,7 +1053,7 @@ var registerVsCodePlugin = (pluginDir) => {
|
|
|
980
1053
|
continue;
|
|
981
1054
|
}
|
|
982
1055
|
if (current !== null) backupFile(settingsPath);
|
|
983
|
-
mkdirSync(
|
|
1056
|
+
mkdirSync(dirname2(settingsPath), { recursive: true });
|
|
984
1057
|
writeFileAtomic(settingsPath, result.content);
|
|
985
1058
|
written.push(settingsPath);
|
|
986
1059
|
}
|
|
@@ -993,16 +1066,16 @@ var setupVsCode = async (apiKey) => {
|
|
|
993
1066
|
const source = resolveBundledVsCodePlugin();
|
|
994
1067
|
if (!source) throw new Error("bundled VS Code plugin not found in this package");
|
|
995
1068
|
await spinner("Installing Pushary plugin", async () => {
|
|
996
|
-
const staging =
|
|
1069
|
+
const staging = join2(dirname2(VSCODE_PLUGIN_DIR), `.pushary-staging-vscode-${process.pid}`);
|
|
997
1070
|
const backup = `${VSCODE_PLUGIN_DIR}.pushary-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
998
|
-
mkdirSync(
|
|
1071
|
+
mkdirSync(dirname2(VSCODE_PLUGIN_DIR), { recursive: true });
|
|
999
1072
|
rmSync(staging, { recursive: true, force: true });
|
|
1000
1073
|
try {
|
|
1001
1074
|
cpSync(source, staging, {
|
|
1002
1075
|
recursive: true,
|
|
1003
1076
|
filter: (p) => !["tools", "node_modules", ".git", ".DS_Store"].includes(basename(p))
|
|
1004
1077
|
});
|
|
1005
|
-
const staged = readJsonSafe(
|
|
1078
|
+
const staged = readJsonSafe(join2(staging, ".claude-plugin", "plugin.json"));
|
|
1006
1079
|
if (staged.kind !== "ok") {
|
|
1007
1080
|
throw new Error("staged VS Code plugin is missing or has an unreadable plugin.json");
|
|
1008
1081
|
}
|
|
@@ -1020,7 +1093,7 @@ var setupVsCode = async (apiKey) => {
|
|
|
1020
1093
|
}
|
|
1021
1094
|
});
|
|
1022
1095
|
await spinner("Linking your API key", async () => {
|
|
1023
|
-
const mcpPath =
|
|
1096
|
+
const mcpPath = join2(VSCODE_PLUGIN_DIR, ".mcp.json");
|
|
1024
1097
|
const mcp = readAgentJson(mcpPath);
|
|
1025
1098
|
const servers = mcp.mcpServers ?? {};
|
|
1026
1099
|
if (servers.pushary) {
|
|
@@ -1160,27 +1233,27 @@ var agentIsWired = (agent) => {
|
|
|
1160
1233
|
return claudeWired({
|
|
1161
1234
|
claudeJson: readJson2(CLAUDE_JSON),
|
|
1162
1235
|
settings: readJson2(CLAUDE_SETTINGS),
|
|
1163
|
-
skillExists: existsSync(
|
|
1236
|
+
skillExists: existsSync(join2(CLAUDE_SKILL_DIR, "SKILL.md"))
|
|
1164
1237
|
});
|
|
1165
1238
|
case "codex": {
|
|
1166
1239
|
let config = null;
|
|
1167
1240
|
try {
|
|
1168
|
-
config = parseTOML(readFileSync(
|
|
1241
|
+
config = parseTOML(readFileSync(join2(CODEX_HOME, "config.toml"), "utf-8"));
|
|
1169
1242
|
} catch {
|
|
1170
1243
|
config = null;
|
|
1171
1244
|
}
|
|
1172
1245
|
return codexWired({
|
|
1173
1246
|
config,
|
|
1174
1247
|
hooks: readJson2(CODEX_HOOKS_JSON),
|
|
1175
|
-
skillExists: existsSync(
|
|
1248
|
+
skillExists: existsSync(join2(CODEX_SKILL_DIR, "SKILL.md"))
|
|
1176
1249
|
});
|
|
1177
1250
|
}
|
|
1178
1251
|
case "gemini_cli":
|
|
1179
1252
|
return geminiWired({ settings: readJson2(GEMINI_SETTINGS) });
|
|
1180
1253
|
case "cursor":
|
|
1181
|
-
return existsSync(
|
|
1254
|
+
return existsSync(join2(CURSOR_PLUGIN_DIR, "mcp.json"));
|
|
1182
1255
|
case "vscode":
|
|
1183
|
-
return existsSync(
|
|
1256
|
+
return existsSync(join2(VSCODE_PLUGIN_DIR, ".mcp.json"));
|
|
1184
1257
|
default:
|
|
1185
1258
|
return true;
|
|
1186
1259
|
}
|
|
@@ -1208,8 +1281,8 @@ var offerProjectInstructions = async (agents, options) => {
|
|
|
1208
1281
|
});
|
|
1209
1282
|
if (!wanted) return;
|
|
1210
1283
|
for (const target of targets) {
|
|
1211
|
-
await spinner(`Writing managed block to ${
|
|
1212
|
-
writeInstructionBlock(
|
|
1284
|
+
await spinner(`Writing managed block to ${join2(process.cwd(), target.file)}`, async () => {
|
|
1285
|
+
writeInstructionBlock(join2(process.cwd(), target.file), renderProjectAgentInstructions(target.label));
|
|
1213
1286
|
}, { optional: true });
|
|
1214
1287
|
}
|
|
1215
1288
|
console.log(` ${dim("Commit the file to share it. Teammates without a key fall back to the terminal.")}`);
|
|
@@ -1360,12 +1433,12 @@ var resolveAgents = async (options) => {
|
|
|
1360
1433
|
});
|
|
1361
1434
|
};
|
|
1362
1435
|
var AGENT_TARGETS = {
|
|
1363
|
-
claude_code: [CLAUDE_JSON, CLAUDE_SETTINGS,
|
|
1364
|
-
codex: [
|
|
1436
|
+
claude_code: [CLAUDE_JSON, CLAUDE_SETTINGS, join2(CLAUDE_SKILL_DIR, "SKILL.md")],
|
|
1437
|
+
codex: [join2(CODEX_HOME, "config.toml"), CODEX_HOOKS_JSON, join2(CODEX_SKILL_DIR, "SKILL.md"), CODEX_AGENTS_MD],
|
|
1365
1438
|
gemini_cli: [GEMINI_SETTINGS, GEMINI_MD],
|
|
1366
1439
|
hermes: ["the Hermes virtualenv (pip install pushary-hermes)"],
|
|
1367
|
-
cursor: [
|
|
1368
|
-
vscode: [
|
|
1440
|
+
cursor: [join2(CURSOR_PLUGIN_DIR, "mcp.json"), CURSOR_USER_HOOKS],
|
|
1441
|
+
vscode: [join2(VSCODE_PLUGIN_DIR, ".mcp.json"), ...vscodeSettingsTargets(existsSync)],
|
|
1369
1442
|
custom: ["nothing (prints connection details only)"]
|
|
1370
1443
|
};
|
|
1371
1444
|
var reportDryRun = (apiKey, agents, keyCheck) => {
|