@pushary/agent-hooks 0.71.0 → 0.73.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 +59 -3
- package/dist/bin/pushary-setup.js +182 -90
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.73.0
|
|
4
|
+
|
|
5
|
+
### Codex could finish setup with no skill installed
|
|
6
|
+
|
|
7
|
+
If your machine already uses skills.sh, setup installs the Pushary skill
|
|
8
|
+
through that CLI so the install registers rather than being invisible. For
|
|
9
|
+
Codex, `skills add --agent codex` prints "copy to Codex", says "Installation
|
|
10
|
+
complete" and exits 0, then writes only `~/.agents/skills/pushary`. Nothing
|
|
11
|
+
lands in `~/.codex/skills`, which is where Codex reads. Setup took the exit
|
|
12
|
+
code as proof, skipped the copy it ships, and Codex ended up with no skill.
|
|
13
|
+
|
|
14
|
+
Doctor caught it, and then gave advice that could not work: clean and set up
|
|
15
|
+
again reinstalled exactly the same way and failed the same check.
|
|
16
|
+
|
|
17
|
+
The exit code no longer decides this. What decides it is whether the run left
|
|
18
|
+
a SKILL.md where that agent reads, which is the same thing doctor looks at,
|
|
19
|
+
and setup writes its own copy when it did not. Anything skills.sh really did
|
|
20
|
+
install is left exactly as it installed it, symlinks included.
|
|
21
|
+
|
|
22
|
+
Everything else about the wiring was fine, so this cost you the skill and
|
|
23
|
+
nothing more. Approvals, hooks and the instructions in `~/.codex/AGENTS.md`
|
|
24
|
+
were all in place.
|
|
25
|
+
|
|
26
|
+
### A skill that will not install no longer stops setup
|
|
27
|
+
|
|
28
|
+
The skill is the one piece of the wiring nothing else depends on. It used to
|
|
29
|
+
be able to abort setup partway, leaving an agent half configured. Now it warns
|
|
30
|
+
and setup finishes the rest, and `pushary doctor` tells you the skill is
|
|
31
|
+
missing.
|
|
32
|
+
|
|
33
|
+
## 0.72.0
|
|
34
|
+
|
|
35
|
+
### Setup tells you when Codex was already quarantined
|
|
36
|
+
|
|
37
|
+
0.71.0 stopped setup from getting your Codex binary deleted. This handles the
|
|
38
|
+
machines where it already happened. What macOS leaves behind passes every check
|
|
39
|
+
setup made: the `codex` on your PATH is a small JavaScript launcher and it
|
|
40
|
+
survives, `which codex` still answers, only the binary it launches is gone. So
|
|
41
|
+
setup wired up an agent that could not start and reported success.
|
|
42
|
+
|
|
43
|
+
It now says so before writing anything, and only when it can prove it: a vendor
|
|
44
|
+
directory that exists and holds nothing the size of a native binary. A layout it
|
|
45
|
+
does not recognise stays quiet, because telling somebody their working install is
|
|
46
|
+
broken is the worse mistake. Reinstall with `npm install -g @openai/codex` or
|
|
47
|
+
`brew install --cask codex`.
|
|
48
|
+
|
|
49
|
+
### Setup no longer runs the Hermes binary either
|
|
50
|
+
|
|
51
|
+
The same hazard, one agent over. When the config edit that enables the plugin
|
|
52
|
+
failed, setup fell back to running `hermes plugins enable pushary` — executing a
|
|
53
|
+
third-party agent binary, which is exactly what cost people their Codex install.
|
|
54
|
+
The fallback also did not work, because `hermes plugins enable` does not see a
|
|
55
|
+
pip install. It is gone, and a failed config edit now tells you the one line to
|
|
56
|
+
add by hand.
|
|
57
|
+
|
|
58
|
+
Executing an agent binary is now a build failure rather than a habit: every
|
|
59
|
+
source file in this package is scanned on every CI run, and the check knows the
|
|
60
|
+
difference between running `codex` and asking `which` where it is.
|
|
61
|
+
|
|
3
62
|
## 0.71.0
|
|
4
63
|
|
|
5
64
|
### Setting up Codex no longer gets Codex deleted by macOS
|
|
@@ -15,9 +74,6 @@ it. The version now comes from the package manifest next to the binary, which
|
|
|
15
74
|
covers npm, bun, pnpm and yarn installs, and from `brew list` for the Homebrew
|
|
16
75
|
cask, which ships no manifest. Same result, nothing launched.
|
|
17
76
|
|
|
18
|
-
If your Codex was already quarantined, reinstall it: `npm install -g @openai/codex`
|
|
19
|
-
or `brew install --cask codex`.
|
|
20
|
-
|
|
21
77
|
## 0.70.0
|
|
22
78
|
|
|
23
79
|
### 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
|
|
@@ -258,6 +294,17 @@ var installSkillViaSkillsCli = (agent) => {
|
|
|
258
294
|
return false;
|
|
259
295
|
}
|
|
260
296
|
};
|
|
297
|
+
var installAgentSkill = async (request, effects) => {
|
|
298
|
+
if (request.skillsCliInUse) {
|
|
299
|
+
const startedAt = effects.now();
|
|
300
|
+
if (effects.runSkillsCli(request.agent)) {
|
|
301
|
+
const modifiedAt = effects.skillModifiedAt(request.agentSkillDir);
|
|
302
|
+
if (modifiedAt !== null && modifiedAt >= startedAt) return "skills-cli";
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
await effects.writePackagedSkill(request.agentSkillDir);
|
|
306
|
+
return "packaged-copy";
|
|
307
|
+
};
|
|
261
308
|
|
|
262
309
|
// src/setup/telemetry.ts
|
|
263
310
|
var currentStep = "start";
|
|
@@ -482,7 +529,7 @@ var readAgentJson = (filePath) => {
|
|
|
482
529
|
);
|
|
483
530
|
};
|
|
484
531
|
var writeJson = (filePath, data) => {
|
|
485
|
-
const dir =
|
|
532
|
+
const dir = dirname2(filePath);
|
|
486
533
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
487
534
|
writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
488
535
|
};
|
|
@@ -561,24 +608,32 @@ var installGlobally2 = async () => {
|
|
|
561
608
|
var _cachedSkillContent = null;
|
|
562
609
|
var fetchSkillContent = async () => {
|
|
563
610
|
if (_cachedSkillContent) return _cachedSkillContent;
|
|
564
|
-
const __dirname =
|
|
611
|
+
const __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
565
612
|
const candidates = [
|
|
566
|
-
|
|
567
|
-
|
|
613
|
+
join2(__dirname, "..", "..", "data", "SKILL.md"),
|
|
614
|
+
join2(__dirname, "..", "data", "SKILL.md")
|
|
568
615
|
];
|
|
569
616
|
const source = candidates.find((path) => existsSync(path));
|
|
570
617
|
if (!source) throw new Error("packaged skill not found");
|
|
571
618
|
_cachedSkillContent = readFileSync(source, "utf-8");
|
|
572
619
|
return _cachedSkillContent;
|
|
573
620
|
};
|
|
574
|
-
var
|
|
575
|
-
|
|
621
|
+
var writePackagedSkill = async (dir) => {
|
|
622
|
+
const content = await fetchSkillContent();
|
|
623
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
624
|
+
writeFileSync(join2(dir, "SKILL.md"), content, "utf-8");
|
|
625
|
+
};
|
|
626
|
+
var skillsCliInUse = () => isInstalled("skills") || existsSync(join2(homedir(), ".agents", "skills"));
|
|
627
|
+
var skillInstallEffects = {
|
|
628
|
+
runSkillsCli: installSkillViaSkillsCli,
|
|
629
|
+
skillModifiedAt: (dir) => statSync(join2(dir, "SKILL.md"), { throwIfNoEntry: false })?.mtimeMs ?? null,
|
|
630
|
+
writePackagedSkill,
|
|
631
|
+
now: () => Date.now()
|
|
632
|
+
};
|
|
633
|
+
var installSkill = async (agent, agentSkillDir) => {
|
|
576
634
|
await spinner("Installing Pushary skill", async () => {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
if (!existsSync(fallbackDir)) mkdirSync(fallbackDir, { recursive: true });
|
|
580
|
-
writeFileSync(join(fallbackDir, "SKILL.md"), content, "utf-8");
|
|
581
|
-
});
|
|
635
|
+
await installAgentSkill({ agent, agentSkillDir, skillsCliInUse: skillsCliInUse() }, skillInstallEffects);
|
|
636
|
+
}, { optional: true });
|
|
582
637
|
};
|
|
583
638
|
var setupClaudeCode = async (apiKey) => {
|
|
584
639
|
console.log(`
|
|
@@ -624,8 +679,8 @@ var resolveHermesPython = () => {
|
|
|
624
679
|
}
|
|
625
680
|
} catch {
|
|
626
681
|
}
|
|
627
|
-
const venvRoot =
|
|
628
|
-
const candidates = IS_WINDOWS ? [
|
|
682
|
+
const venvRoot = join2(homedir(), ".hermes", "hermes-agent", "venv");
|
|
683
|
+
const candidates = IS_WINDOWS ? [join2(venvRoot, "Scripts", "python.exe"), join2(venvRoot, "Scripts", "python3.exe")] : [join2(venvRoot, "bin", "python3"), join2(venvRoot, "bin", "python")];
|
|
629
684
|
return candidates.find(existsSync) ?? null;
|
|
630
685
|
};
|
|
631
686
|
var ensurePip = (python) => {
|
|
@@ -641,13 +696,13 @@ var ensurePip = (python) => {
|
|
|
641
696
|
};
|
|
642
697
|
var enablePusharyPlugin = (python) => {
|
|
643
698
|
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 =
|
|
699
|
+
const scriptPath = join2(tmpdir(), `pushary-hermes-${process.pid}.py`);
|
|
645
700
|
try {
|
|
646
701
|
writeFileSync(scriptPath, snippet.split("; ").join("\n"), "utf-8");
|
|
647
702
|
execSync2(`"${python}" "${scriptPath}"`, { stdio: "pipe", timeout: 15e3 });
|
|
648
|
-
return;
|
|
703
|
+
return true;
|
|
649
704
|
} catch {
|
|
650
|
-
|
|
705
|
+
return false;
|
|
651
706
|
} finally {
|
|
652
707
|
try {
|
|
653
708
|
rmSync(scriptPath, { force: true });
|
|
@@ -674,9 +729,14 @@ var setupHermes = async (_apiKey) => {
|
|
|
674
729
|
ensurePip(python);
|
|
675
730
|
execSync2(`"${python}" -m pip install --upgrade hermes-plugin-pushary`, { stdio: "pipe", timeout: 18e4 });
|
|
676
731
|
});
|
|
732
|
+
let pluginEnabled = false;
|
|
677
733
|
await spinner("Enabling plugin + routing questions to push", async () => {
|
|
678
|
-
enablePusharyPlugin(python);
|
|
734
|
+
pluginEnabled = enablePusharyPlugin(python);
|
|
679
735
|
});
|
|
736
|
+
if (!pluginEnabled) {
|
|
737
|
+
console.log(` ${yellow("!")} Installed the plugin, but could not edit Hermes' config.`);
|
|
738
|
+
noteManual('Add "pushary" to plugins.enabled in ~/.hermes/config.yaml, and "clarify" to agent.disabled_toolsets');
|
|
739
|
+
}
|
|
680
740
|
console.log();
|
|
681
741
|
console.log(` ${dim("What this configured:")}`);
|
|
682
742
|
console.log(` ${dim("\u2022")} Native tools: pushary_notify, pushary_ask, pushary_wait, pushary_cancel`);
|
|
@@ -685,49 +745,71 @@ var setupHermes = async (_apiKey) => {
|
|
|
685
745
|
console.log(` ${dim("\u2022")} Permission gating: set ${bold("PUSHARY_GATE_TOOLS")} to require lock-screen approval for risky tools`);
|
|
686
746
|
console.log(` ${dim("To re-enable terminal prompts:")} remove ${bold("clarify")} from ${dim("agent.disabled_toolsets")} in ~/.hermes/config.yaml`);
|
|
687
747
|
};
|
|
688
|
-
var CODEX_HOOKS_JSON =
|
|
748
|
+
var CODEX_HOOKS_JSON = join2(CODEX_HOME, "hooks.json");
|
|
689
749
|
var CODEX_HOOKS_MIN_VERSION = [0, 122, 0];
|
|
690
750
|
var CODEX_TRUST_VERIFIED_MAX = [0, 142, 2];
|
|
751
|
+
var codexOnPath = () => {
|
|
752
|
+
const whichCmd = IS_WINDOWS ? "where" : "which";
|
|
753
|
+
try {
|
|
754
|
+
const found = execSync2(`${whichCmd} codex`, { encoding: "utf-8", stdio: "pipe", timeout: 5e3 }).split("\n")[0].trim();
|
|
755
|
+
return found || null;
|
|
756
|
+
} catch {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
var codexRealpath = (path) => {
|
|
761
|
+
try {
|
|
762
|
+
return realpathSync(path);
|
|
763
|
+
} catch {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
};
|
|
691
767
|
var detectCodexVersion = () => readCodexVersion({
|
|
692
|
-
resolveOnPath:
|
|
693
|
-
|
|
768
|
+
resolveOnPath: codexOnPath,
|
|
769
|
+
realpath: codexRealpath,
|
|
770
|
+
readFile: (path) => {
|
|
694
771
|
try {
|
|
695
|
-
|
|
696
|
-
return found || null;
|
|
772
|
+
return readFileSync(path, "utf-8");
|
|
697
773
|
} catch {
|
|
698
774
|
return null;
|
|
699
775
|
}
|
|
700
776
|
},
|
|
701
|
-
|
|
777
|
+
brewVersions: () => {
|
|
702
778
|
try {
|
|
703
|
-
return
|
|
779
|
+
return execSync2("brew list --versions codex", {
|
|
780
|
+
encoding: "utf-8",
|
|
781
|
+
stdio: "pipe",
|
|
782
|
+
timeout: 1e4
|
|
783
|
+
}).trim() || null;
|
|
704
784
|
} catch {
|
|
705
785
|
return null;
|
|
706
786
|
}
|
|
707
|
-
}
|
|
708
|
-
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
var detectCodexInstall = () => checkCodexInstall({
|
|
790
|
+
resolveOnPath: codexOnPath,
|
|
791
|
+
realpath: codexRealpath,
|
|
792
|
+
exists: existsSync,
|
|
793
|
+
listDir: (path) => {
|
|
709
794
|
try {
|
|
710
|
-
return
|
|
795
|
+
return readdirSync(path);
|
|
711
796
|
} catch {
|
|
712
797
|
return null;
|
|
713
798
|
}
|
|
714
799
|
},
|
|
715
|
-
|
|
800
|
+
fileSize: (path) => {
|
|
716
801
|
try {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
stdio: "pipe",
|
|
720
|
-
timeout: 1e4
|
|
721
|
-
}).trim() || null;
|
|
802
|
+
const stat = statSync(path);
|
|
803
|
+
return stat.isFile() ? stat.size : null;
|
|
722
804
|
} catch {
|
|
723
805
|
return null;
|
|
724
806
|
}
|
|
725
807
|
},
|
|
726
|
-
join,
|
|
727
|
-
dirname
|
|
808
|
+
join: join2,
|
|
809
|
+
dirname: dirname2
|
|
728
810
|
});
|
|
729
|
-
var codexSupportsHooks = (
|
|
730
|
-
var codexTrustAutoSupported = (
|
|
811
|
+
var codexSupportsHooks = (version) => version !== null && compareCodexVersion(version, CODEX_HOOKS_MIN_VERSION) >= 0;
|
|
812
|
+
var codexTrustAutoSupported = (version) => version !== null && codexSupportsHooks(version) && compareCodexVersion(version, CODEX_TRUST_VERIFIED_MAX) <= 0;
|
|
731
813
|
var removeCodexNotifyEntry = (codexConfig) => {
|
|
732
814
|
let raw = "";
|
|
733
815
|
try {
|
|
@@ -771,6 +853,16 @@ var setupCodex = async (apiKey) => {
|
|
|
771
853
|
console.log(` ${dim("Install Codex and re-run setup to configure.")}`);
|
|
772
854
|
return "skipped";
|
|
773
855
|
}
|
|
856
|
+
if (detectCodexInstall().kind === "binary-missing") {
|
|
857
|
+
console.log(` ${yellow("!")} Your Codex install is missing the binary it runs.`);
|
|
858
|
+
console.log(` ${dim("A Pushary setup before 0.71.0 asked Codex its version. On macOS that hands")}`);
|
|
859
|
+
console.log(` ${dim("the binary to XProtect, which quarantines the Codex CLI and moves it to the")}`);
|
|
860
|
+
console.log(` ${dim("Trash. Setup no longer runs it. Reinstall Codex to get it back:")}`);
|
|
861
|
+
console.log(` ${cyan("npm install -g @openai/codex")}`);
|
|
862
|
+
console.log(` ${dim("Configuring anyway, so it is ready when Codex is.")}`);
|
|
863
|
+
console.log();
|
|
864
|
+
noteManual("Reinstall Codex: a Pushary setup before 0.71.0 got its binary quarantined by macOS");
|
|
865
|
+
}
|
|
774
866
|
await installGlobally2();
|
|
775
867
|
const codexConfig = codexConfigToml();
|
|
776
868
|
await spinner("Adding Pushary MCP server (key embedded, auto-allowed)", async () => {
|
|
@@ -806,7 +898,7 @@ var setupCodex = async (apiKey) => {
|
|
|
806
898
|
removeCodexNotifyEntry(codexConfig);
|
|
807
899
|
});
|
|
808
900
|
} else {
|
|
809
|
-
if (codexVersion
|
|
901
|
+
if (codexVersion !== null) {
|
|
810
902
|
console.log(` ${yellow("!")} This Codex version predates native hooks (needs ${CODEX_HOOKS_MIN_VERSION.join(".")}+).`);
|
|
811
903
|
console.log(` ${dim("Installing the deprecated notify handler instead. Upgrade Codex and re-run setup")}`);
|
|
812
904
|
console.log(` ${dim("to get policy enforcement, phone approvals, and session tracking.")}`);
|
|
@@ -853,17 +945,17 @@ var setupCodex = async (apiKey) => {
|
|
|
853
945
|
}
|
|
854
946
|
};
|
|
855
947
|
var resolveBundledPlugin = () => {
|
|
856
|
-
const dir =
|
|
948
|
+
const dir = dirname2(fileURLToPath(import.meta.url));
|
|
857
949
|
const candidates = [
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
950
|
+
join2(dir, "..", "..", "data", "cursor-plugin"),
|
|
951
|
+
join2(dir, "..", "data", "cursor-plugin"),
|
|
952
|
+
join2(dir, "..", "..", "..", "cursor-plugin"),
|
|
953
|
+
join2(dir, "..", "..", "cursor-plugin")
|
|
862
954
|
];
|
|
863
|
-
return candidates.find((p) => existsSync(
|
|
955
|
+
return candidates.find((p) => existsSync(join2(p, ".cursor-plugin", "plugin.json"))) ?? null;
|
|
864
956
|
};
|
|
865
957
|
var installCursorUserHooks = (gateScript) => {
|
|
866
|
-
const template = readJson(
|
|
958
|
+
const template = readJson(join2(CURSOR_PLUGIN_DIR, "hooks", "hooks.json")).hooks?.beforeShellExecution?.[0];
|
|
867
959
|
if (!template) throw new Error("bundled Cursor hooks.json missing a beforeShellExecution entry");
|
|
868
960
|
const entry = { ...template, command: `node "${gateScript}"` };
|
|
869
961
|
let userHooks = {};
|
|
@@ -885,7 +977,7 @@ var installCursorUserHooks = (gateScript) => {
|
|
|
885
977
|
writeJson(CURSOR_USER_HOOKS, { ...userHooks, version: userHooks.version ?? 1, hooks });
|
|
886
978
|
};
|
|
887
979
|
var neutralizePluginGate = () => {
|
|
888
|
-
const path =
|
|
980
|
+
const path = join2(CURSOR_PLUGIN_DIR, "hooks", "hooks.json");
|
|
889
981
|
if (!existsSync(path)) return;
|
|
890
982
|
const data = readJson(path);
|
|
891
983
|
if (data.hooks && "beforeShellExecution" in data.hooks) {
|
|
@@ -900,7 +992,7 @@ var setupCursor = async (apiKey) => {
|
|
|
900
992
|
const source = resolveBundledPlugin();
|
|
901
993
|
if (!source) throw new Error("bundled Cursor plugin not found in this package");
|
|
902
994
|
await spinner("Installing Pushary plugin", async () => {
|
|
903
|
-
const staging =
|
|
995
|
+
const staging = join2(dirname2(CURSOR_PLUGIN_DIR), `.pushary-staging-${process.pid}`);
|
|
904
996
|
const backup = `${CURSOR_PLUGIN_DIR}.pushary-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
905
997
|
rmSync(staging, { recursive: true, force: true });
|
|
906
998
|
try {
|
|
@@ -908,7 +1000,7 @@ var setupCursor = async (apiKey) => {
|
|
|
908
1000
|
recursive: true,
|
|
909
1001
|
filter: (p) => !["tools", "node_modules", ".git", ".DS_Store"].includes(basename(p))
|
|
910
1002
|
});
|
|
911
|
-
const staged = readJsonSafe(
|
|
1003
|
+
const staged = readJsonSafe(join2(staging, ".cursor-plugin", "plugin.json"));
|
|
912
1004
|
if (staged.kind !== "ok") {
|
|
913
1005
|
throw new Error("staged Cursor plugin is missing or has an unreadable plugin.json");
|
|
914
1006
|
}
|
|
@@ -926,7 +1018,7 @@ var setupCursor = async (apiKey) => {
|
|
|
926
1018
|
}
|
|
927
1019
|
});
|
|
928
1020
|
await spinner("Linking your API key", async () => {
|
|
929
|
-
const mcpPath =
|
|
1021
|
+
const mcpPath = join2(CURSOR_PLUGIN_DIR, "mcp.json");
|
|
930
1022
|
const mcp = readAgentJson(mcpPath);
|
|
931
1023
|
const servers = mcp.mcpServers ?? {};
|
|
932
1024
|
if (servers.pushary) {
|
|
@@ -936,7 +1028,7 @@ var setupCursor = async (apiKey) => {
|
|
|
936
1028
|
}
|
|
937
1029
|
});
|
|
938
1030
|
await spinner("Registering permission gate (~/.cursor/hooks.json)", async () => {
|
|
939
|
-
installCursorUserHooks(
|
|
1031
|
+
installCursorUserHooks(join2(CURSOR_PLUGIN_DIR, "scripts", "pushary-gate.mjs"));
|
|
940
1032
|
neutralizePluginGate();
|
|
941
1033
|
});
|
|
942
1034
|
console.log();
|
|
@@ -948,23 +1040,23 @@ var setupCursor = async (apiKey) => {
|
|
|
948
1040
|
noteManual("Fully quit and reopen Cursor. A Reload Window may not be enough.");
|
|
949
1041
|
};
|
|
950
1042
|
var resolveBundledVsCodePlugin = () => {
|
|
951
|
-
const dir =
|
|
1043
|
+
const dir = dirname2(fileURLToPath(import.meta.url));
|
|
952
1044
|
const candidates = [
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1045
|
+
join2(dir, "..", "..", "data", "vscode-plugin"),
|
|
1046
|
+
join2(dir, "..", "data", "vscode-plugin"),
|
|
1047
|
+
join2(dir, "..", "..", "..", "vscode-plugin"),
|
|
1048
|
+
join2(dir, "..", "..", "vscode-plugin")
|
|
957
1049
|
];
|
|
958
|
-
return candidates.find((p) => existsSync(
|
|
1050
|
+
return candidates.find((p) => existsSync(join2(p, ".claude-plugin", "plugin.json"))) ?? null;
|
|
959
1051
|
};
|
|
960
1052
|
var pinVsCodeGatePath = (pluginDir) => {
|
|
961
|
-
const hooksPath =
|
|
1053
|
+
const hooksPath = join2(pluginDir, "hooks", "hooks.json");
|
|
962
1054
|
const data = readJson(hooksPath);
|
|
963
1055
|
const entries = data.hooks?.PreToolUse;
|
|
964
1056
|
if (!Array.isArray(entries) || entries.length === 0) {
|
|
965
1057
|
throw new Error("bundled VS Code hooks.json is missing a PreToolUse entry");
|
|
966
1058
|
}
|
|
967
|
-
const gate =
|
|
1059
|
+
const gate = join2(pluginDir, "scripts", "pushary-gate.mjs");
|
|
968
1060
|
data.hooks.PreToolUse = entries.map((entry) => ({ ...entry, command: `node "${gate}"` }));
|
|
969
1061
|
writeJson(hooksPath, data);
|
|
970
1062
|
};
|
|
@@ -980,7 +1072,7 @@ var registerVsCodePlugin = (pluginDir) => {
|
|
|
980
1072
|
continue;
|
|
981
1073
|
}
|
|
982
1074
|
if (current !== null) backupFile(settingsPath);
|
|
983
|
-
mkdirSync(
|
|
1075
|
+
mkdirSync(dirname2(settingsPath), { recursive: true });
|
|
984
1076
|
writeFileAtomic(settingsPath, result.content);
|
|
985
1077
|
written.push(settingsPath);
|
|
986
1078
|
}
|
|
@@ -993,16 +1085,16 @@ var setupVsCode = async (apiKey) => {
|
|
|
993
1085
|
const source = resolveBundledVsCodePlugin();
|
|
994
1086
|
if (!source) throw new Error("bundled VS Code plugin not found in this package");
|
|
995
1087
|
await spinner("Installing Pushary plugin", async () => {
|
|
996
|
-
const staging =
|
|
1088
|
+
const staging = join2(dirname2(VSCODE_PLUGIN_DIR), `.pushary-staging-vscode-${process.pid}`);
|
|
997
1089
|
const backup = `${VSCODE_PLUGIN_DIR}.pushary-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
998
|
-
mkdirSync(
|
|
1090
|
+
mkdirSync(dirname2(VSCODE_PLUGIN_DIR), { recursive: true });
|
|
999
1091
|
rmSync(staging, { recursive: true, force: true });
|
|
1000
1092
|
try {
|
|
1001
1093
|
cpSync(source, staging, {
|
|
1002
1094
|
recursive: true,
|
|
1003
1095
|
filter: (p) => !["tools", "node_modules", ".git", ".DS_Store"].includes(basename(p))
|
|
1004
1096
|
});
|
|
1005
|
-
const staged = readJsonSafe(
|
|
1097
|
+
const staged = readJsonSafe(join2(staging, ".claude-plugin", "plugin.json"));
|
|
1006
1098
|
if (staged.kind !== "ok") {
|
|
1007
1099
|
throw new Error("staged VS Code plugin is missing or has an unreadable plugin.json");
|
|
1008
1100
|
}
|
|
@@ -1020,7 +1112,7 @@ var setupVsCode = async (apiKey) => {
|
|
|
1020
1112
|
}
|
|
1021
1113
|
});
|
|
1022
1114
|
await spinner("Linking your API key", async () => {
|
|
1023
|
-
const mcpPath =
|
|
1115
|
+
const mcpPath = join2(VSCODE_PLUGIN_DIR, ".mcp.json");
|
|
1024
1116
|
const mcp = readAgentJson(mcpPath);
|
|
1025
1117
|
const servers = mcp.mcpServers ?? {};
|
|
1026
1118
|
if (servers.pushary) {
|
|
@@ -1160,27 +1252,27 @@ var agentIsWired = (agent) => {
|
|
|
1160
1252
|
return claudeWired({
|
|
1161
1253
|
claudeJson: readJson2(CLAUDE_JSON),
|
|
1162
1254
|
settings: readJson2(CLAUDE_SETTINGS),
|
|
1163
|
-
skillExists: existsSync(
|
|
1255
|
+
skillExists: existsSync(join2(CLAUDE_SKILL_DIR, "SKILL.md"))
|
|
1164
1256
|
});
|
|
1165
1257
|
case "codex": {
|
|
1166
1258
|
let config = null;
|
|
1167
1259
|
try {
|
|
1168
|
-
config = parseTOML(readFileSync(
|
|
1260
|
+
config = parseTOML(readFileSync(join2(CODEX_HOME, "config.toml"), "utf-8"));
|
|
1169
1261
|
} catch {
|
|
1170
1262
|
config = null;
|
|
1171
1263
|
}
|
|
1172
1264
|
return codexWired({
|
|
1173
1265
|
config,
|
|
1174
1266
|
hooks: readJson2(CODEX_HOOKS_JSON),
|
|
1175
|
-
skillExists: existsSync(
|
|
1267
|
+
skillExists: existsSync(join2(CODEX_SKILL_DIR, "SKILL.md"))
|
|
1176
1268
|
});
|
|
1177
1269
|
}
|
|
1178
1270
|
case "gemini_cli":
|
|
1179
1271
|
return geminiWired({ settings: readJson2(GEMINI_SETTINGS) });
|
|
1180
1272
|
case "cursor":
|
|
1181
|
-
return existsSync(
|
|
1273
|
+
return existsSync(join2(CURSOR_PLUGIN_DIR, "mcp.json"));
|
|
1182
1274
|
case "vscode":
|
|
1183
|
-
return existsSync(
|
|
1275
|
+
return existsSync(join2(VSCODE_PLUGIN_DIR, ".mcp.json"));
|
|
1184
1276
|
default:
|
|
1185
1277
|
return true;
|
|
1186
1278
|
}
|
|
@@ -1208,8 +1300,8 @@ var offerProjectInstructions = async (agents, options) => {
|
|
|
1208
1300
|
});
|
|
1209
1301
|
if (!wanted) return;
|
|
1210
1302
|
for (const target of targets) {
|
|
1211
|
-
await spinner(`Writing managed block to ${
|
|
1212
|
-
writeInstructionBlock(
|
|
1303
|
+
await spinner(`Writing managed block to ${join2(process.cwd(), target.file)}`, async () => {
|
|
1304
|
+
writeInstructionBlock(join2(process.cwd(), target.file), renderProjectAgentInstructions(target.label));
|
|
1213
1305
|
}, { optional: true });
|
|
1214
1306
|
}
|
|
1215
1307
|
console.log(` ${dim("Commit the file to share it. Teammates without a key fall back to the terminal.")}`);
|
|
@@ -1360,12 +1452,12 @@ var resolveAgents = async (options) => {
|
|
|
1360
1452
|
});
|
|
1361
1453
|
};
|
|
1362
1454
|
var AGENT_TARGETS = {
|
|
1363
|
-
claude_code: [CLAUDE_JSON, CLAUDE_SETTINGS,
|
|
1364
|
-
codex: [
|
|
1455
|
+
claude_code: [CLAUDE_JSON, CLAUDE_SETTINGS, join2(CLAUDE_SKILL_DIR, "SKILL.md")],
|
|
1456
|
+
codex: [join2(CODEX_HOME, "config.toml"), CODEX_HOOKS_JSON, join2(CODEX_SKILL_DIR, "SKILL.md"), CODEX_AGENTS_MD],
|
|
1365
1457
|
gemini_cli: [GEMINI_SETTINGS, GEMINI_MD],
|
|
1366
1458
|
hermes: ["the Hermes virtualenv (pip install pushary-hermes)"],
|
|
1367
|
-
cursor: [
|
|
1368
|
-
vscode: [
|
|
1459
|
+
cursor: [join2(CURSOR_PLUGIN_DIR, "mcp.json"), CURSOR_USER_HOOKS],
|
|
1460
|
+
vscode: [join2(VSCODE_PLUGIN_DIR, ".mcp.json"), ...vscodeSettingsTargets(existsSync)],
|
|
1369
1461
|
custom: ["nothing (prints connection details only)"]
|
|
1370
1462
|
};
|
|
1371
1463
|
var reportDryRun = (apiKey, agents, keyCheck) => {
|