@primitive.ai/prim 0.1.0-alpha.33 → 0.1.0-alpha.35
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/README.md +15 -6
- package/SKILL.md +2 -1
- package/dist/{chunk-4QJOQIY6.js → chunk-AAGJFO7C.js} +11 -0
- package/dist/chunk-LUPD2JSH.js +78 -0
- package/dist/hooks/post-tool-use.js +8 -1
- package/dist/hooks/pre-tool-use.js +7 -0
- package/dist/hooks/prim-hook.js +11 -6
- package/dist/index.js +834 -163
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
color,
|
|
5
5
|
colorForArea,
|
|
6
6
|
dim,
|
|
7
|
-
stripAnsi
|
|
8
|
-
|
|
7
|
+
stripAnsi,
|
|
8
|
+
stripControlChars
|
|
9
|
+
} from "./chunk-AAGJFO7C.js";
|
|
9
10
|
import {
|
|
10
11
|
checkAffectedDecisions,
|
|
11
12
|
daemonOrDirectGet,
|
|
@@ -19,6 +20,11 @@ import {
|
|
|
19
20
|
listFlushing,
|
|
20
21
|
readMovesFromPath
|
|
21
22
|
} from "./chunk-H4OR42TJ.js";
|
|
23
|
+
import {
|
|
24
|
+
activateRepoBestEffort,
|
|
25
|
+
gitToplevel,
|
|
26
|
+
setRepoActive
|
|
27
|
+
} from "./chunk-LUPD2JSH.js";
|
|
22
28
|
import {
|
|
23
29
|
HttpError,
|
|
24
30
|
REFRESH_TOKEN_PATH,
|
|
@@ -36,12 +42,44 @@ import {
|
|
|
36
42
|
} from "./chunk-UTKQTZHL.js";
|
|
37
43
|
|
|
38
44
|
// src/index.ts
|
|
39
|
-
import { readFileSync as
|
|
40
|
-
import { dirname as
|
|
41
|
-
import { fileURLToPath as
|
|
45
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
46
|
+
import { dirname as dirname9, resolve as resolve5 } from "path";
|
|
47
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
42
48
|
import { Command } from "commander";
|
|
43
49
|
import updateNotifier from "update-notifier";
|
|
44
50
|
|
|
51
|
+
// src/output.ts
|
|
52
|
+
function printJson(data) {
|
|
53
|
+
console.log(JSON.stringify(data, null, 2));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/commands/activation.ts
|
|
57
|
+
function applyActivation(active) {
|
|
58
|
+
const root = gitToplevel();
|
|
59
|
+
if (!root) {
|
|
60
|
+
process.stderr.write(
|
|
61
|
+
`[prim] not a git repository \u2014 run \`prim ${active ? "enable" : "disable"}\` inside a repo
|
|
62
|
+
`
|
|
63
|
+
);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
setRepoActive(process.cwd(), active);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
70
|
+
process.stderr.write(`[prim] failed to set prim.active: ${detail}
|
|
71
|
+
`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
process.stderr.write(`[prim] prim ${active ? "enabled" : "disabled"} in ${root}
|
|
75
|
+
`);
|
|
76
|
+
printJson({ active, repo: root });
|
|
77
|
+
}
|
|
78
|
+
function registerActivationCommands(program2) {
|
|
79
|
+
program2.command("enable").description("Activate prim's hooks in this repo (git config prim.active=true)").action(() => applyActivation(true));
|
|
80
|
+
program2.command("disable").description("Mute prim's hooks in this repo (git config prim.active=false)").action(() => applyActivation(false));
|
|
81
|
+
}
|
|
82
|
+
|
|
45
83
|
// src/commands/auth.ts
|
|
46
84
|
import { exec } from "child_process";
|
|
47
85
|
import { createHash, randomBytes } from "crypto";
|
|
@@ -50,16 +88,160 @@ import { createServer } from "http";
|
|
|
50
88
|
import { platform } from "os";
|
|
51
89
|
import { dirname } from "path";
|
|
52
90
|
|
|
53
|
-
// src/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
91
|
+
// src/commands/auth-pages.ts
|
|
92
|
+
var FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="99.77 99.96 400.85 400.85" fill="currentColor">
|
|
93
|
+
<g>
|
|
94
|
+
<g>
|
|
95
|
+
<path d="M493.8,355.14c-9.16-6.69-36.52-22.75-55.41-31.94-20.92-10.19-59.12-26.94-71.36-30-1.09-.27-2.16-.41-3.19-.41-2.33,0-4.34.71-5.67,2-2.55,2.46-2.42,5.25-1.93,9.22.71,5.74,23.04,66.51,30.38,86.48l3.48,9.51c7.62,20.93,23.48,64.49,32.01,80.15,7.79,14.48,17.3,20.66,31.81,20.66h12.18c24.02,0,33.82-11.81,33.82-40.77l.26-87.05c0-11.21-2.06-14.69-6.38-17.85Z"/>
|
|
96
|
+
<path d="M405.18,113.07c-9.78,17.98-18.92,39.17-23.5,50.2-9.64,23.22-26.04,65.91-27.27,72.7l-.25,1.3c-.73,3.75-1.42,7.29.94,9.14,2.53,1.98,6.73.56,10.1-.58l1.08-.36c21.93-7.21,78.12-37.9,88.28-43.64l5.44-3.07c10.7-6.03,18.4-10.37,24.49-14.61,4.64-3.22,15.42-12.52,15.42-29.16l.21-22.17c.06-9.4-2.84-17.4-8.38-23.14-6.1-6.31-14.99-9.51-26.42-9.51l-40.49.02c-9.2,0-16.92,7.9-19.63,12.87Z"/>
|
|
97
|
+
</g>
|
|
98
|
+
<g>
|
|
99
|
+
<g>
|
|
100
|
+
<path d="M387.33,496.91c2.46-3.03,3.5-7.67,2.72-12.11-6.97-39.55-19.41-89.79-32.47-131.1-1.06-3.34-2.05-6.66-3.04-9.96l-.06-.2c-3.23-10.76-6.28-21.4-10.88-31.03l-.49-1.04c-1.48-3.17-3.01-6.45-7.55-6.7-.11,0-.23,0-.34,0-3.77,0-6.44,3.79-8.56,7.49-7.52,13.16-14.35,28.66-20.96,43.2h0s-2.58,5.69-2.58,5.69c-8.72,19.11-18.88,41.95-28.04,66.45l-9.41,25.16c-3.78,10.1-7.23,20.71-10.55,32.42-1.12,3.96-.67,7.72,1.29,10.57,1.63,2.36,4.86,4.91,8.13,4.91l115.07-.11c3.14,0,5.82-1.25,7.74-3.62Z"/>
|
|
101
|
+
</g>
|
|
102
|
+
<path d="M361.69,100.04h-86.95c-3.21-.01-5.95,1.24-7.71,3.52-1.81,2.34-2.41,5.61-1.65,8.96,1.6,7.02,15.48,35.97,21.37,48.1,7.81,16.07,27.58,54.75,38.81,71.76.32.48.65,1.04.99,1.62,1.73,2.95,3.48,5.91,7.12,5.56,3.84-.36,5.3-3.97,6.48-6.86.21-.51.4-.98.58-1.38,1.86-4.1,13.67-40.82,20.64-65.31,7.2-25.27,12.18-47.95,12.84-53.03.52-4.03-.14-7.16-1.97-9.31-2.05-2.4-5.59-3.62-10.55-3.62Z"/>
|
|
103
|
+
</g>
|
|
104
|
+
</g>
|
|
105
|
+
<g>
|
|
106
|
+
<path d="M298.79,268.76c-7.3-3.09-39.86-11.68-96.8-25.51-24.41-5.93-84.07-17.79-88.54-18.56-.63-.11-1.27-.16-1.9-.16-2.95,0-5.8,1.21-7.83,3.31-2.13,2.21-3.12,5.23-3.12,9.49v95.29c0,4.95.65,8.35,4.01,11.09,3.05,2.5,7.02,3.29,10.9,2.19,21.12-6.05,65.7-19.92,94.48-30.22,25.85-9.25,71.81-28.09,81.04-32.3,7.19-3.28,12.23-5.92,11.69-10.09-.32-2.51-2.32-3.85-3.94-4.54Z"/>
|
|
107
|
+
<path d="M447.09,228.98l-.66.27c-2.97,1.21-5.79,2.35-8.32,3.37-18.39,7.41-49.84,22.26-56.6,25.74l-1.74.89c-6.83,3.48-12.73,6.49-12.73,11.06,0,4.83,8.3,8.33,16.8,11.51.43.16,53.65,19.36,84.63,28.75l1.71.52c6.12,1.86,11.9,3.62,18.39,4.83,3.07.58,6.32-.29,8.68-2.31,2.18-1.87,3.37-4.51,3.37-7.44l-.03-84.37c0-2.55-1.04-4.89-2.92-6.58-1.78-1.6-4.21-2.52-6.66-2.52-.44,0-.88.03-1.32.09-4.06.56-27.25,9.98-42.59,16.21Z"/>
|
|
108
|
+
</g>
|
|
109
|
+
<g>
|
|
110
|
+
<path d="M199.41,487.9c11.63-15.23,42.48-59.57,55.71-81.4,25.25-41.68,55.4-93.54,58.65-100.9,2-4.51,1.9-6.61-.39-8.62-1.14-1.34-2.56-1.94-4.43-1.94-3.14,0-7.09,1.65-10.91,3.25l-.26.11c-.69.29-1.38.58-2.06.85-39.47,15.95-105.3,47.48-143.76,68.85l-4.64,2.57c-12.98,7.17-23.24,12.83-31.79,21.13-9.55,9.27-15.07,19.13-15.07,42.06l.1,27.6c.02,6.98.68,12.88,3.87,18.97,6.64,12.68,18.51,20.25,31.76,20.25l35.12-.14c15.31,0,22.31-5.06,28.12-12.66Z"/>
|
|
111
|
+
<g>
|
|
112
|
+
<path d="M100.86,166.89c0,11.82,6.85,17.37,8.95,18.79,5.81,3.92,37.07,18.76,68.66,30.25,28.43,10.35,107.26,35.67,125.35,36.95,2.87.22,5.62-.95,7.19-3.02,1.62-2.14,2.06-5.3,1.12-8.07-2.26-6.66-26.14-42.52-48.34-75.61-13.95-20.79-36.51-52.08-40.14-56.03l-.59-.64c-3.62-3.97-8.57-9.4-18.67-9.4l-72.02-.15c-17.35,0-30.26,13-31.39,31.62-.58,9.4-.44,15.56-.29,22.68l-.92.05.92-.02c.08,3.76.16,7.65.16,12.59Z"/>
|
|
113
|
+
</g>
|
|
114
|
+
</g>
|
|
115
|
+
</svg>`;
|
|
116
|
+
var WORDMARK_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="115 160 770 175" fill="currentColor">
|
|
117
|
+
<g>
|
|
118
|
+
<path d="M368.76,202.52h-35.98v96.31h12.86v-37.53h23.11c18.6,0,31.08-12.16,31.08-30.28,0-14.19-9.62-28.51-31.08-28.51ZM369.29,249.41h-23.66v-34.95h23.66c10.57,0,17.41,6.52,17.41,16.59,0,11.15-6.82,18.36-17.41,18.36Z"/>
|
|
119
|
+
<path d="M421.81,237.49v.02l-1.19-11.96h-10.37v73.28h12.74v-38.41c0-14.42,8.48-22.69,23.23-22.69h3.18v-12.6h-3.18c-11.23,0-19.45,4.17-24.42,12.36Z"/>
|
|
120
|
+
<rect x="463.25" y="225.55" width="12.74" height="73.28"/>
|
|
121
|
+
<path d="M576.17,223.94h-.02c-10.47,0-20.27,5.19-25.39,13.35-4.13-8.4-12.52-13.35-22.71-13.35-9.66,0-18.66,4.31-23.98,11.37l-.95-9.74h-10.37v73.28h12.74v-44.35c.14-11.48,8.44-19.19,20.66-19.19,9.14,0,14.84,5.8,14.84,15.1v48.44h12.6v-44.35c.14-11.48,8.44-19.19,20.66-19.19,9.14,0,14.84,5.8,14.84,15.1v48.44h12.74v-48.84c0-15.36-10.55-26.07-25.67-26.07Z"/>
|
|
122
|
+
<rect x="617.38" y="225.55" width="12.74" height="73.28"/>
|
|
123
|
+
<path d="M669.43,204.95h-12.72v20.62h-13.29v10.97h13.29v41.11c0,13.67,7.87,21.2,22.15,21.2h8.74v-10.97h-8.74c-6.24,0-9.42-3.44-9.42-10.23v-41.11h18.16v-10.97h-18.16v-20.62Z"/>
|
|
124
|
+
<rect x="702.48" y="225.55" width="12.74" height="73.28"/>
|
|
125
|
+
<polygon points="762.68 282.97 739.67 226.03 739.47 225.55 725.66 225.55 756.22 298.85 769 298.85 799.56 225.55 785.75 225.55 762.68 282.97"/>
|
|
126
|
+
<path d="M868.79,260.98l-.02-.02c-.4-23.19-12.99-37.04-33.66-37.04-23.88,0-34.75,19.97-34.75,38.53,0,22.29,14.84,37.87,36.1,37.87,16.39,0,28.67-9.46,32.03-24.7l.2-.91h-12.44l-.16.52c-2.84,8.92-9.8,13.85-19.61,13.85-13.49,0-22.39-9.26-23.33-24.24h55.65v-3.86ZM813.91,254c2.42-11.82,10.29-18.82,21.18-18.82s18.02,6.85,20.25,18.82h-41.43Z"/>
|
|
127
|
+
<path d="M469.7,199.68c-4.79,0-8.42,3.56-8.42,8.29s3.7,8.42,8.42,8.42,8.29-3.62,8.29-8.42-3.56-8.29-8.29-8.29Z"/>
|
|
128
|
+
<path d="M623.83,199.68c-4.79,0-8.42,3.56-8.42,8.29s3.7,8.42,8.42,8.42,8.29-3.62,8.29-8.42-3.56-8.29-8.29-8.29Z"/>
|
|
129
|
+
<path d="M708.91,199.68c-4.79,0-8.42,3.56-8.42,8.29s3.7,8.42,8.42,8.42,8.29-3.62,8.29-8.42-3.56-8.29-8.29-8.29Z"/>
|
|
130
|
+
</g>
|
|
131
|
+
<g>
|
|
132
|
+
<path d="M131.47,199.69c0,4.45,2.3,6.33,3.48,7.07,2.23,1.41,13.96,7.06,25.86,11.39,9.28,3.38,32.82,10.99,43.37,13.27,1.62.35,5.22,1.11,6.55-.5,1.33-1.61-.5-4.95-1.58-6.74-3.27-5.43-9.92-15.39-16.21-24.76-5.25-7.82-15.34-21.33-15.34-21.33-1.36-1.49-3.23-3.54-7.03-3.54l-28.33-.05c-5.96,0-10.78,4.83-10.78,10.78,0,0,0,.08,0,.22h0v14.08h0v.11Z"/>
|
|
133
|
+
<path d="M232.64,284.23c-2.77-10.24-9.15-29.14-9.73-30.42-.47-1.05-1.23-2.04-2.68-2.04-1.63,0-2.49,1.43-3.28,2.82-2.83,4.95-5.41,10.79-7.89,16.26h0s-.97,2.14-.97,2.14c-3.29,7.19-7.11,15.79-10.56,25.01l-3.54,9.47c-1.42,3.8-2.72,7.79-3.97,12.2-.42,1.49-.25,2.9.49,3.98.61.89,1.83,1.85,3.06,1.85l43.34-.04c1.18,0,2.19-.47,2.91-1.36.93-1.14,1.32-2.89,1.03-4.56-1.85-10.48-4.91-23.22-8.19-35.31Z"/>
|
|
134
|
+
<path d="M132.98,266.34c1.15.94,2.65,1.24,4.11.82,7.95-2.28,24.75-7.5,35.59-11.38,9.74-3.48,27.05-10.57,30.52-12.16,2.71-1.23,4.61-2.23,4.4-3.8-.12-.94-.87-1.45-1.48-1.71-2.75-1.16-15.01-4.4-36.46-9.6-9.2-2.23-32.27-7.05-34.06-7.05-1.11,0-2.19.45-2.95,1.25-.8.83-1.18,1.97-1.18,3.57v35.87c0,1.86.25,3.14,1.51,4.18Z"/>
|
|
135
|
+
<path d="M281.35,217.96c-.67-.6-1.59-.95-2.51-.95-.17,0-.33.01-.5.03-1.53.21-18.47,7.09-19.42,7.47-6.93,2.79-18.77,8.38-21.32,9.69,0,0-5.45,2.56-5.45,4.5,0,1.82,3.13,3.14,6.33,4.33.16.06,20.21,7.29,31.88,10.82l.64.2c2.3.7,4.48,1.36,6.93,1.82,1.16.22,2.38-.11,3.27-.87.82-.7,1.27-1.7,1.27-2.8v-31.76c-.01-.96-.4-1.84-1.11-2.48Z"/>
|
|
136
|
+
<path d="M280.06,270.67c-3.45-2.52-13.76-8.56-20.87-12.02-7.88-3.84-22.27-10.14-26.88-11.29-.41-.1-.81-.15-1.2-.15-.88,0-1.64.27-2.14.75-.96.93-.91,1.98-.73,3.47.27,2.16,8.68,25.04,11.44,32.55,0,0,10.15,27.85,13.37,33.75,2.93,5.45,6.52,7.77,11.98,7.77h6.64c5.96,0,10.78-4.83,10.78-10.78v-37.34c0-4.22-.77-5.53-2.4-6.72Z"/>
|
|
137
|
+
<path d="M227.72,228.15c-.03,1.15.92,2.07,2.06,2.09.01,0,.03,0,.04,0,.02,0,.03,0,.05-.01,1.23-.04,2.63-.5,2.63-.5,8.06-2.65,37.66-19.95,44.22-24.37,1.72-1.16,5.75-4.6,5.75-10.72v-9.37c0-5.95-4.83-10.78-10.78-10.78h-16.9c-3.38,0-6.22,2.91-7.22,4.74-3.59,6.61-6.96,14.39-8.64,18.45-3.55,8.53-8.86,22.45-10.79,28.55-.13.41-.41,1.49-.42,1.91Z"/>
|
|
138
|
+
<path d="M201.91,197.31c2.94,6.05,10.39,20.61,14.62,27.01.47.73,1.52,2.65,3.09,2.63,1.58-.02,2.58-2.92,2.63-3.04.7-1.54,5.09-14.96,7.72-24.17,2.71-9.51,4.54-17.99,4.89-20.37.24-1.6-.05-2.7-.74-3.51-.77-.9-2.11-1.36-3.97-1.36h-32.75c-1.21,0-2.24.47-2.9,1.33-.68.88-.91,2.11-.62,3.37.6,2.64,5.83,13.54,8.05,18.1Z"/>
|
|
139
|
+
<path d="M212.24,250.35c0-.96-.84-1.83-1.76-1.97-1.67-.24-3.97.79-5.47,1.31-14.87,6-39.66,17.87-54.15,25.92l-1.75.97c-4.89,2.7-8.75,4.83-11.97,7.96-3.6,3.49-5.68,7.2-5.68,15.83v14.35c0,5.95,4.83,10.78,10.78,10.78l15.9-.03c5.77,0,8.4-1.91,10.59-4.76,4.38-5.73,16-22.42,20.98-30.64,9.51-15.69,20.57-34.99,21.93-37.7.51-1.03.58-1.49.6-2.01Z"/>
|
|
140
|
+
</g>
|
|
141
|
+
</svg>`;
|
|
142
|
+
var FAVICON_HREF = `data:image/svg+xml;base64,${Buffer.from(FAVICON_SVG).toString("base64")}`;
|
|
143
|
+
function renderPage(o) {
|
|
144
|
+
return `<!doctype html>
|
|
145
|
+
<html lang="en">
|
|
146
|
+
<head>
|
|
147
|
+
<meta charset="utf-8" />
|
|
148
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
149
|
+
<title>${o.docTitle}</title>
|
|
150
|
+
<link rel="icon" type="image/svg+xml" href="${FAVICON_HREF}" />
|
|
151
|
+
<style>
|
|
152
|
+
:root {
|
|
153
|
+
--inter-orange: #ff4f00;
|
|
154
|
+
--light-bg: #f5f5f5;
|
|
155
|
+
--black: #333333;
|
|
156
|
+
--hairline: rgba(0, 0, 0, 0.16);
|
|
157
|
+
}
|
|
158
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
159
|
+
html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
|
160
|
+
body {
|
|
161
|
+
min-height: 100vh;
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
justify-content: center;
|
|
165
|
+
padding: 32px;
|
|
166
|
+
background-color: var(--light-bg);
|
|
167
|
+
color: var(--black);
|
|
168
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
169
|
+
line-height: 1.5;
|
|
170
|
+
overflow: hidden;
|
|
171
|
+
}
|
|
172
|
+
/* The brand frame: the homepage's inset rounded hero canvas, as a hairline. */
|
|
173
|
+
.frame {
|
|
174
|
+
position: fixed;
|
|
175
|
+
inset: 16px;
|
|
176
|
+
border: 1px solid var(--hairline);
|
|
177
|
+
border-radius: 24px;
|
|
178
|
+
pointer-events: none;
|
|
179
|
+
z-index: 1;
|
|
180
|
+
}
|
|
181
|
+
.card { position: relative; z-index: 2; width: 100%; max-width: 460px; text-align: center; }
|
|
182
|
+
|
|
183
|
+
.eyebrow {
|
|
184
|
+
font-family: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
|
|
185
|
+
font-size: 11px; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase;
|
|
186
|
+
color: var(--inter-orange);
|
|
187
|
+
}
|
|
188
|
+
.title {
|
|
189
|
+
font-size: 30px; font-weight: 600; letter-spacing: -0.02em; line-height: 1.1;
|
|
190
|
+
margin: 14px 0 10px;
|
|
191
|
+
}
|
|
192
|
+
.subtitle {
|
|
193
|
+
font-size: 15px; color: rgba(0, 0, 0, 0.55);
|
|
194
|
+
}
|
|
195
|
+
.mark { margin-top: 40px; }
|
|
196
|
+
.mark svg { height: 22px; width: auto; color: rgba(0, 0, 0, 0.38); }
|
|
197
|
+
|
|
198
|
+
@media (max-width: 460px) {
|
|
199
|
+
.title { font-size: 26px; }
|
|
200
|
+
.frame { inset: 10px; border-radius: 18px; }
|
|
201
|
+
}
|
|
202
|
+
</style>
|
|
203
|
+
</head>
|
|
204
|
+
<body>
|
|
205
|
+
<div class="frame" aria-hidden="true"></div>
|
|
206
|
+
<main class="card">
|
|
207
|
+
<p class="eyebrow">${o.eyebrow}</p>
|
|
208
|
+
<h1 class="title">${o.title}</h1>
|
|
209
|
+
<p class="subtitle">${o.detail}</p>
|
|
210
|
+
|
|
211
|
+
<div class="mark" aria-hidden="true">
|
|
212
|
+
${WORDMARK_SVG}
|
|
213
|
+
</div>
|
|
214
|
+
</main>
|
|
215
|
+
</body>
|
|
216
|
+
</html>`;
|
|
217
|
+
}
|
|
218
|
+
var SUCCESS_HTML = renderPage({
|
|
219
|
+
docTitle: "Primitive \u2014 Authenticated",
|
|
220
|
+
eyebrow: "Authenticated",
|
|
221
|
+
title: "Authentication successful!",
|
|
222
|
+
detail: "You can close this tab."
|
|
223
|
+
});
|
|
224
|
+
var FAILURE_HTML = renderPage({
|
|
225
|
+
docTitle: "Primitive \u2014 Authentication failed",
|
|
226
|
+
eyebrow: "Error",
|
|
227
|
+
title: "Authentication failed.",
|
|
228
|
+
detail: "Return to your terminal for details."
|
|
229
|
+
});
|
|
230
|
+
var STATE_MISMATCH_HTML = renderPage({
|
|
231
|
+
docTitle: "Primitive \u2014 Authentication failed",
|
|
232
|
+
eyebrow: "Error",
|
|
233
|
+
title: "State mismatch. Authentication failed.",
|
|
234
|
+
detail: "Return to your terminal for details."
|
|
235
|
+
});
|
|
57
236
|
|
|
58
237
|
// src/commands/auth.ts
|
|
59
238
|
var FILE_MODE = 384;
|
|
60
239
|
var LOCALHOST = "127.0.0.1";
|
|
61
240
|
var CALLBACK_PORT = 19876;
|
|
62
241
|
var CALLBACK_TIMEOUT_MS = 12e4;
|
|
242
|
+
var EXIT_OK = 0;
|
|
243
|
+
var EXIT_FAIL = 1;
|
|
244
|
+
var HTML_HEADERS = { "Content-Type": "text/html; charset=utf-8" };
|
|
63
245
|
var BASE64_PLUS_RE = /\+/g;
|
|
64
246
|
var BASE64_SLASH_RE = /\//g;
|
|
65
247
|
var BASE64_PAD_RE = /=+$/;
|
|
@@ -83,6 +265,55 @@ function saveToken(token) {
|
|
|
83
265
|
}
|
|
84
266
|
writeFileSync(TOKEN_FILE_PATH, token, { mode: FILE_MODE });
|
|
85
267
|
}
|
|
268
|
+
function resolveCallbackPage(params, expectedState) {
|
|
269
|
+
if (params.get("state") !== expectedState) {
|
|
270
|
+
return {
|
|
271
|
+
status: 400,
|
|
272
|
+
html: STATE_MISMATCH_HTML,
|
|
273
|
+
result: {
|
|
274
|
+
authenticated: false,
|
|
275
|
+
error: "state_mismatch",
|
|
276
|
+
detail: "state mismatch on the OAuth callback"
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const providerError = params.get("error");
|
|
281
|
+
if (providerError) {
|
|
282
|
+
return {
|
|
283
|
+
status: 400,
|
|
284
|
+
html: FAILURE_HTML,
|
|
285
|
+
result: {
|
|
286
|
+
authenticated: false,
|
|
287
|
+
error: providerError,
|
|
288
|
+
detail: params.get("error_description") ?? void 0
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const code = params.get("code");
|
|
293
|
+
if (!code) {
|
|
294
|
+
return {
|
|
295
|
+
status: 400,
|
|
296
|
+
html: FAILURE_HTML,
|
|
297
|
+
result: {
|
|
298
|
+
authenticated: false,
|
|
299
|
+
error: "no_code",
|
|
300
|
+
detail: "No authorization code received"
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
return { status: 200, html: SUCCESS_HTML, result: { authenticated: true, code } };
|
|
305
|
+
}
|
|
306
|
+
function reportFailure(error, detail) {
|
|
307
|
+
const human = detail ? `${stripControlChars(error)}: ${stripControlChars(detail)}` : stripControlChars(error);
|
|
308
|
+
console.error(`Authentication failed: ${human}`);
|
|
309
|
+
console.log(JSON.stringify({ authenticated: false, error, detail }));
|
|
310
|
+
process.exitCode = EXIT_FAIL;
|
|
311
|
+
}
|
|
312
|
+
function reportSuccess() {
|
|
313
|
+
console.error(`Authenticated! Token saved to ${TOKEN_FILE_PATH}`);
|
|
314
|
+
console.log(JSON.stringify({ authenticated: true, tokenFile: TOKEN_FILE_PATH }));
|
|
315
|
+
process.exitCode = EXIT_OK;
|
|
316
|
+
}
|
|
86
317
|
function registerAuthCommands(program2) {
|
|
87
318
|
const auth = program2.command("auth").description("Manage CLI authentication");
|
|
88
319
|
auth.command("login").description("Authenticate via browser (WorkOS OAuth)").action(async () => {
|
|
@@ -92,15 +323,23 @@ function registerAuthCommands(program2) {
|
|
|
92
323
|
const res = await fetch(`${siteUrl}/mcp/config`);
|
|
93
324
|
config = await res.json();
|
|
94
325
|
} catch {
|
|
95
|
-
|
|
96
|
-
|
|
326
|
+
reportFailure(
|
|
327
|
+
"config_fetch_failed",
|
|
328
|
+
"Failed to fetch MCP config. Is the Convex backend running?"
|
|
329
|
+
);
|
|
330
|
+
return;
|
|
97
331
|
}
|
|
98
332
|
if (!config.authorization_server || !config.client_id) {
|
|
99
|
-
|
|
100
|
-
|
|
333
|
+
reportFailure("broker_not_configured", "MCP broker is not configured on the server.");
|
|
334
|
+
return;
|
|
101
335
|
}
|
|
102
336
|
const { verifier, challenge } = generatePkce();
|
|
103
337
|
const state = base64url(randomBytes(16));
|
|
338
|
+
const redirectUri = `http://${LOCALHOST}:${CALLBACK_PORT}/callback`;
|
|
339
|
+
let settle;
|
|
340
|
+
const outcome = new Promise((resolve6) => {
|
|
341
|
+
settle = resolve6;
|
|
342
|
+
});
|
|
104
343
|
const server = createServer((req, res) => {
|
|
105
344
|
const url = new URL(req.url ?? "/", `http://${LOCALHOST}`);
|
|
106
345
|
if (url.pathname !== "/callback") {
|
|
@@ -108,42 +347,35 @@ function registerAuthCommands(program2) {
|
|
|
108
347
|
res.end("Not found");
|
|
109
348
|
return;
|
|
110
349
|
}
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
115
|
-
res.end("<h1>State mismatch. Authentication failed.</h1>");
|
|
116
|
-
server.close();
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
119
|
-
if (!code) {
|
|
120
|
-
const error = url.searchParams.get("error_description") ?? "No authorization code received";
|
|
121
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
122
|
-
res.end(`<h1>Authentication failed: ${error}</h1>`);
|
|
123
|
-
server.close();
|
|
124
|
-
process.exit(1);
|
|
125
|
-
}
|
|
126
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
127
|
-
res.end("<h1>Authentication successful!</h1><p>You can close this tab.</p>");
|
|
128
|
-
exchangeCode(siteUrl, code, verifier, `http://${LOCALHOST}:${port}/callback`).then((token) => {
|
|
129
|
-
saveToken(token);
|
|
130
|
-
console.error(`Authenticated! Token saved to ${TOKEN_FILE_PATH}`);
|
|
131
|
-
console.log(JSON.stringify({ authenticated: true, tokenFile: TOKEN_FILE_PATH }));
|
|
132
|
-
server.close();
|
|
133
|
-
process.exit(0);
|
|
134
|
-
}).catch((err) => {
|
|
135
|
-
console.error("Token exchange failed:", err);
|
|
136
|
-
server.close();
|
|
137
|
-
process.exit(1);
|
|
138
|
-
});
|
|
350
|
+
const page = resolveCallbackPage(url.searchParams, state);
|
|
351
|
+
res.writeHead(page.status, HTML_HEADERS);
|
|
352
|
+
res.end(page.html, () => settle(page.result));
|
|
139
353
|
});
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
354
|
+
try {
|
|
355
|
+
await new Promise((resolve6, reject) => {
|
|
356
|
+
const onError = (err) => reject(err);
|
|
357
|
+
server.once("error", onError);
|
|
358
|
+
server.listen(CALLBACK_PORT, LOCALHOST, () => {
|
|
359
|
+
server.removeListener("error", onError);
|
|
360
|
+
resolve6();
|
|
361
|
+
});
|
|
144
362
|
});
|
|
363
|
+
} catch (err) {
|
|
364
|
+
if (err instanceof Error && err.code === "EADDRINUSE") {
|
|
365
|
+
reportFailure(
|
|
366
|
+
"callback_port_in_use",
|
|
367
|
+
`Port ${CALLBACK_PORT} is in use \u2014 another 'prim auth login' may be running. Close it and retry.`
|
|
368
|
+
);
|
|
369
|
+
} else {
|
|
370
|
+
reportFailure(
|
|
371
|
+
"callback_bind_failed",
|
|
372
|
+
`Could not start the local callback server: ${err instanceof Error ? err.message : String(err)}`
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
server.on("error", () => {
|
|
145
378
|
});
|
|
146
|
-
const redirectUri = `http://${LOCALHOST}:${port}/callback`;
|
|
147
379
|
const authorizeUrl = config.authorization_endpoint ?? "https://api.workos.com/user_management/authorize";
|
|
148
380
|
const authUrl = new URL(authorizeUrl);
|
|
149
381
|
authUrl.searchParams.set("client_id", config.client_id);
|
|
@@ -160,11 +392,24 @@ function registerAuthCommands(program2) {
|
|
|
160
392
|
${authUrl.toString()}
|
|
161
393
|
`);
|
|
162
394
|
console.error("Waiting for callback...");
|
|
163
|
-
setTimeout(() => {
|
|
164
|
-
|
|
165
|
-
server.close();
|
|
166
|
-
process.exit(1);
|
|
395
|
+
const timer = setTimeout(() => {
|
|
396
|
+
settle({ authenticated: false, error: "timeout", detail: "Authentication timed out." });
|
|
167
397
|
}, CALLBACK_TIMEOUT_MS);
|
|
398
|
+
timer.unref();
|
|
399
|
+
const result = await outcome;
|
|
400
|
+
clearTimeout(timer);
|
|
401
|
+
server.close();
|
|
402
|
+
if (!result.authenticated) {
|
|
403
|
+
reportFailure(result.error, result.detail);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
const token = await exchangeCode(siteUrl, result.code, verifier, redirectUri);
|
|
408
|
+
saveToken(token);
|
|
409
|
+
reportSuccess();
|
|
410
|
+
} catch (err) {
|
|
411
|
+
reportFailure("token_exchange_failed", err instanceof Error ? err.message : String(err));
|
|
412
|
+
}
|
|
168
413
|
});
|
|
169
414
|
auth.command("set-token <token>").description("Save a bearer token for authenticated CLI calls").action((token) => {
|
|
170
415
|
saveToken(token);
|
|
@@ -282,7 +527,6 @@ async function exchangeCode(siteUrl, code, codeVerifier, redirectUri) {
|
|
|
282
527
|
}
|
|
283
528
|
|
|
284
529
|
// src/commands/claude-install.ts
|
|
285
|
-
import { execSync } from "child_process";
|
|
286
530
|
import {
|
|
287
531
|
closeSync,
|
|
288
532
|
existsSync as existsSync3,
|
|
@@ -376,14 +620,7 @@ var PRIM_BINS = [
|
|
|
376
620
|
var JSON_INDENT = 2;
|
|
377
621
|
var USER_SCOPE_PATH = join2(homedir(), ".claude", "settings.json");
|
|
378
622
|
function projectRoot() {
|
|
379
|
-
|
|
380
|
-
return execSync("git rev-parse --show-toplevel", {
|
|
381
|
-
encoding: "utf-8",
|
|
382
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
383
|
-
}).trim();
|
|
384
|
-
} catch {
|
|
385
|
-
return process.cwd();
|
|
386
|
-
}
|
|
623
|
+
return gitToplevel() ?? process.cwd();
|
|
387
624
|
}
|
|
388
625
|
var projectScopePath = () => join2(projectRoot(), ".claude", "settings.json");
|
|
389
626
|
var CAPTURE_EVENTS = [
|
|
@@ -592,6 +829,9 @@ function performInstall(scope, force) {
|
|
|
592
829
|
if (changed) {
|
|
593
830
|
atomicWrite(path, after);
|
|
594
831
|
}
|
|
832
|
+
if (scope === "project") {
|
|
833
|
+
activateRepoBestEffort(process.cwd());
|
|
834
|
+
}
|
|
595
835
|
return {
|
|
596
836
|
scope,
|
|
597
837
|
path,
|
|
@@ -788,6 +1028,9 @@ function performInstall2(scope, force) {
|
|
|
788
1028
|
if (changed) {
|
|
789
1029
|
atomicWrite(path, after);
|
|
790
1030
|
}
|
|
1031
|
+
if (scope === "project") {
|
|
1032
|
+
activateRepoBestEffort(process.cwd());
|
|
1033
|
+
}
|
|
791
1034
|
return resultFor(scope, path, after, changed);
|
|
792
1035
|
}
|
|
793
1036
|
function performUninstall2(scope) {
|
|
@@ -891,7 +1134,7 @@ var STATUS_PROBE_TIMEOUT_MS = 500;
|
|
|
891
1134
|
var READY_TIMEOUT_MS = 5e3;
|
|
892
1135
|
var READY_POLL_MS = 100;
|
|
893
1136
|
var READY_PROBE_TIMEOUT_MS = 250;
|
|
894
|
-
var
|
|
1137
|
+
var EXIT_OK2 = 0;
|
|
895
1138
|
var EXIT_NOT_RUNNING = 2;
|
|
896
1139
|
var EXIT_BOOTING = 3;
|
|
897
1140
|
function readPidfile() {
|
|
@@ -924,8 +1167,8 @@ function clearStaleArtifacts() {
|
|
|
924
1167
|
}
|
|
925
1168
|
}
|
|
926
1169
|
function sleep(ms) {
|
|
927
|
-
return new Promise((
|
|
928
|
-
const timer = setTimeout(
|
|
1170
|
+
return new Promise((resolve6) => {
|
|
1171
|
+
const timer = setTimeout(resolve6, ms);
|
|
929
1172
|
timer.unref();
|
|
930
1173
|
});
|
|
931
1174
|
}
|
|
@@ -1051,9 +1294,9 @@ function classifyStatus(pidAlive, responding, snapshot, pid) {
|
|
|
1051
1294
|
};
|
|
1052
1295
|
}
|
|
1053
1296
|
if (!snapshot) {
|
|
1054
|
-
return { json: { running: true, responding: true }, exitCode:
|
|
1297
|
+
return { json: { running: true, responding: true }, exitCode: EXIT_OK2 };
|
|
1055
1298
|
}
|
|
1056
|
-
return { json: { running: true, responding: true, ...snapshot }, exitCode:
|
|
1299
|
+
return { json: { running: true, responding: true, ...snapshot }, exitCode: EXIT_OK2 };
|
|
1057
1300
|
}
|
|
1058
1301
|
async function daemonStatus() {
|
|
1059
1302
|
const pid = readPidfile();
|
|
@@ -1080,7 +1323,7 @@ async function daemonStatus() {
|
|
|
1080
1323
|
);
|
|
1081
1324
|
}
|
|
1082
1325
|
console.log(JSON.stringify(json, null, 2));
|
|
1083
|
-
if (exitCode !==
|
|
1326
|
+
if (exitCode !== EXIT_OK2 && !process.exitCode) {
|
|
1084
1327
|
process.exitCode = exitCode;
|
|
1085
1328
|
}
|
|
1086
1329
|
}
|
|
@@ -1332,6 +1575,12 @@ function formatCascadeJson(result) {
|
|
|
1332
1575
|
var RECENT_TIMEOUT_MS = 1e4;
|
|
1333
1576
|
var defaultDeps2 = { getClient };
|
|
1334
1577
|
async function fetchRecent(args, deps = defaultDeps2) {
|
|
1578
|
+
if (args.author !== void 0 && args.author.trim() === "") {
|
|
1579
|
+
return {
|
|
1580
|
+
decisions: [],
|
|
1581
|
+
unavailable: "--author must be a non-empty name"
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1335
1584
|
const params = new URLSearchParams();
|
|
1336
1585
|
if (args.limit !== void 0) {
|
|
1337
1586
|
params.set("limit", String(args.limit));
|
|
@@ -1339,6 +1588,9 @@ async function fetchRecent(args, deps = defaultDeps2) {
|
|
|
1339
1588
|
if (args.since !== void 0) {
|
|
1340
1589
|
params.set("since", args.since);
|
|
1341
1590
|
}
|
|
1591
|
+
if (args.author !== void 0) {
|
|
1592
|
+
params.set("author", args.author);
|
|
1593
|
+
}
|
|
1342
1594
|
try {
|
|
1343
1595
|
const client = deps.getClient();
|
|
1344
1596
|
const res = await daemonOrDirectGet(
|
|
@@ -1347,10 +1599,22 @@ async function fetchRecent(args, deps = defaultDeps2) {
|
|
|
1347
1599
|
client,
|
|
1348
1600
|
RECENT_TIMEOUT_MS
|
|
1349
1601
|
);
|
|
1602
|
+
if (args.author !== void 0 && res.author === void 0 && res.unavailable === void 0) {
|
|
1603
|
+
return {
|
|
1604
|
+
decisions: [],
|
|
1605
|
+
unavailable: "--author requires a newer Primitive backend (no author echo in response); retry without --author for the team-wide feed"
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1350
1608
|
const result = { decisions: res.decisions };
|
|
1351
1609
|
if (res.viewerHasDecisions !== void 0) {
|
|
1352
1610
|
result.viewerHasDecisions = res.viewerHasDecisions;
|
|
1353
1611
|
}
|
|
1612
|
+
if (res.author !== void 0) {
|
|
1613
|
+
result.author = res.author;
|
|
1614
|
+
}
|
|
1615
|
+
if (res.authorHasDecisions !== void 0) {
|
|
1616
|
+
result.authorHasDecisions = res.authorHasDecisions;
|
|
1617
|
+
}
|
|
1354
1618
|
if (res.unavailable !== void 0) {
|
|
1355
1619
|
result.unavailable = res.unavailable;
|
|
1356
1620
|
}
|
|
@@ -1407,10 +1671,20 @@ function formatRecentHuman(result) {
|
|
|
1407
1671
|
if (result.unavailable !== void 0) {
|
|
1408
1672
|
return `[prim] recent \xB7 feed not verified \u2014 ${result.unavailable}`;
|
|
1409
1673
|
}
|
|
1674
|
+
if (result.author !== void 0 && result.decisions.length === 0) {
|
|
1675
|
+
if (result.authorHasDecisions === true) {
|
|
1676
|
+
return `[prim] recent \xB7 ${result.author.name} \xB7 0 decisions in this window (older decisions exist \u2014 widen --since or raise --limit)`;
|
|
1677
|
+
}
|
|
1678
|
+
if (result.authorHasDecisions === false) {
|
|
1679
|
+
return `[prim] recent \xB7 ${result.author.name} \xB7 no feed-visible decisions yet (if unexpected, check prim setup/doctor on their machine and the repo they work in)`;
|
|
1680
|
+
}
|
|
1681
|
+
return `[prim] recent \xB7 ${result.author.name} \xB7 0 decisions`;
|
|
1682
|
+
}
|
|
1683
|
+
const label = result.author === void 0 ? "recent" : `recent \xB7 ${result.author.name}`;
|
|
1410
1684
|
if (result.decisions.length === 0) {
|
|
1411
1685
|
return "[prim] recent \xB7 0 decisions";
|
|
1412
1686
|
}
|
|
1413
|
-
const lines = [`[prim]
|
|
1687
|
+
const lines = [`[prim] ${label} \xB7 ${String(result.decisions.length)} decision(s)`];
|
|
1414
1688
|
for (const row of result.decisions) {
|
|
1415
1689
|
lines.push(formatRecentRow(row));
|
|
1416
1690
|
}
|
|
@@ -1771,10 +2045,14 @@ function registerDecisionsCommands(program2) {
|
|
|
1771
2045
|
decisions.command("recent").description("Show the team-wide chronological decision feed").option("--limit <n>", "Maximum number of rows to return (default 10)").option(
|
|
1772
2046
|
"--since <duration>",
|
|
1773
2047
|
"Lookback window \u2014 accepts `Nm`, `Nh`, `Nd` (minutes / hours / days) or absolute epoch ms"
|
|
2048
|
+
).option(
|
|
2049
|
+
"--author <name>",
|
|
2050
|
+
`Filter to one teammate's decisions \u2014 feed name, "First Last", last name, username, email, or email local-part`
|
|
1774
2051
|
).action(async (opts) => {
|
|
1775
2052
|
const result = await fetchRecent({
|
|
1776
2053
|
limit: opts.limit ? Number.parseInt(opts.limit, 10) : void 0,
|
|
1777
|
-
since: opts.since
|
|
2054
|
+
since: opts.since,
|
|
2055
|
+
author: opts.author
|
|
1778
2056
|
});
|
|
1779
2057
|
console.error(formatRecentHuman(result));
|
|
1780
2058
|
console.log(formatRecentJson(result));
|
|
@@ -2350,9 +2628,10 @@ function registerHermesCommands(program2) {
|
|
|
2350
2628
|
}
|
|
2351
2629
|
|
|
2352
2630
|
// src/commands/hooks.ts
|
|
2353
|
-
import {
|
|
2631
|
+
import { execFileSync } from "child_process";
|
|
2354
2632
|
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
2355
|
-
import {
|
|
2633
|
+
import { homedir as homedir5 } from "os";
|
|
2634
|
+
import { dirname as dirname5, join as join6, resolve } from "path";
|
|
2356
2635
|
import { Option } from "commander";
|
|
2357
2636
|
var PRE_COMMIT = { hookName: "pre-commit", binName: "prim-pre-commit" };
|
|
2358
2637
|
var POST_COMMIT = { hookName: "post-commit", binName: "prim-post-commit" };
|
|
@@ -2365,18 +2644,25 @@ function blockMarkers(spec) {
|
|
|
2365
2644
|
}
|
|
2366
2645
|
var PRIM_BLOCK_START = blockMarkers(PRE_COMMIT).start;
|
|
2367
2646
|
var PRIM_BLOCK_END = blockMarkers(PRE_COMMIT).end;
|
|
2647
|
+
var PRIM_MANAGED_MARK = "prim-managed-hook";
|
|
2648
|
+
var PRIM_CREATED_MARK = "prim-created-hook";
|
|
2368
2649
|
function hookShim(binName) {
|
|
2369
2650
|
return `if command -v ${binName} >/dev/null 2>&1; then
|
|
2370
|
-
${binName}
|
|
2651
|
+
${binName} || true
|
|
2371
2652
|
elif [ -f "./node_modules/.bin/${binName}" ]; then
|
|
2372
|
-
./node_modules/.bin/${binName}
|
|
2653
|
+
./node_modules/.bin/${binName} || true
|
|
2373
2654
|
else
|
|
2374
2655
|
npx --yes -p @primitive.ai/prim ${binName} 2>/dev/null || true
|
|
2375
2656
|
fi`;
|
|
2376
2657
|
}
|
|
2658
|
+
function gatedShim(binName) {
|
|
2659
|
+
return `if [ "$(git config --get prim.active 2>/dev/null)" = "true" ]; then
|
|
2660
|
+
${hookShim(binName)}
|
|
2661
|
+
fi`;
|
|
2662
|
+
}
|
|
2377
2663
|
function dotGitScript(spec) {
|
|
2378
2664
|
return `#!/bin/sh
|
|
2379
|
-
# prim ${spec.hookName} hook \u2014 installed by: prim hooks install
|
|
2665
|
+
# prim ${spec.hookName} hook \u2014 installed by: prim hooks install (${PRIM_MANAGED_MARK})
|
|
2380
2666
|
|
|
2381
2667
|
${hookShim(spec.binName)}
|
|
2382
2668
|
`;
|
|
@@ -2387,10 +2673,35 @@ function huskyBlock(spec) {
|
|
|
2387
2673
|
${hookShim(spec.binName)}
|
|
2388
2674
|
${end}`;
|
|
2389
2675
|
}
|
|
2676
|
+
function gatedBlock(spec) {
|
|
2677
|
+
const { start, end } = blockMarkers(spec);
|
|
2678
|
+
return `${start}
|
|
2679
|
+
${gatedShim(spec.binName)}
|
|
2680
|
+
${end}`;
|
|
2681
|
+
}
|
|
2682
|
+
function mergePrimBlock(hookPath, block, binName) {
|
|
2683
|
+
if (existsSync7(hookPath)) {
|
|
2684
|
+
const existing = readFileSync6(hookPath, "utf-8");
|
|
2685
|
+
if (containsPrimHook(existing, binName)) return false;
|
|
2686
|
+
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
2687
|
+
writeFileSync4(hookPath, `${existing}${separator}${block}
|
|
2688
|
+
`, { mode: 493 });
|
|
2689
|
+
return true;
|
|
2690
|
+
}
|
|
2691
|
+
mkdirSync5(dirname5(hookPath), { recursive: true });
|
|
2692
|
+
writeFileSync4(hookPath, `#!/bin/sh
|
|
2693
|
+
# ${PRIM_CREATED_MARK}
|
|
2694
|
+
|
|
2695
|
+
${block}
|
|
2696
|
+
`, { mode: 493 });
|
|
2697
|
+
return true;
|
|
2698
|
+
}
|
|
2390
2699
|
function getGitRoot() {
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2700
|
+
const root = gitToplevel();
|
|
2701
|
+
if (root === null) {
|
|
2702
|
+
throw new Error("not a git repository (run inside a repo, or use --scope user)");
|
|
2703
|
+
}
|
|
2704
|
+
return root;
|
|
2394
2705
|
}
|
|
2395
2706
|
function detectHusky(gitRoot) {
|
|
2396
2707
|
const huskyDir = resolve(gitRoot, ".husky");
|
|
@@ -2427,25 +2738,13 @@ async function askConfirmation(question) {
|
|
|
2427
2738
|
}
|
|
2428
2739
|
function installToHusky(gitRoot, spec = PRE_COMMIT) {
|
|
2429
2740
|
const hookPath = resolve(gitRoot, ".husky", spec.hookName);
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
}
|
|
2436
|
-
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
2437
|
-
writeFileSync4(hookPath, `${existing}${separator}${huskyBlock(spec)}
|
|
2438
|
-
`, {
|
|
2439
|
-
mode: 493
|
|
2440
|
-
});
|
|
2741
|
+
const existed = existsSync7(hookPath);
|
|
2742
|
+
const wrote = mergePrimBlock(hookPath, huskyBlock(spec), spec.binName);
|
|
2743
|
+
if (!wrote) {
|
|
2744
|
+
console.log(`Prim ${spec.hookName} hook is already installed in .husky/${spec.hookName}.`);
|
|
2745
|
+
} else if (existed) {
|
|
2441
2746
|
console.log(`Appended prim hook block to .husky/${spec.hookName}.`);
|
|
2442
2747
|
} else {
|
|
2443
|
-
writeFileSync4(hookPath, `#!/bin/sh
|
|
2444
|
-
|
|
2445
|
-
${huskyBlock(spec)}
|
|
2446
|
-
`, {
|
|
2447
|
-
mode: 493
|
|
2448
|
-
});
|
|
2449
2748
|
console.log(`Created .husky/${spec.hookName} with prim hook block.`);
|
|
2450
2749
|
}
|
|
2451
2750
|
}
|
|
@@ -2468,6 +2767,161 @@ function installToDotGit(gitRoot, spec = PRE_COMMIT) {
|
|
|
2468
2767
|
writeFileSync4(hookPath, dotGitScript(spec), { mode: 493 });
|
|
2469
2768
|
console.log(`Installed ${spec.hookName} hook at ${hookPath}`);
|
|
2470
2769
|
}
|
|
2770
|
+
var PRIM_GIT_HOOKS_DIR = join6(homedir5(), ".config", "prim", "git-hooks");
|
|
2771
|
+
function expandTilde(p) {
|
|
2772
|
+
return p.startsWith("~/") ? join6(homedir5(), p.slice(2)) : p;
|
|
2773
|
+
}
|
|
2774
|
+
function isOurHooksDir(value) {
|
|
2775
|
+
return value !== "" && expandTilde(value) === PRIM_GIT_HOOKS_DIR;
|
|
2776
|
+
}
|
|
2777
|
+
function gitConfigGet(level) {
|
|
2778
|
+
try {
|
|
2779
|
+
return execFileSync("git", ["config", level, "--get", "core.hooksPath"], {
|
|
2780
|
+
encoding: "utf-8",
|
|
2781
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2782
|
+
}).trim();
|
|
2783
|
+
} catch {
|
|
2784
|
+
return "";
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
var PASSTHROUGH_HOOKS = [
|
|
2788
|
+
"applypatch-msg",
|
|
2789
|
+
"pre-applypatch",
|
|
2790
|
+
"post-applypatch",
|
|
2791
|
+
"pre-merge-commit",
|
|
2792
|
+
"prepare-commit-msg",
|
|
2793
|
+
"commit-msg",
|
|
2794
|
+
"pre-rebase",
|
|
2795
|
+
"post-checkout",
|
|
2796
|
+
"post-merge",
|
|
2797
|
+
"pre-push",
|
|
2798
|
+
"post-rewrite",
|
|
2799
|
+
"pre-auto-gc",
|
|
2800
|
+
"push-to-checkout",
|
|
2801
|
+
"sendemail-validate",
|
|
2802
|
+
"reference-transaction",
|
|
2803
|
+
"post-index-change"
|
|
2804
|
+
// (fsmonitor-watchman is intentionally omitted — it's driven by core.fsmonitor
|
|
2805
|
+
// with a query protocol, not a lifecycle event, so a bare exec stub is wrong.)
|
|
2806
|
+
];
|
|
2807
|
+
function globalHookScript(spec) {
|
|
2808
|
+
const chainExit = spec.hookName === "pre-commit" ? "|| exit $?" : "|| true";
|
|
2809
|
+
return `#!/bin/sh
|
|
2810
|
+
# prim global ${spec.hookName} hook (core.hooksPath) \u2014 managed by prim; do not edit.
|
|
2811
|
+
# Install/uninstall: prim hooks install|uninstall --scope user
|
|
2812
|
+
# Runs prim only where activated \u2014 'prim enable' (this repo) or
|
|
2813
|
+
# 'git config --global prim.active true' (every repo). Chains to the repo's own
|
|
2814
|
+
# hook regardless, so inactive repos are unaffected.
|
|
2815
|
+
${gatedShim(spec.binName)}
|
|
2816
|
+
common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || exit 0
|
|
2817
|
+
repo_hook="$common_dir/hooks/${spec.hookName}"
|
|
2818
|
+
if [ -x "$repo_hook" ] && ! grep -q '${PRIM_MANAGED_MARK}' "$repo_hook" 2>/dev/null; then
|
|
2819
|
+
"$repo_hook" "$@" ${chainExit}
|
|
2820
|
+
fi
|
|
2821
|
+
exit 0
|
|
2822
|
+
`;
|
|
2823
|
+
}
|
|
2824
|
+
function passThroughScript(hookName) {
|
|
2825
|
+
return `#!/bin/sh
|
|
2826
|
+
# prim pass-through hook (core.hooksPath) \u2014 managed by prim; do not edit.
|
|
2827
|
+
common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || exit 0
|
|
2828
|
+
repo_hook="$common_dir/hooks/${hookName}"
|
|
2829
|
+
[ -x "$repo_hook" ] && exec "$repo_hook" "$@"
|
|
2830
|
+
exit 0
|
|
2831
|
+
`;
|
|
2832
|
+
}
|
|
2833
|
+
function ownedHookNames() {
|
|
2834
|
+
return [...HOOKS.map((s) => s.hookName), ...PASSTHROUGH_HOOKS];
|
|
2835
|
+
}
|
|
2836
|
+
function writeOwnHooks() {
|
|
2837
|
+
if (!existsSync7(PRIM_GIT_HOOKS_DIR)) {
|
|
2838
|
+
mkdirSync5(PRIM_GIT_HOOKS_DIR, { recursive: true });
|
|
2839
|
+
}
|
|
2840
|
+
for (const spec of HOOKS) {
|
|
2841
|
+
writeFileSync4(resolve(PRIM_GIT_HOOKS_DIR, spec.hookName), globalHookScript(spec), {
|
|
2842
|
+
mode: 493
|
|
2843
|
+
});
|
|
2844
|
+
}
|
|
2845
|
+
for (const name of PASSTHROUGH_HOOKS) {
|
|
2846
|
+
writeFileSync4(resolve(PRIM_GIT_HOOKS_DIR, name), passThroughScript(name), { mode: 493 });
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
function appendPrimBlock(hookPath, spec) {
|
|
2850
|
+
mergePrimBlock(hookPath, gatedBlock(spec), spec.binName);
|
|
2851
|
+
}
|
|
2852
|
+
function stripPrimBlock(hookPath, spec) {
|
|
2853
|
+
if (!existsSync7(hookPath)) return;
|
|
2854
|
+
const existing = readFileSync6(hookPath, "utf-8");
|
|
2855
|
+
const primCreated = existing.includes(PRIM_CREATED_MARK);
|
|
2856
|
+
const { start, end } = blockMarkers(spec);
|
|
2857
|
+
const s = existing.indexOf(start);
|
|
2858
|
+
const e = existing.indexOf(end);
|
|
2859
|
+
if (s === -1 || e === -1) return;
|
|
2860
|
+
const out = (existing.slice(0, s) + existing.slice(e + end.length)).replace(/\n{2,}$/, "\n");
|
|
2861
|
+
const remainder = out.replaceAll("#!/bin/sh", "").replaceAll(`# ${PRIM_CREATED_MARK}`, "").trim();
|
|
2862
|
+
if (primCreated && remainder === "") {
|
|
2863
|
+
unlinkSync2(hookPath);
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
writeFileSync4(hookPath, out, { mode: 493 });
|
|
2867
|
+
}
|
|
2868
|
+
function installGlobalHooks(opts = {}) {
|
|
2869
|
+
const global = gitConfigGet("--global");
|
|
2870
|
+
if (global === "") {
|
|
2871
|
+
const system = gitConfigGet("--system");
|
|
2872
|
+
if (system !== "" && !isOurHooksDir(system)) {
|
|
2873
|
+
if (!opts.force) {
|
|
2874
|
+
console.error(
|
|
2875
|
+
`[prim] system core.hooksPath is set to ${system}; a --global set would override it, and prim chains only to .git/hooks (not a system dir), so those hooks would stop firing. Skipping \u2014 re-run with --force to override, or run per-repo \`prim hooks install\`.`
|
|
2876
|
+
);
|
|
2877
|
+
return false;
|
|
2878
|
+
}
|
|
2879
|
+
console.error(
|
|
2880
|
+
`[prim] --force: overriding system core.hooksPath ${system}; its hooks will no longer fire (prim chains only to .git/hooks).`
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
writeOwnHooks();
|
|
2884
|
+
execFileSync("git", ["config", "--global", "core.hooksPath", PRIM_GIT_HOOKS_DIR]);
|
|
2885
|
+
console.log(
|
|
2886
|
+
`Installed prim global git hooks; set core.hooksPath to ${PRIM_GIT_HOOKS_DIR}. Repos are opt-in: run \`prim enable\` in each repo to capture, or \`git config --global prim.active true\` for all.`
|
|
2887
|
+
);
|
|
2888
|
+
return true;
|
|
2889
|
+
}
|
|
2890
|
+
if (isOurHooksDir(global)) {
|
|
2891
|
+
writeOwnHooks();
|
|
2892
|
+
console.log(`Prim global git hooks already active (${PRIM_GIT_HOOKS_DIR}); refreshed scripts.`);
|
|
2893
|
+
return true;
|
|
2894
|
+
}
|
|
2895
|
+
const dir = expandTilde(global);
|
|
2896
|
+
for (const spec of HOOKS) {
|
|
2897
|
+
appendPrimBlock(resolve(dir, spec.hookName), spec);
|
|
2898
|
+
}
|
|
2899
|
+
console.log(
|
|
2900
|
+
`Appended prim hooks into existing core.hooksPath dir ${global} (pointer unchanged).`
|
|
2901
|
+
);
|
|
2902
|
+
return true;
|
|
2903
|
+
}
|
|
2904
|
+
function uninstallGlobalHooks() {
|
|
2905
|
+
const global = gitConfigGet("--global");
|
|
2906
|
+
if (isOurHooksDir(global)) {
|
|
2907
|
+
for (const name of ownedHookNames()) {
|
|
2908
|
+
const p = resolve(PRIM_GIT_HOOKS_DIR, name);
|
|
2909
|
+
if (existsSync7(p)) unlinkSync2(p);
|
|
2910
|
+
}
|
|
2911
|
+
execFileSync("git", ["config", "--global", "--unset", "core.hooksPath"]);
|
|
2912
|
+
console.log("Removed prim global git hooks and unset core.hooksPath.");
|
|
2913
|
+
return;
|
|
2914
|
+
}
|
|
2915
|
+
if (global !== "") {
|
|
2916
|
+
const dir = expandTilde(global);
|
|
2917
|
+
for (const spec of HOOKS) {
|
|
2918
|
+
stripPrimBlock(resolve(dir, spec.hookName), spec);
|
|
2919
|
+
}
|
|
2920
|
+
console.log(`Removed the prim block from ${global} (left the dir and core.hooksPath intact).`);
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
2923
|
+
console.log("No prim global git hooks found.");
|
|
2924
|
+
}
|
|
2471
2925
|
function installHooks(gitRoot, target) {
|
|
2472
2926
|
for (const spec of HOOKS) {
|
|
2473
2927
|
if (target === "husky") {
|
|
@@ -2476,6 +2930,7 @@ function installHooks(gitRoot, target) {
|
|
|
2476
2930
|
installToDotGit(gitRoot, spec);
|
|
2477
2931
|
}
|
|
2478
2932
|
}
|
|
2933
|
+
activateRepoBestEffort(gitRoot);
|
|
2479
2934
|
}
|
|
2480
2935
|
function registerHooksCommands(program2) {
|
|
2481
2936
|
const hooks = program2.command("hooks").description("Manage git hooks");
|
|
@@ -2486,36 +2941,58 @@ function registerHooksCommands(program2) {
|
|
|
2486
2941
|
"husky",
|
|
2487
2942
|
"git-hooks"
|
|
2488
2943
|
])
|
|
2489
|
-
).
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
)
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
if (nonInteractive) {
|
|
2500
|
-
throw new Error(
|
|
2501
|
-
"--non-interactive set, refusing to prompt for Husky-hook installation. Pass --yes to confirm or --target=git-hooks to choose."
|
|
2502
|
-
);
|
|
2944
|
+
).addOption(
|
|
2945
|
+
new Option(
|
|
2946
|
+
"--scope <scope>",
|
|
2947
|
+
"project (default, this repo) or user (a global core.hooksPath capturing every repo)"
|
|
2948
|
+
).choices(["project", "user"])
|
|
2949
|
+
).option("--force", "with --scope user, override a system-level core.hooksPath").action(
|
|
2950
|
+
async (opts, command) => {
|
|
2951
|
+
if (opts.scope === "user") {
|
|
2952
|
+
installGlobalHooks({ force: opts.force });
|
|
2953
|
+
return;
|
|
2503
2954
|
}
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
))
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2955
|
+
const globals = command.optsWithGlobals();
|
|
2956
|
+
const nonInteractive = Boolean(
|
|
2957
|
+
globals.nonInteractive || process.env.CI || process.env.PRIM_NON_INTERACTIVE
|
|
2958
|
+
);
|
|
2959
|
+
const gitRoot = getGitRoot();
|
|
2960
|
+
if (opts.target === "husky") return installHooks(gitRoot, "husky");
|
|
2961
|
+
if (opts.target === "git-hooks") return installHooks(gitRoot, "git-hooks");
|
|
2962
|
+
if (detectHusky(gitRoot)) {
|
|
2963
|
+
if (globals.yes) return installHooks(gitRoot, "husky");
|
|
2964
|
+
if (nonInteractive) {
|
|
2965
|
+
throw new Error(
|
|
2966
|
+
"--non-interactive set, refusing to prompt for Husky-hook installation. Pass --yes to confirm or --target=git-hooks to choose."
|
|
2967
|
+
);
|
|
2968
|
+
}
|
|
2969
|
+
if (!process.stdin.isTTY) {
|
|
2970
|
+
console.error(
|
|
2971
|
+
"Note: Husky detected but stdin is not a TTY \u2014 falling back to .git/hooks. Pass --yes for Husky or --non-interactive to fail fast."
|
|
2972
|
+
);
|
|
2973
|
+
} else if (await askConfirmation(
|
|
2974
|
+
"Husky detected. Install prim hooks into .husky/ instead of .git/hooks/?"
|
|
2975
|
+
)) {
|
|
2976
|
+
return installHooks(gitRoot, "husky");
|
|
2977
|
+
} else {
|
|
2978
|
+
console.log("Falling back to .git/hooks install.");
|
|
2979
|
+
}
|
|
2514
2980
|
}
|
|
2981
|
+
installHooks(gitRoot, "git-hooks");
|
|
2982
|
+
}
|
|
2983
|
+
);
|
|
2984
|
+
hooks.command("uninstall").description(
|
|
2985
|
+
"Remove the prim git hooks (.git/hooks, or the global core.hooksPath with --scope user)"
|
|
2986
|
+
).addOption(
|
|
2987
|
+
new Option(
|
|
2988
|
+
"--scope <scope>",
|
|
2989
|
+
"project (default, this repo) or user (global core.hooksPath)"
|
|
2990
|
+
).choices(["project", "user"])
|
|
2991
|
+
).action((opts) => {
|
|
2992
|
+
if (opts.scope === "user") {
|
|
2993
|
+
uninstallGlobalHooks();
|
|
2994
|
+
return;
|
|
2515
2995
|
}
|
|
2516
|
-
installHooks(gitRoot, "git-hooks");
|
|
2517
|
-
});
|
|
2518
|
-
hooks.command("uninstall").description("Remove the prim git hooks (.git/hooks)").action(() => {
|
|
2519
2996
|
const gitRoot = getGitRoot();
|
|
2520
2997
|
for (const spec of HOOKS) {
|
|
2521
2998
|
const hookPath = resolve(gitRoot, ".git", "hooks", spec.hookName);
|
|
@@ -2535,7 +3012,7 @@ function registerHooksCommands(program2) {
|
|
|
2535
3012
|
|
|
2536
3013
|
// src/commands/moves.ts
|
|
2537
3014
|
import { existsSync as existsSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
2538
|
-
import { join as
|
|
3015
|
+
import { join as join7 } from "path";
|
|
2539
3016
|
|
|
2540
3017
|
// src/flusher.ts
|
|
2541
3018
|
import { renameSync as renameSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
@@ -2694,18 +3171,18 @@ function registerMovesCommands(program2) {
|
|
|
2694
3171
|
}
|
|
2695
3172
|
});
|
|
2696
3173
|
moves.command("bind").description("Pin the current directory to an org via .prim/workspace.json").requiredOption("--orgId <orgId>", "Convex organization id").action((opts) => {
|
|
2697
|
-
const dir =
|
|
3174
|
+
const dir = join7(process.cwd(), ".prim");
|
|
2698
3175
|
if (!existsSync8(dir)) {
|
|
2699
3176
|
mkdirSync6(dir, { recursive: true, mode: DIR_MODE });
|
|
2700
3177
|
}
|
|
2701
|
-
const file =
|
|
3178
|
+
const file = join7(process.cwd(), WORKSPACE_FILE);
|
|
2702
3179
|
writeFileSync5(file, JSON.stringify({ orgId: opts.orgId, boundAt: Date.now() }, null, 2), {
|
|
2703
3180
|
mode: FILE_MODE2
|
|
2704
3181
|
});
|
|
2705
3182
|
console.log(`[prim] bound ${process.cwd()} to org ${opts.orgId}`);
|
|
2706
3183
|
});
|
|
2707
3184
|
moves.command("drop").description("Remove the .prim/workspace.json binding from the cwd").action(() => {
|
|
2708
|
-
const file =
|
|
3185
|
+
const file = join7(process.cwd(), WORKSPACE_FILE);
|
|
2709
3186
|
if (!existsSync8(file)) {
|
|
2710
3187
|
console.log("[prim] no workspace binding in cwd");
|
|
2711
3188
|
return;
|
|
@@ -2716,7 +3193,7 @@ function registerMovesCommands(program2) {
|
|
|
2716
3193
|
}
|
|
2717
3194
|
|
|
2718
3195
|
// src/commands/reconcile.ts
|
|
2719
|
-
var
|
|
3196
|
+
var EXIT_OK3 = 0;
|
|
2720
3197
|
var EXIT_USAGE2 = 2;
|
|
2721
3198
|
var EXIT_SERVER = 3;
|
|
2722
3199
|
var HTTP_CLIENT_ERROR_MIN = 400;
|
|
@@ -2779,7 +3256,7 @@ async function performReconcile(idOrShortId, opts = {}) {
|
|
|
2779
3256
|
`
|
|
2780
3257
|
);
|
|
2781
3258
|
console.log(JSON.stringify(response, null, 2));
|
|
2782
|
-
process.exitCode =
|
|
3259
|
+
process.exitCode = EXIT_OK3;
|
|
2783
3260
|
return;
|
|
2784
3261
|
}
|
|
2785
3262
|
process.stderr.write("[prim] reconcile: malformed server response\n");
|
|
@@ -2806,7 +3283,7 @@ import {
|
|
|
2806
3283
|
unlinkSync as unlinkSync5,
|
|
2807
3284
|
writeFileSync as writeFileSync6
|
|
2808
3285
|
} from "fs";
|
|
2809
|
-
import { join as
|
|
3286
|
+
import { join as join8 } from "path";
|
|
2810
3287
|
var DIR_MODE2 = 448;
|
|
2811
3288
|
var FILE_MODE3 = 384;
|
|
2812
3289
|
function ensureDir() {
|
|
@@ -2815,7 +3292,7 @@ function ensureDir() {
|
|
|
2815
3292
|
}
|
|
2816
3293
|
}
|
|
2817
3294
|
function markerPath(sessionId) {
|
|
2818
|
-
return
|
|
3295
|
+
return join8(SESSIONS_DIR, `${sessionId}.json`);
|
|
2819
3296
|
}
|
|
2820
3297
|
function registerSessionCommands(program2) {
|
|
2821
3298
|
const session = program2.command("session").description("Decision Event Pipeline \u2014 session binding markers");
|
|
@@ -2843,7 +3320,7 @@ function registerSessionCommands(program2) {
|
|
|
2843
3320
|
for (const f of files) {
|
|
2844
3321
|
const sessionId = f.replace(/\.json$/, "");
|
|
2845
3322
|
try {
|
|
2846
|
-
const m = JSON.parse(readFileSync7(
|
|
3323
|
+
const m = JSON.parse(readFileSync7(join8(SESSIONS_DIR, f), "utf-8"));
|
|
2847
3324
|
console.log(`${sessionId} org=${m.orgId}`);
|
|
2848
3325
|
} catch {
|
|
2849
3326
|
}
|
|
@@ -2862,6 +3339,8 @@ function registerSessionCommands(program2) {
|
|
|
2862
3339
|
|
|
2863
3340
|
// src/commands/setup.ts
|
|
2864
3341
|
import { spawnSync } from "child_process";
|
|
3342
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
3343
|
+
import { join as join9 } from "path";
|
|
2865
3344
|
var EXIT_INCOMPLETE = 1;
|
|
2866
3345
|
var EXIT_USAGE3 = 2;
|
|
2867
3346
|
var SESSION_LABELS = {
|
|
@@ -2888,11 +3367,57 @@ function planSetupSteps(opts) {
|
|
|
2888
3367
|
required: false
|
|
2889
3368
|
});
|
|
2890
3369
|
}
|
|
2891
|
-
steps.push({
|
|
2892
|
-
|
|
3370
|
+
steps.push({
|
|
3371
|
+
key: "hooks",
|
|
3372
|
+
label: "Git hooks",
|
|
3373
|
+
args: ["hooks", "install", ...scopeArgs],
|
|
3374
|
+
required: true
|
|
3375
|
+
});
|
|
3376
|
+
const skillArgs = ["skill", "install", "--agent", opts.agent, ...scopeArgs];
|
|
2893
3377
|
steps.push({ key: "skill", label: "Agent skill", args: skillArgs, required: true });
|
|
3378
|
+
if (opts.scope === "user") {
|
|
3379
|
+
steps.push({ key: "enable", label: "Activate this repo", args: ["enable"], required: false });
|
|
3380
|
+
}
|
|
3381
|
+
return steps;
|
|
3382
|
+
}
|
|
3383
|
+
var CONFLICT_SESSION = "session";
|
|
3384
|
+
var CONFLICT_HOOKS = "hooks";
|
|
3385
|
+
var CONFLICT_SKILL = "skill";
|
|
3386
|
+
function planCleanupUninstalls(agent, conflicts) {
|
|
3387
|
+
const steps = [];
|
|
3388
|
+
if (conflicts.includes(CONFLICT_SESSION) && agent !== "hermes") {
|
|
3389
|
+
steps.push([agent, "uninstall", "--scope", "project"]);
|
|
3390
|
+
}
|
|
3391
|
+
if (conflicts.includes(CONFLICT_HOOKS)) steps.push(["hooks", "uninstall"]);
|
|
3392
|
+
if (conflicts.includes(CONFLICT_SKILL)) steps.push(["skill", "uninstall", "--agent", agent]);
|
|
2894
3393
|
return steps;
|
|
2895
3394
|
}
|
|
3395
|
+
function detectProjectConflicts(agent, run) {
|
|
3396
|
+
const conflicts = [];
|
|
3397
|
+
if (agent !== "hermes") {
|
|
3398
|
+
try {
|
|
3399
|
+
const parsed = JSON.parse(run([agent, "status"], true).stdout || "{}");
|
|
3400
|
+
if (parsed.project?.gate || parsed.project?.capture) conflicts.push(CONFLICT_SESSION);
|
|
3401
|
+
} catch {
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
try {
|
|
3405
|
+
const root = gitToplevel();
|
|
3406
|
+
const preCommit = root && join9(root, ".git", "hooks", "pre-commit");
|
|
3407
|
+
if (preCommit && existsSync10(preCommit) && readFileSync8(preCommit, "utf-8").includes("prim-pre-commit")) {
|
|
3408
|
+
conflicts.push(CONFLICT_HOOKS);
|
|
3409
|
+
}
|
|
3410
|
+
} catch {
|
|
3411
|
+
}
|
|
3412
|
+
try {
|
|
3413
|
+
const parsed = JSON.parse(
|
|
3414
|
+
run(["skill", "status", "--agent", agent, "--json"], true).stdout || "{}"
|
|
3415
|
+
);
|
|
3416
|
+
if (parsed.installed) conflicts.push(CONFLICT_SKILL);
|
|
3417
|
+
} catch {
|
|
3418
|
+
}
|
|
3419
|
+
return conflicts;
|
|
3420
|
+
}
|
|
2896
3421
|
function detectAgent(env) {
|
|
2897
3422
|
if (env.HERMES_INTERACTIVE) {
|
|
2898
3423
|
return "hermes";
|
|
@@ -2908,7 +3433,14 @@ function resolveAgent(agentFlag, env) {
|
|
|
2908
3433
|
function registerSetupCommand(program2) {
|
|
2909
3434
|
program2.command("setup").description(
|
|
2910
3435
|
"Install everything in one shot (auth, session + git hooks, daemon, skill, welcome)"
|
|
2911
|
-
).option("--agent <agent>", "claude, codex, or hermes (auto-detected when omitted)").option(
|
|
3436
|
+
).option("--agent <agent>", "claude, codex, or hermes (auto-detected when omitted)").option(
|
|
3437
|
+
"--scope <scope>",
|
|
3438
|
+
"user (default \u2014 install once for every repo) or project (this repo only)",
|
|
3439
|
+
"user"
|
|
3440
|
+
).option(
|
|
3441
|
+
"--migrate",
|
|
3442
|
+
"with the default user scope, remove any project-scoped prim config in this repo (else just warn)"
|
|
3443
|
+
).option("--no-daemon", "skip starting the companion daemon").action((opts) => {
|
|
2912
3444
|
const { agent: agentInput, detected } = resolveAgent(opts.agent, process.env);
|
|
2913
3445
|
if (agentInput !== "claude" && agentInput !== "codex" && agentInput !== "hermes") {
|
|
2914
3446
|
process.stderr.write(
|
|
@@ -2927,7 +3459,7 @@ function registerSetupCommand(program2) {
|
|
|
2927
3459
|
const self = process.argv[1];
|
|
2928
3460
|
const run = (args, capture = false) => {
|
|
2929
3461
|
const r = spawnSync(process.execPath, [self, ...args], {
|
|
2930
|
-
stdio: capture ? ["inherit", "pipe", "
|
|
3462
|
+
stdio: capture ? ["inherit", "pipe", "ignore"] : "inherit",
|
|
2931
3463
|
encoding: "utf-8"
|
|
2932
3464
|
});
|
|
2933
3465
|
return { code: r.status ?? 1, stdout: capture ? r.stdout ?? "" : "" };
|
|
@@ -2964,6 +3496,18 @@ function registerSetupCommand(program2) {
|
|
|
2964
3496
|
const { code } = run(step.args);
|
|
2965
3497
|
results[step.key] = code === 0 ? "ok" : step.required ? "failed" : "skipped";
|
|
2966
3498
|
}
|
|
3499
|
+
if (scope === "user") {
|
|
3500
|
+
const conflicts = detectProjectConflicts(agent, run);
|
|
3501
|
+
if (conflicts.length > 0 && opts.migrate) {
|
|
3502
|
+
note(`migrate \xB7 removing project-scoped config (${conflicts.join(", ")})\u2026`);
|
|
3503
|
+
const ok = planCleanupUninstalls(agent, conflicts).map((args) => run(args).code === 0).every(Boolean);
|
|
3504
|
+
results.migrate = ok ? "ok" : "failed";
|
|
3505
|
+
} else if (conflicts.length > 0) {
|
|
3506
|
+
note(
|
|
3507
|
+
`migrate \xB7 project-scoped prim config present in this repo (${conflicts.join(", ")}) \u2014 it will double-fire with the user-scope install. Re-run \`prim setup --migrate\` to remove it.`
|
|
3508
|
+
);
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
2967
3511
|
note("welcome");
|
|
2968
3512
|
results.welcome = run(["welcome"]).code === 0 ? "ok" : "failed";
|
|
2969
3513
|
const failed = Object.entries(results).filter(([, v]) => v === "failed").map(([k]) => k);
|
|
@@ -2978,17 +3522,116 @@ function registerSetupCommand(program2) {
|
|
|
2978
3522
|
// src/commands/skill.ts
|
|
2979
3523
|
import {
|
|
2980
3524
|
closeSync as closeSync4,
|
|
2981
|
-
existsSync as
|
|
3525
|
+
existsSync as existsSync12,
|
|
2982
3526
|
fsyncSync as fsyncSync3,
|
|
2983
3527
|
openSync as openSync4,
|
|
2984
|
-
readFileSync as
|
|
3528
|
+
readFileSync as readFileSync10,
|
|
2985
3529
|
renameSync as renameSync4,
|
|
2986
3530
|
writeFileSync as writeFileSync7
|
|
2987
3531
|
} from "fs";
|
|
2988
|
-
import {
|
|
2989
|
-
import {
|
|
3532
|
+
import { homedir as homedir7 } from "os";
|
|
3533
|
+
import { dirname as dirname7, join as join11, resolve as resolve3 } from "path";
|
|
3534
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2990
3535
|
import { createPatch } from "diff";
|
|
2991
|
-
|
|
3536
|
+
|
|
3537
|
+
// src/commands/claude-plugin.ts
|
|
3538
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync9, readdirSync as readdirSync2, rmSync as rmSync3, rmdirSync } from "fs";
|
|
3539
|
+
import { homedir as homedir6 } from "os";
|
|
3540
|
+
import { dirname as dirname6, join as join10, resolve as resolve2 } from "path";
|
|
3541
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3542
|
+
var __dirname = dirname6(fileURLToPath2(import.meta.url));
|
|
3543
|
+
var PLUGIN_DESCRIPTION = "Primitive decision-graph guidance for the prim CLI \u2014 a model-invoked skill installed by prim skill install.";
|
|
3544
|
+
function resolvePluginDir(cwd, scope) {
|
|
3545
|
+
if (scope && scope !== "user" && scope !== "project") {
|
|
3546
|
+
console.error(`Unknown --scope "${scope}" (expected user or project)`);
|
|
3547
|
+
return null;
|
|
3548
|
+
}
|
|
3549
|
+
const base = scope === "user" ? join10(homedir6(), ".claude") : join10(gitToplevel(cwd) ?? cwd, ".claude");
|
|
3550
|
+
return join10(base, "skills", "prim");
|
|
3551
|
+
}
|
|
3552
|
+
function packageVersion() {
|
|
3553
|
+
let dir = __dirname;
|
|
3554
|
+
while (dir !== dirname6(dir)) {
|
|
3555
|
+
const p = resolve2(dir, "package.json");
|
|
3556
|
+
if (existsSync11(p)) return JSON.parse(readFileSync9(p, "utf-8")).version;
|
|
3557
|
+
dir = dirname6(dir);
|
|
3558
|
+
}
|
|
3559
|
+
return "0.0.0";
|
|
3560
|
+
}
|
|
3561
|
+
function pluginManifest() {
|
|
3562
|
+
const manifest = { name: "prim", description: PLUGIN_DESCRIPTION, version: packageVersion() };
|
|
3563
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
3564
|
+
`;
|
|
3565
|
+
}
|
|
3566
|
+
function removeDirIfEmpty(dir) {
|
|
3567
|
+
if (existsSync11(dir) && readdirSync2(dir).length === 0) rmdirSync(dir);
|
|
3568
|
+
}
|
|
3569
|
+
function pluginPaths(dir) {
|
|
3570
|
+
return {
|
|
3571
|
+
manifestPath: join10(dir, ".claude-plugin", "plugin.json"),
|
|
3572
|
+
skillPath: join10(dir, "SKILL.md")
|
|
3573
|
+
};
|
|
3574
|
+
}
|
|
3575
|
+
function installClaudePlugin(cwd, opts) {
|
|
3576
|
+
const dir = resolvePluginDir(cwd, opts.scope);
|
|
3577
|
+
if (dir === null) return 1;
|
|
3578
|
+
const { manifestPath, skillPath } = pluginPaths(dir);
|
|
3579
|
+
const manifest = pluginManifest();
|
|
3580
|
+
const skill = loadSkill();
|
|
3581
|
+
const manifestCurrent = existsSync11(manifestPath) ? readFileSync9(manifestPath, "utf-8") : null;
|
|
3582
|
+
const skillCurrent = existsSync11(skillPath) ? readFileSync9(skillPath, "utf-8") : null;
|
|
3583
|
+
if (manifestCurrent === manifest && skillCurrent === skill) {
|
|
3584
|
+
console.log("No changes \u2014 prim skill plugin already up to date.");
|
|
3585
|
+
return 0;
|
|
3586
|
+
}
|
|
3587
|
+
if (opts.dryRun) {
|
|
3588
|
+
console.log(`Would write plugin to ${dir} (.claude-plugin/plugin.json + SKILL.md)`);
|
|
3589
|
+
return 0;
|
|
3590
|
+
}
|
|
3591
|
+
mkdirSync8(join10(dir, ".claude-plugin"), { recursive: true });
|
|
3592
|
+
atomicWrite2(manifestPath, manifest);
|
|
3593
|
+
atomicWrite2(skillPath, skill);
|
|
3594
|
+
console.log(`Installed prim skill plugin at ${dir}`);
|
|
3595
|
+
console.log("Restart Claude Code or run /reload-plugins to load it.");
|
|
3596
|
+
if (opts.scope !== "user") {
|
|
3597
|
+
console.log("Project-scope plugins load only when Claude Code launches from this directory.");
|
|
3598
|
+
}
|
|
3599
|
+
return 0;
|
|
3600
|
+
}
|
|
3601
|
+
function uninstallClaudePlugin(cwd, opts) {
|
|
3602
|
+
const dir = resolvePluginDir(cwd, opts.scope);
|
|
3603
|
+
if (dir === null) return 1;
|
|
3604
|
+
const { manifestPath, skillPath } = pluginPaths(dir);
|
|
3605
|
+
if (!existsSync11(manifestPath) && !existsSync11(skillPath)) {
|
|
3606
|
+
console.log(`prim skill plugin not present at ${dir}`);
|
|
3607
|
+
return 0;
|
|
3608
|
+
}
|
|
3609
|
+
rmSync3(manifestPath, { force: true });
|
|
3610
|
+
rmSync3(skillPath, { force: true });
|
|
3611
|
+
removeDirIfEmpty(join10(dir, ".claude-plugin"));
|
|
3612
|
+
removeDirIfEmpty(dir);
|
|
3613
|
+
console.log(`Removed prim skill plugin from ${dir}`);
|
|
3614
|
+
return 0;
|
|
3615
|
+
}
|
|
3616
|
+
function statusClaudePlugin(cwd, opts) {
|
|
3617
|
+
const dir = resolvePluginDir(cwd, opts.scope);
|
|
3618
|
+
if (dir === null) return 1;
|
|
3619
|
+
const { skillPath } = pluginPaths(dir);
|
|
3620
|
+
const installed = existsSync11(skillPath);
|
|
3621
|
+
if (opts.json) {
|
|
3622
|
+
printJson({ installed, target: dir });
|
|
3623
|
+
return installed ? 0 : 1;
|
|
3624
|
+
}
|
|
3625
|
+
if (installed) {
|
|
3626
|
+
console.log(`prim skill plugin installed at ${dir}`);
|
|
3627
|
+
return 0;
|
|
3628
|
+
}
|
|
3629
|
+
console.log(`No prim skill plugin at ${dir}`);
|
|
3630
|
+
return 1;
|
|
3631
|
+
}
|
|
3632
|
+
|
|
3633
|
+
// src/commands/skill.ts
|
|
3634
|
+
var __dirname2 = dirname7(fileURLToPath3(import.meta.url));
|
|
2992
3635
|
var SKILL_BEGIN = "<!-- BEGIN PRIM SKILL v1 -->";
|
|
2993
3636
|
var SKILL_END = "<!-- END PRIM SKILL v1 -->";
|
|
2994
3637
|
var TARGET_CANDIDATES = [
|
|
@@ -3005,17 +3648,25 @@ var AGENT_TARGET = {
|
|
|
3005
3648
|
codex: "AGENTS.md",
|
|
3006
3649
|
hermes: ".hermes.md"
|
|
3007
3650
|
};
|
|
3651
|
+
function userTargetFor(agent) {
|
|
3652
|
+
if (agent === "claude") return join11(homedir7(), ".claude", "CLAUDE.md");
|
|
3653
|
+
if (agent === "codex") return join11(homedir7(), ".codex", "AGENTS.md");
|
|
3654
|
+
if (agent === "hermes") {
|
|
3655
|
+
return join11(process.env.HERMES_HOME ?? join11(homedir7(), ".hermes"), ".hermes.md");
|
|
3656
|
+
}
|
|
3657
|
+
return null;
|
|
3658
|
+
}
|
|
3008
3659
|
function loadSkill() {
|
|
3009
|
-
let dir =
|
|
3010
|
-
while (dir !==
|
|
3011
|
-
const p =
|
|
3012
|
-
if (
|
|
3013
|
-
dir =
|
|
3660
|
+
let dir = __dirname2;
|
|
3661
|
+
while (dir !== dirname7(dir)) {
|
|
3662
|
+
const p = resolve3(dir, "SKILL.md");
|
|
3663
|
+
if (existsSync12(p)) return readFileSync10(p, "utf-8");
|
|
3664
|
+
dir = dirname7(dir);
|
|
3014
3665
|
}
|
|
3015
3666
|
throw new Error("SKILL.md not found in package");
|
|
3016
3667
|
}
|
|
3017
3668
|
function detectTargets(cwd) {
|
|
3018
|
-
return TARGET_CANDIDATES.filter((p) =>
|
|
3669
|
+
return TARGET_CANDIDATES.filter((p) => existsSync12(resolve3(cwd, p)));
|
|
3019
3670
|
}
|
|
3020
3671
|
function detectNewline(content) {
|
|
3021
3672
|
return content.includes("\r\n") ? "\r\n" : "\n";
|
|
@@ -3053,24 +3704,39 @@ function atomicWrite2(target, content) {
|
|
|
3053
3704
|
renameSync4(tmp, target);
|
|
3054
3705
|
}
|
|
3055
3706
|
function resolveTarget(cwd, opts) {
|
|
3056
|
-
if (opts.
|
|
3707
|
+
if (opts.scope && opts.scope !== "user" && opts.scope !== "project") {
|
|
3708
|
+
console.error(`Unknown --scope "${opts.scope}" (expected user or project)`);
|
|
3709
|
+
return null;
|
|
3710
|
+
}
|
|
3711
|
+
if (opts.target) return resolve3(cwd, opts.target);
|
|
3712
|
+
if (opts.scope === "user") {
|
|
3713
|
+
if (!opts.agent) {
|
|
3714
|
+
console.error("--scope user requires --agent (claude, codex, or hermes)");
|
|
3715
|
+
return null;
|
|
3716
|
+
}
|
|
3717
|
+
const userTarget = userTargetFor(opts.agent);
|
|
3718
|
+
if (userTarget) return userTarget;
|
|
3719
|
+
console.error(`Unknown --agent "${opts.agent}" (expected claude, codex, or hermes)`);
|
|
3720
|
+
return null;
|
|
3721
|
+
}
|
|
3057
3722
|
if (opts.agent) {
|
|
3058
3723
|
const mapped = AGENT_TARGET[opts.agent];
|
|
3059
|
-
if (typeof mapped === "string") return
|
|
3724
|
+
if (typeof mapped === "string") return resolve3(cwd, mapped);
|
|
3060
3725
|
console.error(`Unknown --agent "${opts.agent}" (expected claude, codex, or hermes)`);
|
|
3061
3726
|
return null;
|
|
3062
3727
|
}
|
|
3063
3728
|
const matches = detectTargets(cwd);
|
|
3064
|
-
if (matches.length === 0) return
|
|
3065
|
-
if (matches.length === 1) return
|
|
3729
|
+
if (matches.length === 0) return resolve3(cwd, DEFAULT_TARGET);
|
|
3730
|
+
if (matches.length === 1) return resolve3(cwd, matches[0]);
|
|
3066
3731
|
console.error("Multiple rules files detected. Use --target to disambiguate:");
|
|
3067
3732
|
for (const m of matches) console.error(` ${m}`);
|
|
3068
3733
|
return null;
|
|
3069
3734
|
}
|
|
3070
3735
|
function runInstall(cwd, opts) {
|
|
3736
|
+
if (opts.agent === "claude" && !opts.target) return installClaudePlugin(cwd, opts);
|
|
3071
3737
|
const target = resolveTarget(cwd, opts);
|
|
3072
3738
|
if (target === null) return 1;
|
|
3073
|
-
const existing =
|
|
3739
|
+
const existing = existsSync12(target) ? readFileSync10(target, "utf-8") : "";
|
|
3074
3740
|
const eol = existing ? detectNewline(existing) : "\n";
|
|
3075
3741
|
const block = composeBlock(loadSkill(), eol);
|
|
3076
3742
|
const next = applyBlock(existing, block, eol);
|
|
@@ -3087,13 +3753,14 @@ function runInstall(cwd, opts) {
|
|
|
3087
3753
|
return 0;
|
|
3088
3754
|
}
|
|
3089
3755
|
function runUninstall(cwd, opts) {
|
|
3756
|
+
if (opts.agent === "claude" && !opts.target) return uninstallClaudePlugin(cwd, opts);
|
|
3090
3757
|
const target = resolveTarget(cwd, opts);
|
|
3091
3758
|
if (target === null) return 1;
|
|
3092
|
-
if (!
|
|
3759
|
+
if (!existsSync12(target)) {
|
|
3093
3760
|
console.log(`Skill block not present at ${target}`);
|
|
3094
3761
|
return 0;
|
|
3095
3762
|
}
|
|
3096
|
-
const existing =
|
|
3763
|
+
const existing = readFileSync10(target, "utf-8");
|
|
3097
3764
|
const next = removeBlock(existing);
|
|
3098
3765
|
if (next === null) {
|
|
3099
3766
|
console.log(`Skill block not present at ${target}`);
|
|
@@ -3104,12 +3771,13 @@ function runUninstall(cwd, opts) {
|
|
|
3104
3771
|
return 0;
|
|
3105
3772
|
}
|
|
3106
3773
|
function runStatus(cwd, opts) {
|
|
3774
|
+
if (opts.agent === "claude" && !opts.target) return statusClaudePlugin(cwd, opts);
|
|
3107
3775
|
const target = resolveTarget(cwd, opts);
|
|
3108
3776
|
if (target === null) return 1;
|
|
3109
|
-
const fileExists =
|
|
3777
|
+
const fileExists = existsSync12(target);
|
|
3110
3778
|
let installed = false;
|
|
3111
3779
|
if (fileExists) {
|
|
3112
|
-
const content =
|
|
3780
|
+
const content = readFileSync10(target, "utf-8");
|
|
3113
3781
|
installed = content.includes(SKILL_BEGIN) && content.includes(SKILL_END);
|
|
3114
3782
|
}
|
|
3115
3783
|
if (opts.json) {
|
|
@@ -3129,7 +3797,7 @@ function runStatus(cwd, opts) {
|
|
|
3129
3797
|
}
|
|
3130
3798
|
function registerSkillCommands(program2) {
|
|
3131
3799
|
const skill = program2.command("skill").description("Manage the prim skill in your project rules file");
|
|
3132
|
-
skill.command("install").description("Install the prim skill block into your project rules file").option("--target <path>", "Path to the rules file (overrides auto-detection)").option("--agent <agent>", "claude, codex, or hermes (selects the default rules file)").option("--dry-run", "Print a unified diff without writing").action((opts) => {
|
|
3800
|
+
skill.command("install").description("Install the prim skill block into your project rules file").option("--target <path>", "Path to the rules file (overrides auto-detection)").option("--agent <agent>", "claude, codex, or hermes (selects the default rules file)").option("--scope <scope>", "project (default, this repo) or user (machine-global \u2014 every repo)").option("--dry-run", "Print a unified diff without writing").action((opts) => {
|
|
3133
3801
|
try {
|
|
3134
3802
|
process.exit(runInstall(process.cwd(), opts));
|
|
3135
3803
|
} catch (err) {
|
|
@@ -3137,27 +3805,27 @@ function registerSkillCommands(program2) {
|
|
|
3137
3805
|
process.exit(2);
|
|
3138
3806
|
}
|
|
3139
3807
|
});
|
|
3140
|
-
skill.command("uninstall").description("Remove the prim skill block from your project rules file").option("--target <path>", "Path to the rules file (overrides auto-detection)").option("--agent <agent>", "claude, codex, or hermes (selects the default rules file)").action((opts) => {
|
|
3808
|
+
skill.command("uninstall").description("Remove the prim skill block from your project rules file").option("--target <path>", "Path to the rules file (overrides auto-detection)").option("--agent <agent>", "claude, codex, or hermes (selects the default rules file)").option("--scope <scope>", "project (default, this repo) or user (machine-global \u2014 every repo)").action((opts) => {
|
|
3141
3809
|
process.exit(runUninstall(process.cwd(), opts));
|
|
3142
3810
|
});
|
|
3143
|
-
skill.command("status").description("Report whether the prim skill block is installed").option("--target <path>", "Path to the rules file (overrides auto-detection)").option("--agent <agent>", "claude, codex, or hermes (selects the default rules file)").option("--json", "Output as JSON").action((opts) => {
|
|
3811
|
+
skill.command("status").description("Report whether the prim skill block is installed").option("--target <path>", "Path to the rules file (overrides auto-detection)").option("--agent <agent>", "claude, codex, or hermes (selects the default rules file)").option("--scope <scope>", "project (default, this repo) or user (machine-global \u2014 every repo)").option("--json", "Output as JSON").action((opts) => {
|
|
3144
3812
|
process.exit(runStatus(process.cwd(), opts));
|
|
3145
3813
|
});
|
|
3146
3814
|
}
|
|
3147
3815
|
|
|
3148
3816
|
// src/commands/statusline.ts
|
|
3149
|
-
import { readFileSync as
|
|
3150
|
-
import { dirname as
|
|
3151
|
-
import { fileURLToPath as
|
|
3817
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
3818
|
+
import { dirname as dirname8, resolve as resolve4 } from "path";
|
|
3819
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3152
3820
|
var STATUSLINE_TIMEOUT_MS = 200;
|
|
3153
3821
|
var STATUSLINE_NAME_CAP = 3;
|
|
3154
3822
|
function readPackageVersion() {
|
|
3155
3823
|
try {
|
|
3156
|
-
const here =
|
|
3157
|
-
const candidates = [
|
|
3824
|
+
const here = dirname8(fileURLToPath4(import.meta.url));
|
|
3825
|
+
const candidates = [resolve4(here, "../../package.json"), resolve4(here, "../package.json")];
|
|
3158
3826
|
for (const path of candidates) {
|
|
3159
3827
|
try {
|
|
3160
|
-
const pkg2 = JSON.parse(
|
|
3828
|
+
const pkg2 = JSON.parse(readFileSync11(path, "utf-8"));
|
|
3161
3829
|
if (pkg2.version) {
|
|
3162
3830
|
return pkg2.version;
|
|
3163
3831
|
}
|
|
@@ -3222,6 +3890,7 @@ var REVERSE_PROMPT_LINES = [
|
|
|
3222
3890
|
"to focus on those goals?"
|
|
3223
3891
|
];
|
|
3224
3892
|
var REVERSE_PROMPT = REVERSE_PROMPT_LINES.join(" ");
|
|
3893
|
+
var REVERSE_PROMPT_TEMPLATE = "We're interested in learning your current goals, as well as what you're not focusing on to achieve those goals. We found the following:\n\n$FOUND_GOALS\n\nHow do these look to you? Would you like to change these goals?";
|
|
3225
3894
|
var CALLOUT_TITLE = "Your turn";
|
|
3226
3895
|
var CALLOUT_INDENT = " ";
|
|
3227
3896
|
function ruledQuestion(lines) {
|
|
@@ -3308,6 +3977,7 @@ function welcomeJson(state) {
|
|
|
3308
3977
|
welcomed: true,
|
|
3309
3978
|
org: "seed",
|
|
3310
3979
|
reversePrompt: REVERSE_PROMPT,
|
|
3980
|
+
reversePromptTemplate: REVERSE_PROMPT_TEMPLATE,
|
|
3311
3981
|
recent: state.recent
|
|
3312
3982
|
};
|
|
3313
3983
|
}
|
|
@@ -3324,8 +3994,8 @@ function registerWelcomeCommand(program2, deps = { getClient }) {
|
|
|
3324
3994
|
}
|
|
3325
3995
|
|
|
3326
3996
|
// src/index.ts
|
|
3327
|
-
var
|
|
3328
|
-
var pkg = JSON.parse(
|
|
3997
|
+
var __dirname3 = dirname9(fileURLToPath5(import.meta.url));
|
|
3998
|
+
var pkg = JSON.parse(readFileSync12(resolve5(__dirname3, "../package.json"), "utf-8"));
|
|
3329
3999
|
updateNotifier({ pkg }).notify();
|
|
3330
4000
|
var program = new Command();
|
|
3331
4001
|
program.name("prim").description("CLI for Primitive's decision graph").version(pkg.version).option("-y, --yes", "auto-confirm prompts").option(
|
|
@@ -3334,6 +4004,7 @@ program.name("prim").description("CLI for Primitive's decision graph").version(p
|
|
|
3334
4004
|
);
|
|
3335
4005
|
registerAuthCommands(program);
|
|
3336
4006
|
registerHooksCommands(program);
|
|
4007
|
+
registerActivationCommands(program);
|
|
3337
4008
|
registerSkillCommands(program);
|
|
3338
4009
|
registerMovesCommands(program);
|
|
3339
4010
|
registerSessionCommands(program);
|