@pushary/agent-hooks 0.70.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 +44 -0
- package/dist/bin/pushary-setup.js +213 -66
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
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
|
+
|
|
32
|
+
## 0.71.0
|
|
33
|
+
|
|
34
|
+
### Setting up Codex no longer gets Codex deleted by macOS
|
|
35
|
+
|
|
36
|
+
Setup ran `codex --version` to decide whether your Codex was new enough for
|
|
37
|
+
native hooks. On macOS, executing a binary hands it to XProtect, and current
|
|
38
|
+
definitions false-positive on the Codex CLI: macOS killed it and moved it to the
|
|
39
|
+
Trash. Reading a version number destroyed the tool it was asking, mid-setup, and
|
|
40
|
+
the only thing you saw was "codex was not opened because it contains malware".
|
|
41
|
+
|
|
42
|
+
Setup no longer runs Codex, or any other agent's binary, to learn something about
|
|
43
|
+
it. The version now comes from the package manifest next to the binary, which
|
|
44
|
+
covers npm, bun, pnpm and yarn installs, and from `brew list` for the Homebrew
|
|
45
|
+
cask, which ships no manifest. Same result, nothing launched.
|
|
46
|
+
|
|
3
47
|
## 0.70.0
|
|
4
48
|
|
|
5
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 } 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";
|
|
@@ -198,6 +198,89 @@ var describeClosing = (facts) => {
|
|
|
198
198
|
};
|
|
199
199
|
};
|
|
200
200
|
|
|
201
|
+
// src/setup/codex-version.ts
|
|
202
|
+
import { dirname, join } from "path";
|
|
203
|
+
var parseCodexVersion = (raw) => {
|
|
204
|
+
const match = raw.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
205
|
+
if (!match) return null;
|
|
206
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
207
|
+
};
|
|
208
|
+
var compareCodexVersion = (a, b) => {
|
|
209
|
+
for (let i = 0; i < 3; i++) {
|
|
210
|
+
if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1;
|
|
211
|
+
}
|
|
212
|
+
return 0;
|
|
213
|
+
};
|
|
214
|
+
var versionFromManifest = (probe, binPath) => {
|
|
215
|
+
let dir = dirname(binPath);
|
|
216
|
+
for (let depth = 0; depth < 4; depth++) {
|
|
217
|
+
const raw = probe.readFile(join(dir, "package.json"));
|
|
218
|
+
if (raw) {
|
|
219
|
+
try {
|
|
220
|
+
const { version } = JSON.parse(raw);
|
|
221
|
+
if (typeof version === "string") {
|
|
222
|
+
const parsed = parseCodexVersion(version);
|
|
223
|
+
if (parsed) return parsed;
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const parent = dirname(dir);
|
|
229
|
+
if (parent === dir) break;
|
|
230
|
+
dir = parent;
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
};
|
|
234
|
+
var readCodexVersion = (probe) => {
|
|
235
|
+
const onPath = probe.resolveOnPath();
|
|
236
|
+
if (onPath) {
|
|
237
|
+
const fromManifest = versionFromManifest(probe, probe.realpath(onPath) ?? onPath);
|
|
238
|
+
if (fromManifest) return fromManifest;
|
|
239
|
+
}
|
|
240
|
+
const brew = probe.brewVersions();
|
|
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;
|
|
253
|
+
}
|
|
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] };
|
|
282
|
+
};
|
|
283
|
+
|
|
201
284
|
// src/skills-cli.ts
|
|
202
285
|
import { execSync } from "child_process";
|
|
203
286
|
var installSkillViaSkillsCli = (agent) => {
|
|
@@ -435,7 +518,7 @@ var readAgentJson = (filePath) => {
|
|
|
435
518
|
);
|
|
436
519
|
};
|
|
437
520
|
var writeJson = (filePath, data) => {
|
|
438
|
-
const dir =
|
|
521
|
+
const dir = dirname2(filePath);
|
|
439
522
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
440
523
|
writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
441
524
|
};
|
|
@@ -514,23 +597,23 @@ var installGlobally2 = async () => {
|
|
|
514
597
|
var _cachedSkillContent = null;
|
|
515
598
|
var fetchSkillContent = async () => {
|
|
516
599
|
if (_cachedSkillContent) return _cachedSkillContent;
|
|
517
|
-
const __dirname =
|
|
600
|
+
const __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
518
601
|
const candidates = [
|
|
519
|
-
|
|
520
|
-
|
|
602
|
+
join2(__dirname, "..", "..", "data", "SKILL.md"),
|
|
603
|
+
join2(__dirname, "..", "data", "SKILL.md")
|
|
521
604
|
];
|
|
522
605
|
const source = candidates.find((path) => existsSync(path));
|
|
523
606
|
if (!source) throw new Error("packaged skill not found");
|
|
524
607
|
_cachedSkillContent = readFileSync(source, "utf-8");
|
|
525
608
|
return _cachedSkillContent;
|
|
526
609
|
};
|
|
527
|
-
var skillsCliInUse = () => isInstalled("skills") || existsSync(
|
|
610
|
+
var skillsCliInUse = () => isInstalled("skills") || existsSync(join2(homedir(), ".agents", "skills"));
|
|
528
611
|
var installSkill = async (agent, fallbackDir) => {
|
|
529
612
|
await spinner("Installing Pushary skill", async () => {
|
|
530
613
|
if (skillsCliInUse() && installSkillViaSkillsCli(agent)) return;
|
|
531
614
|
const content = await fetchSkillContent();
|
|
532
615
|
if (!existsSync(fallbackDir)) mkdirSync(fallbackDir, { recursive: true });
|
|
533
|
-
writeFileSync(
|
|
616
|
+
writeFileSync(join2(fallbackDir, "SKILL.md"), content, "utf-8");
|
|
534
617
|
});
|
|
535
618
|
};
|
|
536
619
|
var setupClaudeCode = async (apiKey) => {
|
|
@@ -577,8 +660,8 @@ var resolveHermesPython = () => {
|
|
|
577
660
|
}
|
|
578
661
|
} catch {
|
|
579
662
|
}
|
|
580
|
-
const venvRoot =
|
|
581
|
-
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")];
|
|
582
665
|
return candidates.find(existsSync) ?? null;
|
|
583
666
|
};
|
|
584
667
|
var ensurePip = (python) => {
|
|
@@ -594,13 +677,13 @@ var ensurePip = (python) => {
|
|
|
594
677
|
};
|
|
595
678
|
var enablePusharyPlugin = (python) => {
|
|
596
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)';
|
|
597
|
-
const scriptPath =
|
|
680
|
+
const scriptPath = join2(tmpdir(), `pushary-hermes-${process.pid}.py`);
|
|
598
681
|
try {
|
|
599
682
|
writeFileSync(scriptPath, snippet.split("; ").join("\n"), "utf-8");
|
|
600
683
|
execSync2(`"${python}" "${scriptPath}"`, { stdio: "pipe", timeout: 15e3 });
|
|
601
|
-
return;
|
|
684
|
+
return true;
|
|
602
685
|
} catch {
|
|
603
|
-
|
|
686
|
+
return false;
|
|
604
687
|
} finally {
|
|
605
688
|
try {
|
|
606
689
|
rmSync(scriptPath, { force: true });
|
|
@@ -627,9 +710,14 @@ var setupHermes = async (_apiKey) => {
|
|
|
627
710
|
ensurePip(python);
|
|
628
711
|
execSync2(`"${python}" -m pip install --upgrade hermes-plugin-pushary`, { stdio: "pipe", timeout: 18e4 });
|
|
629
712
|
});
|
|
713
|
+
let pluginEnabled = false;
|
|
630
714
|
await spinner("Enabling plugin + routing questions to push", async () => {
|
|
631
|
-
enablePusharyPlugin(python);
|
|
715
|
+
pluginEnabled = enablePusharyPlugin(python);
|
|
632
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
|
+
}
|
|
633
721
|
console.log();
|
|
634
722
|
console.log(` ${dim("What this configured:")}`);
|
|
635
723
|
console.log(` ${dim("\u2022")} Native tools: pushary_notify, pushary_ask, pushary_wait, pushary_cancel`);
|
|
@@ -638,27 +726,69 @@ var setupHermes = async (_apiKey) => {
|
|
|
638
726
|
console.log(` ${dim("\u2022")} Permission gating: set ${bold("PUSHARY_GATE_TOOLS")} to require lock-screen approval for risky tools`);
|
|
639
727
|
console.log(` ${dim("To re-enable terminal prompts:")} remove ${bold("clarify")} from ${dim("agent.disabled_toolsets")} in ~/.hermes/config.yaml`);
|
|
640
728
|
};
|
|
641
|
-
var CODEX_HOOKS_JSON =
|
|
729
|
+
var CODEX_HOOKS_JSON = join2(CODEX_HOME, "hooks.json");
|
|
642
730
|
var CODEX_HOOKS_MIN_VERSION = [0, 122, 0];
|
|
643
731
|
var CODEX_TRUST_VERIFIED_MAX = [0, 142, 2];
|
|
644
|
-
var
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1;
|
|
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;
|
|
652
739
|
}
|
|
653
|
-
return 0;
|
|
654
740
|
};
|
|
655
|
-
var
|
|
741
|
+
var codexRealpath = (path) => {
|
|
656
742
|
try {
|
|
657
|
-
return
|
|
743
|
+
return realpathSync(path);
|
|
658
744
|
} catch {
|
|
659
745
|
return null;
|
|
660
746
|
}
|
|
661
747
|
};
|
|
748
|
+
var detectCodexVersion = () => readCodexVersion({
|
|
749
|
+
resolveOnPath: codexOnPath,
|
|
750
|
+
realpath: codexRealpath,
|
|
751
|
+
readFile: (path) => {
|
|
752
|
+
try {
|
|
753
|
+
return readFileSync(path, "utf-8");
|
|
754
|
+
} catch {
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
},
|
|
758
|
+
brewVersions: () => {
|
|
759
|
+
try {
|
|
760
|
+
return execSync2("brew list --versions codex", {
|
|
761
|
+
encoding: "utf-8",
|
|
762
|
+
stdio: "pipe",
|
|
763
|
+
timeout: 1e4
|
|
764
|
+
}).trim() || null;
|
|
765
|
+
} catch {
|
|
766
|
+
return null;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
var detectCodexInstall = () => checkCodexInstall({
|
|
771
|
+
resolveOnPath: codexOnPath,
|
|
772
|
+
realpath: codexRealpath,
|
|
773
|
+
exists: existsSync,
|
|
774
|
+
listDir: (path) => {
|
|
775
|
+
try {
|
|
776
|
+
return readdirSync(path);
|
|
777
|
+
} catch {
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
fileSize: (path) => {
|
|
782
|
+
try {
|
|
783
|
+
const stat = statSync(path);
|
|
784
|
+
return stat.isFile() ? stat.size : null;
|
|
785
|
+
} catch {
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
join: join2,
|
|
790
|
+
dirname: dirname2
|
|
791
|
+
});
|
|
662
792
|
var codexSupportsHooks = (version) => version !== null && compareCodexVersion(version, CODEX_HOOKS_MIN_VERSION) >= 0;
|
|
663
793
|
var codexTrustAutoSupported = (version) => version !== null && codexSupportsHooks(version) && compareCodexVersion(version, CODEX_TRUST_VERIFIED_MAX) <= 0;
|
|
664
794
|
var removeCodexNotifyEntry = (codexConfig) => {
|
|
@@ -704,6 +834,16 @@ var setupCodex = async (apiKey) => {
|
|
|
704
834
|
console.log(` ${dim("Install Codex and re-run setup to configure.")}`);
|
|
705
835
|
return "skipped";
|
|
706
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
|
+
}
|
|
707
847
|
await installGlobally2();
|
|
708
848
|
const codexConfig = codexConfigToml();
|
|
709
849
|
await spinner("Adding Pushary MCP server (key embedded, auto-allowed)", async () => {
|
|
@@ -711,7 +851,7 @@ var setupCodex = async (apiKey) => {
|
|
|
711
851
|
addCodexMcpServer(config, apiKey);
|
|
712
852
|
writeFileAtomic(codexConfig, stringifyTOML(config), KEY_FILE_MODE);
|
|
713
853
|
});
|
|
714
|
-
const codexVersion =
|
|
854
|
+
const codexVersion = detectCodexVersion();
|
|
715
855
|
const hooksSupported = codexSupportsHooks(codexVersion);
|
|
716
856
|
const trustAuto = codexTrustAutoSupported(codexVersion);
|
|
717
857
|
let trusted = false;
|
|
@@ -739,9 +879,16 @@ var setupCodex = async (apiKey) => {
|
|
|
739
879
|
removeCodexNotifyEntry(codexConfig);
|
|
740
880
|
});
|
|
741
881
|
} else {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
882
|
+
if (codexVersion !== null) {
|
|
883
|
+
console.log(` ${yellow("!")} This Codex version predates native hooks (needs ${CODEX_HOOKS_MIN_VERSION.join(".")}+).`);
|
|
884
|
+
console.log(` ${dim("Installing the deprecated notify handler instead. Upgrade Codex and re-run setup")}`);
|
|
885
|
+
console.log(` ${dim("to get policy enforcement, phone approvals, and session tracking.")}`);
|
|
886
|
+
} else {
|
|
887
|
+
console.log(` ${yellow("!")} Could not read your Codex version from its install, so hooks were skipped.`);
|
|
888
|
+
console.log(` ${dim("Deliberately not run: asking the binary its version is what macOS quarantines.")}`);
|
|
889
|
+
console.log(` ${dim(`Installing the notify handler, which works on every version. If Codex is ${CODEX_HOOKS_MIN_VERSION.join(".")}+,`)}`);
|
|
890
|
+
console.log(` ${dim("re-run setup after installing it through npm or Homebrew to get native hooks.")}`);
|
|
891
|
+
}
|
|
745
892
|
await spinner("Adding notify handler for Codex events (deprecated)", async () => {
|
|
746
893
|
addCodexNotifyEntry(codexConfig);
|
|
747
894
|
});
|
|
@@ -779,17 +926,17 @@ var setupCodex = async (apiKey) => {
|
|
|
779
926
|
}
|
|
780
927
|
};
|
|
781
928
|
var resolveBundledPlugin = () => {
|
|
782
|
-
const dir =
|
|
929
|
+
const dir = dirname2(fileURLToPath(import.meta.url));
|
|
783
930
|
const candidates = [
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
931
|
+
join2(dir, "..", "..", "data", "cursor-plugin"),
|
|
932
|
+
join2(dir, "..", "data", "cursor-plugin"),
|
|
933
|
+
join2(dir, "..", "..", "..", "cursor-plugin"),
|
|
934
|
+
join2(dir, "..", "..", "cursor-plugin")
|
|
788
935
|
];
|
|
789
|
-
return candidates.find((p) => existsSync(
|
|
936
|
+
return candidates.find((p) => existsSync(join2(p, ".cursor-plugin", "plugin.json"))) ?? null;
|
|
790
937
|
};
|
|
791
938
|
var installCursorUserHooks = (gateScript) => {
|
|
792
|
-
const template = readJson(
|
|
939
|
+
const template = readJson(join2(CURSOR_PLUGIN_DIR, "hooks", "hooks.json")).hooks?.beforeShellExecution?.[0];
|
|
793
940
|
if (!template) throw new Error("bundled Cursor hooks.json missing a beforeShellExecution entry");
|
|
794
941
|
const entry = { ...template, command: `node "${gateScript}"` };
|
|
795
942
|
let userHooks = {};
|
|
@@ -811,7 +958,7 @@ var installCursorUserHooks = (gateScript) => {
|
|
|
811
958
|
writeJson(CURSOR_USER_HOOKS, { ...userHooks, version: userHooks.version ?? 1, hooks });
|
|
812
959
|
};
|
|
813
960
|
var neutralizePluginGate = () => {
|
|
814
|
-
const path =
|
|
961
|
+
const path = join2(CURSOR_PLUGIN_DIR, "hooks", "hooks.json");
|
|
815
962
|
if (!existsSync(path)) return;
|
|
816
963
|
const data = readJson(path);
|
|
817
964
|
if (data.hooks && "beforeShellExecution" in data.hooks) {
|
|
@@ -826,7 +973,7 @@ var setupCursor = async (apiKey) => {
|
|
|
826
973
|
const source = resolveBundledPlugin();
|
|
827
974
|
if (!source) throw new Error("bundled Cursor plugin not found in this package");
|
|
828
975
|
await spinner("Installing Pushary plugin", async () => {
|
|
829
|
-
const staging =
|
|
976
|
+
const staging = join2(dirname2(CURSOR_PLUGIN_DIR), `.pushary-staging-${process.pid}`);
|
|
830
977
|
const backup = `${CURSOR_PLUGIN_DIR}.pushary-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
831
978
|
rmSync(staging, { recursive: true, force: true });
|
|
832
979
|
try {
|
|
@@ -834,7 +981,7 @@ var setupCursor = async (apiKey) => {
|
|
|
834
981
|
recursive: true,
|
|
835
982
|
filter: (p) => !["tools", "node_modules", ".git", ".DS_Store"].includes(basename(p))
|
|
836
983
|
});
|
|
837
|
-
const staged = readJsonSafe(
|
|
984
|
+
const staged = readJsonSafe(join2(staging, ".cursor-plugin", "plugin.json"));
|
|
838
985
|
if (staged.kind !== "ok") {
|
|
839
986
|
throw new Error("staged Cursor plugin is missing or has an unreadable plugin.json");
|
|
840
987
|
}
|
|
@@ -852,7 +999,7 @@ var setupCursor = async (apiKey) => {
|
|
|
852
999
|
}
|
|
853
1000
|
});
|
|
854
1001
|
await spinner("Linking your API key", async () => {
|
|
855
|
-
const mcpPath =
|
|
1002
|
+
const mcpPath = join2(CURSOR_PLUGIN_DIR, "mcp.json");
|
|
856
1003
|
const mcp = readAgentJson(mcpPath);
|
|
857
1004
|
const servers = mcp.mcpServers ?? {};
|
|
858
1005
|
if (servers.pushary) {
|
|
@@ -862,7 +1009,7 @@ var setupCursor = async (apiKey) => {
|
|
|
862
1009
|
}
|
|
863
1010
|
});
|
|
864
1011
|
await spinner("Registering permission gate (~/.cursor/hooks.json)", async () => {
|
|
865
|
-
installCursorUserHooks(
|
|
1012
|
+
installCursorUserHooks(join2(CURSOR_PLUGIN_DIR, "scripts", "pushary-gate.mjs"));
|
|
866
1013
|
neutralizePluginGate();
|
|
867
1014
|
});
|
|
868
1015
|
console.log();
|
|
@@ -874,23 +1021,23 @@ var setupCursor = async (apiKey) => {
|
|
|
874
1021
|
noteManual("Fully quit and reopen Cursor. A Reload Window may not be enough.");
|
|
875
1022
|
};
|
|
876
1023
|
var resolveBundledVsCodePlugin = () => {
|
|
877
|
-
const dir =
|
|
1024
|
+
const dir = dirname2(fileURLToPath(import.meta.url));
|
|
878
1025
|
const candidates = [
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1026
|
+
join2(dir, "..", "..", "data", "vscode-plugin"),
|
|
1027
|
+
join2(dir, "..", "data", "vscode-plugin"),
|
|
1028
|
+
join2(dir, "..", "..", "..", "vscode-plugin"),
|
|
1029
|
+
join2(dir, "..", "..", "vscode-plugin")
|
|
883
1030
|
];
|
|
884
|
-
return candidates.find((p) => existsSync(
|
|
1031
|
+
return candidates.find((p) => existsSync(join2(p, ".claude-plugin", "plugin.json"))) ?? null;
|
|
885
1032
|
};
|
|
886
1033
|
var pinVsCodeGatePath = (pluginDir) => {
|
|
887
|
-
const hooksPath =
|
|
1034
|
+
const hooksPath = join2(pluginDir, "hooks", "hooks.json");
|
|
888
1035
|
const data = readJson(hooksPath);
|
|
889
1036
|
const entries = data.hooks?.PreToolUse;
|
|
890
1037
|
if (!Array.isArray(entries) || entries.length === 0) {
|
|
891
1038
|
throw new Error("bundled VS Code hooks.json is missing a PreToolUse entry");
|
|
892
1039
|
}
|
|
893
|
-
const gate =
|
|
1040
|
+
const gate = join2(pluginDir, "scripts", "pushary-gate.mjs");
|
|
894
1041
|
data.hooks.PreToolUse = entries.map((entry) => ({ ...entry, command: `node "${gate}"` }));
|
|
895
1042
|
writeJson(hooksPath, data);
|
|
896
1043
|
};
|
|
@@ -906,7 +1053,7 @@ var registerVsCodePlugin = (pluginDir) => {
|
|
|
906
1053
|
continue;
|
|
907
1054
|
}
|
|
908
1055
|
if (current !== null) backupFile(settingsPath);
|
|
909
|
-
mkdirSync(
|
|
1056
|
+
mkdirSync(dirname2(settingsPath), { recursive: true });
|
|
910
1057
|
writeFileAtomic(settingsPath, result.content);
|
|
911
1058
|
written.push(settingsPath);
|
|
912
1059
|
}
|
|
@@ -919,16 +1066,16 @@ var setupVsCode = async (apiKey) => {
|
|
|
919
1066
|
const source = resolveBundledVsCodePlugin();
|
|
920
1067
|
if (!source) throw new Error("bundled VS Code plugin not found in this package");
|
|
921
1068
|
await spinner("Installing Pushary plugin", async () => {
|
|
922
|
-
const staging =
|
|
1069
|
+
const staging = join2(dirname2(VSCODE_PLUGIN_DIR), `.pushary-staging-vscode-${process.pid}`);
|
|
923
1070
|
const backup = `${VSCODE_PLUGIN_DIR}.pushary-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
924
|
-
mkdirSync(
|
|
1071
|
+
mkdirSync(dirname2(VSCODE_PLUGIN_DIR), { recursive: true });
|
|
925
1072
|
rmSync(staging, { recursive: true, force: true });
|
|
926
1073
|
try {
|
|
927
1074
|
cpSync(source, staging, {
|
|
928
1075
|
recursive: true,
|
|
929
1076
|
filter: (p) => !["tools", "node_modules", ".git", ".DS_Store"].includes(basename(p))
|
|
930
1077
|
});
|
|
931
|
-
const staged = readJsonSafe(
|
|
1078
|
+
const staged = readJsonSafe(join2(staging, ".claude-plugin", "plugin.json"));
|
|
932
1079
|
if (staged.kind !== "ok") {
|
|
933
1080
|
throw new Error("staged VS Code plugin is missing or has an unreadable plugin.json");
|
|
934
1081
|
}
|
|
@@ -946,7 +1093,7 @@ var setupVsCode = async (apiKey) => {
|
|
|
946
1093
|
}
|
|
947
1094
|
});
|
|
948
1095
|
await spinner("Linking your API key", async () => {
|
|
949
|
-
const mcpPath =
|
|
1096
|
+
const mcpPath = join2(VSCODE_PLUGIN_DIR, ".mcp.json");
|
|
950
1097
|
const mcp = readAgentJson(mcpPath);
|
|
951
1098
|
const servers = mcp.mcpServers ?? {};
|
|
952
1099
|
if (servers.pushary) {
|
|
@@ -1086,27 +1233,27 @@ var agentIsWired = (agent) => {
|
|
|
1086
1233
|
return claudeWired({
|
|
1087
1234
|
claudeJson: readJson2(CLAUDE_JSON),
|
|
1088
1235
|
settings: readJson2(CLAUDE_SETTINGS),
|
|
1089
|
-
skillExists: existsSync(
|
|
1236
|
+
skillExists: existsSync(join2(CLAUDE_SKILL_DIR, "SKILL.md"))
|
|
1090
1237
|
});
|
|
1091
1238
|
case "codex": {
|
|
1092
1239
|
let config = null;
|
|
1093
1240
|
try {
|
|
1094
|
-
config = parseTOML(readFileSync(
|
|
1241
|
+
config = parseTOML(readFileSync(join2(CODEX_HOME, "config.toml"), "utf-8"));
|
|
1095
1242
|
} catch {
|
|
1096
1243
|
config = null;
|
|
1097
1244
|
}
|
|
1098
1245
|
return codexWired({
|
|
1099
1246
|
config,
|
|
1100
1247
|
hooks: readJson2(CODEX_HOOKS_JSON),
|
|
1101
|
-
skillExists: existsSync(
|
|
1248
|
+
skillExists: existsSync(join2(CODEX_SKILL_DIR, "SKILL.md"))
|
|
1102
1249
|
});
|
|
1103
1250
|
}
|
|
1104
1251
|
case "gemini_cli":
|
|
1105
1252
|
return geminiWired({ settings: readJson2(GEMINI_SETTINGS) });
|
|
1106
1253
|
case "cursor":
|
|
1107
|
-
return existsSync(
|
|
1254
|
+
return existsSync(join2(CURSOR_PLUGIN_DIR, "mcp.json"));
|
|
1108
1255
|
case "vscode":
|
|
1109
|
-
return existsSync(
|
|
1256
|
+
return existsSync(join2(VSCODE_PLUGIN_DIR, ".mcp.json"));
|
|
1110
1257
|
default:
|
|
1111
1258
|
return true;
|
|
1112
1259
|
}
|
|
@@ -1134,8 +1281,8 @@ var offerProjectInstructions = async (agents, options) => {
|
|
|
1134
1281
|
});
|
|
1135
1282
|
if (!wanted) return;
|
|
1136
1283
|
for (const target of targets) {
|
|
1137
|
-
await spinner(`Writing managed block to ${
|
|
1138
|
-
writeInstructionBlock(
|
|
1284
|
+
await spinner(`Writing managed block to ${join2(process.cwd(), target.file)}`, async () => {
|
|
1285
|
+
writeInstructionBlock(join2(process.cwd(), target.file), renderProjectAgentInstructions(target.label));
|
|
1139
1286
|
}, { optional: true });
|
|
1140
1287
|
}
|
|
1141
1288
|
console.log(` ${dim("Commit the file to share it. Teammates without a key fall back to the terminal.")}`);
|
|
@@ -1286,12 +1433,12 @@ var resolveAgents = async (options) => {
|
|
|
1286
1433
|
});
|
|
1287
1434
|
};
|
|
1288
1435
|
var AGENT_TARGETS = {
|
|
1289
|
-
claude_code: [CLAUDE_JSON, CLAUDE_SETTINGS,
|
|
1290
|
-
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],
|
|
1291
1438
|
gemini_cli: [GEMINI_SETTINGS, GEMINI_MD],
|
|
1292
1439
|
hermes: ["the Hermes virtualenv (pip install pushary-hermes)"],
|
|
1293
|
-
cursor: [
|
|
1294
|
-
vscode: [
|
|
1440
|
+
cursor: [join2(CURSOR_PLUGIN_DIR, "mcp.json"), CURSOR_USER_HOOKS],
|
|
1441
|
+
vscode: [join2(VSCODE_PLUGIN_DIR, ".mcp.json"), ...vscodeSettingsTargets(existsSync)],
|
|
1295
1442
|
custom: ["nothing (prints connection details only)"]
|
|
1296
1443
|
};
|
|
1297
1444
|
var reportDryRun = (apiKey, agents, keyCheck) => {
|