@primitive.ai/prim 0.1.0-alpha.50 → 0.1.0-alpha.52
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/SKILL.md +1 -1
- package/dist/{chunk-S5NUHFAD.js → chunk-4GLVJLEQ.js} +5 -5
- package/dist/chunk-ROL7SDXX.js +594 -0
- package/dist/hooks/prim-hook.js +1 -1
- package/dist/hooks/session-start.js +104 -68
- package/dist/index.js +22 -317
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: prim
|
|
3
|
-
description: Use the prim CLI for Primitive
|
|
3
|
+
description: Use the prim CLI for Primitive’s decision graph. MUST INVOKE before finishing any coding, planning, specification, or review task where the user or agent chose between plausible approaches or established or changed a lasting goal, priority, constraint, invariant, default, commitment, tradeoff, exception, or shared instruction—even when Primitive was not mentioned. Also invoke for Primitive setup, reading decisions, conflict gates, reconcile, rationale confirmations, linking, and team presence. SKIP routine implementation that merely follows an existing decision, temporary tactics, and unrelated uses of “decision.”
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Working with the prim CLI
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
function buildHookOutput(options) {
|
|
3
3
|
const output = {};
|
|
4
4
|
if (options.systemMessage) output.systemMessage = options.systemMessage;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
};
|
|
5
|
+
const fields = {};
|
|
6
|
+
if (options.additionalContext) fields.additionalContext = options.additionalContext;
|
|
7
|
+
if (options.reloadSkills) fields.reloadSkills = true;
|
|
8
|
+
if (Object.keys(fields).length > 0) {
|
|
9
|
+
output.hookSpecificOutput = { hookEventName: "SessionStart", ...fields };
|
|
10
10
|
}
|
|
11
11
|
return output;
|
|
12
12
|
}
|
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
import {
|
|
2
|
+
gitToplevel
|
|
3
|
+
} from "./chunk-R5ZJRSLV.js";
|
|
4
|
+
import {
|
|
5
|
+
withFileLock
|
|
6
|
+
} from "./chunk-DWDEQRWN.js";
|
|
7
|
+
|
|
8
|
+
// src/commands/skill.ts
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
10
|
+
import {
|
|
11
|
+
constants as constants2,
|
|
12
|
+
closeSync as closeSync2,
|
|
13
|
+
existsSync as existsSync2,
|
|
14
|
+
fstatSync as fstatSync2,
|
|
15
|
+
fsyncSync,
|
|
16
|
+
lstatSync as lstatSync2,
|
|
17
|
+
openSync as openSync2,
|
|
18
|
+
readFileSync as readFileSync2,
|
|
19
|
+
readSync as readSync2,
|
|
20
|
+
renameSync,
|
|
21
|
+
rmSync as rmSync2,
|
|
22
|
+
writeFileSync
|
|
23
|
+
} from "fs";
|
|
24
|
+
import { homedir as homedir2 } from "os";
|
|
25
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
26
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
27
|
+
import { createPatch } from "diff";
|
|
28
|
+
import { parse as parseYaml2 } from "yaml";
|
|
29
|
+
|
|
30
|
+
// src/output.ts
|
|
31
|
+
function printJson(data) {
|
|
32
|
+
console.log(JSON.stringify(data, null, 2));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/commands/claude-plugin.ts
|
|
36
|
+
import { createHash } from "crypto";
|
|
37
|
+
import {
|
|
38
|
+
constants,
|
|
39
|
+
closeSync,
|
|
40
|
+
existsSync,
|
|
41
|
+
fstatSync,
|
|
42
|
+
lstatSync,
|
|
43
|
+
mkdirSync,
|
|
44
|
+
openSync,
|
|
45
|
+
readFileSync,
|
|
46
|
+
readSync,
|
|
47
|
+
readdirSync,
|
|
48
|
+
realpathSync,
|
|
49
|
+
rmSync,
|
|
50
|
+
rmdirSync
|
|
51
|
+
} from "fs";
|
|
52
|
+
import { homedir } from "os";
|
|
53
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
54
|
+
import { fileURLToPath } from "url";
|
|
55
|
+
import { parse as parseYaml } from "yaml";
|
|
56
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
57
|
+
var PLUGIN_DESCRIPTION = "Primitive decision-graph guidance for the prim CLI \u2014 a model-invoked skill installed by prim skill install.";
|
|
58
|
+
var MAX_MANIFEST_BYTES = 64 * 1024;
|
|
59
|
+
var MAX_SKILL_BYTES = 1024 * 1024;
|
|
60
|
+
var REFRESH_LOCK_TIMEOUT_MS = 150;
|
|
61
|
+
var REFRESH_LOCK_POLL_MS = 10;
|
|
62
|
+
function pluginDirFor(cwd, scope) {
|
|
63
|
+
const base = scope === "user" ? join(homedir(), ".claude") : join(gitToplevel(cwd) ?? cwd, ".claude");
|
|
64
|
+
return join(base, "skills", "prim");
|
|
65
|
+
}
|
|
66
|
+
function resolvePluginDir(cwd, scope) {
|
|
67
|
+
if (scope && scope !== "user" && scope !== "project") {
|
|
68
|
+
console.error(`Unknown --scope "${scope}" (expected user or project)`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return pluginDirFor(cwd, scope === "user" ? "user" : "project");
|
|
72
|
+
}
|
|
73
|
+
function packageVersion() {
|
|
74
|
+
let dir = __dirname;
|
|
75
|
+
while (dir !== dirname(dir)) {
|
|
76
|
+
const p = resolve(dir, "package.json");
|
|
77
|
+
if (existsSync(p)) return JSON.parse(readFileSync(p, "utf-8")).version;
|
|
78
|
+
dir = dirname(dir);
|
|
79
|
+
}
|
|
80
|
+
return "0.0.0";
|
|
81
|
+
}
|
|
82
|
+
function pluginManifest(version) {
|
|
83
|
+
const manifest = { name: "prim", description: PLUGIN_DESCRIPTION, version };
|
|
84
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
function renderClaudePlugin() {
|
|
88
|
+
const version = packageVersion();
|
|
89
|
+
return { manifest: pluginManifest(version), skill: loadSkill(), version };
|
|
90
|
+
}
|
|
91
|
+
function removeDirIfEmpty(dir) {
|
|
92
|
+
if (existsSync(dir) && readdirSync(dir).length === 0) rmdirSync(dir);
|
|
93
|
+
}
|
|
94
|
+
function pluginPaths(dir) {
|
|
95
|
+
return {
|
|
96
|
+
manifestPath: join(dir, ".claude-plugin", "plugin.json"),
|
|
97
|
+
skillPath: join(dir, "SKILL.md")
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function parseSemver(value) {
|
|
101
|
+
if (typeof value !== "string" || value.length > 256) return;
|
|
102
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u.exec(
|
|
103
|
+
value
|
|
104
|
+
);
|
|
105
|
+
if (!match) return;
|
|
106
|
+
const prerelease = match[4]?.split(".") ?? [];
|
|
107
|
+
if (prerelease.some((part) => /^\d+$/u.test(part) && part.length > 1 && part.startsWith("0"))) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
return { core: [BigInt(match[1]), BigInt(match[2]), BigInt(match[3])], prerelease };
|
|
111
|
+
}
|
|
112
|
+
function compareSemver(left, right) {
|
|
113
|
+
const a = parseSemver(left);
|
|
114
|
+
const b = parseSemver(right);
|
|
115
|
+
if (!a || !b) return;
|
|
116
|
+
for (let i = 0; i < a.core.length; i += 1) {
|
|
117
|
+
if (a.core[i] !== b.core[i]) return a.core[i] < b.core[i] ? -1 : 1;
|
|
118
|
+
}
|
|
119
|
+
if (a.prerelease.length === 0 || b.prerelease.length === 0) {
|
|
120
|
+
return a.prerelease.length === b.prerelease.length ? 0 : a.prerelease.length === 0 ? 1 : -1;
|
|
121
|
+
}
|
|
122
|
+
const length = Math.max(a.prerelease.length, b.prerelease.length);
|
|
123
|
+
for (let i = 0; i < length; i += 1) {
|
|
124
|
+
const x = a.prerelease[i];
|
|
125
|
+
const y = b.prerelease[i];
|
|
126
|
+
if (x === void 0 || y === void 0) return x === void 0 ? -1 : 1;
|
|
127
|
+
if (x === y) continue;
|
|
128
|
+
const xNumeric = /^\d+$/u.test(x);
|
|
129
|
+
const yNumeric = /^\d+$/u.test(y);
|
|
130
|
+
if (xNumeric && yNumeric) return BigInt(x) < BigInt(y) ? -1 : 1;
|
|
131
|
+
if (xNumeric !== yNumeric) return xNumeric ? -1 : 1;
|
|
132
|
+
return x < y ? -1 : 1;
|
|
133
|
+
}
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
function pathIsInside(root, path) {
|
|
137
|
+
const rel = relative(root, path);
|
|
138
|
+
return rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
139
|
+
}
|
|
140
|
+
function managedPathsAreSafe(paths, projectRoot) {
|
|
141
|
+
try {
|
|
142
|
+
const managed = [paths.manifestPath, paths.skillPath];
|
|
143
|
+
if (managed.some((path) => !lstatSync(path).isFile())) return false;
|
|
144
|
+
if (projectRoot === void 0) return true;
|
|
145
|
+
const realRoot = realpathSync(projectRoot);
|
|
146
|
+
return managed.every((path) => pathIsInside(realRoot, realpathSync(path)));
|
|
147
|
+
} catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function readBoundedRegularFile(path, maxBytes) {
|
|
152
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
153
|
+
try {
|
|
154
|
+
const stat = fstatSync(fd);
|
|
155
|
+
if (!stat.isFile() || stat.size > maxBytes) throw new Error("unsafe managed file");
|
|
156
|
+
const chunks = [];
|
|
157
|
+
let total = 0;
|
|
158
|
+
while (total <= maxBytes) {
|
|
159
|
+
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1 - total));
|
|
160
|
+
const bytesRead = readSync(fd, chunk, 0, chunk.length, null);
|
|
161
|
+
if (bytesRead === 0) break;
|
|
162
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
163
|
+
total += bytesRead;
|
|
164
|
+
}
|
|
165
|
+
if (total > maxBytes) throw new Error("unsafe managed file");
|
|
166
|
+
return Buffer.concat(chunks, total).toString("utf8");
|
|
167
|
+
} finally {
|
|
168
|
+
closeSync(fd);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function hasUsablePrimFrontmatter(skill) {
|
|
172
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/u.exec(skill);
|
|
173
|
+
if (!match) return false;
|
|
174
|
+
try {
|
|
175
|
+
const parsed = parseYaml(match[1]);
|
|
176
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return false;
|
|
177
|
+
const frontmatter = parsed;
|
|
178
|
+
return frontmatter.name === "prim" && typeof frontmatter.description === "string" && frontmatter.description.trim().length > 0 && match[2].trim().length > 0;
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function inspectRecognizedInstallation(paths, projectRoot) {
|
|
184
|
+
if (!existsSync(paths.manifestPath) || !existsSync(paths.skillPath)) return;
|
|
185
|
+
if (!managedPathsAreSafe(paths, projectRoot)) return;
|
|
186
|
+
const manifest = readBoundedRegularFile(paths.manifestPath, MAX_MANIFEST_BYTES);
|
|
187
|
+
const parsed = JSON.parse(manifest);
|
|
188
|
+
if (parsed.name !== "prim") return;
|
|
189
|
+
const skill = readBoundedRegularFile(paths.skillPath, MAX_SKILL_BYTES);
|
|
190
|
+
return {
|
|
191
|
+
manifest,
|
|
192
|
+
manifestVersion: parsed.version,
|
|
193
|
+
skill,
|
|
194
|
+
usable: hasUsablePrimFrontmatter(skill)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function refreshLockDir(physicalPluginDir) {
|
|
198
|
+
const cacheRoot = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
|
|
199
|
+
const identity = createHash("sha256").update(physicalPluginDir).digest("hex");
|
|
200
|
+
return join(cacheRoot, "prim", "skill-refresh-locks", `${identity}.lock`);
|
|
201
|
+
}
|
|
202
|
+
async function refreshClaudePlugins(cwd, options = {}) {
|
|
203
|
+
const result = { installed: 0, refreshed: 0 };
|
|
204
|
+
const candidates = [];
|
|
205
|
+
if (options.includeProject !== false) {
|
|
206
|
+
const projectRoot = options.projectRoot ?? gitToplevel(cwd);
|
|
207
|
+
if (projectRoot !== null && isAbsolute(projectRoot)) {
|
|
208
|
+
candidates.push({ dir: join(projectRoot, ".claude", "skills", "prim"), projectRoot });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
candidates.push({ dir: pluginDirFor(cwd, "user") });
|
|
212
|
+
const seen = /* @__PURE__ */ new Set();
|
|
213
|
+
let desired;
|
|
214
|
+
for (const { dir, projectRoot } of candidates) {
|
|
215
|
+
let counted = false;
|
|
216
|
+
const markInstalled = () => {
|
|
217
|
+
if (!counted) {
|
|
218
|
+
result.installed += 1;
|
|
219
|
+
counted = true;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const refreshCandidate = () => {
|
|
223
|
+
const { manifestPath, skillPath } = pluginPaths(dir);
|
|
224
|
+
const paths = { manifestPath, skillPath };
|
|
225
|
+
try {
|
|
226
|
+
let changed = false;
|
|
227
|
+
const current = inspectRecognizedInstallation(paths, projectRoot);
|
|
228
|
+
if (!current) return;
|
|
229
|
+
desired ??= renderClaudePlugin();
|
|
230
|
+
if (projectRoot !== void 0 && current.usable && compareSemver(current.manifestVersion, desired.version) === 1) {
|
|
231
|
+
markInstalled();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (current.manifest !== desired.manifest) {
|
|
235
|
+
if (!managedPathsAreSafe(paths, projectRoot)) throw new Error("unsafe managed path");
|
|
236
|
+
atomicWrite(manifestPath, desired.manifest);
|
|
237
|
+
if (!changed) result.refreshed += 1;
|
|
238
|
+
changed = true;
|
|
239
|
+
}
|
|
240
|
+
if (current.skill !== desired.skill) {
|
|
241
|
+
if (!managedPathsAreSafe(paths, projectRoot)) throw new Error("unsafe managed path");
|
|
242
|
+
atomicWrite(skillPath, desired.skill);
|
|
243
|
+
if (!changed) result.refreshed += 1;
|
|
244
|
+
changed = true;
|
|
245
|
+
}
|
|
246
|
+
markInstalled();
|
|
247
|
+
} catch {
|
|
248
|
+
try {
|
|
249
|
+
if (inspectRecognizedInstallation(paths, projectRoot)?.usable) markInstalled();
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
try {
|
|
255
|
+
const paths = pluginPaths(dir);
|
|
256
|
+
if (!existsSync(paths.manifestPath) || !existsSync(paths.skillPath)) continue;
|
|
257
|
+
const physicalPluginDir = realpathSync(dir);
|
|
258
|
+
if (seen.has(physicalPluginDir)) continue;
|
|
259
|
+
seen.add(physicalPluginDir);
|
|
260
|
+
await withFileLock(refreshLockDir(physicalPluginDir), refreshCandidate, {
|
|
261
|
+
timeoutMs: REFRESH_LOCK_TIMEOUT_MS,
|
|
262
|
+
pollMs: REFRESH_LOCK_POLL_MS
|
|
263
|
+
});
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
function installClaudePlugin(cwd, opts) {
|
|
270
|
+
const dir = resolvePluginDir(cwd, opts.scope);
|
|
271
|
+
if (dir === null) return 1;
|
|
272
|
+
const { manifestPath, skillPath } = pluginPaths(dir);
|
|
273
|
+
const { manifest, skill } = renderClaudePlugin();
|
|
274
|
+
const manifestCurrent = existsSync(manifestPath) ? readFileSync(manifestPath, "utf-8") : null;
|
|
275
|
+
const skillCurrent = existsSync(skillPath) ? readFileSync(skillPath, "utf-8") : null;
|
|
276
|
+
if (manifestCurrent === manifest && skillCurrent === skill) {
|
|
277
|
+
console.log("No changes \u2014 prim skill plugin already up to date.");
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
280
|
+
if (opts.dryRun) {
|
|
281
|
+
console.log(`Would write plugin to ${dir} (.claude-plugin/plugin.json + SKILL.md)`);
|
|
282
|
+
return 0;
|
|
283
|
+
}
|
|
284
|
+
mkdirSync(join(dir, ".claude-plugin"), { recursive: true });
|
|
285
|
+
atomicWrite(manifestPath, manifest);
|
|
286
|
+
atomicWrite(skillPath, skill);
|
|
287
|
+
console.log(`Installed prim skill plugin at ${dir}`);
|
|
288
|
+
console.log("Restart Claude Code or run /reload-plugins to load it.");
|
|
289
|
+
if (opts.scope !== "user") {
|
|
290
|
+
console.log("Project-scope plugins load only when Claude Code launches from this directory.");
|
|
291
|
+
}
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
294
|
+
function uninstallClaudePlugin(cwd, opts) {
|
|
295
|
+
const dir = resolvePluginDir(cwd, opts.scope);
|
|
296
|
+
if (dir === null) return 1;
|
|
297
|
+
const { manifestPath, skillPath } = pluginPaths(dir);
|
|
298
|
+
if (!existsSync(manifestPath) && !existsSync(skillPath)) {
|
|
299
|
+
console.log(`prim skill plugin not present at ${dir}`);
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
rmSync(manifestPath, { force: true });
|
|
303
|
+
rmSync(skillPath, { force: true });
|
|
304
|
+
removeDirIfEmpty(join(dir, ".claude-plugin"));
|
|
305
|
+
removeDirIfEmpty(dir);
|
|
306
|
+
console.log(`Removed prim skill plugin from ${dir}`);
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
function statusClaudePlugin(cwd, opts) {
|
|
310
|
+
const dir = resolvePluginDir(cwd, opts.scope);
|
|
311
|
+
if (dir === null) return 1;
|
|
312
|
+
const { skillPath } = pluginPaths(dir);
|
|
313
|
+
const installed = existsSync(skillPath);
|
|
314
|
+
if (opts.json) {
|
|
315
|
+
printJson({ installed, target: dir });
|
|
316
|
+
return installed ? 0 : 1;
|
|
317
|
+
}
|
|
318
|
+
if (installed) {
|
|
319
|
+
console.log(`prim skill plugin installed at ${dir}`);
|
|
320
|
+
return 0;
|
|
321
|
+
}
|
|
322
|
+
console.log(`No prim skill plugin at ${dir}`);
|
|
323
|
+
return 1;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/commands/skill.ts
|
|
327
|
+
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
328
|
+
var SKILL_BEGIN = "<!-- BEGIN PRIM SKILL v1 -->";
|
|
329
|
+
var SKILL_END = "<!-- END PRIM SKILL v1 -->";
|
|
330
|
+
var CODEX_GUIDANCE_LIMIT_BYTES = 1024 * 1024;
|
|
331
|
+
var CODEX_DEFAULT_PROJECT_GUIDANCE_BYTES = 32 * 1024;
|
|
332
|
+
var SKILL_BEGIN_BUFFER = Buffer.from(SKILL_BEGIN);
|
|
333
|
+
var SKILL_END_BUFFER = Buffer.from(SKILL_END);
|
|
334
|
+
function readCodexGuidance(path) {
|
|
335
|
+
try {
|
|
336
|
+
const entry = lstatSync2(path);
|
|
337
|
+
if (entry.isSymbolicLink() || !entry.isFile()) return null;
|
|
338
|
+
const fd = openSync2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
339
|
+
try {
|
|
340
|
+
const opened = fstatSync2(fd);
|
|
341
|
+
if (!opened.isFile() || opened.size > CODEX_GUIDANCE_LIMIT_BYTES) return null;
|
|
342
|
+
const bytes = Buffer.allocUnsafe(CODEX_GUIDANCE_LIMIT_BYTES + 1);
|
|
343
|
+
let length = 0;
|
|
344
|
+
while (length < bytes.length) {
|
|
345
|
+
const read = readSync2(fd, bytes, length, bytes.length - length, length);
|
|
346
|
+
if (read === 0) break;
|
|
347
|
+
length += read;
|
|
348
|
+
}
|
|
349
|
+
return length > CODEX_GUIDANCE_LIMIT_BYTES ? null : bytes.subarray(0, length);
|
|
350
|
+
} finally {
|
|
351
|
+
closeSync2(fd);
|
|
352
|
+
}
|
|
353
|
+
} catch (error) {
|
|
354
|
+
const code = error.code;
|
|
355
|
+
return code === "ENOENT" || code === "ENOTDIR" ? void 0 : null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function effectiveCodexGuidance(scope) {
|
|
359
|
+
const override = readCodexGuidance(join2(scope, "AGENTS.override.md"));
|
|
360
|
+
if (override === null) return null;
|
|
361
|
+
if (override !== void 0 && override.toString("utf8").trim().length > 0) return override;
|
|
362
|
+
const base = readCodexGuidance(join2(scope, "AGENTS.md"));
|
|
363
|
+
return base ?? null;
|
|
364
|
+
}
|
|
365
|
+
function hasUsablePrimBlock(content, visibleBytes) {
|
|
366
|
+
const begin = content.indexOf(SKILL_BEGIN_BUFFER);
|
|
367
|
+
const end = content.indexOf(SKILL_END_BUFFER);
|
|
368
|
+
if (begin === -1 || end <= begin + SKILL_BEGIN_BUFFER.length || end + SKILL_END_BUFFER.length > visibleBytes || content.indexOf(SKILL_BEGIN_BUFFER, begin + SKILL_BEGIN_BUFFER.length) !== -1 || content.indexOf(SKILL_END_BUFFER, end + SKILL_END_BUFFER.length) !== -1) {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
const block = content.subarray(begin + SKILL_BEGIN_BUFFER.length, end).toString("utf8").trim();
|
|
372
|
+
const match = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)([\s\S]*)$/u.exec(block);
|
|
373
|
+
if (!match || match[2].trim().length === 0) return false;
|
|
374
|
+
try {
|
|
375
|
+
const metadata = parseYaml2(match[1]);
|
|
376
|
+
if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata)) return false;
|
|
377
|
+
const fields = metadata;
|
|
378
|
+
return fields.name === "prim" && typeof fields.description === "string" && fields.description.trim().length > 0;
|
|
379
|
+
} catch {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function hasUsableCodexGuidance(projectRoot) {
|
|
384
|
+
try {
|
|
385
|
+
const codexHome = process.env.CODEX_HOME?.trim() ? resolve2(process.env.CODEX_HOME) : join2(homedir2(), ".codex");
|
|
386
|
+
const scopes = [
|
|
387
|
+
{ path: codexHome, visibleBytes: CODEX_GUIDANCE_LIMIT_BYTES },
|
|
388
|
+
{ path: projectRoot, visibleBytes: CODEX_DEFAULT_PROJECT_GUIDANCE_BYTES }
|
|
389
|
+
];
|
|
390
|
+
return scopes.some(({ path, visibleBytes }) => {
|
|
391
|
+
const guidance = effectiveCodexGuidance(path);
|
|
392
|
+
return guidance !== null && hasUsablePrimBlock(guidance, visibleBytes);
|
|
393
|
+
});
|
|
394
|
+
} catch {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
var TARGET_CANDIDATES = [
|
|
399
|
+
"CLAUDE.md",
|
|
400
|
+
"AGENTS.md",
|
|
401
|
+
".hermes.md",
|
|
402
|
+
".cursor/rules",
|
|
403
|
+
".windsurfrules",
|
|
404
|
+
".github/instructions/primitive.md"
|
|
405
|
+
];
|
|
406
|
+
var DEFAULT_TARGET = "CLAUDE.md";
|
|
407
|
+
var AGENT_TARGET = {
|
|
408
|
+
claude: "CLAUDE.md",
|
|
409
|
+
codex: "AGENTS.md",
|
|
410
|
+
hermes: ".hermes.md"
|
|
411
|
+
};
|
|
412
|
+
function userTargetFor(agent) {
|
|
413
|
+
if (agent === "claude") return join2(homedir2(), ".claude", "CLAUDE.md");
|
|
414
|
+
if (agent === "codex") return join2(homedir2(), ".codex", "AGENTS.md");
|
|
415
|
+
if (agent === "hermes") {
|
|
416
|
+
return join2(process.env.HERMES_HOME ?? join2(homedir2(), ".hermes"), ".hermes.md");
|
|
417
|
+
}
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
function loadSkill() {
|
|
421
|
+
let dir = __dirname2;
|
|
422
|
+
while (dir !== dirname2(dir)) {
|
|
423
|
+
const p = resolve2(dir, "SKILL.md");
|
|
424
|
+
if (existsSync2(p)) return readFileSync2(p, "utf-8");
|
|
425
|
+
dir = dirname2(dir);
|
|
426
|
+
}
|
|
427
|
+
throw new Error("SKILL.md not found in package");
|
|
428
|
+
}
|
|
429
|
+
function detectTargets(cwd) {
|
|
430
|
+
return TARGET_CANDIDATES.filter((p) => existsSync2(resolve2(cwd, p)));
|
|
431
|
+
}
|
|
432
|
+
function detectNewline(content) {
|
|
433
|
+
return content.includes("\r\n") ? "\r\n" : "\n";
|
|
434
|
+
}
|
|
435
|
+
function composeBlock(skill, eol) {
|
|
436
|
+
const body = skill.replace(/\r?\n/g, eol);
|
|
437
|
+
return `${SKILL_BEGIN}${eol}${body}${eol}${SKILL_END}`;
|
|
438
|
+
}
|
|
439
|
+
function applyBlock(existing, block, eol) {
|
|
440
|
+
const b = existing.indexOf(SKILL_BEGIN);
|
|
441
|
+
const e = existing.indexOf(SKILL_END);
|
|
442
|
+
if (b !== -1 && e !== -1) {
|
|
443
|
+
return existing.slice(0, b) + block + existing.slice(e + SKILL_END.length);
|
|
444
|
+
}
|
|
445
|
+
if (existing.length === 0) return `${block}${eol}`;
|
|
446
|
+
const sep2 = existing.endsWith(eol) ? "" : eol;
|
|
447
|
+
return `${existing}${sep2}${block}${eol}`;
|
|
448
|
+
}
|
|
449
|
+
function removeBlock(existing) {
|
|
450
|
+
const b = existing.indexOf(SKILL_BEGIN);
|
|
451
|
+
const e = existing.indexOf(SKILL_END);
|
|
452
|
+
if (b === -1 || e === -1) return null;
|
|
453
|
+
const out = existing.slice(0, b) + existing.slice(e + SKILL_END.length);
|
|
454
|
+
return out.replace(/(\r?\n){2,}$/, "$1");
|
|
455
|
+
}
|
|
456
|
+
function atomicWrite(target, content) {
|
|
457
|
+
const tmp = `${target}.${randomUUID()}.tmp`;
|
|
458
|
+
let temporaryCreated = false;
|
|
459
|
+
try {
|
|
460
|
+
const fd = openSync2(tmp, "wx");
|
|
461
|
+
temporaryCreated = true;
|
|
462
|
+
try {
|
|
463
|
+
writeFileSync(fd, content);
|
|
464
|
+
fsyncSync(fd);
|
|
465
|
+
} finally {
|
|
466
|
+
closeSync2(fd);
|
|
467
|
+
}
|
|
468
|
+
renameSync(tmp, target);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
if (temporaryCreated) {
|
|
471
|
+
try {
|
|
472
|
+
rmSync2(tmp, { force: true });
|
|
473
|
+
} catch {
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
function resolveTarget(cwd, opts) {
|
|
480
|
+
if (opts.scope && opts.scope !== "user" && opts.scope !== "project") {
|
|
481
|
+
console.error(`Unknown --scope "${opts.scope}" (expected user or project)`);
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
if (opts.target) return resolve2(cwd, opts.target);
|
|
485
|
+
if (opts.scope === "user") {
|
|
486
|
+
if (!opts.agent) {
|
|
487
|
+
console.error("--scope user requires --agent (claude, codex, or hermes)");
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
const userTarget = userTargetFor(opts.agent);
|
|
491
|
+
if (userTarget) return userTarget;
|
|
492
|
+
console.error(`Unknown --agent "${opts.agent}" (expected claude, codex, or hermes)`);
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
if (opts.agent) {
|
|
496
|
+
const mapped = AGENT_TARGET[opts.agent];
|
|
497
|
+
if (typeof mapped === "string") return resolve2(cwd, mapped);
|
|
498
|
+
console.error(`Unknown --agent "${opts.agent}" (expected claude, codex, or hermes)`);
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
const matches = detectTargets(cwd);
|
|
502
|
+
if (matches.length === 0) return resolve2(cwd, DEFAULT_TARGET);
|
|
503
|
+
if (matches.length === 1) return resolve2(cwd, matches[0]);
|
|
504
|
+
console.error("Multiple rules files detected. Use --target to disambiguate:");
|
|
505
|
+
for (const m of matches) console.error(` ${m}`);
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
function runInstall(cwd, opts) {
|
|
509
|
+
if (opts.agent === "claude" && !opts.target) return installClaudePlugin(cwd, opts);
|
|
510
|
+
const target = resolveTarget(cwd, opts);
|
|
511
|
+
if (target === null) return 1;
|
|
512
|
+
const existing = existsSync2(target) ? readFileSync2(target, "utf-8") : "";
|
|
513
|
+
const eol = existing ? detectNewline(existing) : "\n";
|
|
514
|
+
const block = composeBlock(loadSkill(), eol);
|
|
515
|
+
const next = applyBlock(existing, block, eol);
|
|
516
|
+
if (next === existing) {
|
|
517
|
+
console.log("No changes \u2014 skill block already up to date.");
|
|
518
|
+
return 0;
|
|
519
|
+
}
|
|
520
|
+
if (opts.dryRun) {
|
|
521
|
+
process.stdout.write(createPatch(target, existing, next, "current", "proposed"));
|
|
522
|
+
return 0;
|
|
523
|
+
}
|
|
524
|
+
atomicWrite(target, next);
|
|
525
|
+
console.log(`Wrote ${Buffer.byteLength(next)} bytes to ${target}`);
|
|
526
|
+
return 0;
|
|
527
|
+
}
|
|
528
|
+
function runUninstall(cwd, opts) {
|
|
529
|
+
if (opts.agent === "claude" && !opts.target) return uninstallClaudePlugin(cwd, opts);
|
|
530
|
+
const target = resolveTarget(cwd, opts);
|
|
531
|
+
if (target === null) return 1;
|
|
532
|
+
if (!existsSync2(target)) {
|
|
533
|
+
console.log(`Skill block not present at ${target}`);
|
|
534
|
+
return 0;
|
|
535
|
+
}
|
|
536
|
+
const existing = readFileSync2(target, "utf-8");
|
|
537
|
+
const next = removeBlock(existing);
|
|
538
|
+
if (next === null) {
|
|
539
|
+
console.log(`Skill block not present at ${target}`);
|
|
540
|
+
return 0;
|
|
541
|
+
}
|
|
542
|
+
atomicWrite(target, next);
|
|
543
|
+
console.log(`Removed skill block from ${target}`);
|
|
544
|
+
return 0;
|
|
545
|
+
}
|
|
546
|
+
function runStatus(cwd, opts) {
|
|
547
|
+
if (opts.agent === "claude" && !opts.target) return statusClaudePlugin(cwd, opts);
|
|
548
|
+
const target = resolveTarget(cwd, opts);
|
|
549
|
+
if (target === null) return 1;
|
|
550
|
+
const fileExists = existsSync2(target);
|
|
551
|
+
let installed = false;
|
|
552
|
+
if (fileExists) {
|
|
553
|
+
const content = readFileSync2(target, "utf-8");
|
|
554
|
+
installed = content.includes(SKILL_BEGIN) && content.includes(SKILL_END);
|
|
555
|
+
}
|
|
556
|
+
if (opts.json) {
|
|
557
|
+
printJson({ installed, target });
|
|
558
|
+
return installed ? 0 : 1;
|
|
559
|
+
}
|
|
560
|
+
if (!fileExists) {
|
|
561
|
+
console.log(`No rules file at ${target}`);
|
|
562
|
+
return 1;
|
|
563
|
+
}
|
|
564
|
+
if (installed) {
|
|
565
|
+
console.log(`PRIM SKILL v1 installed at ${target}`);
|
|
566
|
+
return 0;
|
|
567
|
+
}
|
|
568
|
+
console.log(`No PRIM SKILL block at ${target}`);
|
|
569
|
+
return 1;
|
|
570
|
+
}
|
|
571
|
+
function registerSkillCommands(program) {
|
|
572
|
+
const skill = program.command("skill").description("Manage the prim skill in your project rules file");
|
|
573
|
+
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) => {
|
|
574
|
+
try {
|
|
575
|
+
process.exit(runInstall(process.cwd(), opts));
|
|
576
|
+
} catch (err) {
|
|
577
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
578
|
+
process.exit(2);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
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) => {
|
|
582
|
+
process.exit(runUninstall(process.cwd(), opts));
|
|
583
|
+
});
|
|
584
|
+
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) => {
|
|
585
|
+
process.exit(runStatus(process.cwd(), opts));
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export {
|
|
590
|
+
printJson,
|
|
591
|
+
refreshClaudePlugins,
|
|
592
|
+
hasUsableCodexGuidance,
|
|
593
|
+
registerSkillCommands
|
|
594
|
+
};
|
package/dist/hooks/prim-hook.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
hasUsableCodexGuidance,
|
|
4
|
+
refreshClaudePlugins
|
|
5
|
+
} from "../chunk-ROL7SDXX.js";
|
|
2
6
|
import {
|
|
3
7
|
buildHookOutput,
|
|
4
8
|
handoffHookOutput
|
|
5
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-4GLVJLEQ.js";
|
|
6
10
|
import {
|
|
7
11
|
FEEDBACK_DEADLINE_MS,
|
|
8
12
|
acknowledgeDecisionFeedback,
|
|
@@ -17,6 +21,7 @@ import {
|
|
|
17
21
|
warmBinCache
|
|
18
22
|
} from "../chunk-HSZPTVKU.js";
|
|
19
23
|
import {
|
|
24
|
+
gitToplevel,
|
|
20
25
|
isRepoActiveForCapture
|
|
21
26
|
} from "../chunk-R5ZJRSLV.js";
|
|
22
27
|
import {
|
|
@@ -53,69 +58,38 @@ function kickDaemonEnsure(options = {}) {
|
|
|
53
58
|
|
|
54
59
|
// src/hooks/reauth-notice.ts
|
|
55
60
|
var REAUTH_NOTICE = "[prim] authentication ended \u2014 run `prim auth login` to resume decision capture";
|
|
56
|
-
function
|
|
57
|
-
if (agent === "codex") {
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
if (agent === "claude_code") {
|
|
61
|
-
return buildHookOutput({ systemMessage: REAUTH_NOTICE });
|
|
62
|
-
}
|
|
61
|
+
function reauthNoticeFields(agent) {
|
|
62
|
+
if (agent === "codex") return { additionalContext: REAUTH_NOTICE };
|
|
63
|
+
if (agent === "claude_code") return { systemMessage: REAUTH_NOTICE };
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
// src/hooks/session-start.ts
|
|
67
|
-
var STDIN_TIMEOUT_MS = 1e3;
|
|
67
|
+
// src/hooks/session-start-core.ts
|
|
68
68
|
var DAEMON_TIMEOUT_MS = 250;
|
|
69
|
-
var
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return
|
|
69
|
+
var PRIM_TRIGGER = "When this task chooses between plausible approaches or establishes or changes a lasting goal, priority, constraint, invariant, default, commitment, tradeoff, exception, or shared instruction";
|
|
70
|
+
var PRIM_EXCLUSIONS = "Never invoke `prim` for routine implementation that merely follows an existing decision made before this task or for temporary tactics; they never qualify, including for evaluation.";
|
|
71
|
+
function primReminder(action, rationaleAction) {
|
|
72
|
+
return `Primitive is active in this repository. ${PRIM_TRIGGER}, ${action} before finishing. ${PRIM_EXCLUSIONS} When a direct request replaces one lasting default with another but supplies no rationale, complete the work, ${rationaleAction}, and ask one concise rationale question at the task boundary, even if the user requested only implementation or recording fails.`;
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
reject(new Error("stdin read timeout"));
|
|
79
|
-
}, STDIN_TIMEOUT_MS);
|
|
80
|
-
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
81
|
-
process.stdin.on("end", () => {
|
|
82
|
-
clearTimeout(timer);
|
|
83
|
-
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
84
|
-
});
|
|
85
|
-
process.stdin.on("error", (err) => {
|
|
86
|
-
clearTimeout(timer);
|
|
87
|
-
reject(err);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
async function main() {
|
|
92
|
-
const feedbackSignal = AbortSignal.timeout(FEEDBACK_DEADLINE_MS);
|
|
93
|
-
warmBinCache();
|
|
94
|
-
const agent = parseAgent(process.argv);
|
|
95
|
-
let raw;
|
|
96
|
-
try {
|
|
97
|
-
raw = await readStdin();
|
|
98
|
-
} catch {
|
|
99
|
-
await emitOutput(buildHookOutput({}));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
74
|
+
var PRIM_SKILL_REMINDER = primReminder("invoke the `prim` skill", "invoke the skill");
|
|
75
|
+
var CODEX_PRIM_ACTION = "follow the installed Prim workflow and use the `prim` CLI";
|
|
76
|
+
var CODEX_PRIM_REMINDER = primReminder(CODEX_PRIM_ACTION, CODEX_PRIM_ACTION);
|
|
77
|
+
async function processSessionStart(raw, agent, feedbackSignal = AbortSignal.timeout(FEEDBACK_DEADLINE_MS)) {
|
|
102
78
|
let envelope;
|
|
103
79
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
80
|
+
const parsed = JSON.parse(raw);
|
|
81
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
82
|
+
return { output: buildHookOutput({}) };
|
|
83
|
+
}
|
|
84
|
+
envelope = normalizeEnvelope(parsed, agent);
|
|
108
85
|
} catch {
|
|
109
|
-
|
|
110
|
-
return;
|
|
86
|
+
return { output: buildHookOutput({}) };
|
|
111
87
|
}
|
|
112
88
|
if (envelope.hook_event_name !== "SessionStart") {
|
|
113
|
-
|
|
114
|
-
return;
|
|
89
|
+
return { output: buildHookOutput({}) };
|
|
115
90
|
}
|
|
116
91
|
if (typeof envelope.session_id !== "string" || envelope.session_id.length === 0) {
|
|
117
|
-
|
|
118
|
-
return;
|
|
92
|
+
return { output: buildHookOutput({}) };
|
|
119
93
|
}
|
|
120
94
|
kickDaemonEnsure();
|
|
121
95
|
await daemonRequest(
|
|
@@ -123,11 +97,26 @@ async function main() {
|
|
|
123
97
|
{ sessionId: envelope.session_id },
|
|
124
98
|
{ timeoutMs: DAEMON_TIMEOUT_MS }
|
|
125
99
|
);
|
|
100
|
+
const cwd = envelope.cwd ?? process.cwd();
|
|
101
|
+
let active = false;
|
|
102
|
+
let skillState = { installed: 0, refreshed: 0 };
|
|
103
|
+
if (agent === "claude_code") {
|
|
104
|
+
try {
|
|
105
|
+
const projectRoot = gitToplevel(cwd);
|
|
106
|
+
active = projectRoot !== null && isRepoActiveForCapture(cwd);
|
|
107
|
+
skillState = await refreshClaudePlugins(
|
|
108
|
+
cwd,
|
|
109
|
+
active && projectRoot !== null ? { includeProject: true, projectRoot } : { includeProject: false }
|
|
110
|
+
);
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
}
|
|
126
114
|
if (isSessionEnded()) {
|
|
127
|
-
const notice =
|
|
115
|
+
const notice = reauthNoticeFields(agent);
|
|
128
116
|
if (notice) {
|
|
129
|
-
|
|
130
|
-
|
|
117
|
+
return {
|
|
118
|
+
output: buildHookOutput({ ...notice, reloadSkills: skillState.refreshed > 0 })
|
|
119
|
+
};
|
|
131
120
|
}
|
|
132
121
|
}
|
|
133
122
|
if (agent === "codex") {
|
|
@@ -138,16 +127,20 @@ async function main() {
|
|
|
138
127
|
{ callerEnv: getSiteUrl() },
|
|
139
128
|
{ timeoutMs: DAEMON_TIMEOUT_MS }
|
|
140
129
|
);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
);
|
|
145
|
-
|
|
130
|
+
const presence = snapshot && !snapshot.presenceStale && typeof snapshot.onlineCount === "number" ? `[prim] team: ${snapshot.onlineCount} online` : void 0;
|
|
131
|
+
let proactive = false;
|
|
132
|
+
try {
|
|
133
|
+
const projectRoot = gitToplevel(cwd);
|
|
134
|
+
proactive = projectRoot !== null && isRepoActiveForCapture(cwd) && hasUsableCodexGuidance(projectRoot);
|
|
135
|
+
} catch {
|
|
146
136
|
}
|
|
137
|
+
const additionalContext = [proactive ? CODEX_PRIM_REMINDER : void 0, presence].filter((value) => value !== void 0).join("\n\n");
|
|
138
|
+
return { output: buildHookOutput({ additionalContext }) };
|
|
147
139
|
}
|
|
148
140
|
if (agent === "claude_code") {
|
|
149
|
-
const
|
|
150
|
-
|
|
141
|
+
const additionalContext = active && skillState.installed > 0 ? PRIM_SKILL_REMINDER : void 0;
|
|
142
|
+
const reloadSkills = skillState.refreshed > 0;
|
|
143
|
+
if (active) {
|
|
151
144
|
const identity = getOrCreateWorkspaceId(cwd);
|
|
152
145
|
if (identity.status === "ready") {
|
|
153
146
|
const lease = await leaseDecisionFeedback({
|
|
@@ -156,21 +149,64 @@ async function main() {
|
|
|
156
149
|
signal: feedbackSignal
|
|
157
150
|
});
|
|
158
151
|
const rendered = lease ? renderFeedback(lease) : void 0;
|
|
159
|
-
|
|
160
|
-
buildHookOutput({
|
|
161
|
-
|
|
152
|
+
return {
|
|
153
|
+
output: buildHookOutput({
|
|
154
|
+
systemMessage: rendered?.systemMessage,
|
|
155
|
+
additionalContext,
|
|
156
|
+
reloadSkills
|
|
157
|
+
}),
|
|
158
|
+
acknowledge: rendered ? async () => {
|
|
162
159
|
await acknowledgeDecisionFeedback({
|
|
163
160
|
workspaceId: identity.workspaceId,
|
|
164
161
|
deliveries: rendered.deliveries,
|
|
165
162
|
signal: feedbackSignal
|
|
166
163
|
});
|
|
167
164
|
} : void 0
|
|
168
|
-
|
|
169
|
-
return;
|
|
165
|
+
};
|
|
170
166
|
}
|
|
171
167
|
}
|
|
168
|
+
return { output: buildHookOutput({ additionalContext, reloadSkills }) };
|
|
169
|
+
}
|
|
170
|
+
return { output: buildHookOutput({}) };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/hooks/session-start.ts
|
|
174
|
+
var STDIN_TIMEOUT_MS = 1e3;
|
|
175
|
+
var outputAttempted = false;
|
|
176
|
+
function emitOutput(output, acknowledge) {
|
|
177
|
+
outputAttempted = true;
|
|
178
|
+
return handoffHookOutput(output, acknowledge);
|
|
179
|
+
}
|
|
180
|
+
function readStdin() {
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
const chunks = [];
|
|
183
|
+
const timer = setTimeout(() => {
|
|
184
|
+
reject(new Error("stdin read timeout"));
|
|
185
|
+
}, STDIN_TIMEOUT_MS);
|
|
186
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
187
|
+
process.stdin.on("end", () => {
|
|
188
|
+
clearTimeout(timer);
|
|
189
|
+
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
190
|
+
});
|
|
191
|
+
process.stdin.on("error", (err) => {
|
|
192
|
+
clearTimeout(timer);
|
|
193
|
+
reject(err);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
async function main() {
|
|
198
|
+
const feedbackSignal = AbortSignal.timeout(FEEDBACK_DEADLINE_MS);
|
|
199
|
+
warmBinCache();
|
|
200
|
+
const agent = parseAgent(process.argv);
|
|
201
|
+
let raw;
|
|
202
|
+
try {
|
|
203
|
+
raw = await readStdin();
|
|
204
|
+
} catch {
|
|
205
|
+
await emitOutput(buildHookOutput({}));
|
|
206
|
+
return;
|
|
172
207
|
}
|
|
173
|
-
await
|
|
208
|
+
const result = await processSessionStart(raw, agent, feedbackSignal);
|
|
209
|
+
await emitOutput(result.output, result.acknowledge);
|
|
174
210
|
}
|
|
175
211
|
void main().catch(async () => {
|
|
176
212
|
if (!outputAttempted) await emitOutput(buildHookOutput({}));
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,10 @@ import {
|
|
|
22
22
|
pendingJournalStats,
|
|
23
23
|
readMovesFromPath
|
|
24
24
|
} from "./chunk-BOTKPLTI.js";
|
|
25
|
+
import {
|
|
26
|
+
printJson,
|
|
27
|
+
registerSkillCommands
|
|
28
|
+
} from "./chunk-ROL7SDXX.js";
|
|
25
29
|
import {
|
|
26
30
|
fetchFeedbackCapability
|
|
27
31
|
} from "./chunk-WELBNODJ.js";
|
|
@@ -63,17 +67,12 @@ import {
|
|
|
63
67
|
} from "./chunk-UTKQTZHL.js";
|
|
64
68
|
|
|
65
69
|
// src/index.ts
|
|
66
|
-
import { readFileSync as
|
|
67
|
-
import { dirname as
|
|
68
|
-
import { fileURLToPath as
|
|
70
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
71
|
+
import { dirname as dirname7, resolve as resolve4 } from "path";
|
|
72
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
69
73
|
import { Command } from "commander";
|
|
70
74
|
import updateNotifier from "update-notifier";
|
|
71
75
|
|
|
72
|
-
// src/output.ts
|
|
73
|
-
function printJson(data) {
|
|
74
|
-
console.log(JSON.stringify(data, null, 2));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
76
|
// src/commands/activation.ts
|
|
78
77
|
function applyActivation(active) {
|
|
79
78
|
const root = gitToplevel();
|
|
@@ -468,8 +467,8 @@ function registerAuthCommands(program2) {
|
|
|
468
467
|
const state = base64url(randomBytes(16));
|
|
469
468
|
const redirectUri = `http://${LOCALHOST}:${CALLBACK_PORT}/callback`;
|
|
470
469
|
let settle;
|
|
471
|
-
const outcome = new Promise((
|
|
472
|
-
settle =
|
|
470
|
+
const outcome = new Promise((resolve5) => {
|
|
471
|
+
settle = resolve5;
|
|
473
472
|
});
|
|
474
473
|
const server = createServer((req, res) => {
|
|
475
474
|
const url = new URL(req.url ?? "/", `http://${LOCALHOST}`);
|
|
@@ -483,12 +482,12 @@ function registerAuthCommands(program2) {
|
|
|
483
482
|
res.end(page.html, () => settle(page.result));
|
|
484
483
|
});
|
|
485
484
|
try {
|
|
486
|
-
await new Promise((
|
|
485
|
+
await new Promise((resolve5, reject) => {
|
|
487
486
|
const onError = (err) => reject(err);
|
|
488
487
|
server.once("error", onError);
|
|
489
488
|
server.listen(CALLBACK_PORT, LOCALHOST, () => {
|
|
490
489
|
server.removeListener("error", onError);
|
|
491
|
-
|
|
490
|
+
resolve5();
|
|
492
491
|
});
|
|
493
492
|
});
|
|
494
493
|
} catch (err) {
|
|
@@ -1767,7 +1766,7 @@ function clearStaleArtifacts() {
|
|
|
1767
1766
|
}
|
|
1768
1767
|
}
|
|
1769
1768
|
function sleep(ms) {
|
|
1770
|
-
return new Promise((
|
|
1769
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
1771
1770
|
}
|
|
1772
1771
|
function spawnDaemon(options) {
|
|
1773
1772
|
const file = binFile(DAEMON_BIN);
|
|
@@ -4871,319 +4870,25 @@ function registerSetupCommand(program2, dependencies = {}) {
|
|
|
4871
4870
|
});
|
|
4872
4871
|
}
|
|
4873
4872
|
|
|
4874
|
-
// src/commands/skill.ts
|
|
4875
|
-
import {
|
|
4876
|
-
closeSync as closeSync4,
|
|
4877
|
-
existsSync as existsSync12,
|
|
4878
|
-
fsyncSync as fsyncSync3,
|
|
4879
|
-
openSync as openSync4,
|
|
4880
|
-
readFileSync as readFileSync10,
|
|
4881
|
-
renameSync as renameSync5,
|
|
4882
|
-
writeFileSync as writeFileSync7
|
|
4883
|
-
} from "fs";
|
|
4884
|
-
import { homedir as homedir8 } from "os";
|
|
4885
|
-
import { dirname as dirname7, join as join12, resolve as resolve4 } from "path";
|
|
4886
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4887
|
-
import { createPatch } from "diff";
|
|
4888
|
-
|
|
4889
|
-
// src/commands/claude-plugin.ts
|
|
4890
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync9, readdirSync as readdirSync2, rmSync as rmSync3, rmdirSync } from "fs";
|
|
4891
|
-
import { homedir as homedir7 } from "os";
|
|
4892
|
-
import { dirname as dirname6, join as join11, resolve as resolve3 } from "path";
|
|
4893
|
-
import { fileURLToPath } from "url";
|
|
4894
|
-
var __dirname = dirname6(fileURLToPath(import.meta.url));
|
|
4895
|
-
var PLUGIN_DESCRIPTION = "Primitive decision-graph guidance for the prim CLI \u2014 a model-invoked skill installed by prim skill install.";
|
|
4896
|
-
function resolvePluginDir(cwd, scope) {
|
|
4897
|
-
if (scope && scope !== "user" && scope !== "project") {
|
|
4898
|
-
console.error(`Unknown --scope "${scope}" (expected user or project)`);
|
|
4899
|
-
return null;
|
|
4900
|
-
}
|
|
4901
|
-
const base = scope === "user" ? join11(homedir7(), ".claude") : join11(gitToplevel(cwd) ?? cwd, ".claude");
|
|
4902
|
-
return join11(base, "skills", "prim");
|
|
4903
|
-
}
|
|
4904
|
-
function packageVersion() {
|
|
4905
|
-
let dir = __dirname;
|
|
4906
|
-
while (dir !== dirname6(dir)) {
|
|
4907
|
-
const p = resolve3(dir, "package.json");
|
|
4908
|
-
if (existsSync11(p)) return JSON.parse(readFileSync9(p, "utf-8")).version;
|
|
4909
|
-
dir = dirname6(dir);
|
|
4910
|
-
}
|
|
4911
|
-
return "0.0.0";
|
|
4912
|
-
}
|
|
4913
|
-
function pluginManifest() {
|
|
4914
|
-
const manifest = { name: "prim", description: PLUGIN_DESCRIPTION, version: packageVersion() };
|
|
4915
|
-
return `${JSON.stringify(manifest, null, 2)}
|
|
4916
|
-
`;
|
|
4917
|
-
}
|
|
4918
|
-
function removeDirIfEmpty(dir) {
|
|
4919
|
-
if (existsSync11(dir) && readdirSync2(dir).length === 0) rmdirSync(dir);
|
|
4920
|
-
}
|
|
4921
|
-
function pluginPaths(dir) {
|
|
4922
|
-
return {
|
|
4923
|
-
manifestPath: join11(dir, ".claude-plugin", "plugin.json"),
|
|
4924
|
-
skillPath: join11(dir, "SKILL.md")
|
|
4925
|
-
};
|
|
4926
|
-
}
|
|
4927
|
-
function installClaudePlugin(cwd, opts) {
|
|
4928
|
-
const dir = resolvePluginDir(cwd, opts.scope);
|
|
4929
|
-
if (dir === null) return 1;
|
|
4930
|
-
const { manifestPath, skillPath } = pluginPaths(dir);
|
|
4931
|
-
const manifest = pluginManifest();
|
|
4932
|
-
const skill = loadSkill();
|
|
4933
|
-
const manifestCurrent = existsSync11(manifestPath) ? readFileSync9(manifestPath, "utf-8") : null;
|
|
4934
|
-
const skillCurrent = existsSync11(skillPath) ? readFileSync9(skillPath, "utf-8") : null;
|
|
4935
|
-
if (manifestCurrent === manifest && skillCurrent === skill) {
|
|
4936
|
-
console.log("No changes \u2014 prim skill plugin already up to date.");
|
|
4937
|
-
return 0;
|
|
4938
|
-
}
|
|
4939
|
-
if (opts.dryRun) {
|
|
4940
|
-
console.log(`Would write plugin to ${dir} (.claude-plugin/plugin.json + SKILL.md)`);
|
|
4941
|
-
return 0;
|
|
4942
|
-
}
|
|
4943
|
-
mkdirSync8(join11(dir, ".claude-plugin"), { recursive: true });
|
|
4944
|
-
atomicWrite3(manifestPath, manifest);
|
|
4945
|
-
atomicWrite3(skillPath, skill);
|
|
4946
|
-
console.log(`Installed prim skill plugin at ${dir}`);
|
|
4947
|
-
console.log("Restart Claude Code or run /reload-plugins to load it.");
|
|
4948
|
-
if (opts.scope !== "user") {
|
|
4949
|
-
console.log("Project-scope plugins load only when Claude Code launches from this directory.");
|
|
4950
|
-
}
|
|
4951
|
-
return 0;
|
|
4952
|
-
}
|
|
4953
|
-
function uninstallClaudePlugin(cwd, opts) {
|
|
4954
|
-
const dir = resolvePluginDir(cwd, opts.scope);
|
|
4955
|
-
if (dir === null) return 1;
|
|
4956
|
-
const { manifestPath, skillPath } = pluginPaths(dir);
|
|
4957
|
-
if (!existsSync11(manifestPath) && !existsSync11(skillPath)) {
|
|
4958
|
-
console.log(`prim skill plugin not present at ${dir}`);
|
|
4959
|
-
return 0;
|
|
4960
|
-
}
|
|
4961
|
-
rmSync3(manifestPath, { force: true });
|
|
4962
|
-
rmSync3(skillPath, { force: true });
|
|
4963
|
-
removeDirIfEmpty(join11(dir, ".claude-plugin"));
|
|
4964
|
-
removeDirIfEmpty(dir);
|
|
4965
|
-
console.log(`Removed prim skill plugin from ${dir}`);
|
|
4966
|
-
return 0;
|
|
4967
|
-
}
|
|
4968
|
-
function statusClaudePlugin(cwd, opts) {
|
|
4969
|
-
const dir = resolvePluginDir(cwd, opts.scope);
|
|
4970
|
-
if (dir === null) return 1;
|
|
4971
|
-
const { skillPath } = pluginPaths(dir);
|
|
4972
|
-
const installed = existsSync11(skillPath);
|
|
4973
|
-
if (opts.json) {
|
|
4974
|
-
printJson({ installed, target: dir });
|
|
4975
|
-
return installed ? 0 : 1;
|
|
4976
|
-
}
|
|
4977
|
-
if (installed) {
|
|
4978
|
-
console.log(`prim skill plugin installed at ${dir}`);
|
|
4979
|
-
return 0;
|
|
4980
|
-
}
|
|
4981
|
-
console.log(`No prim skill plugin at ${dir}`);
|
|
4982
|
-
return 1;
|
|
4983
|
-
}
|
|
4984
|
-
|
|
4985
|
-
// src/commands/skill.ts
|
|
4986
|
-
var __dirname2 = dirname7(fileURLToPath2(import.meta.url));
|
|
4987
|
-
var SKILL_BEGIN = "<!-- BEGIN PRIM SKILL v1 -->";
|
|
4988
|
-
var SKILL_END = "<!-- END PRIM SKILL v1 -->";
|
|
4989
|
-
var TARGET_CANDIDATES = [
|
|
4990
|
-
"CLAUDE.md",
|
|
4991
|
-
"AGENTS.md",
|
|
4992
|
-
".hermes.md",
|
|
4993
|
-
".cursor/rules",
|
|
4994
|
-
".windsurfrules",
|
|
4995
|
-
".github/instructions/primitive.md"
|
|
4996
|
-
];
|
|
4997
|
-
var DEFAULT_TARGET = "CLAUDE.md";
|
|
4998
|
-
var AGENT_TARGET = {
|
|
4999
|
-
claude: "CLAUDE.md",
|
|
5000
|
-
codex: "AGENTS.md",
|
|
5001
|
-
hermes: ".hermes.md"
|
|
5002
|
-
};
|
|
5003
|
-
function userTargetFor(agent) {
|
|
5004
|
-
if (agent === "claude") return join12(homedir8(), ".claude", "CLAUDE.md");
|
|
5005
|
-
if (agent === "codex") return join12(homedir8(), ".codex", "AGENTS.md");
|
|
5006
|
-
if (agent === "hermes") {
|
|
5007
|
-
return join12(process.env.HERMES_HOME ?? join12(homedir8(), ".hermes"), ".hermes.md");
|
|
5008
|
-
}
|
|
5009
|
-
return null;
|
|
5010
|
-
}
|
|
5011
|
-
function loadSkill() {
|
|
5012
|
-
let dir = __dirname2;
|
|
5013
|
-
while (dir !== dirname7(dir)) {
|
|
5014
|
-
const p = resolve4(dir, "SKILL.md");
|
|
5015
|
-
if (existsSync12(p)) return readFileSync10(p, "utf-8");
|
|
5016
|
-
dir = dirname7(dir);
|
|
5017
|
-
}
|
|
5018
|
-
throw new Error("SKILL.md not found in package");
|
|
5019
|
-
}
|
|
5020
|
-
function detectTargets(cwd) {
|
|
5021
|
-
return TARGET_CANDIDATES.filter((p) => existsSync12(resolve4(cwd, p)));
|
|
5022
|
-
}
|
|
5023
|
-
function detectNewline(content) {
|
|
5024
|
-
return content.includes("\r\n") ? "\r\n" : "\n";
|
|
5025
|
-
}
|
|
5026
|
-
function composeBlock(skill, eol) {
|
|
5027
|
-
const body = skill.replace(/\r?\n/g, eol);
|
|
5028
|
-
return `${SKILL_BEGIN}${eol}${body}${eol}${SKILL_END}`;
|
|
5029
|
-
}
|
|
5030
|
-
function applyBlock(existing, block, eol) {
|
|
5031
|
-
const b = existing.indexOf(SKILL_BEGIN);
|
|
5032
|
-
const e = existing.indexOf(SKILL_END);
|
|
5033
|
-
if (b !== -1 && e !== -1) {
|
|
5034
|
-
return existing.slice(0, b) + block + existing.slice(e + SKILL_END.length);
|
|
5035
|
-
}
|
|
5036
|
-
if (existing.length === 0) return `${block}${eol}`;
|
|
5037
|
-
const sep = existing.endsWith(eol) ? "" : eol;
|
|
5038
|
-
return `${existing}${sep}${block}${eol}`;
|
|
5039
|
-
}
|
|
5040
|
-
function removeBlock(existing) {
|
|
5041
|
-
const b = existing.indexOf(SKILL_BEGIN);
|
|
5042
|
-
const e = existing.indexOf(SKILL_END);
|
|
5043
|
-
if (b === -1 || e === -1) return null;
|
|
5044
|
-
const out = existing.slice(0, b) + existing.slice(e + SKILL_END.length);
|
|
5045
|
-
return out.replace(/(\r?\n){2,}$/, "$1");
|
|
5046
|
-
}
|
|
5047
|
-
function atomicWrite3(target, content) {
|
|
5048
|
-
const tmp = `${target}.tmp`;
|
|
5049
|
-
writeFileSync7(tmp, content);
|
|
5050
|
-
const fd = openSync4(tmp, "r+");
|
|
5051
|
-
try {
|
|
5052
|
-
fsyncSync3(fd);
|
|
5053
|
-
} finally {
|
|
5054
|
-
closeSync4(fd);
|
|
5055
|
-
}
|
|
5056
|
-
renameSync5(tmp, target);
|
|
5057
|
-
}
|
|
5058
|
-
function resolveTarget(cwd, opts) {
|
|
5059
|
-
if (opts.scope && opts.scope !== "user" && opts.scope !== "project") {
|
|
5060
|
-
console.error(`Unknown --scope "${opts.scope}" (expected user or project)`);
|
|
5061
|
-
return null;
|
|
5062
|
-
}
|
|
5063
|
-
if (opts.target) return resolve4(cwd, opts.target);
|
|
5064
|
-
if (opts.scope === "user") {
|
|
5065
|
-
if (!opts.agent) {
|
|
5066
|
-
console.error("--scope user requires --agent (claude, codex, or hermes)");
|
|
5067
|
-
return null;
|
|
5068
|
-
}
|
|
5069
|
-
const userTarget = userTargetFor(opts.agent);
|
|
5070
|
-
if (userTarget) return userTarget;
|
|
5071
|
-
console.error(`Unknown --agent "${opts.agent}" (expected claude, codex, or hermes)`);
|
|
5072
|
-
return null;
|
|
5073
|
-
}
|
|
5074
|
-
if (opts.agent) {
|
|
5075
|
-
const mapped = AGENT_TARGET[opts.agent];
|
|
5076
|
-
if (typeof mapped === "string") return resolve4(cwd, mapped);
|
|
5077
|
-
console.error(`Unknown --agent "${opts.agent}" (expected claude, codex, or hermes)`);
|
|
5078
|
-
return null;
|
|
5079
|
-
}
|
|
5080
|
-
const matches = detectTargets(cwd);
|
|
5081
|
-
if (matches.length === 0) return resolve4(cwd, DEFAULT_TARGET);
|
|
5082
|
-
if (matches.length === 1) return resolve4(cwd, matches[0]);
|
|
5083
|
-
console.error("Multiple rules files detected. Use --target to disambiguate:");
|
|
5084
|
-
for (const m of matches) console.error(` ${m}`);
|
|
5085
|
-
return null;
|
|
5086
|
-
}
|
|
5087
|
-
function runInstall(cwd, opts) {
|
|
5088
|
-
if (opts.agent === "claude" && !opts.target) return installClaudePlugin(cwd, opts);
|
|
5089
|
-
const target = resolveTarget(cwd, opts);
|
|
5090
|
-
if (target === null) return 1;
|
|
5091
|
-
const existing = existsSync12(target) ? readFileSync10(target, "utf-8") : "";
|
|
5092
|
-
const eol = existing ? detectNewline(existing) : "\n";
|
|
5093
|
-
const block = composeBlock(loadSkill(), eol);
|
|
5094
|
-
const next = applyBlock(existing, block, eol);
|
|
5095
|
-
if (next === existing) {
|
|
5096
|
-
console.log("No changes \u2014 skill block already up to date.");
|
|
5097
|
-
return 0;
|
|
5098
|
-
}
|
|
5099
|
-
if (opts.dryRun) {
|
|
5100
|
-
process.stdout.write(createPatch(target, existing, next, "current", "proposed"));
|
|
5101
|
-
return 0;
|
|
5102
|
-
}
|
|
5103
|
-
atomicWrite3(target, next);
|
|
5104
|
-
console.log(`Wrote ${Buffer.byteLength(next)} bytes to ${target}`);
|
|
5105
|
-
return 0;
|
|
5106
|
-
}
|
|
5107
|
-
function runUninstall(cwd, opts) {
|
|
5108
|
-
if (opts.agent === "claude" && !opts.target) return uninstallClaudePlugin(cwd, opts);
|
|
5109
|
-
const target = resolveTarget(cwd, opts);
|
|
5110
|
-
if (target === null) return 1;
|
|
5111
|
-
if (!existsSync12(target)) {
|
|
5112
|
-
console.log(`Skill block not present at ${target}`);
|
|
5113
|
-
return 0;
|
|
5114
|
-
}
|
|
5115
|
-
const existing = readFileSync10(target, "utf-8");
|
|
5116
|
-
const next = removeBlock(existing);
|
|
5117
|
-
if (next === null) {
|
|
5118
|
-
console.log(`Skill block not present at ${target}`);
|
|
5119
|
-
return 0;
|
|
5120
|
-
}
|
|
5121
|
-
atomicWrite3(target, next);
|
|
5122
|
-
console.log(`Removed skill block from ${target}`);
|
|
5123
|
-
return 0;
|
|
5124
|
-
}
|
|
5125
|
-
function runStatus(cwd, opts) {
|
|
5126
|
-
if (opts.agent === "claude" && !opts.target) return statusClaudePlugin(cwd, opts);
|
|
5127
|
-
const target = resolveTarget(cwd, opts);
|
|
5128
|
-
if (target === null) return 1;
|
|
5129
|
-
const fileExists = existsSync12(target);
|
|
5130
|
-
let installed = false;
|
|
5131
|
-
if (fileExists) {
|
|
5132
|
-
const content = readFileSync10(target, "utf-8");
|
|
5133
|
-
installed = content.includes(SKILL_BEGIN) && content.includes(SKILL_END);
|
|
5134
|
-
}
|
|
5135
|
-
if (opts.json) {
|
|
5136
|
-
printJson({ installed, target });
|
|
5137
|
-
return installed ? 0 : 1;
|
|
5138
|
-
}
|
|
5139
|
-
if (!fileExists) {
|
|
5140
|
-
console.log(`No rules file at ${target}`);
|
|
5141
|
-
return 1;
|
|
5142
|
-
}
|
|
5143
|
-
if (installed) {
|
|
5144
|
-
console.log(`PRIM SKILL v1 installed at ${target}`);
|
|
5145
|
-
return 0;
|
|
5146
|
-
}
|
|
5147
|
-
console.log(`No PRIM SKILL block at ${target}`);
|
|
5148
|
-
return 1;
|
|
5149
|
-
}
|
|
5150
|
-
function registerSkillCommands(program2) {
|
|
5151
|
-
const skill = program2.command("skill").description("Manage the prim skill in your project rules file");
|
|
5152
|
-
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) => {
|
|
5153
|
-
try {
|
|
5154
|
-
process.exit(runInstall(process.cwd(), opts));
|
|
5155
|
-
} catch (err) {
|
|
5156
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
5157
|
-
process.exit(2);
|
|
5158
|
-
}
|
|
5159
|
-
});
|
|
5160
|
-
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) => {
|
|
5161
|
-
process.exit(runUninstall(process.cwd(), opts));
|
|
5162
|
-
});
|
|
5163
|
-
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) => {
|
|
5164
|
-
process.exit(runStatus(process.cwd(), opts));
|
|
5165
|
-
});
|
|
5166
|
-
}
|
|
5167
|
-
|
|
5168
4873
|
// src/commands/statusline.ts
|
|
5169
|
-
import { readFileSync as
|
|
5170
|
-
import { dirname as
|
|
5171
|
-
import { fileURLToPath
|
|
4874
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
4875
|
+
import { dirname as dirname6, resolve as resolve3 } from "path";
|
|
4876
|
+
import { fileURLToPath } from "url";
|
|
5172
4877
|
var STATUSLINE_TIMEOUT_MS = 200;
|
|
5173
4878
|
var STATUSLINE_NAME_CAP = 3;
|
|
5174
4879
|
function readPackageVersion() {
|
|
5175
4880
|
try {
|
|
5176
|
-
const here =
|
|
4881
|
+
const here = dirname6(fileURLToPath(import.meta.url));
|
|
5177
4882
|
const candidates = [
|
|
5178
4883
|
// The supervised runtime stages a tiny manifest beside the standalone
|
|
5179
4884
|
// statusline bundle, so it remains versioned without loading the package.
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
4885
|
+
resolve3(here, "manifest.json"),
|
|
4886
|
+
resolve3(here, "../../package.json"),
|
|
4887
|
+
resolve3(here, "../package.json")
|
|
5183
4888
|
];
|
|
5184
4889
|
for (const path of candidates) {
|
|
5185
4890
|
try {
|
|
5186
|
-
const pkg2 = JSON.parse(
|
|
4891
|
+
const pkg2 = JSON.parse(readFileSync9(path, "utf-8"));
|
|
5187
4892
|
if (pkg2.version) {
|
|
5188
4893
|
return pkg2.version;
|
|
5189
4894
|
}
|
|
@@ -5370,8 +5075,8 @@ function registerWelcomeCommand(program2, deps = { getClient }) {
|
|
|
5370
5075
|
}
|
|
5371
5076
|
|
|
5372
5077
|
// src/index.ts
|
|
5373
|
-
var
|
|
5374
|
-
var pkg = JSON.parse(
|
|
5078
|
+
var __dirname = dirname7(fileURLToPath2(import.meta.url));
|
|
5079
|
+
var pkg = JSON.parse(readFileSync10(resolve4(__dirname, "../package.json"), "utf-8"));
|
|
5375
5080
|
updateNotifier({ pkg }).notify();
|
|
5376
5081
|
var program = new Command();
|
|
5377
5082
|
program.name("prim").description("CLI for Primitive's decision graph").version(pkg.version).option("-y, --yes", "auto-confirm prompts").option(
|
package/package.json
CHANGED