@primitive.ai/prim 0.1.0-alpha.34 → 0.1.0-alpha.36
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 +29 -11
- package/SKILL.md +16 -12
- 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 +803 -168
- 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
|
}
|
|
@@ -1862,7 +2105,7 @@ function registerDecisionsCommands(program2) {
|
|
|
1862
2105
|
"Functional area (auth, data, infra, ui, api, billing, mobile, docs, testing)"
|
|
1863
2106
|
).option("--decided <items>", "Comma-separated option(s) chosen").option("--alternatives <items>", "Comma-separated options considered but rejected").option("--confidence <level>", "high | medium | low (default high)").option("--reversibility <level>", "high | low (default high)").option(
|
|
1864
2107
|
"--files <paths>",
|
|
1865
|
-
"Comma-separated repo-relative paths this decision governs (
|
|
2108
|
+
"Comma-separated repo-relative paths this decision governs (the files Conflict Gates would check \u2014 not currently enabled)"
|
|
1866
2109
|
).action(async (opts) => {
|
|
1867
2110
|
const request = {
|
|
1868
2111
|
intent: opts.intent,
|
|
@@ -2385,9 +2628,10 @@ function registerHermesCommands(program2) {
|
|
|
2385
2628
|
}
|
|
2386
2629
|
|
|
2387
2630
|
// src/commands/hooks.ts
|
|
2388
|
-
import {
|
|
2631
|
+
import { execFileSync } from "child_process";
|
|
2389
2632
|
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
2390
|
-
import {
|
|
2633
|
+
import { homedir as homedir5 } from "os";
|
|
2634
|
+
import { dirname as dirname5, join as join6, resolve } from "path";
|
|
2391
2635
|
import { Option } from "commander";
|
|
2392
2636
|
var PRE_COMMIT = { hookName: "pre-commit", binName: "prim-pre-commit" };
|
|
2393
2637
|
var POST_COMMIT = { hookName: "post-commit", binName: "prim-post-commit" };
|
|
@@ -2400,18 +2644,25 @@ function blockMarkers(spec) {
|
|
|
2400
2644
|
}
|
|
2401
2645
|
var PRIM_BLOCK_START = blockMarkers(PRE_COMMIT).start;
|
|
2402
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";
|
|
2403
2649
|
function hookShim(binName) {
|
|
2404
2650
|
return `if command -v ${binName} >/dev/null 2>&1; then
|
|
2405
|
-
${binName}
|
|
2651
|
+
${binName} || true
|
|
2406
2652
|
elif [ -f "./node_modules/.bin/${binName}" ]; then
|
|
2407
|
-
./node_modules/.bin/${binName}
|
|
2653
|
+
./node_modules/.bin/${binName} || true
|
|
2408
2654
|
else
|
|
2409
2655
|
npx --yes -p @primitive.ai/prim ${binName} 2>/dev/null || true
|
|
2410
2656
|
fi`;
|
|
2411
2657
|
}
|
|
2658
|
+
function gatedShim(binName) {
|
|
2659
|
+
return `if [ "$(git config --get prim.active 2>/dev/null)" = "true" ]; then
|
|
2660
|
+
${hookShim(binName)}
|
|
2661
|
+
fi`;
|
|
2662
|
+
}
|
|
2412
2663
|
function dotGitScript(spec) {
|
|
2413
2664
|
return `#!/bin/sh
|
|
2414
|
-
# prim ${spec.hookName} hook \u2014 installed by: prim hooks install
|
|
2665
|
+
# prim ${spec.hookName} hook \u2014 installed by: prim hooks install (${PRIM_MANAGED_MARK})
|
|
2415
2666
|
|
|
2416
2667
|
${hookShim(spec.binName)}
|
|
2417
2668
|
`;
|
|
@@ -2422,10 +2673,35 @@ function huskyBlock(spec) {
|
|
|
2422
2673
|
${hookShim(spec.binName)}
|
|
2423
2674
|
${end}`;
|
|
2424
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
|
+
}
|
|
2425
2699
|
function getGitRoot() {
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
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;
|
|
2429
2705
|
}
|
|
2430
2706
|
function detectHusky(gitRoot) {
|
|
2431
2707
|
const huskyDir = resolve(gitRoot, ".husky");
|
|
@@ -2462,25 +2738,13 @@ async function askConfirmation(question) {
|
|
|
2462
2738
|
}
|
|
2463
2739
|
function installToHusky(gitRoot, spec = PRE_COMMIT) {
|
|
2464
2740
|
const hookPath = resolve(gitRoot, ".husky", spec.hookName);
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
}
|
|
2471
|
-
const separator = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
2472
|
-
writeFileSync4(hookPath, `${existing}${separator}${huskyBlock(spec)}
|
|
2473
|
-
`, {
|
|
2474
|
-
mode: 493
|
|
2475
|
-
});
|
|
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) {
|
|
2476
2746
|
console.log(`Appended prim hook block to .husky/${spec.hookName}.`);
|
|
2477
2747
|
} else {
|
|
2478
|
-
writeFileSync4(hookPath, `#!/bin/sh
|
|
2479
|
-
|
|
2480
|
-
${huskyBlock(spec)}
|
|
2481
|
-
`, {
|
|
2482
|
-
mode: 493
|
|
2483
|
-
});
|
|
2484
2748
|
console.log(`Created .husky/${spec.hookName} with prim hook block.`);
|
|
2485
2749
|
}
|
|
2486
2750
|
}
|
|
@@ -2503,6 +2767,161 @@ function installToDotGit(gitRoot, spec = PRE_COMMIT) {
|
|
|
2503
2767
|
writeFileSync4(hookPath, dotGitScript(spec), { mode: 493 });
|
|
2504
2768
|
console.log(`Installed ${spec.hookName} hook at ${hookPath}`);
|
|
2505
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
|
+
}
|
|
2506
2925
|
function installHooks(gitRoot, target) {
|
|
2507
2926
|
for (const spec of HOOKS) {
|
|
2508
2927
|
if (target === "husky") {
|
|
@@ -2511,6 +2930,7 @@ function installHooks(gitRoot, target) {
|
|
|
2511
2930
|
installToDotGit(gitRoot, spec);
|
|
2512
2931
|
}
|
|
2513
2932
|
}
|
|
2933
|
+
activateRepoBestEffort(gitRoot);
|
|
2514
2934
|
}
|
|
2515
2935
|
function registerHooksCommands(program2) {
|
|
2516
2936
|
const hooks = program2.command("hooks").description("Manage git hooks");
|
|
@@ -2521,36 +2941,58 @@ function registerHooksCommands(program2) {
|
|
|
2521
2941
|
"husky",
|
|
2522
2942
|
"git-hooks"
|
|
2523
2943
|
])
|
|
2524
|
-
).
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
)
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
if (nonInteractive) {
|
|
2535
|
-
throw new Error(
|
|
2536
|
-
"--non-interactive set, refusing to prompt for Husky-hook installation. Pass --yes to confirm or --target=git-hooks to choose."
|
|
2537
|
-
);
|
|
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;
|
|
2538
2954
|
}
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
))
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
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
|
+
}
|
|
2549
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;
|
|
2550
2995
|
}
|
|
2551
|
-
installHooks(gitRoot, "git-hooks");
|
|
2552
|
-
});
|
|
2553
|
-
hooks.command("uninstall").description("Remove the prim git hooks (.git/hooks)").action(() => {
|
|
2554
2996
|
const gitRoot = getGitRoot();
|
|
2555
2997
|
for (const spec of HOOKS) {
|
|
2556
2998
|
const hookPath = resolve(gitRoot, ".git", "hooks", spec.hookName);
|
|
@@ -2570,7 +3012,7 @@ function registerHooksCommands(program2) {
|
|
|
2570
3012
|
|
|
2571
3013
|
// src/commands/moves.ts
|
|
2572
3014
|
import { existsSync as existsSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
2573
|
-
import { join as
|
|
3015
|
+
import { join as join7 } from "path";
|
|
2574
3016
|
|
|
2575
3017
|
// src/flusher.ts
|
|
2576
3018
|
import { renameSync as renameSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
@@ -2729,18 +3171,18 @@ function registerMovesCommands(program2) {
|
|
|
2729
3171
|
}
|
|
2730
3172
|
});
|
|
2731
3173
|
moves.command("bind").description("Pin the current directory to an org via .prim/workspace.json").requiredOption("--orgId <orgId>", "Convex organization id").action((opts) => {
|
|
2732
|
-
const dir =
|
|
3174
|
+
const dir = join7(process.cwd(), ".prim");
|
|
2733
3175
|
if (!existsSync8(dir)) {
|
|
2734
3176
|
mkdirSync6(dir, { recursive: true, mode: DIR_MODE });
|
|
2735
3177
|
}
|
|
2736
|
-
const file =
|
|
3178
|
+
const file = join7(process.cwd(), WORKSPACE_FILE);
|
|
2737
3179
|
writeFileSync5(file, JSON.stringify({ orgId: opts.orgId, boundAt: Date.now() }, null, 2), {
|
|
2738
3180
|
mode: FILE_MODE2
|
|
2739
3181
|
});
|
|
2740
3182
|
console.log(`[prim] bound ${process.cwd()} to org ${opts.orgId}`);
|
|
2741
3183
|
});
|
|
2742
3184
|
moves.command("drop").description("Remove the .prim/workspace.json binding from the cwd").action(() => {
|
|
2743
|
-
const file =
|
|
3185
|
+
const file = join7(process.cwd(), WORKSPACE_FILE);
|
|
2744
3186
|
if (!existsSync8(file)) {
|
|
2745
3187
|
console.log("[prim] no workspace binding in cwd");
|
|
2746
3188
|
return;
|
|
@@ -2751,7 +3193,7 @@ function registerMovesCommands(program2) {
|
|
|
2751
3193
|
}
|
|
2752
3194
|
|
|
2753
3195
|
// src/commands/reconcile.ts
|
|
2754
|
-
var
|
|
3196
|
+
var EXIT_OK3 = 0;
|
|
2755
3197
|
var EXIT_USAGE2 = 2;
|
|
2756
3198
|
var EXIT_SERVER = 3;
|
|
2757
3199
|
var HTTP_CLIENT_ERROR_MIN = 400;
|
|
@@ -2814,7 +3256,7 @@ async function performReconcile(idOrShortId, opts = {}) {
|
|
|
2814
3256
|
`
|
|
2815
3257
|
);
|
|
2816
3258
|
console.log(JSON.stringify(response, null, 2));
|
|
2817
|
-
process.exitCode =
|
|
3259
|
+
process.exitCode = EXIT_OK3;
|
|
2818
3260
|
return;
|
|
2819
3261
|
}
|
|
2820
3262
|
process.stderr.write("[prim] reconcile: malformed server response\n");
|
|
@@ -2823,7 +3265,7 @@ async function performReconcile(idOrShortId, opts = {}) {
|
|
|
2823
3265
|
}
|
|
2824
3266
|
function registerReconcileCommands(program2) {
|
|
2825
3267
|
program2.command("reconcile <idOrShortId>").description(
|
|
2826
|
-
"Issue a single-use bypass for a
|
|
3268
|
+
"Issue a single-use bypass for a decision flagged by Conflict Gates Enforcement (not currently enabled)"
|
|
2827
3269
|
).option(
|
|
2828
3270
|
"--flag <conflictFlagId>",
|
|
2829
3271
|
"Specific flag id to bind the bypass to (default: the decision's latest unack'd flag)"
|
|
@@ -2841,7 +3283,7 @@ import {
|
|
|
2841
3283
|
unlinkSync as unlinkSync5,
|
|
2842
3284
|
writeFileSync as writeFileSync6
|
|
2843
3285
|
} from "fs";
|
|
2844
|
-
import { join as
|
|
3286
|
+
import { join as join8 } from "path";
|
|
2845
3287
|
var DIR_MODE2 = 448;
|
|
2846
3288
|
var FILE_MODE3 = 384;
|
|
2847
3289
|
function ensureDir() {
|
|
@@ -2850,7 +3292,7 @@ function ensureDir() {
|
|
|
2850
3292
|
}
|
|
2851
3293
|
}
|
|
2852
3294
|
function markerPath(sessionId) {
|
|
2853
|
-
return
|
|
3295
|
+
return join8(SESSIONS_DIR, `${sessionId}.json`);
|
|
2854
3296
|
}
|
|
2855
3297
|
function registerSessionCommands(program2) {
|
|
2856
3298
|
const session = program2.command("session").description("Decision Event Pipeline \u2014 session binding markers");
|
|
@@ -2878,7 +3320,7 @@ function registerSessionCommands(program2) {
|
|
|
2878
3320
|
for (const f of files) {
|
|
2879
3321
|
const sessionId = f.replace(/\.json$/, "");
|
|
2880
3322
|
try {
|
|
2881
|
-
const m = JSON.parse(readFileSync7(
|
|
3323
|
+
const m = JSON.parse(readFileSync7(join8(SESSIONS_DIR, f), "utf-8"));
|
|
2882
3324
|
console.log(`${sessionId} org=${m.orgId}`);
|
|
2883
3325
|
} catch {
|
|
2884
3326
|
}
|
|
@@ -2897,6 +3339,8 @@ function registerSessionCommands(program2) {
|
|
|
2897
3339
|
|
|
2898
3340
|
// src/commands/setup.ts
|
|
2899
3341
|
import { spawnSync } from "child_process";
|
|
3342
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
3343
|
+
import { join as join9 } from "path";
|
|
2900
3344
|
var EXIT_INCOMPLETE = 1;
|
|
2901
3345
|
var EXIT_USAGE3 = 2;
|
|
2902
3346
|
var SESSION_LABELS = {
|
|
@@ -2923,11 +3367,57 @@ function planSetupSteps(opts) {
|
|
|
2923
3367
|
required: false
|
|
2924
3368
|
});
|
|
2925
3369
|
}
|
|
2926
|
-
steps.push({
|
|
2927
|
-
|
|
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];
|
|
2928
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]);
|
|
2929
3393
|
return steps;
|
|
2930
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
|
+
}
|
|
2931
3421
|
function detectAgent(env) {
|
|
2932
3422
|
if (env.HERMES_INTERACTIVE) {
|
|
2933
3423
|
return "hermes";
|
|
@@ -2943,7 +3433,14 @@ function resolveAgent(agentFlag, env) {
|
|
|
2943
3433
|
function registerSetupCommand(program2) {
|
|
2944
3434
|
program2.command("setup").description(
|
|
2945
3435
|
"Install everything in one shot (auth, session + git hooks, daemon, skill, welcome)"
|
|
2946
|
-
).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) => {
|
|
2947
3444
|
const { agent: agentInput, detected } = resolveAgent(opts.agent, process.env);
|
|
2948
3445
|
if (agentInput !== "claude" && agentInput !== "codex" && agentInput !== "hermes") {
|
|
2949
3446
|
process.stderr.write(
|
|
@@ -2962,7 +3459,7 @@ function registerSetupCommand(program2) {
|
|
|
2962
3459
|
const self = process.argv[1];
|
|
2963
3460
|
const run = (args, capture = false) => {
|
|
2964
3461
|
const r = spawnSync(process.execPath, [self, ...args], {
|
|
2965
|
-
stdio: capture ? ["inherit", "pipe", "
|
|
3462
|
+
stdio: capture ? ["inherit", "pipe", "ignore"] : "inherit",
|
|
2966
3463
|
encoding: "utf-8"
|
|
2967
3464
|
});
|
|
2968
3465
|
return { code: r.status ?? 1, stdout: capture ? r.stdout ?? "" : "" };
|
|
@@ -2999,6 +3496,18 @@ function registerSetupCommand(program2) {
|
|
|
2999
3496
|
const { code } = run(step.args);
|
|
3000
3497
|
results[step.key] = code === 0 ? "ok" : step.required ? "failed" : "skipped";
|
|
3001
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
|
+
}
|
|
3002
3511
|
note("welcome");
|
|
3003
3512
|
results.welcome = run(["welcome"]).code === 0 ? "ok" : "failed";
|
|
3004
3513
|
const failed = Object.entries(results).filter(([, v]) => v === "failed").map(([k]) => k);
|
|
@@ -3013,17 +3522,116 @@ function registerSetupCommand(program2) {
|
|
|
3013
3522
|
// src/commands/skill.ts
|
|
3014
3523
|
import {
|
|
3015
3524
|
closeSync as closeSync4,
|
|
3016
|
-
existsSync as
|
|
3525
|
+
existsSync as existsSync12,
|
|
3017
3526
|
fsyncSync as fsyncSync3,
|
|
3018
3527
|
openSync as openSync4,
|
|
3019
|
-
readFileSync as
|
|
3528
|
+
readFileSync as readFileSync10,
|
|
3020
3529
|
renameSync as renameSync4,
|
|
3021
3530
|
writeFileSync as writeFileSync7
|
|
3022
3531
|
} from "fs";
|
|
3023
|
-
import {
|
|
3024
|
-
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";
|
|
3025
3535
|
import { createPatch } from "diff";
|
|
3026
|
-
|
|
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));
|
|
3027
3635
|
var SKILL_BEGIN = "<!-- BEGIN PRIM SKILL v1 -->";
|
|
3028
3636
|
var SKILL_END = "<!-- END PRIM SKILL v1 -->";
|
|
3029
3637
|
var TARGET_CANDIDATES = [
|
|
@@ -3040,17 +3648,25 @@ var AGENT_TARGET = {
|
|
|
3040
3648
|
codex: "AGENTS.md",
|
|
3041
3649
|
hermes: ".hermes.md"
|
|
3042
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
|
+
}
|
|
3043
3659
|
function loadSkill() {
|
|
3044
|
-
let dir =
|
|
3045
|
-
while (dir !==
|
|
3046
|
-
const p =
|
|
3047
|
-
if (
|
|
3048
|
-
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);
|
|
3049
3665
|
}
|
|
3050
3666
|
throw new Error("SKILL.md not found in package");
|
|
3051
3667
|
}
|
|
3052
3668
|
function detectTargets(cwd) {
|
|
3053
|
-
return TARGET_CANDIDATES.filter((p) =>
|
|
3669
|
+
return TARGET_CANDIDATES.filter((p) => existsSync12(resolve3(cwd, p)));
|
|
3054
3670
|
}
|
|
3055
3671
|
function detectNewline(content) {
|
|
3056
3672
|
return content.includes("\r\n") ? "\r\n" : "\n";
|
|
@@ -3088,24 +3704,39 @@ function atomicWrite2(target, content) {
|
|
|
3088
3704
|
renameSync4(tmp, target);
|
|
3089
3705
|
}
|
|
3090
3706
|
function resolveTarget(cwd, opts) {
|
|
3091
|
-
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
|
+
}
|
|
3092
3722
|
if (opts.agent) {
|
|
3093
3723
|
const mapped = AGENT_TARGET[opts.agent];
|
|
3094
|
-
if (typeof mapped === "string") return
|
|
3724
|
+
if (typeof mapped === "string") return resolve3(cwd, mapped);
|
|
3095
3725
|
console.error(`Unknown --agent "${opts.agent}" (expected claude, codex, or hermes)`);
|
|
3096
3726
|
return null;
|
|
3097
3727
|
}
|
|
3098
3728
|
const matches = detectTargets(cwd);
|
|
3099
|
-
if (matches.length === 0) return
|
|
3100
|
-
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]);
|
|
3101
3731
|
console.error("Multiple rules files detected. Use --target to disambiguate:");
|
|
3102
3732
|
for (const m of matches) console.error(` ${m}`);
|
|
3103
3733
|
return null;
|
|
3104
3734
|
}
|
|
3105
3735
|
function runInstall(cwd, opts) {
|
|
3736
|
+
if (opts.agent === "claude" && !opts.target) return installClaudePlugin(cwd, opts);
|
|
3106
3737
|
const target = resolveTarget(cwd, opts);
|
|
3107
3738
|
if (target === null) return 1;
|
|
3108
|
-
const existing =
|
|
3739
|
+
const existing = existsSync12(target) ? readFileSync10(target, "utf-8") : "";
|
|
3109
3740
|
const eol = existing ? detectNewline(existing) : "\n";
|
|
3110
3741
|
const block = composeBlock(loadSkill(), eol);
|
|
3111
3742
|
const next = applyBlock(existing, block, eol);
|
|
@@ -3122,13 +3753,14 @@ function runInstall(cwd, opts) {
|
|
|
3122
3753
|
return 0;
|
|
3123
3754
|
}
|
|
3124
3755
|
function runUninstall(cwd, opts) {
|
|
3756
|
+
if (opts.agent === "claude" && !opts.target) return uninstallClaudePlugin(cwd, opts);
|
|
3125
3757
|
const target = resolveTarget(cwd, opts);
|
|
3126
3758
|
if (target === null) return 1;
|
|
3127
|
-
if (!
|
|
3759
|
+
if (!existsSync12(target)) {
|
|
3128
3760
|
console.log(`Skill block not present at ${target}`);
|
|
3129
3761
|
return 0;
|
|
3130
3762
|
}
|
|
3131
|
-
const existing =
|
|
3763
|
+
const existing = readFileSync10(target, "utf-8");
|
|
3132
3764
|
const next = removeBlock(existing);
|
|
3133
3765
|
if (next === null) {
|
|
3134
3766
|
console.log(`Skill block not present at ${target}`);
|
|
@@ -3139,12 +3771,13 @@ function runUninstall(cwd, opts) {
|
|
|
3139
3771
|
return 0;
|
|
3140
3772
|
}
|
|
3141
3773
|
function runStatus(cwd, opts) {
|
|
3774
|
+
if (opts.agent === "claude" && !opts.target) return statusClaudePlugin(cwd, opts);
|
|
3142
3775
|
const target = resolveTarget(cwd, opts);
|
|
3143
3776
|
if (target === null) return 1;
|
|
3144
|
-
const fileExists =
|
|
3777
|
+
const fileExists = existsSync12(target);
|
|
3145
3778
|
let installed = false;
|
|
3146
3779
|
if (fileExists) {
|
|
3147
|
-
const content =
|
|
3780
|
+
const content = readFileSync10(target, "utf-8");
|
|
3148
3781
|
installed = content.includes(SKILL_BEGIN) && content.includes(SKILL_END);
|
|
3149
3782
|
}
|
|
3150
3783
|
if (opts.json) {
|
|
@@ -3164,7 +3797,7 @@ function runStatus(cwd, opts) {
|
|
|
3164
3797
|
}
|
|
3165
3798
|
function registerSkillCommands(program2) {
|
|
3166
3799
|
const skill = program2.command("skill").description("Manage the prim skill in your project rules file");
|
|
3167
|
-
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) => {
|
|
3168
3801
|
try {
|
|
3169
3802
|
process.exit(runInstall(process.cwd(), opts));
|
|
3170
3803
|
} catch (err) {
|
|
@@ -3172,27 +3805,27 @@ function registerSkillCommands(program2) {
|
|
|
3172
3805
|
process.exit(2);
|
|
3173
3806
|
}
|
|
3174
3807
|
});
|
|
3175
|
-
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) => {
|
|
3176
3809
|
process.exit(runUninstall(process.cwd(), opts));
|
|
3177
3810
|
});
|
|
3178
|
-
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) => {
|
|
3179
3812
|
process.exit(runStatus(process.cwd(), opts));
|
|
3180
3813
|
});
|
|
3181
3814
|
}
|
|
3182
3815
|
|
|
3183
3816
|
// src/commands/statusline.ts
|
|
3184
|
-
import { readFileSync as
|
|
3185
|
-
import { dirname as
|
|
3186
|
-
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";
|
|
3187
3820
|
var STATUSLINE_TIMEOUT_MS = 200;
|
|
3188
3821
|
var STATUSLINE_NAME_CAP = 3;
|
|
3189
3822
|
function readPackageVersion() {
|
|
3190
3823
|
try {
|
|
3191
|
-
const here =
|
|
3192
|
-
const candidates = [
|
|
3824
|
+
const here = dirname8(fileURLToPath4(import.meta.url));
|
|
3825
|
+
const candidates = [resolve4(here, "../../package.json"), resolve4(here, "../package.json")];
|
|
3193
3826
|
for (const path of candidates) {
|
|
3194
3827
|
try {
|
|
3195
|
-
const pkg2 = JSON.parse(
|
|
3828
|
+
const pkg2 = JSON.parse(readFileSync11(path, "utf-8"));
|
|
3196
3829
|
if (pkg2.version) {
|
|
3197
3830
|
return pkg2.version;
|
|
3198
3831
|
}
|
|
@@ -3257,6 +3890,7 @@ var REVERSE_PROMPT_LINES = [
|
|
|
3257
3890
|
"to focus on those goals?"
|
|
3258
3891
|
];
|
|
3259
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?";
|
|
3260
3894
|
var CALLOUT_TITLE = "Your turn";
|
|
3261
3895
|
var CALLOUT_INDENT = " ";
|
|
3262
3896
|
function ruledQuestion(lines) {
|
|
@@ -3291,14 +3925,13 @@ function formatWelcome(state) {
|
|
|
3291
3925
|
bold(color("Welcome to Primitive", "green")),
|
|
3292
3926
|
"",
|
|
3293
3927
|
"Primitive captures the decisions your team makes while coding into a",
|
|
3294
|
-
"shared graph \u2014
|
|
3295
|
-
"they land.",
|
|
3928
|
+
"shared graph \u2014 automatically, as you work.",
|
|
3296
3929
|
"",
|
|
3297
3930
|
bold("How it works"),
|
|
3298
3931
|
bullet("Capture is automatic \u2014 keep coding; your decisions are recorded for you."),
|
|
3299
|
-
bullet("
|
|
3300
|
-
" load-bearing decision
|
|
3301
|
-
"
|
|
3932
|
+
bullet("Conflict Gates (with Enforcement) flag or block edits that conflict"),
|
|
3933
|
+
" with a load-bearing decision \u2014 not currently enabled. Contact",
|
|
3934
|
+
" support@getprimitive.ai to turn them on for your team.",
|
|
3302
3935
|
bullet('Occasional yes/no prompts confirm the "why" behind a decision \u2014'),
|
|
3303
3936
|
" answering keeps the graph trustworthy.",
|
|
3304
3937
|
""
|
|
@@ -3343,6 +3976,7 @@ function welcomeJson(state) {
|
|
|
3343
3976
|
welcomed: true,
|
|
3344
3977
|
org: "seed",
|
|
3345
3978
|
reversePrompt: REVERSE_PROMPT,
|
|
3979
|
+
reversePromptTemplate: REVERSE_PROMPT_TEMPLATE,
|
|
3346
3980
|
recent: state.recent
|
|
3347
3981
|
};
|
|
3348
3982
|
}
|
|
@@ -3359,8 +3993,8 @@ function registerWelcomeCommand(program2, deps = { getClient }) {
|
|
|
3359
3993
|
}
|
|
3360
3994
|
|
|
3361
3995
|
// src/index.ts
|
|
3362
|
-
var
|
|
3363
|
-
var pkg = JSON.parse(
|
|
3996
|
+
var __dirname3 = dirname9(fileURLToPath5(import.meta.url));
|
|
3997
|
+
var pkg = JSON.parse(readFileSync12(resolve5(__dirname3, "../package.json"), "utf-8"));
|
|
3364
3998
|
updateNotifier({ pkg }).notify();
|
|
3365
3999
|
var program = new Command();
|
|
3366
4000
|
program.name("prim").description("CLI for Primitive's decision graph").version(pkg.version).option("-y, --yes", "auto-confirm prompts").option(
|
|
@@ -3369,6 +4003,7 @@ program.name("prim").description("CLI for Primitive's decision graph").version(p
|
|
|
3369
4003
|
);
|
|
3370
4004
|
registerAuthCommands(program);
|
|
3371
4005
|
registerHooksCommands(program);
|
|
4006
|
+
registerActivationCommands(program);
|
|
3372
4007
|
registerSkillCommands(program);
|
|
3373
4008
|
registerMovesCommands(program);
|
|
3374
4009
|
registerSessionCommands(program);
|