@primitive.ai/prim 0.1.0-alpha.36 → 0.1.0-alpha.38
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/daemon/server.js +5 -1
- 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 +163 -199
- 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
|
+
};
|
package/dist/daemon/server.js
CHANGED
|
@@ -47,6 +47,7 @@ var activeSessionId = process.env.PRIM_DAEMON_SESSION_ID ?? `daemon-${process.pi
|
|
|
47
47
|
var lastHeartbeatAt;
|
|
48
48
|
var lastOnlineCount;
|
|
49
49
|
var lastOnlineNames;
|
|
50
|
+
var lastOnlineTeammates;
|
|
50
51
|
var lastOkAtLocal;
|
|
51
52
|
var heartbeatTimer;
|
|
52
53
|
var tokenCheckTimer;
|
|
@@ -92,6 +93,7 @@ async function sendHeartbeat() {
|
|
|
92
93
|
}
|
|
93
94
|
lastOnlineCount = typeof result.onlineCount === "number" ? result.onlineCount : void 0;
|
|
94
95
|
lastOnlineNames = Array.isArray(result.onlineNames) ? result.onlineNames : void 0;
|
|
96
|
+
lastOnlineTeammates = Array.isArray(result.onlineTeammates) ? result.onlineTeammates : void 0;
|
|
95
97
|
}
|
|
96
98
|
} catch (err) {
|
|
97
99
|
process.stderr.write(
|
|
@@ -148,6 +150,7 @@ function handleStatusSnapshot(params) {
|
|
|
148
150
|
...base,
|
|
149
151
|
onlineCount: void 0,
|
|
150
152
|
onlineNames: void 0,
|
|
153
|
+
onlineTeammates: void 0,
|
|
151
154
|
presenceStale: false,
|
|
152
155
|
envMismatch: true
|
|
153
156
|
};
|
|
@@ -156,10 +159,11 @@ function handleStatusSnapshot(params) {
|
|
|
156
159
|
const presenceStale = lastOkAtLocal !== void 0 && !presenceFresh;
|
|
157
160
|
return {
|
|
158
161
|
...base,
|
|
159
|
-
// Withhold a frozen count/names once they're no longer fresh; the
|
|
162
|
+
// Withhold a frozen count/names/teammates once they're no longer fresh; the
|
|
160
163
|
// statusline shows "presence: stale" rather than a confident, wrong list.
|
|
161
164
|
onlineCount: presenceFresh ? lastOnlineCount : void 0,
|
|
162
165
|
onlineNames: presenceFresh ? lastOnlineNames : void 0,
|
|
166
|
+
onlineTeammates: presenceFresh ? lastOnlineTeammates : void 0,
|
|
163
167
|
presenceStale
|
|
164
168
|
};
|
|
165
169
|
}
|
|
@@ -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,30 +1051,42 @@ ${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
|
-
function
|
|
1111
|
-
if (
|
|
1059
|
+
function formatLabeled(labels, cap) {
|
|
1060
|
+
if (labels === void 0) {
|
|
1112
1061
|
return "\u2014";
|
|
1113
1062
|
}
|
|
1114
|
-
if (
|
|
1063
|
+
if (labels.length === 0) {
|
|
1115
1064
|
return "just you";
|
|
1116
1065
|
}
|
|
1117
|
-
if (
|
|
1118
|
-
return
|
|
1066
|
+
if (labels.length <= cap) {
|
|
1067
|
+
return labels.join(", ");
|
|
1119
1068
|
}
|
|
1120
|
-
return `${
|
|
1069
|
+
return `${labels.slice(0, cap).join(", ")} +${String(labels.length - cap)}`;
|
|
1070
|
+
}
|
|
1071
|
+
function formatTeammates(names, cap) {
|
|
1072
|
+
return formatLabeled(names, cap);
|
|
1073
|
+
}
|
|
1074
|
+
function formatTeammatesWithArea(teammates, cap) {
|
|
1075
|
+
return formatLabeled(
|
|
1076
|
+
teammates?.map((t) => {
|
|
1077
|
+
const area = t.area?.trim();
|
|
1078
|
+
return area ? `${t.name} - ${area}` : t.name;
|
|
1079
|
+
}),
|
|
1080
|
+
cap
|
|
1081
|
+
);
|
|
1121
1082
|
}
|
|
1122
1083
|
|
|
1123
1084
|
// src/commands/daemon.ts
|
|
1124
1085
|
var DAEMON_BIN = "prim-daemon-server";
|
|
1125
|
-
var CONFIG_DIR =
|
|
1126
|
-
var PID_PATH =
|
|
1127
|
-
var SOCK_PATH =
|
|
1128
|
-
var LOG_PATH =
|
|
1086
|
+
var CONFIG_DIR = join3(homedir3(), ".config", "prim");
|
|
1087
|
+
var PID_PATH = join3(CONFIG_DIR, "daemon.pid");
|
|
1088
|
+
var SOCK_PATH = join3(CONFIG_DIR, "sock");
|
|
1089
|
+
var LOG_PATH = join3(CONFIG_DIR, "daemon.log");
|
|
1129
1090
|
var CONFIG_DIR_MODE = 448;
|
|
1130
1091
|
var LOG_FILE_MODE = 384;
|
|
1131
1092
|
var STOP_TIMEOUT_MS = 5e3;
|
|
@@ -1138,10 +1099,10 @@ var EXIT_OK2 = 0;
|
|
|
1138
1099
|
var EXIT_NOT_RUNNING = 2;
|
|
1139
1100
|
var EXIT_BOOTING = 3;
|
|
1140
1101
|
function readPidfile() {
|
|
1141
|
-
if (!
|
|
1102
|
+
if (!existsSync3(PID_PATH)) {
|
|
1142
1103
|
return null;
|
|
1143
1104
|
}
|
|
1144
|
-
const raw =
|
|
1105
|
+
const raw = readFileSync3(PID_PATH, "utf-8").trim();
|
|
1145
1106
|
const pid = Number(raw);
|
|
1146
1107
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
1147
1108
|
return null;
|
|
@@ -1177,10 +1138,10 @@ function spawnDaemon(options) {
|
|
|
1177
1138
|
return file ? spawn(process.execPath, [file], options) : spawn(DAEMON_BIN, [], options);
|
|
1178
1139
|
}
|
|
1179
1140
|
function openDaemonLog(configDir = CONFIG_DIR) {
|
|
1180
|
-
if (!
|
|
1141
|
+
if (!existsSync3(configDir)) {
|
|
1181
1142
|
mkdirSync3(configDir, { recursive: true, mode: CONFIG_DIR_MODE });
|
|
1182
1143
|
}
|
|
1183
|
-
return openSync2(
|
|
1144
|
+
return openSync2(join3(configDir, "daemon.log"), "a", LOG_FILE_MODE);
|
|
1184
1145
|
}
|
|
1185
1146
|
async function waitForReady() {
|
|
1186
1147
|
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
@@ -2171,7 +2132,7 @@ function registerDecisionsCommands(program2) {
|
|
|
2171
2132
|
}
|
|
2172
2133
|
|
|
2173
2134
|
// src/commands/doctor.ts
|
|
2174
|
-
import { existsSync as
|
|
2135
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2175
2136
|
var DAEMON_PROBE_TIMEOUT_MS = 500;
|
|
2176
2137
|
var CONNECTIVITY_TIMEOUT_MS = 3e3;
|
|
2177
2138
|
var MS_PER_SECOND = 1e3;
|
|
@@ -2189,7 +2150,7 @@ function checkAuth() {
|
|
|
2189
2150
|
return { name: "auth", status: "fail", detail: "no token \u2014 run `prim auth login`" };
|
|
2190
2151
|
}
|
|
2191
2152
|
const expiresAt = getTokenExpiresAt();
|
|
2192
|
-
const hasRefresh =
|
|
2153
|
+
const hasRefresh = existsSync4(REFRESH_TOKEN_PATH);
|
|
2193
2154
|
if (expiresAt !== void 0 && Date.now() >= expiresAt) {
|
|
2194
2155
|
return hasRefresh ? { name: "auth", status: "warn", detail: "access token expired (refresh available)" } : {
|
|
2195
2156
|
name: "auth",
|
|
@@ -2289,17 +2250,17 @@ function registerDoctorCommands(program2) {
|
|
|
2289
2250
|
import {
|
|
2290
2251
|
chmodSync,
|
|
2291
2252
|
closeSync as closeSync3,
|
|
2292
|
-
existsSync as
|
|
2253
|
+
existsSync as existsSync5,
|
|
2293
2254
|
fsyncSync as fsyncSync2,
|
|
2294
2255
|
mkdirSync as mkdirSync4,
|
|
2295
2256
|
openSync as openSync3,
|
|
2296
|
-
readFileSync as
|
|
2257
|
+
readFileSync as readFileSync4,
|
|
2297
2258
|
renameSync as renameSync2,
|
|
2298
2259
|
rmSync as rmSync2,
|
|
2299
2260
|
writeFileSync as writeFileSync3
|
|
2300
2261
|
} from "fs";
|
|
2301
2262
|
import { homedir as homedir4 } from "os";
|
|
2302
|
-
import { dirname as
|
|
2263
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
2303
2264
|
import { Document, parseDocument, stringify } from "yaml";
|
|
2304
2265
|
var CAPTURE_BIN3 = "prim-hook";
|
|
2305
2266
|
var GATE_BIN3 = "prim-pre-tool-use";
|
|
@@ -2318,13 +2279,13 @@ var PRIM_BINS3 = [
|
|
|
2318
2279
|
SESSION_END_BIN2
|
|
2319
2280
|
];
|
|
2320
2281
|
function hermesHome() {
|
|
2321
|
-
return process.env.HERMES_HOME ??
|
|
2282
|
+
return process.env.HERMES_HOME ?? join4(homedir4(), ".hermes");
|
|
2322
2283
|
}
|
|
2323
2284
|
function configPath() {
|
|
2324
|
-
return
|
|
2285
|
+
return join4(hermesHome(), "config.yaml");
|
|
2325
2286
|
}
|
|
2326
2287
|
function shimPath() {
|
|
2327
|
-
return
|
|
2288
|
+
return join4(hermesHome(), "agent-hooks", "prim-shim.sh");
|
|
2328
2289
|
}
|
|
2329
2290
|
var SHIM_SCRIPT = `#!/bin/sh
|
|
2330
2291
|
# prim Hermes hook shim \u2014 managed by \`prim hermes install\`. Hermes runs hooks
|
|
@@ -2413,10 +2374,10 @@ function isCaptureInstalled(hooks) {
|
|
|
2413
2374
|
);
|
|
2414
2375
|
}
|
|
2415
2376
|
function readDoc(path) {
|
|
2416
|
-
if (!
|
|
2377
|
+
if (!existsSync5(path)) {
|
|
2417
2378
|
return new Document();
|
|
2418
2379
|
}
|
|
2419
|
-
const raw =
|
|
2380
|
+
const raw = readFileSync4(path, "utf-8");
|
|
2420
2381
|
return raw.trim().length === 0 ? new Document() : parseDocument(raw);
|
|
2421
2382
|
}
|
|
2422
2383
|
function readHooks(doc) {
|
|
@@ -2488,8 +2449,8 @@ function setAutoAccept(raw) {
|
|
|
2488
2449
|
`;
|
|
2489
2450
|
}
|
|
2490
2451
|
function atomicWriteFile(path, content) {
|
|
2491
|
-
const dir =
|
|
2492
|
-
if (!
|
|
2452
|
+
const dir = dirname3(path);
|
|
2453
|
+
if (!existsSync5(dir)) {
|
|
2493
2454
|
mkdirSync4(dir, { recursive: true });
|
|
2494
2455
|
}
|
|
2495
2456
|
const tmp = `${path}.tmp.${String(process.pid)}`;
|
|
@@ -2513,8 +2474,8 @@ function assertMergeValid(before, after) {
|
|
|
2513
2474
|
}
|
|
2514
2475
|
function writeShim() {
|
|
2515
2476
|
const path = shimPath();
|
|
2516
|
-
const dir =
|
|
2517
|
-
if (!
|
|
2477
|
+
const dir = dirname3(path);
|
|
2478
|
+
if (!existsSync5(dir)) {
|
|
2518
2479
|
mkdirSync4(dir, { recursive: true });
|
|
2519
2480
|
}
|
|
2520
2481
|
writeFileSync3(path, SHIM_SCRIPT, "utf-8");
|
|
@@ -2534,7 +2495,7 @@ function readHooksFromRaw(raw) {
|
|
|
2534
2495
|
}
|
|
2535
2496
|
function performInstall3(opts) {
|
|
2536
2497
|
const path = configPath();
|
|
2537
|
-
const raw =
|
|
2498
|
+
const raw = existsSync5(path) ? readFileSync4(path, "utf-8") : "";
|
|
2538
2499
|
const existing = readHooksFromRaw(raw);
|
|
2539
2500
|
const desired = applyInstall3(existing, opts.force);
|
|
2540
2501
|
let next = raw;
|
|
@@ -2560,11 +2521,11 @@ function performInstall3(opts) {
|
|
|
2560
2521
|
}
|
|
2561
2522
|
function performUninstall3() {
|
|
2562
2523
|
const path = configPath();
|
|
2563
|
-
if (!
|
|
2524
|
+
if (!existsSync5(path)) {
|
|
2564
2525
|
removeShim();
|
|
2565
2526
|
return { path, gate: false, capture: false, autoAccept: false, changed: false };
|
|
2566
2527
|
}
|
|
2567
|
-
const raw =
|
|
2528
|
+
const raw = readFileSync4(path, "utf-8");
|
|
2568
2529
|
const existing = readHooksFromRaw(raw);
|
|
2569
2530
|
const remaining = applyUninstall3(existing);
|
|
2570
2531
|
const next = JSON.stringify(existing) === JSON.stringify(remaining) ? raw : spliceHooks(raw, remaining);
|
|
@@ -2584,7 +2545,7 @@ function performUninstall3() {
|
|
|
2584
2545
|
}
|
|
2585
2546
|
function performStatus3() {
|
|
2586
2547
|
const path = configPath();
|
|
2587
|
-
const hooks =
|
|
2548
|
+
const hooks = existsSync5(path) ? readHooks(readDoc(path)) : {};
|
|
2588
2549
|
return { path, gate: isGateInstalled3(hooks), capture: isCaptureInstalled(hooks) };
|
|
2589
2550
|
}
|
|
2590
2551
|
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 +2590,9 @@ function registerHermesCommands(program2) {
|
|
|
2629
2590
|
|
|
2630
2591
|
// src/commands/hooks.ts
|
|
2631
2592
|
import { execFileSync } from "child_process";
|
|
2632
|
-
import { existsSync as
|
|
2593
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
2633
2594
|
import { homedir as homedir5 } from "os";
|
|
2634
|
-
import { dirname as
|
|
2595
|
+
import { dirname as dirname4, join as join5, resolve } from "path";
|
|
2635
2596
|
import { Option } from "commander";
|
|
2636
2597
|
var PRE_COMMIT = { hookName: "pre-commit", binName: "prim-pre-commit" };
|
|
2637
2598
|
var POST_COMMIT = { hookName: "post-commit", binName: "prim-post-commit" };
|
|
@@ -2680,15 +2641,15 @@ ${gatedShim(spec.binName)}
|
|
|
2680
2641
|
${end}`;
|
|
2681
2642
|
}
|
|
2682
2643
|
function mergePrimBlock(hookPath, block, binName) {
|
|
2683
|
-
if (
|
|
2684
|
-
const existing =
|
|
2644
|
+
if (existsSync6(hookPath)) {
|
|
2645
|
+
const existing = readFileSync5(hookPath, "utf-8");
|
|
2685
2646
|
if (containsPrimHook(existing, binName)) return false;
|
|
2686
2647
|
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
2687
2648
|
writeFileSync4(hookPath, `${existing}${separator}${block}
|
|
2688
2649
|
`, { mode: 493 });
|
|
2689
2650
|
return true;
|
|
2690
2651
|
}
|
|
2691
|
-
mkdirSync5(
|
|
2652
|
+
mkdirSync5(dirname4(hookPath), { recursive: true });
|
|
2692
2653
|
writeFileSync4(hookPath, `#!/bin/sh
|
|
2693
2654
|
# ${PRIM_CREATED_MARK}
|
|
2694
2655
|
|
|
@@ -2705,13 +2666,13 @@ function getGitRoot() {
|
|
|
2705
2666
|
}
|
|
2706
2667
|
function detectHusky(gitRoot) {
|
|
2707
2668
|
const huskyDir = resolve(gitRoot, ".husky");
|
|
2708
|
-
if (!
|
|
2709
|
-
if (
|
|
2710
|
-
if (
|
|
2669
|
+
if (!existsSync6(huskyDir)) return false;
|
|
2670
|
+
if (existsSync6(resolve(huskyDir, "_"))) return true;
|
|
2671
|
+
if (existsSync6(resolve(huskyDir, "pre-commit"))) return true;
|
|
2711
2672
|
const pkgPath = resolve(gitRoot, "package.json");
|
|
2712
|
-
if (
|
|
2673
|
+
if (existsSync6(pkgPath)) {
|
|
2713
2674
|
try {
|
|
2714
|
-
const pkg2 = JSON.parse(
|
|
2675
|
+
const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
2715
2676
|
const scripts = pkg2.scripts ?? {};
|
|
2716
2677
|
if (/husky/i.test(scripts.prepare ?? "") || /husky/i.test(scripts.postinstall ?? "")) {
|
|
2717
2678
|
return true;
|
|
@@ -2738,7 +2699,7 @@ async function askConfirmation(question) {
|
|
|
2738
2699
|
}
|
|
2739
2700
|
function installToHusky(gitRoot, spec = PRE_COMMIT) {
|
|
2740
2701
|
const hookPath = resolve(gitRoot, ".husky", spec.hookName);
|
|
2741
|
-
const existed =
|
|
2702
|
+
const existed = existsSync6(hookPath);
|
|
2742
2703
|
const wrote = mergePrimBlock(hookPath, huskyBlock(spec), spec.binName);
|
|
2743
2704
|
if (!wrote) {
|
|
2744
2705
|
console.log(`Prim ${spec.hookName} hook is already installed in .husky/${spec.hookName}.`);
|
|
@@ -2751,11 +2712,11 @@ function installToHusky(gitRoot, spec = PRE_COMMIT) {
|
|
|
2751
2712
|
function installToDotGit(gitRoot, spec = PRE_COMMIT) {
|
|
2752
2713
|
const hooksDir = resolve(gitRoot, ".git", "hooks");
|
|
2753
2714
|
const hookPath = resolve(hooksDir, spec.hookName);
|
|
2754
|
-
if (!
|
|
2715
|
+
if (!existsSync6(hooksDir)) {
|
|
2755
2716
|
mkdirSync5(hooksDir, { recursive: true });
|
|
2756
2717
|
}
|
|
2757
|
-
if (
|
|
2758
|
-
const existing =
|
|
2718
|
+
if (existsSync6(hookPath)) {
|
|
2719
|
+
const existing = readFileSync5(hookPath, "utf-8");
|
|
2759
2720
|
if (containsPrimHook(existing, spec.binName)) {
|
|
2760
2721
|
console.log(`Prim ${spec.hookName} hook is already installed at ${hookPath}.`);
|
|
2761
2722
|
return;
|
|
@@ -2767,9 +2728,9 @@ function installToDotGit(gitRoot, spec = PRE_COMMIT) {
|
|
|
2767
2728
|
writeFileSync4(hookPath, dotGitScript(spec), { mode: 493 });
|
|
2768
2729
|
console.log(`Installed ${spec.hookName} hook at ${hookPath}`);
|
|
2769
2730
|
}
|
|
2770
|
-
var PRIM_GIT_HOOKS_DIR =
|
|
2731
|
+
var PRIM_GIT_HOOKS_DIR = join5(homedir5(), ".config", "prim", "git-hooks");
|
|
2771
2732
|
function expandTilde(p) {
|
|
2772
|
-
return p.startsWith("~/") ?
|
|
2733
|
+
return p.startsWith("~/") ? join5(homedir5(), p.slice(2)) : p;
|
|
2773
2734
|
}
|
|
2774
2735
|
function isOurHooksDir(value) {
|
|
2775
2736
|
return value !== "" && expandTilde(value) === PRIM_GIT_HOOKS_DIR;
|
|
@@ -2834,7 +2795,7 @@ function ownedHookNames() {
|
|
|
2834
2795
|
return [...HOOKS.map((s) => s.hookName), ...PASSTHROUGH_HOOKS];
|
|
2835
2796
|
}
|
|
2836
2797
|
function writeOwnHooks() {
|
|
2837
|
-
if (!
|
|
2798
|
+
if (!existsSync6(PRIM_GIT_HOOKS_DIR)) {
|
|
2838
2799
|
mkdirSync5(PRIM_GIT_HOOKS_DIR, { recursive: true });
|
|
2839
2800
|
}
|
|
2840
2801
|
for (const spec of HOOKS) {
|
|
@@ -2850,8 +2811,8 @@ function appendPrimBlock(hookPath, spec) {
|
|
|
2850
2811
|
mergePrimBlock(hookPath, gatedBlock(spec), spec.binName);
|
|
2851
2812
|
}
|
|
2852
2813
|
function stripPrimBlock(hookPath, spec) {
|
|
2853
|
-
if (!
|
|
2854
|
-
const existing =
|
|
2814
|
+
if (!existsSync6(hookPath)) return;
|
|
2815
|
+
const existing = readFileSync5(hookPath, "utf-8");
|
|
2855
2816
|
const primCreated = existing.includes(PRIM_CREATED_MARK);
|
|
2856
2817
|
const { start, end } = blockMarkers(spec);
|
|
2857
2818
|
const s = existing.indexOf(start);
|
|
@@ -2906,7 +2867,7 @@ function uninstallGlobalHooks() {
|
|
|
2906
2867
|
if (isOurHooksDir(global)) {
|
|
2907
2868
|
for (const name of ownedHookNames()) {
|
|
2908
2869
|
const p = resolve(PRIM_GIT_HOOKS_DIR, name);
|
|
2909
|
-
if (
|
|
2870
|
+
if (existsSync6(p)) unlinkSync2(p);
|
|
2910
2871
|
}
|
|
2911
2872
|
execFileSync("git", ["config", "--global", "--unset", "core.hooksPath"]);
|
|
2912
2873
|
console.log("Removed prim global git hooks and unset core.hooksPath.");
|
|
@@ -2996,11 +2957,11 @@ function registerHooksCommands(program2) {
|
|
|
2996
2957
|
const gitRoot = getGitRoot();
|
|
2997
2958
|
for (const spec of HOOKS) {
|
|
2998
2959
|
const hookPath = resolve(gitRoot, ".git", "hooks", spec.hookName);
|
|
2999
|
-
if (!
|
|
2960
|
+
if (!existsSync6(hookPath)) {
|
|
3000
2961
|
console.log(`No ${spec.hookName} hook found.`);
|
|
3001
2962
|
continue;
|
|
3002
2963
|
}
|
|
3003
|
-
if (containsPrimHook(
|
|
2964
|
+
if (containsPrimHook(readFileSync5(hookPath, "utf-8"), spec.binName)) {
|
|
3004
2965
|
unlinkSync2(hookPath);
|
|
3005
2966
|
console.log(`Removed ${spec.hookName} hook at ${hookPath}`);
|
|
3006
2967
|
} else {
|
|
@@ -3011,8 +2972,8 @@ function registerHooksCommands(program2) {
|
|
|
3011
2972
|
}
|
|
3012
2973
|
|
|
3013
2974
|
// src/commands/moves.ts
|
|
3014
|
-
import { existsSync as
|
|
3015
|
-
import { join as
|
|
2975
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
2976
|
+
import { join as join6 } from "path";
|
|
3016
2977
|
|
|
3017
2978
|
// src/flusher.ts
|
|
3018
2979
|
import { renameSync as renameSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
@@ -3171,19 +3132,19 @@ function registerMovesCommands(program2) {
|
|
|
3171
3132
|
}
|
|
3172
3133
|
});
|
|
3173
3134
|
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 (!
|
|
3135
|
+
const dir = join6(process.cwd(), ".prim");
|
|
3136
|
+
if (!existsSync7(dir)) {
|
|
3176
3137
|
mkdirSync6(dir, { recursive: true, mode: DIR_MODE });
|
|
3177
3138
|
}
|
|
3178
|
-
const file =
|
|
3139
|
+
const file = join6(process.cwd(), WORKSPACE_FILE);
|
|
3179
3140
|
writeFileSync5(file, JSON.stringify({ orgId: opts.orgId, boundAt: Date.now() }, null, 2), {
|
|
3180
3141
|
mode: FILE_MODE2
|
|
3181
3142
|
});
|
|
3182
3143
|
console.log(`[prim] bound ${process.cwd()} to org ${opts.orgId}`);
|
|
3183
3144
|
});
|
|
3184
3145
|
moves.command("drop").description("Remove the .prim/workspace.json binding from the cwd").action(() => {
|
|
3185
|
-
const file =
|
|
3186
|
-
if (!
|
|
3146
|
+
const file = join6(process.cwd(), WORKSPACE_FILE);
|
|
3147
|
+
if (!existsSync7(file)) {
|
|
3187
3148
|
console.log("[prim] no workspace binding in cwd");
|
|
3188
3149
|
return;
|
|
3189
3150
|
}
|
|
@@ -3276,23 +3237,23 @@ function registerReconcileCommands(program2) {
|
|
|
3276
3237
|
|
|
3277
3238
|
// src/commands/session.ts
|
|
3278
3239
|
import {
|
|
3279
|
-
existsSync as
|
|
3240
|
+
existsSync as existsSync8,
|
|
3280
3241
|
mkdirSync as mkdirSync7,
|
|
3281
|
-
readFileSync as
|
|
3242
|
+
readFileSync as readFileSync6,
|
|
3282
3243
|
readdirSync,
|
|
3283
3244
|
unlinkSync as unlinkSync5,
|
|
3284
3245
|
writeFileSync as writeFileSync6
|
|
3285
3246
|
} from "fs";
|
|
3286
|
-
import { join as
|
|
3247
|
+
import { join as join7 } from "path";
|
|
3287
3248
|
var DIR_MODE2 = 448;
|
|
3288
3249
|
var FILE_MODE3 = 384;
|
|
3289
3250
|
function ensureDir() {
|
|
3290
|
-
if (!
|
|
3251
|
+
if (!existsSync8(SESSIONS_DIR)) {
|
|
3291
3252
|
mkdirSync7(SESSIONS_DIR, { recursive: true, mode: DIR_MODE2 });
|
|
3292
3253
|
}
|
|
3293
3254
|
}
|
|
3294
3255
|
function markerPath(sessionId) {
|
|
3295
|
-
return
|
|
3256
|
+
return join7(SESSIONS_DIR, `${sessionId}.json`);
|
|
3296
3257
|
}
|
|
3297
3258
|
function registerSessionCommands(program2) {
|
|
3298
3259
|
const session = program2.command("session").description("Decision Event Pipeline \u2014 session binding markers");
|
|
@@ -3308,7 +3269,7 @@ function registerSessionCommands(program2) {
|
|
|
3308
3269
|
console.log(`[prim] session ${sessionId} bound to org ${opts.orgId}`);
|
|
3309
3270
|
});
|
|
3310
3271
|
session.command("list").description("List active session markers").action(() => {
|
|
3311
|
-
if (!
|
|
3272
|
+
if (!existsSync8(SESSIONS_DIR)) {
|
|
3312
3273
|
console.log("[prim] no session markers");
|
|
3313
3274
|
return;
|
|
3314
3275
|
}
|
|
@@ -3320,7 +3281,7 @@ function registerSessionCommands(program2) {
|
|
|
3320
3281
|
for (const f of files) {
|
|
3321
3282
|
const sessionId = f.replace(/\.json$/, "");
|
|
3322
3283
|
try {
|
|
3323
|
-
const m = JSON.parse(
|
|
3284
|
+
const m = JSON.parse(readFileSync6(join7(SESSIONS_DIR, f), "utf-8"));
|
|
3324
3285
|
console.log(`${sessionId} org=${m.orgId}`);
|
|
3325
3286
|
} catch {
|
|
3326
3287
|
}
|
|
@@ -3328,7 +3289,7 @@ function registerSessionCommands(program2) {
|
|
|
3328
3289
|
});
|
|
3329
3290
|
session.command("drop <sessionId>").description("Remove a session marker").action((sessionId) => {
|
|
3330
3291
|
const p = markerPath(sessionId);
|
|
3331
|
-
if (!
|
|
3292
|
+
if (!existsSync8(p)) {
|
|
3332
3293
|
console.log(`[prim] no marker for session ${sessionId}`);
|
|
3333
3294
|
return;
|
|
3334
3295
|
}
|
|
@@ -3339,8 +3300,8 @@ function registerSessionCommands(program2) {
|
|
|
3339
3300
|
|
|
3340
3301
|
// src/commands/setup.ts
|
|
3341
3302
|
import { spawnSync } from "child_process";
|
|
3342
|
-
import { existsSync as
|
|
3343
|
-
import { join as
|
|
3303
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
|
|
3304
|
+
import { join as join8 } from "path";
|
|
3344
3305
|
var EXIT_INCOMPLETE = 1;
|
|
3345
3306
|
var EXIT_USAGE3 = 2;
|
|
3346
3307
|
var SESSION_LABELS = {
|
|
@@ -3403,8 +3364,8 @@ function detectProjectConflicts(agent, run) {
|
|
|
3403
3364
|
}
|
|
3404
3365
|
try {
|
|
3405
3366
|
const root = gitToplevel();
|
|
3406
|
-
const preCommit = root &&
|
|
3407
|
-
if (preCommit &&
|
|
3367
|
+
const preCommit = root && join8(root, ".git", "hooks", "pre-commit");
|
|
3368
|
+
if (preCommit && existsSync9(preCommit) && readFileSync7(preCommit, "utf-8").includes("prim-pre-commit")) {
|
|
3408
3369
|
conflicts.push(CONFLICT_HOOKS);
|
|
3409
3370
|
}
|
|
3410
3371
|
} catch {
|
|
@@ -3522,39 +3483,39 @@ function registerSetupCommand(program2) {
|
|
|
3522
3483
|
// src/commands/skill.ts
|
|
3523
3484
|
import {
|
|
3524
3485
|
closeSync as closeSync4,
|
|
3525
|
-
existsSync as
|
|
3486
|
+
existsSync as existsSync11,
|
|
3526
3487
|
fsyncSync as fsyncSync3,
|
|
3527
3488
|
openSync as openSync4,
|
|
3528
|
-
readFileSync as
|
|
3489
|
+
readFileSync as readFileSync9,
|
|
3529
3490
|
renameSync as renameSync4,
|
|
3530
3491
|
writeFileSync as writeFileSync7
|
|
3531
3492
|
} from "fs";
|
|
3532
3493
|
import { homedir as homedir7 } from "os";
|
|
3533
|
-
import { dirname as
|
|
3534
|
-
import { fileURLToPath as
|
|
3494
|
+
import { dirname as dirname6, join as join10, resolve as resolve3 } from "path";
|
|
3495
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3535
3496
|
import { createPatch } from "diff";
|
|
3536
3497
|
|
|
3537
3498
|
// src/commands/claude-plugin.ts
|
|
3538
|
-
import { existsSync as
|
|
3499
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync8, readdirSync as readdirSync2, rmSync as rmSync3, rmdirSync } from "fs";
|
|
3539
3500
|
import { homedir as homedir6 } from "os";
|
|
3540
|
-
import { dirname as
|
|
3541
|
-
import { fileURLToPath
|
|
3542
|
-
var __dirname =
|
|
3501
|
+
import { dirname as dirname5, join as join9, resolve as resolve2 } from "path";
|
|
3502
|
+
import { fileURLToPath } from "url";
|
|
3503
|
+
var __dirname = dirname5(fileURLToPath(import.meta.url));
|
|
3543
3504
|
var PLUGIN_DESCRIPTION = "Primitive decision-graph guidance for the prim CLI \u2014 a model-invoked skill installed by prim skill install.";
|
|
3544
3505
|
function resolvePluginDir(cwd, scope) {
|
|
3545
3506
|
if (scope && scope !== "user" && scope !== "project") {
|
|
3546
3507
|
console.error(`Unknown --scope "${scope}" (expected user or project)`);
|
|
3547
3508
|
return null;
|
|
3548
3509
|
}
|
|
3549
|
-
const base = scope === "user" ?
|
|
3550
|
-
return
|
|
3510
|
+
const base = scope === "user" ? join9(homedir6(), ".claude") : join9(gitToplevel(cwd) ?? cwd, ".claude");
|
|
3511
|
+
return join9(base, "skills", "prim");
|
|
3551
3512
|
}
|
|
3552
3513
|
function packageVersion() {
|
|
3553
3514
|
let dir = __dirname;
|
|
3554
|
-
while (dir !==
|
|
3515
|
+
while (dir !== dirname5(dir)) {
|
|
3555
3516
|
const p = resolve2(dir, "package.json");
|
|
3556
|
-
if (
|
|
3557
|
-
dir =
|
|
3517
|
+
if (existsSync10(p)) return JSON.parse(readFileSync8(p, "utf-8")).version;
|
|
3518
|
+
dir = dirname5(dir);
|
|
3558
3519
|
}
|
|
3559
3520
|
return "0.0.0";
|
|
3560
3521
|
}
|
|
@@ -3564,12 +3525,12 @@ function pluginManifest() {
|
|
|
3564
3525
|
`;
|
|
3565
3526
|
}
|
|
3566
3527
|
function removeDirIfEmpty(dir) {
|
|
3567
|
-
if (
|
|
3528
|
+
if (existsSync10(dir) && readdirSync2(dir).length === 0) rmdirSync(dir);
|
|
3568
3529
|
}
|
|
3569
3530
|
function pluginPaths(dir) {
|
|
3570
3531
|
return {
|
|
3571
|
-
manifestPath:
|
|
3572
|
-
skillPath:
|
|
3532
|
+
manifestPath: join9(dir, ".claude-plugin", "plugin.json"),
|
|
3533
|
+
skillPath: join9(dir, "SKILL.md")
|
|
3573
3534
|
};
|
|
3574
3535
|
}
|
|
3575
3536
|
function installClaudePlugin(cwd, opts) {
|
|
@@ -3578,8 +3539,8 @@ function installClaudePlugin(cwd, opts) {
|
|
|
3578
3539
|
const { manifestPath, skillPath } = pluginPaths(dir);
|
|
3579
3540
|
const manifest = pluginManifest();
|
|
3580
3541
|
const skill = loadSkill();
|
|
3581
|
-
const manifestCurrent =
|
|
3582
|
-
const skillCurrent =
|
|
3542
|
+
const manifestCurrent = existsSync10(manifestPath) ? readFileSync8(manifestPath, "utf-8") : null;
|
|
3543
|
+
const skillCurrent = existsSync10(skillPath) ? readFileSync8(skillPath, "utf-8") : null;
|
|
3583
3544
|
if (manifestCurrent === manifest && skillCurrent === skill) {
|
|
3584
3545
|
console.log("No changes \u2014 prim skill plugin already up to date.");
|
|
3585
3546
|
return 0;
|
|
@@ -3588,7 +3549,7 @@ function installClaudePlugin(cwd, opts) {
|
|
|
3588
3549
|
console.log(`Would write plugin to ${dir} (.claude-plugin/plugin.json + SKILL.md)`);
|
|
3589
3550
|
return 0;
|
|
3590
3551
|
}
|
|
3591
|
-
mkdirSync8(
|
|
3552
|
+
mkdirSync8(join9(dir, ".claude-plugin"), { recursive: true });
|
|
3592
3553
|
atomicWrite2(manifestPath, manifest);
|
|
3593
3554
|
atomicWrite2(skillPath, skill);
|
|
3594
3555
|
console.log(`Installed prim skill plugin at ${dir}`);
|
|
@@ -3602,13 +3563,13 @@ function uninstallClaudePlugin(cwd, opts) {
|
|
|
3602
3563
|
const dir = resolvePluginDir(cwd, opts.scope);
|
|
3603
3564
|
if (dir === null) return 1;
|
|
3604
3565
|
const { manifestPath, skillPath } = pluginPaths(dir);
|
|
3605
|
-
if (!
|
|
3566
|
+
if (!existsSync10(manifestPath) && !existsSync10(skillPath)) {
|
|
3606
3567
|
console.log(`prim skill plugin not present at ${dir}`);
|
|
3607
3568
|
return 0;
|
|
3608
3569
|
}
|
|
3609
3570
|
rmSync3(manifestPath, { force: true });
|
|
3610
3571
|
rmSync3(skillPath, { force: true });
|
|
3611
|
-
removeDirIfEmpty(
|
|
3572
|
+
removeDirIfEmpty(join9(dir, ".claude-plugin"));
|
|
3612
3573
|
removeDirIfEmpty(dir);
|
|
3613
3574
|
console.log(`Removed prim skill plugin from ${dir}`);
|
|
3614
3575
|
return 0;
|
|
@@ -3617,7 +3578,7 @@ function statusClaudePlugin(cwd, opts) {
|
|
|
3617
3578
|
const dir = resolvePluginDir(cwd, opts.scope);
|
|
3618
3579
|
if (dir === null) return 1;
|
|
3619
3580
|
const { skillPath } = pluginPaths(dir);
|
|
3620
|
-
const installed =
|
|
3581
|
+
const installed = existsSync10(skillPath);
|
|
3621
3582
|
if (opts.json) {
|
|
3622
3583
|
printJson({ installed, target: dir });
|
|
3623
3584
|
return installed ? 0 : 1;
|
|
@@ -3631,7 +3592,7 @@ function statusClaudePlugin(cwd, opts) {
|
|
|
3631
3592
|
}
|
|
3632
3593
|
|
|
3633
3594
|
// src/commands/skill.ts
|
|
3634
|
-
var __dirname2 =
|
|
3595
|
+
var __dirname2 = dirname6(fileURLToPath2(import.meta.url));
|
|
3635
3596
|
var SKILL_BEGIN = "<!-- BEGIN PRIM SKILL v1 -->";
|
|
3636
3597
|
var SKILL_END = "<!-- END PRIM SKILL v1 -->";
|
|
3637
3598
|
var TARGET_CANDIDATES = [
|
|
@@ -3649,24 +3610,24 @@ var AGENT_TARGET = {
|
|
|
3649
3610
|
hermes: ".hermes.md"
|
|
3650
3611
|
};
|
|
3651
3612
|
function userTargetFor(agent) {
|
|
3652
|
-
if (agent === "claude") return
|
|
3653
|
-
if (agent === "codex") return
|
|
3613
|
+
if (agent === "claude") return join10(homedir7(), ".claude", "CLAUDE.md");
|
|
3614
|
+
if (agent === "codex") return join10(homedir7(), ".codex", "AGENTS.md");
|
|
3654
3615
|
if (agent === "hermes") {
|
|
3655
|
-
return
|
|
3616
|
+
return join10(process.env.HERMES_HOME ?? join10(homedir7(), ".hermes"), ".hermes.md");
|
|
3656
3617
|
}
|
|
3657
3618
|
return null;
|
|
3658
3619
|
}
|
|
3659
3620
|
function loadSkill() {
|
|
3660
3621
|
let dir = __dirname2;
|
|
3661
|
-
while (dir !==
|
|
3622
|
+
while (dir !== dirname6(dir)) {
|
|
3662
3623
|
const p = resolve3(dir, "SKILL.md");
|
|
3663
|
-
if (
|
|
3664
|
-
dir =
|
|
3624
|
+
if (existsSync11(p)) return readFileSync9(p, "utf-8");
|
|
3625
|
+
dir = dirname6(dir);
|
|
3665
3626
|
}
|
|
3666
3627
|
throw new Error("SKILL.md not found in package");
|
|
3667
3628
|
}
|
|
3668
3629
|
function detectTargets(cwd) {
|
|
3669
|
-
return TARGET_CANDIDATES.filter((p) =>
|
|
3630
|
+
return TARGET_CANDIDATES.filter((p) => existsSync11(resolve3(cwd, p)));
|
|
3670
3631
|
}
|
|
3671
3632
|
function detectNewline(content) {
|
|
3672
3633
|
return content.includes("\r\n") ? "\r\n" : "\n";
|
|
@@ -3736,7 +3697,7 @@ function runInstall(cwd, opts) {
|
|
|
3736
3697
|
if (opts.agent === "claude" && !opts.target) return installClaudePlugin(cwd, opts);
|
|
3737
3698
|
const target = resolveTarget(cwd, opts);
|
|
3738
3699
|
if (target === null) return 1;
|
|
3739
|
-
const existing =
|
|
3700
|
+
const existing = existsSync11(target) ? readFileSync9(target, "utf-8") : "";
|
|
3740
3701
|
const eol = existing ? detectNewline(existing) : "\n";
|
|
3741
3702
|
const block = composeBlock(loadSkill(), eol);
|
|
3742
3703
|
const next = applyBlock(existing, block, eol);
|
|
@@ -3756,11 +3717,11 @@ function runUninstall(cwd, opts) {
|
|
|
3756
3717
|
if (opts.agent === "claude" && !opts.target) return uninstallClaudePlugin(cwd, opts);
|
|
3757
3718
|
const target = resolveTarget(cwd, opts);
|
|
3758
3719
|
if (target === null) return 1;
|
|
3759
|
-
if (!
|
|
3720
|
+
if (!existsSync11(target)) {
|
|
3760
3721
|
console.log(`Skill block not present at ${target}`);
|
|
3761
3722
|
return 0;
|
|
3762
3723
|
}
|
|
3763
|
-
const existing =
|
|
3724
|
+
const existing = readFileSync9(target, "utf-8");
|
|
3764
3725
|
const next = removeBlock(existing);
|
|
3765
3726
|
if (next === null) {
|
|
3766
3727
|
console.log(`Skill block not present at ${target}`);
|
|
@@ -3774,10 +3735,10 @@ function runStatus(cwd, opts) {
|
|
|
3774
3735
|
if (opts.agent === "claude" && !opts.target) return statusClaudePlugin(cwd, opts);
|
|
3775
3736
|
const target = resolveTarget(cwd, opts);
|
|
3776
3737
|
if (target === null) return 1;
|
|
3777
|
-
const fileExists =
|
|
3738
|
+
const fileExists = existsSync11(target);
|
|
3778
3739
|
let installed = false;
|
|
3779
3740
|
if (fileExists) {
|
|
3780
|
-
const content =
|
|
3741
|
+
const content = readFileSync9(target, "utf-8");
|
|
3781
3742
|
installed = content.includes(SKILL_BEGIN) && content.includes(SKILL_END);
|
|
3782
3743
|
}
|
|
3783
3744
|
if (opts.json) {
|
|
@@ -3814,18 +3775,18 @@ function registerSkillCommands(program2) {
|
|
|
3814
3775
|
}
|
|
3815
3776
|
|
|
3816
3777
|
// src/commands/statusline.ts
|
|
3817
|
-
import { readFileSync as
|
|
3818
|
-
import { dirname as
|
|
3819
|
-
import { fileURLToPath as
|
|
3778
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
3779
|
+
import { dirname as dirname7, resolve as resolve4 } from "path";
|
|
3780
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3820
3781
|
var STATUSLINE_TIMEOUT_MS = 200;
|
|
3821
3782
|
var STATUSLINE_NAME_CAP = 3;
|
|
3822
3783
|
function readPackageVersion() {
|
|
3823
3784
|
try {
|
|
3824
|
-
const here =
|
|
3785
|
+
const here = dirname7(fileURLToPath3(import.meta.url));
|
|
3825
3786
|
const candidates = [resolve4(here, "../../package.json"), resolve4(here, "../package.json")];
|
|
3826
3787
|
for (const path of candidates) {
|
|
3827
3788
|
try {
|
|
3828
|
-
const pkg2 = JSON.parse(
|
|
3789
|
+
const pkg2 = JSON.parse(readFileSync10(path, "utf-8"));
|
|
3829
3790
|
if (pkg2.version) {
|
|
3830
3791
|
return pkg2.version;
|
|
3831
3792
|
}
|
|
@@ -3862,7 +3823,9 @@ async function renderStatusline() {
|
|
|
3862
3823
|
return `primitive ${version} (daemon: live \xB7 presence: stale)`;
|
|
3863
3824
|
}
|
|
3864
3825
|
let team;
|
|
3865
|
-
if (snapshot.
|
|
3826
|
+
if (snapshot.onlineTeammates !== void 0) {
|
|
3827
|
+
team = `team: ${formatTeammatesWithArea(snapshot.onlineTeammates, STATUSLINE_NAME_CAP)}`;
|
|
3828
|
+
} else if (snapshot.onlineNames !== void 0) {
|
|
3866
3829
|
team = `team: ${formatTeammates(snapshot.onlineNames, STATUSLINE_NAME_CAP)}`;
|
|
3867
3830
|
} else if (typeof snapshot.onlineCount === "number") {
|
|
3868
3831
|
team = `team: ${String(snapshot.onlineCount)} online`;
|
|
@@ -3874,6 +3837,7 @@ async function renderStatusline() {
|
|
|
3874
3837
|
function registerStatuslineCommands(program2) {
|
|
3875
3838
|
program2.command("statusline").description("Render the Claude Code statusLine for the prim companion daemon").action(async () => {
|
|
3876
3839
|
try {
|
|
3840
|
+
warmBinCache();
|
|
3877
3841
|
const line = await renderStatusline();
|
|
3878
3842
|
process.stdout.write(line);
|
|
3879
3843
|
} catch {
|
|
@@ -3993,8 +3957,8 @@ function registerWelcomeCommand(program2, deps = { getClient }) {
|
|
|
3993
3957
|
}
|
|
3994
3958
|
|
|
3995
3959
|
// src/index.ts
|
|
3996
|
-
var __dirname3 =
|
|
3997
|
-
var pkg = JSON.parse(
|
|
3960
|
+
var __dirname3 = dirname8(fileURLToPath4(import.meta.url));
|
|
3961
|
+
var pkg = JSON.parse(readFileSync11(resolve5(__dirname3, "../package.json"), "utf-8"));
|
|
3998
3962
|
updateNotifier({ pkg }).notify();
|
|
3999
3963
|
var program = new Command();
|
|
4000
3964
|
program.name("prim").description("CLI for Primitive's decision graph").version(pkg.version).option("-y, --yes", "auto-confirm prompts").option(
|
package/package.json
CHANGED