@primitive.ai/prim 0.1.0-alpha.36 → 0.1.0-alpha.37
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/dist/chunk-HSZPTVKU.js +113 -0
- package/dist/hooks/post-tool-use.js +4 -0
- package/dist/hooks/pre-tool-use.js +4 -0
- package/dist/hooks/prim-hook.js +4 -0
- package/dist/hooks/session-start.js +4 -0
- package/dist/index.js +142 -192
- package/package.json +1 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/lib/bin-cache.ts
|
|
2
|
+
import { chmodSync, mkdirSync, renameSync, writeFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join as join2 } from "path";
|
|
5
|
+
|
|
6
|
+
// src/lib/bin-path.ts
|
|
7
|
+
import { existsSync, readFileSync } from "fs";
|
|
8
|
+
import { dirname, isAbsolute, join } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
var PKG_NAME = "@primitive.ai/prim";
|
|
11
|
+
var ROOT_WALK_LIMIT = 6;
|
|
12
|
+
var NPX_FALLBACK = `npx --yes -p ${PKG_NAME}@latest`;
|
|
13
|
+
var BIN_CACHE_DIR_SH = "${XDG_CACHE_HOME:-$HOME/.cache}/prim/bin";
|
|
14
|
+
var BIN_CACHE_TTL_MIN_DEFAULT = 1440;
|
|
15
|
+
var resolvedRoot;
|
|
16
|
+
function locateRoot() {
|
|
17
|
+
if (resolvedRoot !== void 0) {
|
|
18
|
+
return resolvedRoot;
|
|
19
|
+
}
|
|
20
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
for (let depth = 0; depth < ROOT_WALK_LIMIT; depth++) {
|
|
22
|
+
const manifestPath = join(dir, "package.json");
|
|
23
|
+
if (existsSync(manifestPath)) {
|
|
24
|
+
try {
|
|
25
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
26
|
+
if (manifest.name === PKG_NAME && manifest.bin) {
|
|
27
|
+
resolvedRoot = { dir, bin: manifest.bin };
|
|
28
|
+
return resolvedRoot;
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const parent = dirname(dir);
|
|
34
|
+
if (parent === dir) {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
dir = parent;
|
|
38
|
+
}
|
|
39
|
+
resolvedRoot = null;
|
|
40
|
+
return resolvedRoot;
|
|
41
|
+
}
|
|
42
|
+
function binFile(bin) {
|
|
43
|
+
const root = locateRoot();
|
|
44
|
+
const rel = root?.bin[bin];
|
|
45
|
+
if (!root || !rel) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return isAbsolute(rel) ? rel : join(root.dir, rel);
|
|
49
|
+
}
|
|
50
|
+
function hookShimCommand(bin, args = "", opts = {}) {
|
|
51
|
+
const invoke = (cmd) => args ? `${cmd} ${args}` : cmd;
|
|
52
|
+
const ladder = `if command -v ${bin} >/dev/null 2>&1; then ${invoke(bin)}; elif [ -f "./node_modules/.bin/${bin}" ]; then ${invoke(`./node_modules/.bin/${bin}`)}; else ${invoke(`${NPX_FALLBACK} ${bin}`)}; fi`;
|
|
53
|
+
if (opts.cacheRead === false) {
|
|
54
|
+
return ladder;
|
|
55
|
+
}
|
|
56
|
+
const execArgs = args ? ` ${args}` : "";
|
|
57
|
+
const cacheBranch = `d="${BIN_CACHE_DIR_SH}"; if [ "\${PRIM_BIN_CACHE:-1}" != "0" ] && [ -f "$d/${bin}" ] && [ -f "$d/node" ] && [ -n "$(find "$d/${bin}" -mmin "-\${PRIM_BIN_CACHE_TTL_MIN:-${BIN_CACHE_TTL_MIN_DEFAULT}}" 2>/dev/null)" ]; then n=$(cat "$d/node"); p=$(cat "$d/${bin}"); if [ -x "$n" ] && [ -f "$p" ]; then export PRIM_BIN_CACHE_HIT=1; exec "$n" "$p"${execArgs}; fi; fi; `;
|
|
58
|
+
return cacheBranch + ladder;
|
|
59
|
+
}
|
|
60
|
+
function detachedHookShimCommand(bin, args = "") {
|
|
61
|
+
return `payload=$(cat); { trap '' HUP; export npm_config_fetch_retries=2 npm_config_fetch_retry_mintimeout=10000 npm_config_fetch_retry_maxtimeout=10000 npm_config_fetch_timeout=60000; printf '%s' "$payload" | { ${hookShimCommand(bin, args, { cacheRead: false })}; }; } </dev/null >/dev/null 2>&1 &`;
|
|
62
|
+
}
|
|
63
|
+
function commandMatchesBin(command, bin) {
|
|
64
|
+
if (!command) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const c = command.trim();
|
|
68
|
+
if (c === bin || c.startsWith(`${bin} `)) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return c.includes(`command -v ${bin} `);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/lib/bin-cache.ts
|
|
75
|
+
var CACHED_BINS = ["prim", "prim-hook", "prim-pre-tool-use", "prim-post-tool-use"];
|
|
76
|
+
function binCacheDir() {
|
|
77
|
+
const base = process.env.XDG_CACHE_HOME || join2(homedir(), ".cache");
|
|
78
|
+
return join2(base, "prim", "bin");
|
|
79
|
+
}
|
|
80
|
+
function writeAtomic(path, content) {
|
|
81
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
82
|
+
writeFileSync(tmp, content, { mode: 384 });
|
|
83
|
+
renameSync(tmp, path);
|
|
84
|
+
}
|
|
85
|
+
function warmBinCache() {
|
|
86
|
+
try {
|
|
87
|
+
if (process.env.PRIM_BIN_CACHE_HIT) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (process.env.PRIM_BIN_CACHE === "0") {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const dir = binCacheDir();
|
|
94
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
95
|
+
chmodSync(dir, 448);
|
|
96
|
+
writeAtomic(join2(dir, "node"), process.execPath);
|
|
97
|
+
for (const bin of CACHED_BINS) {
|
|
98
|
+
const file = binFile(bin);
|
|
99
|
+
if (file) {
|
|
100
|
+
writeAtomic(join2(dir, bin), file);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
binFile,
|
|
109
|
+
hookShimCommand,
|
|
110
|
+
detachedHookShimCommand,
|
|
111
|
+
commandMatchesBin,
|
|
112
|
+
warmBinCache
|
|
113
|
+
};
|
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
isRepoActiveForCapture
|
|
14
14
|
} from "../chunk-LUPD2JSH.js";
|
|
15
|
+
import {
|
|
16
|
+
warmBinCache
|
|
17
|
+
} from "../chunk-HSZPTVKU.js";
|
|
15
18
|
import {
|
|
16
19
|
getClient
|
|
17
20
|
} from "../chunk-26VA3ADF.js";
|
|
@@ -100,6 +103,7 @@ async function ingestMove(move) {
|
|
|
100
103
|
);
|
|
101
104
|
}
|
|
102
105
|
async function main() {
|
|
106
|
+
warmBinCache();
|
|
103
107
|
const agent = parseAgent(process.argv);
|
|
104
108
|
let raw;
|
|
105
109
|
try {
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import {
|
|
3
3
|
isRepoActiveForCapture
|
|
4
4
|
} from "../chunk-LUPD2JSH.js";
|
|
5
|
+
import {
|
|
6
|
+
warmBinCache
|
|
7
|
+
} from "../chunk-HSZPTVKU.js";
|
|
5
8
|
import {
|
|
6
9
|
getClient,
|
|
7
10
|
getSiteUrl
|
|
@@ -262,6 +265,7 @@ async function checkOneFile(file) {
|
|
|
262
265
|
);
|
|
263
266
|
}
|
|
264
267
|
async function main() {
|
|
268
|
+
warmBinCache();
|
|
265
269
|
let raw;
|
|
266
270
|
try {
|
|
267
271
|
raw = await readStdin();
|
package/dist/hooks/prim-hook.js
CHANGED
|
@@ -13,6 +13,9 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
isRepoActiveForCapture
|
|
15
15
|
} from "../chunk-LUPD2JSH.js";
|
|
16
|
+
import {
|
|
17
|
+
warmBinCache
|
|
18
|
+
} from "../chunk-HSZPTVKU.js";
|
|
16
19
|
import "../chunk-26VA3ADF.js";
|
|
17
20
|
import {
|
|
18
21
|
normalizeEnvelope,
|
|
@@ -41,6 +44,7 @@ function spawnBackgroundFlush() {
|
|
|
41
44
|
}).unref();
|
|
42
45
|
}
|
|
43
46
|
try {
|
|
47
|
+
warmBinCache();
|
|
44
48
|
const agent = parseAgent(process.argv);
|
|
45
49
|
const raw = readFileSync(0, "utf-8");
|
|
46
50
|
const parsed = normalizeEnvelope(JSON.parse(raw), agent);
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
warmBinCache
|
|
4
|
+
} from "../chunk-HSZPTVKU.js";
|
|
2
5
|
import {
|
|
3
6
|
getSiteUrl
|
|
4
7
|
} from "../chunk-26VA3ADF.js";
|
|
@@ -45,6 +48,7 @@ function emit(additionalContext) {
|
|
|
45
48
|
`);
|
|
46
49
|
}
|
|
47
50
|
async function main() {
|
|
51
|
+
warmBinCache();
|
|
48
52
|
const agent = parseAgent(process.argv);
|
|
49
53
|
let raw;
|
|
50
54
|
try {
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,13 @@ import {
|
|
|
25
25
|
gitToplevel,
|
|
26
26
|
setRepoActive
|
|
27
27
|
} from "./chunk-LUPD2JSH.js";
|
|
28
|
+
import {
|
|
29
|
+
binFile,
|
|
30
|
+
commandMatchesBin,
|
|
31
|
+
detachedHookShimCommand,
|
|
32
|
+
hookShimCommand,
|
|
33
|
+
warmBinCache
|
|
34
|
+
} from "./chunk-HSZPTVKU.js";
|
|
28
35
|
import {
|
|
29
36
|
HttpError,
|
|
30
37
|
REFRESH_TOKEN_PATH,
|
|
@@ -42,9 +49,9 @@ import {
|
|
|
42
49
|
} from "./chunk-UTKQTZHL.js";
|
|
43
50
|
|
|
44
51
|
// src/index.ts
|
|
45
|
-
import { readFileSync as
|
|
46
|
-
import { dirname as
|
|
47
|
-
import { fileURLToPath as
|
|
52
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
53
|
+
import { dirname as dirname8, resolve as resolve5 } from "path";
|
|
54
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
48
55
|
import { Command } from "commander";
|
|
49
56
|
import updateNotifier from "update-notifier";
|
|
50
57
|
|
|
@@ -529,78 +536,16 @@ async function exchangeCode(siteUrl, code, codeVerifier, redirectUri) {
|
|
|
529
536
|
// src/commands/claude-install.ts
|
|
530
537
|
import {
|
|
531
538
|
closeSync,
|
|
532
|
-
existsSync as
|
|
539
|
+
existsSync as existsSync2,
|
|
533
540
|
fsyncSync,
|
|
534
541
|
mkdirSync as mkdirSync2,
|
|
535
542
|
openSync,
|
|
536
|
-
readFileSync as
|
|
543
|
+
readFileSync as readFileSync2,
|
|
537
544
|
renameSync,
|
|
538
545
|
writeFileSync as writeFileSync2
|
|
539
546
|
} from "fs";
|
|
540
547
|
import { homedir } from "os";
|
|
541
|
-
import { dirname as
|
|
542
|
-
|
|
543
|
-
// src/lib/bin-path.ts
|
|
544
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
545
|
-
import { dirname as dirname2, isAbsolute, join } from "path";
|
|
546
|
-
import { fileURLToPath } from "url";
|
|
547
|
-
var PKG_NAME = "@primitive.ai/prim";
|
|
548
|
-
var ROOT_WALK_LIMIT = 6;
|
|
549
|
-
var NPX_FALLBACK = `npx --yes -p ${PKG_NAME}@latest`;
|
|
550
|
-
var resolvedRoot;
|
|
551
|
-
function locateRoot() {
|
|
552
|
-
if (resolvedRoot !== void 0) {
|
|
553
|
-
return resolvedRoot;
|
|
554
|
-
}
|
|
555
|
-
let dir = dirname2(fileURLToPath(import.meta.url));
|
|
556
|
-
for (let depth = 0; depth < ROOT_WALK_LIMIT; depth++) {
|
|
557
|
-
const manifestPath = join(dir, "package.json");
|
|
558
|
-
if (existsSync2(manifestPath)) {
|
|
559
|
-
try {
|
|
560
|
-
const manifest = JSON.parse(readFileSync2(manifestPath, "utf-8"));
|
|
561
|
-
if (manifest.name === PKG_NAME && manifest.bin) {
|
|
562
|
-
resolvedRoot = { dir, bin: manifest.bin };
|
|
563
|
-
return resolvedRoot;
|
|
564
|
-
}
|
|
565
|
-
} catch {
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
const parent = dirname2(dir);
|
|
569
|
-
if (parent === dir) {
|
|
570
|
-
break;
|
|
571
|
-
}
|
|
572
|
-
dir = parent;
|
|
573
|
-
}
|
|
574
|
-
resolvedRoot = null;
|
|
575
|
-
return resolvedRoot;
|
|
576
|
-
}
|
|
577
|
-
function binFile(bin) {
|
|
578
|
-
const root = locateRoot();
|
|
579
|
-
const rel = root?.bin[bin];
|
|
580
|
-
if (!root || !rel) {
|
|
581
|
-
return null;
|
|
582
|
-
}
|
|
583
|
-
return isAbsolute(rel) ? rel : join(root.dir, rel);
|
|
584
|
-
}
|
|
585
|
-
function hookShimCommand(bin, args = "") {
|
|
586
|
-
const invoke = (cmd) => args ? `${cmd} ${args}` : cmd;
|
|
587
|
-
return `if command -v ${bin} >/dev/null 2>&1; then ${invoke(bin)}; elif [ -f "./node_modules/.bin/${bin}" ]; then ${invoke(`./node_modules/.bin/${bin}`)}; else ${invoke(`${NPX_FALLBACK} ${bin}`)}; fi`;
|
|
588
|
-
}
|
|
589
|
-
function detachedHookShimCommand(bin, args = "") {
|
|
590
|
-
return `payload=$(cat); { trap '' HUP; export npm_config_fetch_retries=2 npm_config_fetch_retry_mintimeout=10000 npm_config_fetch_retry_maxtimeout=10000 npm_config_fetch_timeout=60000; printf '%s' "$payload" | { ${hookShimCommand(bin, args)}; }; } </dev/null >/dev/null 2>&1 &`;
|
|
591
|
-
}
|
|
592
|
-
function commandMatchesBin(command, bin) {
|
|
593
|
-
if (!command) {
|
|
594
|
-
return false;
|
|
595
|
-
}
|
|
596
|
-
const c = command.trim();
|
|
597
|
-
if (c === bin || c.startsWith(`${bin} `)) {
|
|
598
|
-
return true;
|
|
599
|
-
}
|
|
600
|
-
return c.includes(`command -v ${bin} `);
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// src/commands/claude-install.ts
|
|
548
|
+
import { dirname as dirname2, join } from "path";
|
|
604
549
|
var CAPTURE_BIN = "prim-hook";
|
|
605
550
|
var GATE_BIN = "prim-pre-tool-use";
|
|
606
551
|
var POST_TOOL_USE_BIN = "prim-post-tool-use";
|
|
@@ -618,11 +563,11 @@ var PRIM_BINS = [
|
|
|
618
563
|
SESSION_END_BIN
|
|
619
564
|
];
|
|
620
565
|
var JSON_INDENT = 2;
|
|
621
|
-
var USER_SCOPE_PATH =
|
|
566
|
+
var USER_SCOPE_PATH = join(homedir(), ".claude", "settings.json");
|
|
622
567
|
function projectRoot() {
|
|
623
568
|
return gitToplevel() ?? process.cwd();
|
|
624
569
|
}
|
|
625
|
-
var projectScopePath = () =>
|
|
570
|
+
var projectScopePath = () => join(projectRoot(), ".claude", "settings.json");
|
|
626
571
|
var CAPTURE_EVENTS = [
|
|
627
572
|
"SessionStart",
|
|
628
573
|
"UserPromptSubmit",
|
|
@@ -632,8 +577,8 @@ var CAPTURE_EVENTS = [
|
|
|
632
577
|
"SessionEnd",
|
|
633
578
|
"SubagentStop"
|
|
634
579
|
];
|
|
635
|
-
function makeRegistration(event, matcher, bin, args = "") {
|
|
636
|
-
return { event, matcher, bin, command: hookShimCommand(bin, args) };
|
|
580
|
+
function makeRegistration(event, matcher, bin, args = "", opts = {}) {
|
|
581
|
+
return { event, matcher, bin, command: hookShimCommand(bin, args, opts) };
|
|
637
582
|
}
|
|
638
583
|
function makeDetachedRegistration(event, matcher, bin, args = "") {
|
|
639
584
|
return { event, matcher, bin, command: detachedHookShimCommand(bin, args) };
|
|
@@ -644,7 +589,9 @@ var REGISTRATIONS = [
|
|
|
644
589
|
),
|
|
645
590
|
makeRegistration("PreToolUse", "Edit|Write|MultiEdit", GATE_BIN),
|
|
646
591
|
makeRegistration("PostToolUse", "Edit|Write|MultiEdit", POST_TOOL_USE_BIN),
|
|
647
|
-
|
|
592
|
+
// Bare ladder (no branch-0): SessionStart must re-resolve @latest each session
|
|
593
|
+
// to refresh the bin cache the other hooks read. See lib/bin-cache.ts.
|
|
594
|
+
makeRegistration("SessionStart", "*", SESSION_START_BIN, "", { cacheRead: false }),
|
|
648
595
|
makeDetachedRegistration("SessionEnd", "*", SESSION_END_BIN)
|
|
649
596
|
];
|
|
650
597
|
var PRIM_PERMISSION_RULE = "Bash(npx --yes @primitive.ai/prim*)";
|
|
@@ -662,10 +609,10 @@ function settingsPathFor(scope) {
|
|
|
662
609
|
return scope === "user" ? USER_SCOPE_PATH : projectScopePath();
|
|
663
610
|
}
|
|
664
611
|
function readSettings(path) {
|
|
665
|
-
if (!
|
|
612
|
+
if (!existsSync2(path)) {
|
|
666
613
|
return {};
|
|
667
614
|
}
|
|
668
|
-
const raw =
|
|
615
|
+
const raw = readFileSync2(path, "utf-8");
|
|
669
616
|
try {
|
|
670
617
|
return JSON.parse(raw);
|
|
671
618
|
} catch (err) {
|
|
@@ -806,8 +753,8 @@ function isGateInstalled(settings) {
|
|
|
806
753
|
return (settings.hooks?.PreToolUse ?? []).some((e) => entryHasCommand(e, GATE_BIN));
|
|
807
754
|
}
|
|
808
755
|
function atomicWrite(path, content) {
|
|
809
|
-
const dir =
|
|
810
|
-
if (!
|
|
756
|
+
const dir = dirname2(path);
|
|
757
|
+
if (!existsSync2(dir)) {
|
|
811
758
|
mkdirSync2(dir, { recursive: true });
|
|
812
759
|
}
|
|
813
760
|
const tmp = `${path}.tmp.${String(Date.now())}`;
|
|
@@ -955,7 +902,7 @@ ${line("project", result.project)}`);
|
|
|
955
902
|
|
|
956
903
|
// src/commands/codex-install.ts
|
|
957
904
|
import { homedir as homedir2 } from "os";
|
|
958
|
-
import { join as
|
|
905
|
+
import { join as join2 } from "path";
|
|
959
906
|
var CAPTURE_BIN2 = "prim-hook";
|
|
960
907
|
var GATE_BIN2 = "prim-pre-tool-use";
|
|
961
908
|
var POST_TOOL_USE_BIN2 = "prim-post-tool-use";
|
|
@@ -975,10 +922,12 @@ var CODEX_REGISTRATIONS = [
|
|
|
975
922
|
...CODEX_CAPTURE_EVENTS.map((event) => makeRegistration(event, "*", CAPTURE_BIN2, CODEX_ARGS)),
|
|
976
923
|
makeRegistration("PreToolUse", "apply_patch", GATE_BIN2, CODEX_ARGS),
|
|
977
924
|
makeRegistration("PostToolUse", "apply_patch", POST_TOOL_USE_BIN2, CODEX_ARGS),
|
|
978
|
-
|
|
925
|
+
// Bare ladder (no branch-0): SessionStart re-resolves @latest each session to
|
|
926
|
+
// refresh the bin cache the other hooks read. See lib/bin-cache.ts.
|
|
927
|
+
makeRegistration("SessionStart", "*", SESSION_START_BIN2, CODEX_ARGS, { cacheRead: false })
|
|
979
928
|
];
|
|
980
|
-
var USER_SCOPE_PATH2 =
|
|
981
|
-
var projectScopePath2 = () =>
|
|
929
|
+
var USER_SCOPE_PATH2 = join2(homedir2(), ".codex", "hooks.json");
|
|
930
|
+
var projectScopePath2 = () => join2(projectRoot(), ".codex", "hooks.json");
|
|
982
931
|
function settingsPathFor2(scope) {
|
|
983
932
|
return scope === "user" ? USER_SCOPE_PATH2 : projectScopePath2();
|
|
984
933
|
}
|
|
@@ -1102,9 +1051,9 @@ ${line("project", result.project)}`);
|
|
|
1102
1051
|
|
|
1103
1052
|
// src/commands/daemon.ts
|
|
1104
1053
|
import { spawn } from "child_process";
|
|
1105
|
-
import { closeSync as closeSync2, existsSync as
|
|
1054
|
+
import { closeSync as closeSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, openSync as openSync2, readFileSync as readFileSync3, unlinkSync } from "fs";
|
|
1106
1055
|
import { homedir as homedir3 } from "os";
|
|
1107
|
-
import { join as
|
|
1056
|
+
import { join as join3 } from "path";
|
|
1108
1057
|
|
|
1109
1058
|
// src/lib/presence.ts
|
|
1110
1059
|
function formatTeammates(names, cap) {
|
|
@@ -1122,10 +1071,10 @@ function formatTeammates(names, cap) {
|
|
|
1122
1071
|
|
|
1123
1072
|
// src/commands/daemon.ts
|
|
1124
1073
|
var DAEMON_BIN = "prim-daemon-server";
|
|
1125
|
-
var CONFIG_DIR =
|
|
1126
|
-
var PID_PATH =
|
|
1127
|
-
var SOCK_PATH =
|
|
1128
|
-
var LOG_PATH =
|
|
1074
|
+
var CONFIG_DIR = join3(homedir3(), ".config", "prim");
|
|
1075
|
+
var PID_PATH = join3(CONFIG_DIR, "daemon.pid");
|
|
1076
|
+
var SOCK_PATH = join3(CONFIG_DIR, "sock");
|
|
1077
|
+
var LOG_PATH = join3(CONFIG_DIR, "daemon.log");
|
|
1129
1078
|
var CONFIG_DIR_MODE = 448;
|
|
1130
1079
|
var LOG_FILE_MODE = 384;
|
|
1131
1080
|
var STOP_TIMEOUT_MS = 5e3;
|
|
@@ -1138,10 +1087,10 @@ var EXIT_OK2 = 0;
|
|
|
1138
1087
|
var EXIT_NOT_RUNNING = 2;
|
|
1139
1088
|
var EXIT_BOOTING = 3;
|
|
1140
1089
|
function readPidfile() {
|
|
1141
|
-
if (!
|
|
1090
|
+
if (!existsSync3(PID_PATH)) {
|
|
1142
1091
|
return null;
|
|
1143
1092
|
}
|
|
1144
|
-
const raw =
|
|
1093
|
+
const raw = readFileSync3(PID_PATH, "utf-8").trim();
|
|
1145
1094
|
const pid = Number(raw);
|
|
1146
1095
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
1147
1096
|
return null;
|
|
@@ -1177,10 +1126,10 @@ function spawnDaemon(options) {
|
|
|
1177
1126
|
return file ? spawn(process.execPath, [file], options) : spawn(DAEMON_BIN, [], options);
|
|
1178
1127
|
}
|
|
1179
1128
|
function openDaemonLog(configDir = CONFIG_DIR) {
|
|
1180
|
-
if (!
|
|
1129
|
+
if (!existsSync3(configDir)) {
|
|
1181
1130
|
mkdirSync3(configDir, { recursive: true, mode: CONFIG_DIR_MODE });
|
|
1182
1131
|
}
|
|
1183
|
-
return openSync2(
|
|
1132
|
+
return openSync2(join3(configDir, "daemon.log"), "a", LOG_FILE_MODE);
|
|
1184
1133
|
}
|
|
1185
1134
|
async function waitForReady() {
|
|
1186
1135
|
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
@@ -2171,7 +2120,7 @@ function registerDecisionsCommands(program2) {
|
|
|
2171
2120
|
}
|
|
2172
2121
|
|
|
2173
2122
|
// src/commands/doctor.ts
|
|
2174
|
-
import { existsSync as
|
|
2123
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2175
2124
|
var DAEMON_PROBE_TIMEOUT_MS = 500;
|
|
2176
2125
|
var CONNECTIVITY_TIMEOUT_MS = 3e3;
|
|
2177
2126
|
var MS_PER_SECOND = 1e3;
|
|
@@ -2189,7 +2138,7 @@ function checkAuth() {
|
|
|
2189
2138
|
return { name: "auth", status: "fail", detail: "no token \u2014 run `prim auth login`" };
|
|
2190
2139
|
}
|
|
2191
2140
|
const expiresAt = getTokenExpiresAt();
|
|
2192
|
-
const hasRefresh =
|
|
2141
|
+
const hasRefresh = existsSync4(REFRESH_TOKEN_PATH);
|
|
2193
2142
|
if (expiresAt !== void 0 && Date.now() >= expiresAt) {
|
|
2194
2143
|
return hasRefresh ? { name: "auth", status: "warn", detail: "access token expired (refresh available)" } : {
|
|
2195
2144
|
name: "auth",
|
|
@@ -2289,17 +2238,17 @@ function registerDoctorCommands(program2) {
|
|
|
2289
2238
|
import {
|
|
2290
2239
|
chmodSync,
|
|
2291
2240
|
closeSync as closeSync3,
|
|
2292
|
-
existsSync as
|
|
2241
|
+
existsSync as existsSync5,
|
|
2293
2242
|
fsyncSync as fsyncSync2,
|
|
2294
2243
|
mkdirSync as mkdirSync4,
|
|
2295
2244
|
openSync as openSync3,
|
|
2296
|
-
readFileSync as
|
|
2245
|
+
readFileSync as readFileSync4,
|
|
2297
2246
|
renameSync as renameSync2,
|
|
2298
2247
|
rmSync as rmSync2,
|
|
2299
2248
|
writeFileSync as writeFileSync3
|
|
2300
2249
|
} from "fs";
|
|
2301
2250
|
import { homedir as homedir4 } from "os";
|
|
2302
|
-
import { dirname as
|
|
2251
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
2303
2252
|
import { Document, parseDocument, stringify } from "yaml";
|
|
2304
2253
|
var CAPTURE_BIN3 = "prim-hook";
|
|
2305
2254
|
var GATE_BIN3 = "prim-pre-tool-use";
|
|
@@ -2318,13 +2267,13 @@ var PRIM_BINS3 = [
|
|
|
2318
2267
|
SESSION_END_BIN2
|
|
2319
2268
|
];
|
|
2320
2269
|
function hermesHome() {
|
|
2321
|
-
return process.env.HERMES_HOME ??
|
|
2270
|
+
return process.env.HERMES_HOME ?? join4(homedir4(), ".hermes");
|
|
2322
2271
|
}
|
|
2323
2272
|
function configPath() {
|
|
2324
|
-
return
|
|
2273
|
+
return join4(hermesHome(), "config.yaml");
|
|
2325
2274
|
}
|
|
2326
2275
|
function shimPath() {
|
|
2327
|
-
return
|
|
2276
|
+
return join4(hermesHome(), "agent-hooks", "prim-shim.sh");
|
|
2328
2277
|
}
|
|
2329
2278
|
var SHIM_SCRIPT = `#!/bin/sh
|
|
2330
2279
|
# prim Hermes hook shim \u2014 managed by \`prim hermes install\`. Hermes runs hooks
|
|
@@ -2413,10 +2362,10 @@ function isCaptureInstalled(hooks) {
|
|
|
2413
2362
|
);
|
|
2414
2363
|
}
|
|
2415
2364
|
function readDoc(path) {
|
|
2416
|
-
if (!
|
|
2365
|
+
if (!existsSync5(path)) {
|
|
2417
2366
|
return new Document();
|
|
2418
2367
|
}
|
|
2419
|
-
const raw =
|
|
2368
|
+
const raw = readFileSync4(path, "utf-8");
|
|
2420
2369
|
return raw.trim().length === 0 ? new Document() : parseDocument(raw);
|
|
2421
2370
|
}
|
|
2422
2371
|
function readHooks(doc) {
|
|
@@ -2488,8 +2437,8 @@ function setAutoAccept(raw) {
|
|
|
2488
2437
|
`;
|
|
2489
2438
|
}
|
|
2490
2439
|
function atomicWriteFile(path, content) {
|
|
2491
|
-
const dir =
|
|
2492
|
-
if (!
|
|
2440
|
+
const dir = dirname3(path);
|
|
2441
|
+
if (!existsSync5(dir)) {
|
|
2493
2442
|
mkdirSync4(dir, { recursive: true });
|
|
2494
2443
|
}
|
|
2495
2444
|
const tmp = `${path}.tmp.${String(process.pid)}`;
|
|
@@ -2513,8 +2462,8 @@ function assertMergeValid(before, after) {
|
|
|
2513
2462
|
}
|
|
2514
2463
|
function writeShim() {
|
|
2515
2464
|
const path = shimPath();
|
|
2516
|
-
const dir =
|
|
2517
|
-
if (!
|
|
2465
|
+
const dir = dirname3(path);
|
|
2466
|
+
if (!existsSync5(dir)) {
|
|
2518
2467
|
mkdirSync4(dir, { recursive: true });
|
|
2519
2468
|
}
|
|
2520
2469
|
writeFileSync3(path, SHIM_SCRIPT, "utf-8");
|
|
@@ -2534,7 +2483,7 @@ function readHooksFromRaw(raw) {
|
|
|
2534
2483
|
}
|
|
2535
2484
|
function performInstall3(opts) {
|
|
2536
2485
|
const path = configPath();
|
|
2537
|
-
const raw =
|
|
2486
|
+
const raw = existsSync5(path) ? readFileSync4(path, "utf-8") : "";
|
|
2538
2487
|
const existing = readHooksFromRaw(raw);
|
|
2539
2488
|
const desired = applyInstall3(existing, opts.force);
|
|
2540
2489
|
let next = raw;
|
|
@@ -2560,11 +2509,11 @@ function performInstall3(opts) {
|
|
|
2560
2509
|
}
|
|
2561
2510
|
function performUninstall3() {
|
|
2562
2511
|
const path = configPath();
|
|
2563
|
-
if (!
|
|
2512
|
+
if (!existsSync5(path)) {
|
|
2564
2513
|
removeShim();
|
|
2565
2514
|
return { path, gate: false, capture: false, autoAccept: false, changed: false };
|
|
2566
2515
|
}
|
|
2567
|
-
const raw =
|
|
2516
|
+
const raw = readFileSync4(path, "utf-8");
|
|
2568
2517
|
const existing = readHooksFromRaw(raw);
|
|
2569
2518
|
const remaining = applyUninstall3(existing);
|
|
2570
2519
|
const next = JSON.stringify(existing) === JSON.stringify(remaining) ? raw : spliceHooks(raw, remaining);
|
|
@@ -2584,7 +2533,7 @@ function performUninstall3() {
|
|
|
2584
2533
|
}
|
|
2585
2534
|
function performStatus3() {
|
|
2586
2535
|
const path = configPath();
|
|
2587
|
-
const hooks =
|
|
2536
|
+
const hooks = existsSync5(path) ? readHooks(readDoc(path)) : {};
|
|
2588
2537
|
return { path, gate: isGateInstalled3(hooks), capture: isCaptureInstalled(hooks) };
|
|
2589
2538
|
}
|
|
2590
2539
|
var TRUST_NOTICE2 = "[prim] Hermes requires hook consent: it prompts once per (event, command) on a TTY and records approval in ~/.hermes/shell-hooks-allowlist.json. To pre-approve non-interactively, re-run with --auto-accept (sets hooks_auto_accept: true), export HERMES_ACCEPT_HOOKS=1, or start Hermes with `hermes --accept-hooks chat`. Until approved, the hooks will not fire.";
|
|
@@ -2629,9 +2578,9 @@ function registerHermesCommands(program2) {
|
|
|
2629
2578
|
|
|
2630
2579
|
// src/commands/hooks.ts
|
|
2631
2580
|
import { execFileSync } from "child_process";
|
|
2632
|
-
import { existsSync as
|
|
2581
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
2633
2582
|
import { homedir as homedir5 } from "os";
|
|
2634
|
-
import { dirname as
|
|
2583
|
+
import { dirname as dirname4, join as join5, resolve } from "path";
|
|
2635
2584
|
import { Option } from "commander";
|
|
2636
2585
|
var PRE_COMMIT = { hookName: "pre-commit", binName: "prim-pre-commit" };
|
|
2637
2586
|
var POST_COMMIT = { hookName: "post-commit", binName: "prim-post-commit" };
|
|
@@ -2680,15 +2629,15 @@ ${gatedShim(spec.binName)}
|
|
|
2680
2629
|
${end}`;
|
|
2681
2630
|
}
|
|
2682
2631
|
function mergePrimBlock(hookPath, block, binName) {
|
|
2683
|
-
if (
|
|
2684
|
-
const existing =
|
|
2632
|
+
if (existsSync6(hookPath)) {
|
|
2633
|
+
const existing = readFileSync5(hookPath, "utf-8");
|
|
2685
2634
|
if (containsPrimHook(existing, binName)) return false;
|
|
2686
2635
|
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
2687
2636
|
writeFileSync4(hookPath, `${existing}${separator}${block}
|
|
2688
2637
|
`, { mode: 493 });
|
|
2689
2638
|
return true;
|
|
2690
2639
|
}
|
|
2691
|
-
mkdirSync5(
|
|
2640
|
+
mkdirSync5(dirname4(hookPath), { recursive: true });
|
|
2692
2641
|
writeFileSync4(hookPath, `#!/bin/sh
|
|
2693
2642
|
# ${PRIM_CREATED_MARK}
|
|
2694
2643
|
|
|
@@ -2705,13 +2654,13 @@ function getGitRoot() {
|
|
|
2705
2654
|
}
|
|
2706
2655
|
function detectHusky(gitRoot) {
|
|
2707
2656
|
const huskyDir = resolve(gitRoot, ".husky");
|
|
2708
|
-
if (!
|
|
2709
|
-
if (
|
|
2710
|
-
if (
|
|
2657
|
+
if (!existsSync6(huskyDir)) return false;
|
|
2658
|
+
if (existsSync6(resolve(huskyDir, "_"))) return true;
|
|
2659
|
+
if (existsSync6(resolve(huskyDir, "pre-commit"))) return true;
|
|
2711
2660
|
const pkgPath = resolve(gitRoot, "package.json");
|
|
2712
|
-
if (
|
|
2661
|
+
if (existsSync6(pkgPath)) {
|
|
2713
2662
|
try {
|
|
2714
|
-
const pkg2 = JSON.parse(
|
|
2663
|
+
const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
2715
2664
|
const scripts = pkg2.scripts ?? {};
|
|
2716
2665
|
if (/husky/i.test(scripts.prepare ?? "") || /husky/i.test(scripts.postinstall ?? "")) {
|
|
2717
2666
|
return true;
|
|
@@ -2738,7 +2687,7 @@ async function askConfirmation(question) {
|
|
|
2738
2687
|
}
|
|
2739
2688
|
function installToHusky(gitRoot, spec = PRE_COMMIT) {
|
|
2740
2689
|
const hookPath = resolve(gitRoot, ".husky", spec.hookName);
|
|
2741
|
-
const existed =
|
|
2690
|
+
const existed = existsSync6(hookPath);
|
|
2742
2691
|
const wrote = mergePrimBlock(hookPath, huskyBlock(spec), spec.binName);
|
|
2743
2692
|
if (!wrote) {
|
|
2744
2693
|
console.log(`Prim ${spec.hookName} hook is already installed in .husky/${spec.hookName}.`);
|
|
@@ -2751,11 +2700,11 @@ function installToHusky(gitRoot, spec = PRE_COMMIT) {
|
|
|
2751
2700
|
function installToDotGit(gitRoot, spec = PRE_COMMIT) {
|
|
2752
2701
|
const hooksDir = resolve(gitRoot, ".git", "hooks");
|
|
2753
2702
|
const hookPath = resolve(hooksDir, spec.hookName);
|
|
2754
|
-
if (!
|
|
2703
|
+
if (!existsSync6(hooksDir)) {
|
|
2755
2704
|
mkdirSync5(hooksDir, { recursive: true });
|
|
2756
2705
|
}
|
|
2757
|
-
if (
|
|
2758
|
-
const existing =
|
|
2706
|
+
if (existsSync6(hookPath)) {
|
|
2707
|
+
const existing = readFileSync5(hookPath, "utf-8");
|
|
2759
2708
|
if (containsPrimHook(existing, spec.binName)) {
|
|
2760
2709
|
console.log(`Prim ${spec.hookName} hook is already installed at ${hookPath}.`);
|
|
2761
2710
|
return;
|
|
@@ -2767,9 +2716,9 @@ function installToDotGit(gitRoot, spec = PRE_COMMIT) {
|
|
|
2767
2716
|
writeFileSync4(hookPath, dotGitScript(spec), { mode: 493 });
|
|
2768
2717
|
console.log(`Installed ${spec.hookName} hook at ${hookPath}`);
|
|
2769
2718
|
}
|
|
2770
|
-
var PRIM_GIT_HOOKS_DIR =
|
|
2719
|
+
var PRIM_GIT_HOOKS_DIR = join5(homedir5(), ".config", "prim", "git-hooks");
|
|
2771
2720
|
function expandTilde(p) {
|
|
2772
|
-
return p.startsWith("~/") ?
|
|
2721
|
+
return p.startsWith("~/") ? join5(homedir5(), p.slice(2)) : p;
|
|
2773
2722
|
}
|
|
2774
2723
|
function isOurHooksDir(value) {
|
|
2775
2724
|
return value !== "" && expandTilde(value) === PRIM_GIT_HOOKS_DIR;
|
|
@@ -2834,7 +2783,7 @@ function ownedHookNames() {
|
|
|
2834
2783
|
return [...HOOKS.map((s) => s.hookName), ...PASSTHROUGH_HOOKS];
|
|
2835
2784
|
}
|
|
2836
2785
|
function writeOwnHooks() {
|
|
2837
|
-
if (!
|
|
2786
|
+
if (!existsSync6(PRIM_GIT_HOOKS_DIR)) {
|
|
2838
2787
|
mkdirSync5(PRIM_GIT_HOOKS_DIR, { recursive: true });
|
|
2839
2788
|
}
|
|
2840
2789
|
for (const spec of HOOKS) {
|
|
@@ -2850,8 +2799,8 @@ function appendPrimBlock(hookPath, spec) {
|
|
|
2850
2799
|
mergePrimBlock(hookPath, gatedBlock(spec), spec.binName);
|
|
2851
2800
|
}
|
|
2852
2801
|
function stripPrimBlock(hookPath, spec) {
|
|
2853
|
-
if (!
|
|
2854
|
-
const existing =
|
|
2802
|
+
if (!existsSync6(hookPath)) return;
|
|
2803
|
+
const existing = readFileSync5(hookPath, "utf-8");
|
|
2855
2804
|
const primCreated = existing.includes(PRIM_CREATED_MARK);
|
|
2856
2805
|
const { start, end } = blockMarkers(spec);
|
|
2857
2806
|
const s = existing.indexOf(start);
|
|
@@ -2906,7 +2855,7 @@ function uninstallGlobalHooks() {
|
|
|
2906
2855
|
if (isOurHooksDir(global)) {
|
|
2907
2856
|
for (const name of ownedHookNames()) {
|
|
2908
2857
|
const p = resolve(PRIM_GIT_HOOKS_DIR, name);
|
|
2909
|
-
if (
|
|
2858
|
+
if (existsSync6(p)) unlinkSync2(p);
|
|
2910
2859
|
}
|
|
2911
2860
|
execFileSync("git", ["config", "--global", "--unset", "core.hooksPath"]);
|
|
2912
2861
|
console.log("Removed prim global git hooks and unset core.hooksPath.");
|
|
@@ -2996,11 +2945,11 @@ function registerHooksCommands(program2) {
|
|
|
2996
2945
|
const gitRoot = getGitRoot();
|
|
2997
2946
|
for (const spec of HOOKS) {
|
|
2998
2947
|
const hookPath = resolve(gitRoot, ".git", "hooks", spec.hookName);
|
|
2999
|
-
if (!
|
|
2948
|
+
if (!existsSync6(hookPath)) {
|
|
3000
2949
|
console.log(`No ${spec.hookName} hook found.`);
|
|
3001
2950
|
continue;
|
|
3002
2951
|
}
|
|
3003
|
-
if (containsPrimHook(
|
|
2952
|
+
if (containsPrimHook(readFileSync5(hookPath, "utf-8"), spec.binName)) {
|
|
3004
2953
|
unlinkSync2(hookPath);
|
|
3005
2954
|
console.log(`Removed ${spec.hookName} hook at ${hookPath}`);
|
|
3006
2955
|
} else {
|
|
@@ -3011,8 +2960,8 @@ function registerHooksCommands(program2) {
|
|
|
3011
2960
|
}
|
|
3012
2961
|
|
|
3013
2962
|
// src/commands/moves.ts
|
|
3014
|
-
import { existsSync as
|
|
3015
|
-
import { join as
|
|
2963
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
2964
|
+
import { join as join6 } from "path";
|
|
3016
2965
|
|
|
3017
2966
|
// src/flusher.ts
|
|
3018
2967
|
import { renameSync as renameSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
@@ -3171,19 +3120,19 @@ function registerMovesCommands(program2) {
|
|
|
3171
3120
|
}
|
|
3172
3121
|
});
|
|
3173
3122
|
moves.command("bind").description("Pin the current directory to an org via .prim/workspace.json").requiredOption("--orgId <orgId>", "Convex organization id").action((opts) => {
|
|
3174
|
-
const dir =
|
|
3175
|
-
if (!
|
|
3123
|
+
const dir = join6(process.cwd(), ".prim");
|
|
3124
|
+
if (!existsSync7(dir)) {
|
|
3176
3125
|
mkdirSync6(dir, { recursive: true, mode: DIR_MODE });
|
|
3177
3126
|
}
|
|
3178
|
-
const file =
|
|
3127
|
+
const file = join6(process.cwd(), WORKSPACE_FILE);
|
|
3179
3128
|
writeFileSync5(file, JSON.stringify({ orgId: opts.orgId, boundAt: Date.now() }, null, 2), {
|
|
3180
3129
|
mode: FILE_MODE2
|
|
3181
3130
|
});
|
|
3182
3131
|
console.log(`[prim] bound ${process.cwd()} to org ${opts.orgId}`);
|
|
3183
3132
|
});
|
|
3184
3133
|
moves.command("drop").description("Remove the .prim/workspace.json binding from the cwd").action(() => {
|
|
3185
|
-
const file =
|
|
3186
|
-
if (!
|
|
3134
|
+
const file = join6(process.cwd(), WORKSPACE_FILE);
|
|
3135
|
+
if (!existsSync7(file)) {
|
|
3187
3136
|
console.log("[prim] no workspace binding in cwd");
|
|
3188
3137
|
return;
|
|
3189
3138
|
}
|
|
@@ -3276,23 +3225,23 @@ function registerReconcileCommands(program2) {
|
|
|
3276
3225
|
|
|
3277
3226
|
// src/commands/session.ts
|
|
3278
3227
|
import {
|
|
3279
|
-
existsSync as
|
|
3228
|
+
existsSync as existsSync8,
|
|
3280
3229
|
mkdirSync as mkdirSync7,
|
|
3281
|
-
readFileSync as
|
|
3230
|
+
readFileSync as readFileSync6,
|
|
3282
3231
|
readdirSync,
|
|
3283
3232
|
unlinkSync as unlinkSync5,
|
|
3284
3233
|
writeFileSync as writeFileSync6
|
|
3285
3234
|
} from "fs";
|
|
3286
|
-
import { join as
|
|
3235
|
+
import { join as join7 } from "path";
|
|
3287
3236
|
var DIR_MODE2 = 448;
|
|
3288
3237
|
var FILE_MODE3 = 384;
|
|
3289
3238
|
function ensureDir() {
|
|
3290
|
-
if (!
|
|
3239
|
+
if (!existsSync8(SESSIONS_DIR)) {
|
|
3291
3240
|
mkdirSync7(SESSIONS_DIR, { recursive: true, mode: DIR_MODE2 });
|
|
3292
3241
|
}
|
|
3293
3242
|
}
|
|
3294
3243
|
function markerPath(sessionId) {
|
|
3295
|
-
return
|
|
3244
|
+
return join7(SESSIONS_DIR, `${sessionId}.json`);
|
|
3296
3245
|
}
|
|
3297
3246
|
function registerSessionCommands(program2) {
|
|
3298
3247
|
const session = program2.command("session").description("Decision Event Pipeline \u2014 session binding markers");
|
|
@@ -3308,7 +3257,7 @@ function registerSessionCommands(program2) {
|
|
|
3308
3257
|
console.log(`[prim] session ${sessionId} bound to org ${opts.orgId}`);
|
|
3309
3258
|
});
|
|
3310
3259
|
session.command("list").description("List active session markers").action(() => {
|
|
3311
|
-
if (!
|
|
3260
|
+
if (!existsSync8(SESSIONS_DIR)) {
|
|
3312
3261
|
console.log("[prim] no session markers");
|
|
3313
3262
|
return;
|
|
3314
3263
|
}
|
|
@@ -3320,7 +3269,7 @@ function registerSessionCommands(program2) {
|
|
|
3320
3269
|
for (const f of files) {
|
|
3321
3270
|
const sessionId = f.replace(/\.json$/, "");
|
|
3322
3271
|
try {
|
|
3323
|
-
const m = JSON.parse(
|
|
3272
|
+
const m = JSON.parse(readFileSync6(join7(SESSIONS_DIR, f), "utf-8"));
|
|
3324
3273
|
console.log(`${sessionId} org=${m.orgId}`);
|
|
3325
3274
|
} catch {
|
|
3326
3275
|
}
|
|
@@ -3328,7 +3277,7 @@ function registerSessionCommands(program2) {
|
|
|
3328
3277
|
});
|
|
3329
3278
|
session.command("drop <sessionId>").description("Remove a session marker").action((sessionId) => {
|
|
3330
3279
|
const p = markerPath(sessionId);
|
|
3331
|
-
if (!
|
|
3280
|
+
if (!existsSync8(p)) {
|
|
3332
3281
|
console.log(`[prim] no marker for session ${sessionId}`);
|
|
3333
3282
|
return;
|
|
3334
3283
|
}
|
|
@@ -3339,8 +3288,8 @@ function registerSessionCommands(program2) {
|
|
|
3339
3288
|
|
|
3340
3289
|
// src/commands/setup.ts
|
|
3341
3290
|
import { spawnSync } from "child_process";
|
|
3342
|
-
import { existsSync as
|
|
3343
|
-
import { join as
|
|
3291
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
3292
|
+
import { join as join8 } from "path";
|
|
3344
3293
|
var EXIT_INCOMPLETE = 1;
|
|
3345
3294
|
var EXIT_USAGE3 = 2;
|
|
3346
3295
|
var SESSION_LABELS = {
|
|
@@ -3403,8 +3352,8 @@ function detectProjectConflicts(agent, run) {
|
|
|
3403
3352
|
}
|
|
3404
3353
|
try {
|
|
3405
3354
|
const root = gitToplevel();
|
|
3406
|
-
const preCommit = root &&
|
|
3407
|
-
if (preCommit &&
|
|
3355
|
+
const preCommit = root && join8(root, ".git", "hooks", "pre-commit");
|
|
3356
|
+
if (preCommit && existsSync9(preCommit) && readFileSync7(preCommit, "utf-8").includes("prim-pre-commit")) {
|
|
3408
3357
|
conflicts.push(CONFLICT_HOOKS);
|
|
3409
3358
|
}
|
|
3410
3359
|
} catch {
|
|
@@ -3522,39 +3471,39 @@ function registerSetupCommand(program2) {
|
|
|
3522
3471
|
// src/commands/skill.ts
|
|
3523
3472
|
import {
|
|
3524
3473
|
closeSync as closeSync4,
|
|
3525
|
-
existsSync as
|
|
3474
|
+
existsSync as existsSync11,
|
|
3526
3475
|
fsyncSync as fsyncSync3,
|
|
3527
3476
|
openSync as openSync4,
|
|
3528
|
-
readFileSync as
|
|
3477
|
+
readFileSync as readFileSync9,
|
|
3529
3478
|
renameSync as renameSync4,
|
|
3530
3479
|
writeFileSync as writeFileSync7
|
|
3531
3480
|
} from "fs";
|
|
3532
3481
|
import { homedir as homedir7 } from "os";
|
|
3533
|
-
import { dirname as
|
|
3534
|
-
import { fileURLToPath as
|
|
3482
|
+
import { dirname as dirname6, join as join10, resolve as resolve3 } from "path";
|
|
3483
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3535
3484
|
import { createPatch } from "diff";
|
|
3536
3485
|
|
|
3537
3486
|
// src/commands/claude-plugin.ts
|
|
3538
|
-
import { existsSync as
|
|
3487
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync8, readdirSync as readdirSync2, rmSync as rmSync3, rmdirSync } from "fs";
|
|
3539
3488
|
import { homedir as homedir6 } from "os";
|
|
3540
|
-
import { dirname as
|
|
3541
|
-
import { fileURLToPath
|
|
3542
|
-
var __dirname =
|
|
3489
|
+
import { dirname as dirname5, join as join9, resolve as resolve2 } from "path";
|
|
3490
|
+
import { fileURLToPath } from "url";
|
|
3491
|
+
var __dirname = dirname5(fileURLToPath(import.meta.url));
|
|
3543
3492
|
var PLUGIN_DESCRIPTION = "Primitive decision-graph guidance for the prim CLI \u2014 a model-invoked skill installed by prim skill install.";
|
|
3544
3493
|
function resolvePluginDir(cwd, scope) {
|
|
3545
3494
|
if (scope && scope !== "user" && scope !== "project") {
|
|
3546
3495
|
console.error(`Unknown --scope "${scope}" (expected user or project)`);
|
|
3547
3496
|
return null;
|
|
3548
3497
|
}
|
|
3549
|
-
const base = scope === "user" ?
|
|
3550
|
-
return
|
|
3498
|
+
const base = scope === "user" ? join9(homedir6(), ".claude") : join9(gitToplevel(cwd) ?? cwd, ".claude");
|
|
3499
|
+
return join9(base, "skills", "prim");
|
|
3551
3500
|
}
|
|
3552
3501
|
function packageVersion() {
|
|
3553
3502
|
let dir = __dirname;
|
|
3554
|
-
while (dir !==
|
|
3503
|
+
while (dir !== dirname5(dir)) {
|
|
3555
3504
|
const p = resolve2(dir, "package.json");
|
|
3556
|
-
if (
|
|
3557
|
-
dir =
|
|
3505
|
+
if (existsSync10(p)) return JSON.parse(readFileSync8(p, "utf-8")).version;
|
|
3506
|
+
dir = dirname5(dir);
|
|
3558
3507
|
}
|
|
3559
3508
|
return "0.0.0";
|
|
3560
3509
|
}
|
|
@@ -3564,12 +3513,12 @@ function pluginManifest() {
|
|
|
3564
3513
|
`;
|
|
3565
3514
|
}
|
|
3566
3515
|
function removeDirIfEmpty(dir) {
|
|
3567
|
-
if (
|
|
3516
|
+
if (existsSync10(dir) && readdirSync2(dir).length === 0) rmdirSync(dir);
|
|
3568
3517
|
}
|
|
3569
3518
|
function pluginPaths(dir) {
|
|
3570
3519
|
return {
|
|
3571
|
-
manifestPath:
|
|
3572
|
-
skillPath:
|
|
3520
|
+
manifestPath: join9(dir, ".claude-plugin", "plugin.json"),
|
|
3521
|
+
skillPath: join9(dir, "SKILL.md")
|
|
3573
3522
|
};
|
|
3574
3523
|
}
|
|
3575
3524
|
function installClaudePlugin(cwd, opts) {
|
|
@@ -3578,8 +3527,8 @@ function installClaudePlugin(cwd, opts) {
|
|
|
3578
3527
|
const { manifestPath, skillPath } = pluginPaths(dir);
|
|
3579
3528
|
const manifest = pluginManifest();
|
|
3580
3529
|
const skill = loadSkill();
|
|
3581
|
-
const manifestCurrent =
|
|
3582
|
-
const skillCurrent =
|
|
3530
|
+
const manifestCurrent = existsSync10(manifestPath) ? readFileSync8(manifestPath, "utf-8") : null;
|
|
3531
|
+
const skillCurrent = existsSync10(skillPath) ? readFileSync8(skillPath, "utf-8") : null;
|
|
3583
3532
|
if (manifestCurrent === manifest && skillCurrent === skill) {
|
|
3584
3533
|
console.log("No changes \u2014 prim skill plugin already up to date.");
|
|
3585
3534
|
return 0;
|
|
@@ -3588,7 +3537,7 @@ function installClaudePlugin(cwd, opts) {
|
|
|
3588
3537
|
console.log(`Would write plugin to ${dir} (.claude-plugin/plugin.json + SKILL.md)`);
|
|
3589
3538
|
return 0;
|
|
3590
3539
|
}
|
|
3591
|
-
mkdirSync8(
|
|
3540
|
+
mkdirSync8(join9(dir, ".claude-plugin"), { recursive: true });
|
|
3592
3541
|
atomicWrite2(manifestPath, manifest);
|
|
3593
3542
|
atomicWrite2(skillPath, skill);
|
|
3594
3543
|
console.log(`Installed prim skill plugin at ${dir}`);
|
|
@@ -3602,13 +3551,13 @@ function uninstallClaudePlugin(cwd, opts) {
|
|
|
3602
3551
|
const dir = resolvePluginDir(cwd, opts.scope);
|
|
3603
3552
|
if (dir === null) return 1;
|
|
3604
3553
|
const { manifestPath, skillPath } = pluginPaths(dir);
|
|
3605
|
-
if (!
|
|
3554
|
+
if (!existsSync10(manifestPath) && !existsSync10(skillPath)) {
|
|
3606
3555
|
console.log(`prim skill plugin not present at ${dir}`);
|
|
3607
3556
|
return 0;
|
|
3608
3557
|
}
|
|
3609
3558
|
rmSync3(manifestPath, { force: true });
|
|
3610
3559
|
rmSync3(skillPath, { force: true });
|
|
3611
|
-
removeDirIfEmpty(
|
|
3560
|
+
removeDirIfEmpty(join9(dir, ".claude-plugin"));
|
|
3612
3561
|
removeDirIfEmpty(dir);
|
|
3613
3562
|
console.log(`Removed prim skill plugin from ${dir}`);
|
|
3614
3563
|
return 0;
|
|
@@ -3617,7 +3566,7 @@ function statusClaudePlugin(cwd, opts) {
|
|
|
3617
3566
|
const dir = resolvePluginDir(cwd, opts.scope);
|
|
3618
3567
|
if (dir === null) return 1;
|
|
3619
3568
|
const { skillPath } = pluginPaths(dir);
|
|
3620
|
-
const installed =
|
|
3569
|
+
const installed = existsSync10(skillPath);
|
|
3621
3570
|
if (opts.json) {
|
|
3622
3571
|
printJson({ installed, target: dir });
|
|
3623
3572
|
return installed ? 0 : 1;
|
|
@@ -3631,7 +3580,7 @@ function statusClaudePlugin(cwd, opts) {
|
|
|
3631
3580
|
}
|
|
3632
3581
|
|
|
3633
3582
|
// src/commands/skill.ts
|
|
3634
|
-
var __dirname2 =
|
|
3583
|
+
var __dirname2 = dirname6(fileURLToPath2(import.meta.url));
|
|
3635
3584
|
var SKILL_BEGIN = "<!-- BEGIN PRIM SKILL v1 -->";
|
|
3636
3585
|
var SKILL_END = "<!-- END PRIM SKILL v1 -->";
|
|
3637
3586
|
var TARGET_CANDIDATES = [
|
|
@@ -3649,24 +3598,24 @@ var AGENT_TARGET = {
|
|
|
3649
3598
|
hermes: ".hermes.md"
|
|
3650
3599
|
};
|
|
3651
3600
|
function userTargetFor(agent) {
|
|
3652
|
-
if (agent === "claude") return
|
|
3653
|
-
if (agent === "codex") return
|
|
3601
|
+
if (agent === "claude") return join10(homedir7(), ".claude", "CLAUDE.md");
|
|
3602
|
+
if (agent === "codex") return join10(homedir7(), ".codex", "AGENTS.md");
|
|
3654
3603
|
if (agent === "hermes") {
|
|
3655
|
-
return
|
|
3604
|
+
return join10(process.env.HERMES_HOME ?? join10(homedir7(), ".hermes"), ".hermes.md");
|
|
3656
3605
|
}
|
|
3657
3606
|
return null;
|
|
3658
3607
|
}
|
|
3659
3608
|
function loadSkill() {
|
|
3660
3609
|
let dir = __dirname2;
|
|
3661
|
-
while (dir !==
|
|
3610
|
+
while (dir !== dirname6(dir)) {
|
|
3662
3611
|
const p = resolve3(dir, "SKILL.md");
|
|
3663
|
-
if (
|
|
3664
|
-
dir =
|
|
3612
|
+
if (existsSync11(p)) return readFileSync9(p, "utf-8");
|
|
3613
|
+
dir = dirname6(dir);
|
|
3665
3614
|
}
|
|
3666
3615
|
throw new Error("SKILL.md not found in package");
|
|
3667
3616
|
}
|
|
3668
3617
|
function detectTargets(cwd) {
|
|
3669
|
-
return TARGET_CANDIDATES.filter((p) =>
|
|
3618
|
+
return TARGET_CANDIDATES.filter((p) => existsSync11(resolve3(cwd, p)));
|
|
3670
3619
|
}
|
|
3671
3620
|
function detectNewline(content) {
|
|
3672
3621
|
return content.includes("\r\n") ? "\r\n" : "\n";
|
|
@@ -3736,7 +3685,7 @@ function runInstall(cwd, opts) {
|
|
|
3736
3685
|
if (opts.agent === "claude" && !opts.target) return installClaudePlugin(cwd, opts);
|
|
3737
3686
|
const target = resolveTarget(cwd, opts);
|
|
3738
3687
|
if (target === null) return 1;
|
|
3739
|
-
const existing =
|
|
3688
|
+
const existing = existsSync11(target) ? readFileSync9(target, "utf-8") : "";
|
|
3740
3689
|
const eol = existing ? detectNewline(existing) : "\n";
|
|
3741
3690
|
const block = composeBlock(loadSkill(), eol);
|
|
3742
3691
|
const next = applyBlock(existing, block, eol);
|
|
@@ -3756,11 +3705,11 @@ function runUninstall(cwd, opts) {
|
|
|
3756
3705
|
if (opts.agent === "claude" && !opts.target) return uninstallClaudePlugin(cwd, opts);
|
|
3757
3706
|
const target = resolveTarget(cwd, opts);
|
|
3758
3707
|
if (target === null) return 1;
|
|
3759
|
-
if (!
|
|
3708
|
+
if (!existsSync11(target)) {
|
|
3760
3709
|
console.log(`Skill block not present at ${target}`);
|
|
3761
3710
|
return 0;
|
|
3762
3711
|
}
|
|
3763
|
-
const existing =
|
|
3712
|
+
const existing = readFileSync9(target, "utf-8");
|
|
3764
3713
|
const next = removeBlock(existing);
|
|
3765
3714
|
if (next === null) {
|
|
3766
3715
|
console.log(`Skill block not present at ${target}`);
|
|
@@ -3774,10 +3723,10 @@ function runStatus(cwd, opts) {
|
|
|
3774
3723
|
if (opts.agent === "claude" && !opts.target) return statusClaudePlugin(cwd, opts);
|
|
3775
3724
|
const target = resolveTarget(cwd, opts);
|
|
3776
3725
|
if (target === null) return 1;
|
|
3777
|
-
const fileExists =
|
|
3726
|
+
const fileExists = existsSync11(target);
|
|
3778
3727
|
let installed = false;
|
|
3779
3728
|
if (fileExists) {
|
|
3780
|
-
const content =
|
|
3729
|
+
const content = readFileSync9(target, "utf-8");
|
|
3781
3730
|
installed = content.includes(SKILL_BEGIN) && content.includes(SKILL_END);
|
|
3782
3731
|
}
|
|
3783
3732
|
if (opts.json) {
|
|
@@ -3814,18 +3763,18 @@ function registerSkillCommands(program2) {
|
|
|
3814
3763
|
}
|
|
3815
3764
|
|
|
3816
3765
|
// src/commands/statusline.ts
|
|
3817
|
-
import { readFileSync as
|
|
3818
|
-
import { dirname as
|
|
3819
|
-
import { fileURLToPath as
|
|
3766
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
3767
|
+
import { dirname as dirname7, resolve as resolve4 } from "path";
|
|
3768
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3820
3769
|
var STATUSLINE_TIMEOUT_MS = 200;
|
|
3821
3770
|
var STATUSLINE_NAME_CAP = 3;
|
|
3822
3771
|
function readPackageVersion() {
|
|
3823
3772
|
try {
|
|
3824
|
-
const here =
|
|
3773
|
+
const here = dirname7(fileURLToPath3(import.meta.url));
|
|
3825
3774
|
const candidates = [resolve4(here, "../../package.json"), resolve4(here, "../package.json")];
|
|
3826
3775
|
for (const path of candidates) {
|
|
3827
3776
|
try {
|
|
3828
|
-
const pkg2 = JSON.parse(
|
|
3777
|
+
const pkg2 = JSON.parse(readFileSync10(path, "utf-8"));
|
|
3829
3778
|
if (pkg2.version) {
|
|
3830
3779
|
return pkg2.version;
|
|
3831
3780
|
}
|
|
@@ -3874,6 +3823,7 @@ async function renderStatusline() {
|
|
|
3874
3823
|
function registerStatuslineCommands(program2) {
|
|
3875
3824
|
program2.command("statusline").description("Render the Claude Code statusLine for the prim companion daemon").action(async () => {
|
|
3876
3825
|
try {
|
|
3826
|
+
warmBinCache();
|
|
3877
3827
|
const line = await renderStatusline();
|
|
3878
3828
|
process.stdout.write(line);
|
|
3879
3829
|
} catch {
|
|
@@ -3993,8 +3943,8 @@ function registerWelcomeCommand(program2, deps = { getClient }) {
|
|
|
3993
3943
|
}
|
|
3994
3944
|
|
|
3995
3945
|
// src/index.ts
|
|
3996
|
-
var __dirname3 =
|
|
3997
|
-
var pkg = JSON.parse(
|
|
3946
|
+
var __dirname3 = dirname8(fileURLToPath4(import.meta.url));
|
|
3947
|
+
var pkg = JSON.parse(readFileSync11(resolve5(__dirname3, "../package.json"), "utf-8"));
|
|
3998
3948
|
updateNotifier({ pkg }).notify();
|
|
3999
3949
|
var program = new Command();
|
|
4000
3950
|
program.name("prim").description("CLI for Primitive's decision graph").version(pkg.version).option("-y, --yes", "auto-confirm prompts").option(
|
package/package.json
CHANGED