@primitive.ai/prim 0.1.0-alpha.18 → 0.1.0-alpha.20
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 +80 -37
- package/SKILL.md +40 -183
- package/dist/{chunk-6SIEWWUL.js → chunk-26VA3ADF.js} +1 -3
- package/dist/{chunk-TPQ3X244.js → chunk-E5UZXMZL.js} +2 -31
- package/dist/daemon/server.js +1 -1
- package/dist/hooks/post-tool-use.js +1 -1
- package/dist/hooks/pre-commit.js +4 -157
- package/dist/hooks/pre-tool-use.js +1 -1
- package/dist/index.js +252 -567
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -7,9 +7,8 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
checkAffectedDecisions,
|
|
9
9
|
daemonOrDirectGet,
|
|
10
|
-
formatDecisionsWarning
|
|
11
|
-
|
|
12
|
-
} from "./chunk-TPQ3X244.js";
|
|
10
|
+
formatDecisionsWarning
|
|
11
|
+
} from "./chunk-E5UZXMZL.js";
|
|
13
12
|
import {
|
|
14
13
|
HttpError,
|
|
15
14
|
REFRESH_TOKEN_PATH,
|
|
@@ -20,7 +19,7 @@ import {
|
|
|
20
19
|
getSiteUrl,
|
|
21
20
|
getTokenExpiresAt,
|
|
22
21
|
saveTokenExpiry
|
|
23
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-26VA3ADF.js";
|
|
24
23
|
import {
|
|
25
24
|
JOURNAL_DIR,
|
|
26
25
|
SESSIONS_DIR,
|
|
@@ -34,9 +33,9 @@ import {
|
|
|
34
33
|
} from "./chunk-UTKQTZHL.js";
|
|
35
34
|
|
|
36
35
|
// src/index.ts
|
|
37
|
-
import { readFileSync as
|
|
38
|
-
import { dirname as
|
|
39
|
-
import { fileURLToPath as
|
|
36
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
37
|
+
import { dirname as dirname6, resolve as resolve4 } from "path";
|
|
38
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
40
39
|
import { Command } from "commander";
|
|
41
40
|
import updateNotifier from "update-notifier";
|
|
42
41
|
|
|
@@ -125,7 +124,8 @@ function registerAuthCommands(program2) {
|
|
|
125
124
|
res.end("<h1>Authentication successful!</h1><p>You can close this tab.</p>");
|
|
126
125
|
exchangeCode(siteUrl, code, verifier, `http://${LOCALHOST}:${port}/callback`).then((token) => {
|
|
127
126
|
saveToken(token);
|
|
128
|
-
console.
|
|
127
|
+
console.error(`Authenticated! Token saved to ${TOKEN_FILE_PATH}`);
|
|
128
|
+
console.log(JSON.stringify({ authenticated: true, tokenFile: TOKEN_FILE_PATH }));
|
|
129
129
|
server.close();
|
|
130
130
|
process.exit(0);
|
|
131
131
|
}).catch((err) => {
|
|
@@ -151,12 +151,12 @@ function registerAuthCommands(program2) {
|
|
|
151
151
|
authUrl.searchParams.set("state", state);
|
|
152
152
|
authUrl.searchParams.set("code_challenge", challenge);
|
|
153
153
|
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
154
|
-
console.
|
|
154
|
+
console.error("Opening browser for authentication...");
|
|
155
155
|
openBrowser(authUrl.toString());
|
|
156
|
-
console.
|
|
156
|
+
console.error(`If the browser doesn't open, visit:
|
|
157
157
|
${authUrl.toString()}
|
|
158
158
|
`);
|
|
159
|
-
console.
|
|
159
|
+
console.error("Waiting for callback...");
|
|
160
160
|
setTimeout(() => {
|
|
161
161
|
console.error("Authentication timed out.");
|
|
162
162
|
server.close();
|
|
@@ -281,33 +281,94 @@ async function exchangeCode(siteUrl, code, codeVerifier, redirectUri) {
|
|
|
281
281
|
// src/commands/claude-install.ts
|
|
282
282
|
import {
|
|
283
283
|
closeSync,
|
|
284
|
-
existsSync as
|
|
284
|
+
existsSync as existsSync3,
|
|
285
285
|
fsyncSync,
|
|
286
286
|
mkdirSync as mkdirSync2,
|
|
287
287
|
openSync,
|
|
288
|
-
readFileSync as
|
|
288
|
+
readFileSync as readFileSync3,
|
|
289
289
|
renameSync,
|
|
290
290
|
writeFileSync as writeFileSync2
|
|
291
291
|
} from "fs";
|
|
292
292
|
import { homedir } from "os";
|
|
293
|
-
import { dirname as
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
var
|
|
293
|
+
import { dirname as dirname3, join as join2 } from "path";
|
|
294
|
+
|
|
295
|
+
// src/lib/bin-path.ts
|
|
296
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
297
|
+
import { dirname as dirname2, isAbsolute, join } from "path";
|
|
298
|
+
import { fileURLToPath } from "url";
|
|
299
|
+
var PKG_NAME = "@primitive.ai/prim";
|
|
300
|
+
var ROOT_WALK_LIMIT = 6;
|
|
301
|
+
var NPX_FALLBACK = `npx --yes -p ${PKG_NAME}@latest`;
|
|
302
|
+
var resolvedRoot;
|
|
303
|
+
function locateRoot() {
|
|
304
|
+
if (resolvedRoot !== void 0) {
|
|
305
|
+
return resolvedRoot;
|
|
306
|
+
}
|
|
307
|
+
let dir = dirname2(fileURLToPath(import.meta.url));
|
|
308
|
+
for (let depth = 0; depth < ROOT_WALK_LIMIT; depth++) {
|
|
309
|
+
const manifestPath = join(dir, "package.json");
|
|
310
|
+
if (existsSync2(manifestPath)) {
|
|
311
|
+
try {
|
|
312
|
+
const manifest = JSON.parse(readFileSync2(manifestPath, "utf-8"));
|
|
313
|
+
if (manifest.name === PKG_NAME && manifest.bin) {
|
|
314
|
+
resolvedRoot = { dir, bin: manifest.bin };
|
|
315
|
+
return resolvedRoot;
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const parent = dirname2(dir);
|
|
321
|
+
if (parent === dir) {
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
dir = parent;
|
|
325
|
+
}
|
|
326
|
+
resolvedRoot = null;
|
|
327
|
+
return resolvedRoot;
|
|
328
|
+
}
|
|
329
|
+
function binFile(bin) {
|
|
330
|
+
const root = locateRoot();
|
|
331
|
+
const rel = root?.bin[bin];
|
|
332
|
+
if (!root || !rel) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
return isAbsolute(rel) ? rel : join(root.dir, rel);
|
|
336
|
+
}
|
|
337
|
+
function hookShimCommand(bin, args = "") {
|
|
338
|
+
const invoke = (cmd) => args ? `${cmd} ${args}` : cmd;
|
|
339
|
+
return `if command -v ${bin} >/dev/null 2>&1; then ${invoke(bin)}; elif [ -f "./node_modules/.bin/${bin}" ]; then ${invoke(`./node_modules/.bin/${bin}`)}; else ${invoke(`${NPX_FALLBACK} ${bin}`)}; fi`;
|
|
340
|
+
}
|
|
341
|
+
function commandMatchesBin(command, bin) {
|
|
342
|
+
if (!command) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
const c = command.trim();
|
|
346
|
+
if (c === bin || c.startsWith(`${bin} `)) {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
return c.includes(`command -v ${bin} `);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// src/commands/claude-install.ts
|
|
353
|
+
var CAPTURE_BIN = "prim-hook";
|
|
354
|
+
var GATE_BIN = "prim-pre-tool-use";
|
|
355
|
+
var POST_TOOL_USE_BIN = "prim-post-tool-use";
|
|
356
|
+
var SESSION_START_BIN = "prim-session-start";
|
|
357
|
+
var SESSION_END_BIN = "prim-session-end";
|
|
358
|
+
var STATUSLINE_BIN = "prim";
|
|
359
|
+
var STATUSLINE_ARGS = "statusline";
|
|
360
|
+
var STATUSLINE_COMMAND = hookShimCommand(STATUSLINE_BIN, STATUSLINE_ARGS);
|
|
300
361
|
var STATUSLINE_REFRESH_SECONDS = 5;
|
|
301
|
-
var
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
]
|
|
362
|
+
var PRIM_BINS = [
|
|
363
|
+
CAPTURE_BIN,
|
|
364
|
+
GATE_BIN,
|
|
365
|
+
POST_TOOL_USE_BIN,
|
|
366
|
+
SESSION_START_BIN,
|
|
367
|
+
SESSION_END_BIN
|
|
368
|
+
];
|
|
308
369
|
var JSON_INDENT = 2;
|
|
309
|
-
var USER_SCOPE_PATH =
|
|
310
|
-
var PROJECT_SCOPE_PATH =
|
|
370
|
+
var USER_SCOPE_PATH = join2(homedir(), ".claude", "settings.json");
|
|
371
|
+
var PROJECT_SCOPE_PATH = join2(process.cwd(), ".claude", "settings.json");
|
|
311
372
|
var CAPTURE_EVENTS = [
|
|
312
373
|
"SessionStart",
|
|
313
374
|
"UserPromptSubmit",
|
|
@@ -317,21 +378,24 @@ var CAPTURE_EVENTS = [
|
|
|
317
378
|
"SessionEnd",
|
|
318
379
|
"SubagentStop"
|
|
319
380
|
];
|
|
381
|
+
function makeRegistration(event, matcher, bin, args = "") {
|
|
382
|
+
return { event, matcher, bin, command: hookShimCommand(bin, args) };
|
|
383
|
+
}
|
|
320
384
|
var REGISTRATIONS = [
|
|
321
|
-
...CAPTURE_EVENTS.map((event) => (
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
385
|
+
...CAPTURE_EVENTS.map((event) => makeRegistration(event, "*", CAPTURE_BIN)),
|
|
386
|
+
makeRegistration("PreToolUse", "Edit|Write|MultiEdit", GATE_BIN),
|
|
387
|
+
makeRegistration("PostToolUse", "Edit|Write|MultiEdit", POST_TOOL_USE_BIN),
|
|
388
|
+
makeRegistration("SessionStart", "*", SESSION_START_BIN),
|
|
389
|
+
makeRegistration("SessionEnd", "*", SESSION_END_BIN)
|
|
326
390
|
];
|
|
327
391
|
function settingsPathFor(scope) {
|
|
328
392
|
return scope === "user" ? USER_SCOPE_PATH : PROJECT_SCOPE_PATH;
|
|
329
393
|
}
|
|
330
394
|
function readSettings(path) {
|
|
331
|
-
if (!
|
|
395
|
+
if (!existsSync3(path)) {
|
|
332
396
|
return {};
|
|
333
397
|
}
|
|
334
|
-
const raw =
|
|
398
|
+
const raw = readFileSync3(path, "utf-8");
|
|
335
399
|
try {
|
|
336
400
|
return JSON.parse(raw);
|
|
337
401
|
} catch (err) {
|
|
@@ -339,16 +403,16 @@ function readSettings(path) {
|
|
|
339
403
|
throw new Error(`${path} is not valid JSON: ${detail}`);
|
|
340
404
|
}
|
|
341
405
|
}
|
|
342
|
-
function entryHasCommand(entry,
|
|
343
|
-
return entry.hooks?.some((h) => h.command
|
|
406
|
+
function entryHasCommand(entry, bin) {
|
|
407
|
+
return entry.hooks?.some((h) => commandMatchesBin(h.command, bin)) ?? false;
|
|
344
408
|
}
|
|
345
409
|
function canonicalEntry(reg) {
|
|
346
410
|
return { matcher: reg.matcher, hooks: [{ type: "command", command: reg.command }] };
|
|
347
411
|
}
|
|
348
|
-
function stripCommand(list,
|
|
412
|
+
function stripCommand(list, bin) {
|
|
349
413
|
const out = [];
|
|
350
414
|
for (const e of list) {
|
|
351
|
-
const hooks = (e.hooks ?? []).filter((h) => h.command
|
|
415
|
+
const hooks = (e.hooks ?? []).filter((h) => !commandMatchesBin(h.command, bin));
|
|
352
416
|
if (hooks.length > 0) {
|
|
353
417
|
out.push({ ...e, hooks });
|
|
354
418
|
}
|
|
@@ -362,11 +426,16 @@ function ensureRegistration(list, reg, force) {
|
|
|
362
426
|
if (hasCanonical && !force) {
|
|
363
427
|
return list;
|
|
364
428
|
}
|
|
365
|
-
return [...stripCommand(list, reg.
|
|
429
|
+
return [...stripCommand(list, reg.bin), canonicalEntry(reg)];
|
|
366
430
|
}
|
|
431
|
+
var LEGACY_STATUSLINE_COMMAND = "prim statusline";
|
|
367
432
|
function isPrimStatusLine(settings) {
|
|
368
433
|
const s = settings.statusLine;
|
|
369
|
-
|
|
434
|
+
if (s?.type !== "command") {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
const c = (s.command ?? "").trim();
|
|
438
|
+
return c === LEGACY_STATUSLINE_COMMAND || c.includes("@primitive.ai/prim") && c.includes("statusline");
|
|
370
439
|
}
|
|
371
440
|
function applyStatusLine(settings) {
|
|
372
441
|
if (settings.statusLine && !isPrimStatusLine(settings)) {
|
|
@@ -393,8 +462,8 @@ function applyUninstall(settings) {
|
|
|
393
462
|
const hooks = {};
|
|
394
463
|
for (const event of Object.keys(source)) {
|
|
395
464
|
let list = source[event] ?? [];
|
|
396
|
-
for (const
|
|
397
|
-
list = stripCommand(list,
|
|
465
|
+
for (const bin of PRIM_BINS) {
|
|
466
|
+
list = stripCommand(list, bin);
|
|
398
467
|
}
|
|
399
468
|
if (list.length > 0) {
|
|
400
469
|
hooks[event] = list;
|
|
@@ -408,18 +477,18 @@ function applyUninstall(settings) {
|
|
|
408
477
|
}
|
|
409
478
|
function captureInstalled(settings) {
|
|
410
479
|
return CAPTURE_EVENTS.some(
|
|
411
|
-
(event) => (settings.hooks?.[event] ?? []).some((e) => entryHasCommand(e,
|
|
480
|
+
(event) => (settings.hooks?.[event] ?? []).some((e) => entryHasCommand(e, CAPTURE_BIN))
|
|
412
481
|
);
|
|
413
482
|
}
|
|
414
483
|
function statuslineInstalled(settings) {
|
|
415
484
|
return isPrimStatusLine(settings);
|
|
416
485
|
}
|
|
417
486
|
function isGateInstalled(settings) {
|
|
418
|
-
return (settings.hooks?.PreToolUse ?? []).some((e) => entryHasCommand(e,
|
|
487
|
+
return (settings.hooks?.PreToolUse ?? []).some((e) => entryHasCommand(e, GATE_BIN));
|
|
419
488
|
}
|
|
420
489
|
function atomicWrite(path, content) {
|
|
421
|
-
const dir =
|
|
422
|
-
if (!
|
|
490
|
+
const dir = dirname3(path);
|
|
491
|
+
if (!existsSync3(dir)) {
|
|
423
492
|
mkdirSync2(dir, { recursive: true });
|
|
424
493
|
}
|
|
425
494
|
const tmp = `${path}.tmp.${String(Date.now())}`;
|
|
@@ -535,11 +604,12 @@ ${line("project", result.project)}`);
|
|
|
535
604
|
|
|
536
605
|
// src/commands/codex-install.ts
|
|
537
606
|
import { homedir as homedir2 } from "os";
|
|
538
|
-
import { join as
|
|
539
|
-
var
|
|
540
|
-
var
|
|
541
|
-
var
|
|
542
|
-
var
|
|
607
|
+
import { join as join3 } from "path";
|
|
608
|
+
var CAPTURE_BIN2 = "prim-hook";
|
|
609
|
+
var GATE_BIN2 = "prim-pre-tool-use";
|
|
610
|
+
var POST_TOOL_USE_BIN2 = "prim-post-tool-use";
|
|
611
|
+
var SESSION_START_BIN2 = "prim-session-start";
|
|
612
|
+
var CODEX_ARGS = "--agent codex";
|
|
543
613
|
var JSON_INDENT2 = 2;
|
|
544
614
|
var CODEX_CAPTURE_EVENTS = [
|
|
545
615
|
"SessionStart",
|
|
@@ -549,20 +619,15 @@ var CODEX_CAPTURE_EVENTS = [
|
|
|
549
619
|
"Stop",
|
|
550
620
|
"SubagentStop"
|
|
551
621
|
];
|
|
552
|
-
var
|
|
553
|
-
CAPTURE_COMMAND2,
|
|
554
|
-
GATE_COMMAND2,
|
|
555
|
-
POST_TOOL_USE_COMMAND2,
|
|
556
|
-
SESSION_START_COMMAND2
|
|
557
|
-
]);
|
|
622
|
+
var PRIM_BINS2 = [CAPTURE_BIN2, GATE_BIN2, POST_TOOL_USE_BIN2, SESSION_START_BIN2];
|
|
558
623
|
var CODEX_REGISTRATIONS = [
|
|
559
|
-
...CODEX_CAPTURE_EVENTS.map((event) => (
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
624
|
+
...CODEX_CAPTURE_EVENTS.map((event) => makeRegistration(event, "*", CAPTURE_BIN2, CODEX_ARGS)),
|
|
625
|
+
makeRegistration("PreToolUse", "apply_patch", GATE_BIN2, CODEX_ARGS),
|
|
626
|
+
makeRegistration("PostToolUse", "apply_patch", POST_TOOL_USE_BIN2, CODEX_ARGS),
|
|
627
|
+
makeRegistration("SessionStart", "*", SESSION_START_BIN2, CODEX_ARGS)
|
|
563
628
|
];
|
|
564
|
-
var USER_SCOPE_PATH2 =
|
|
565
|
-
var PROJECT_SCOPE_PATH2 =
|
|
629
|
+
var USER_SCOPE_PATH2 = join3(homedir2(), ".codex", "hooks.json");
|
|
630
|
+
var PROJECT_SCOPE_PATH2 = join3(process.cwd(), ".codex", "hooks.json");
|
|
566
631
|
function settingsPathFor2(scope) {
|
|
567
632
|
return scope === "user" ? USER_SCOPE_PATH2 : PROJECT_SCOPE_PATH2;
|
|
568
633
|
}
|
|
@@ -578,8 +643,8 @@ function applyUninstall2(settings) {
|
|
|
578
643
|
const hooks = {};
|
|
579
644
|
for (const event of Object.keys(source)) {
|
|
580
645
|
let list = source[event] ?? [];
|
|
581
|
-
for (const
|
|
582
|
-
list = stripCommand(list,
|
|
646
|
+
for (const bin of PRIM_BINS2) {
|
|
647
|
+
list = stripCommand(list, bin);
|
|
583
648
|
}
|
|
584
649
|
if (list.length > 0) {
|
|
585
650
|
hooks[event] = list;
|
|
@@ -589,11 +654,11 @@ function applyUninstall2(settings) {
|
|
|
589
654
|
}
|
|
590
655
|
function captureInstalled2(settings) {
|
|
591
656
|
return CODEX_CAPTURE_EVENTS.some(
|
|
592
|
-
(event) => (settings.hooks?.[event] ?? []).some((e) => entryHasCommand(e,
|
|
657
|
+
(event) => (settings.hooks?.[event] ?? []).some((e) => entryHasCommand(e, CAPTURE_BIN2))
|
|
593
658
|
);
|
|
594
659
|
}
|
|
595
660
|
function isGateInstalled2(settings) {
|
|
596
|
-
return (settings.hooks?.PreToolUse ?? []).some((e) => entryHasCommand(e,
|
|
661
|
+
return (settings.hooks?.PreToolUse ?? []).some((e) => entryHasCommand(e, GATE_BIN2));
|
|
597
662
|
}
|
|
598
663
|
function resultFor(scope, path, after, changed) {
|
|
599
664
|
return {
|
|
@@ -681,138 +746,25 @@ ${line("project", result.project)}`);
|
|
|
681
746
|
});
|
|
682
747
|
}
|
|
683
748
|
|
|
684
|
-
// src/commands/context.ts
|
|
685
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
686
|
-
function registerContextCommands(program2) {
|
|
687
|
-
const context = program2.command("context").description("Manage contexts");
|
|
688
|
-
context.command("list").description("List contexts").option("-s, --scope <scope>", "Filter by scope: project, global, external").option("-t, --project-id <projectId>", "List contexts linked to a specific project").option("--json", "Output as JSON").action(async (opts) => {
|
|
689
|
-
const client = getClient();
|
|
690
|
-
const params = new URLSearchParams();
|
|
691
|
-
if (opts.projectId) {
|
|
692
|
-
params.set("taskId", opts.projectId);
|
|
693
|
-
}
|
|
694
|
-
if (opts.scope) {
|
|
695
|
-
params.set("scope", opts.scope === "project" ? "task" : opts.scope);
|
|
696
|
-
}
|
|
697
|
-
const contexts = await client.get(`/api/cli/contexts?${params.toString()}`);
|
|
698
|
-
if (opts.json) {
|
|
699
|
-
printJson(contexts);
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
printContextList(contexts);
|
|
703
|
-
});
|
|
704
|
-
context.command("get <contextId>").description("Get a context by ID").option("--json", "Output as JSON (default behavior; accepted for symmetry)").action(async (contextId) => {
|
|
705
|
-
const client = getClient();
|
|
706
|
-
const ctx = await client.get(`/api/cli/contexts/${contextId}`);
|
|
707
|
-
printJson(ctx);
|
|
708
|
-
});
|
|
709
|
-
context.command("create").description("Create a new context").requiredOption("-s, --scope <scope>", "Scope: project, global, external").requiredOption("-n, --name <name>", "Context name").option("-t, --text <text>", "Context text content").option("-f, --file <path>", "Read text content from file").option("--project-id <projectId>", "Link to project(s), comma-separated").option("--spec", "Mark as a spec document").option("--json", "Output as JSON").action(
|
|
710
|
-
async (opts) => {
|
|
711
|
-
const client = getClient();
|
|
712
|
-
let text = opts.text;
|
|
713
|
-
if (opts.file) {
|
|
714
|
-
text = readFileSync3(opts.file, "utf-8");
|
|
715
|
-
}
|
|
716
|
-
const taskIds = opts.projectId ? opts.projectId.split(",").map((id) => id.trim()) : void 0;
|
|
717
|
-
const result = await client.post("/api/cli/contexts", {
|
|
718
|
-
scope: opts.scope === "project" ? "task" : opts.scope,
|
|
719
|
-
name: opts.name,
|
|
720
|
-
text,
|
|
721
|
-
taskIds,
|
|
722
|
-
isSpecDocument: opts.spec ?? false
|
|
723
|
-
});
|
|
724
|
-
if (opts.json) {
|
|
725
|
-
printJson({ _id: result._id });
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
console.error(`Created context: ${result._id}`);
|
|
729
|
-
console.log(result._id);
|
|
730
|
-
}
|
|
731
|
-
);
|
|
732
|
-
context.command("update <contextId>").description("Update a context").option("-n, --name <name>", "New name").option("-t, --text <text>", "New text content").option("-f, --file <path>", "Read text content from file").option("--json", "Output as JSON").action(
|
|
733
|
-
async (contextId, opts) => {
|
|
734
|
-
const client = getClient();
|
|
735
|
-
let text = opts.text;
|
|
736
|
-
if (opts.file) {
|
|
737
|
-
text = readFileSync3(opts.file, "utf-8");
|
|
738
|
-
}
|
|
739
|
-
await client.patch(`/api/cli/contexts/${contextId}`, {
|
|
740
|
-
name: opts.name,
|
|
741
|
-
text
|
|
742
|
-
});
|
|
743
|
-
if (opts.json) {
|
|
744
|
-
printJson({ _id: contextId });
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
console.error(`Updated context: ${contextId}`);
|
|
748
|
-
console.log(contextId);
|
|
749
|
-
}
|
|
750
|
-
);
|
|
751
|
-
context.command("delete <contextId>").description("Delete a context").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
752
|
-
const client = getClient();
|
|
753
|
-
await client.delete(`/api/cli/contexts/${contextId}`);
|
|
754
|
-
if (opts.json) {
|
|
755
|
-
printJson({ _id: contextId });
|
|
756
|
-
return;
|
|
757
|
-
}
|
|
758
|
-
console.error(`Deleted context: ${contextId}`);
|
|
759
|
-
console.log(contextId);
|
|
760
|
-
});
|
|
761
|
-
context.command("link <contextId>").description("Link a context to a project").requiredOption("--project <projectId>", "Project ID to link to").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
762
|
-
const client = getClient();
|
|
763
|
-
await client.post(`/api/cli/contexts/${contextId}/link`, {
|
|
764
|
-
taskId: opts.project
|
|
765
|
-
});
|
|
766
|
-
if (opts.json) {
|
|
767
|
-
printJson({ _id: contextId, project: opts.project });
|
|
768
|
-
return;
|
|
769
|
-
}
|
|
770
|
-
console.error(`Linked context ${contextId} to project ${opts.project}`);
|
|
771
|
-
console.log(contextId);
|
|
772
|
-
});
|
|
773
|
-
context.command("unlink <contextId>").description("Unlink a context from a project").requiredOption("--project <projectId>", "Project ID to unlink from").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
774
|
-
const client = getClient();
|
|
775
|
-
await client.post(`/api/cli/contexts/${contextId}/unlink`, {
|
|
776
|
-
taskId: opts.project
|
|
777
|
-
});
|
|
778
|
-
if (opts.json) {
|
|
779
|
-
printJson({ _id: contextId, project: opts.project });
|
|
780
|
-
return;
|
|
781
|
-
}
|
|
782
|
-
console.error(`Unlinked context ${contextId} from project ${opts.project}`);
|
|
783
|
-
console.log(contextId);
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
function printContextList(contexts) {
|
|
787
|
-
if (contexts.length === 0) {
|
|
788
|
-
console.error("No contexts found.");
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
for (const ctx of contexts) {
|
|
792
|
-
const scope = ctx.scope === "task" ? "project" : ctx.scope ?? "project";
|
|
793
|
-
const spec = ctx.isSpecDocument ? " [SPEC]" : "";
|
|
794
|
-
const name = ctx.name ?? ctx.title ?? "(unnamed)";
|
|
795
|
-
console.log(`${ctx._id} ${scope.padEnd(8)} ${name}${spec}`);
|
|
796
|
-
}
|
|
797
|
-
console.error(`
|
|
798
|
-
${contexts.length} context(s)`);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
749
|
// src/commands/daemon.ts
|
|
802
750
|
import { spawn } from "child_process";
|
|
803
|
-
import { existsSync as
|
|
751
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
804
752
|
import { homedir as homedir3 } from "os";
|
|
805
|
-
import { join as
|
|
753
|
+
import { join as join4 } from "path";
|
|
806
754
|
var DAEMON_BIN = "prim-daemon-server";
|
|
807
|
-
var PID_PATH =
|
|
808
|
-
var SOCK_PATH =
|
|
755
|
+
var PID_PATH = join4(homedir3(), ".config", "prim", "daemon.pid");
|
|
756
|
+
var SOCK_PATH = join4(homedir3(), ".config", "prim", "sock");
|
|
809
757
|
var STOP_TIMEOUT_MS = 5e3;
|
|
810
758
|
var STOP_POLL_MS = 100;
|
|
811
759
|
var STATUS_PROBE_TIMEOUT_MS = 500;
|
|
812
|
-
var
|
|
760
|
+
var READY_TIMEOUT_MS = 5e3;
|
|
761
|
+
var READY_POLL_MS = 100;
|
|
762
|
+
var READY_PROBE_TIMEOUT_MS = 250;
|
|
763
|
+
var EXIT_OK = 0;
|
|
813
764
|
var EXIT_NOT_RUNNING = 2;
|
|
765
|
+
var EXIT_BOOTING = 3;
|
|
814
766
|
function readPidfile() {
|
|
815
|
-
if (!
|
|
767
|
+
if (!existsSync4(PID_PATH)) {
|
|
816
768
|
return null;
|
|
817
769
|
}
|
|
818
770
|
const raw = readFileSync4(PID_PATH, "utf-8").trim();
|
|
@@ -846,6 +798,20 @@ function sleep(ms) {
|
|
|
846
798
|
timer.unref();
|
|
847
799
|
});
|
|
848
800
|
}
|
|
801
|
+
function spawnDaemon(options) {
|
|
802
|
+
const file = binFile(DAEMON_BIN);
|
|
803
|
+
return file ? spawn(process.execPath, [file], options) : spawn(DAEMON_BIN, [], options);
|
|
804
|
+
}
|
|
805
|
+
async function waitForReady() {
|
|
806
|
+
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
807
|
+
while (Date.now() < deadline) {
|
|
808
|
+
if (await daemonIsLive(READY_PROBE_TIMEOUT_MS)) {
|
|
809
|
+
return true;
|
|
810
|
+
}
|
|
811
|
+
await sleep(READY_POLL_MS);
|
|
812
|
+
}
|
|
813
|
+
return daemonIsLive(READY_PROBE_TIMEOUT_MS);
|
|
814
|
+
}
|
|
849
815
|
async function daemonStart(opts) {
|
|
850
816
|
const existing = readPidfile();
|
|
851
817
|
if (existing?.alive) {
|
|
@@ -858,29 +824,32 @@ async function daemonStart(opts) {
|
|
|
858
824
|
clearStaleArtifacts();
|
|
859
825
|
}
|
|
860
826
|
if (opts.foreground) {
|
|
861
|
-
const child2 =
|
|
827
|
+
const child2 = spawnDaemon({ stdio: "inherit" });
|
|
862
828
|
child2.on("exit", (code) => {
|
|
863
829
|
process.exit(code ?? 0);
|
|
864
830
|
});
|
|
865
831
|
return;
|
|
866
832
|
}
|
|
867
|
-
const child =
|
|
868
|
-
detached: true,
|
|
869
|
-
stdio: ["ignore", "ignore", "ignore"]
|
|
870
|
-
});
|
|
833
|
+
const child = spawnDaemon({ detached: true, stdio: ["ignore", "ignore", "ignore"] });
|
|
871
834
|
child.unref();
|
|
872
|
-
await
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
process.stderr.write(
|
|
876
|
-
`)
|
|
877
|
-
|
|
835
|
+
const live = await waitForReady();
|
|
836
|
+
if (live) {
|
|
837
|
+
const after = readPidfile();
|
|
838
|
+
process.stderr.write(
|
|
839
|
+
`[prim] \u2713 daemon started (pid=${after?.pid ?? "?"}, socket=${SOCK_PATH})
|
|
840
|
+
`
|
|
841
|
+
);
|
|
842
|
+
console.log(JSON.stringify({ started: true, pid: after?.pid }, null, 2));
|
|
878
843
|
return;
|
|
879
844
|
}
|
|
880
845
|
process.stderr.write(
|
|
881
|
-
|
|
846
|
+
`[prim] \u2717 daemon start: spawned but the socket did not respond within ${READY_TIMEOUT_MS}ms (check that \`${DAEMON_BIN}\` resolves, and see its log)
|
|
847
|
+
`
|
|
882
848
|
);
|
|
883
849
|
console.log(JSON.stringify({ started: false }, null, 2));
|
|
850
|
+
if (!process.exitCode) {
|
|
851
|
+
process.exitCode = EXIT_NOT_RUNNING;
|
|
852
|
+
}
|
|
884
853
|
}
|
|
885
854
|
async function daemonStop() {
|
|
886
855
|
const existing = readPidfile();
|
|
@@ -922,41 +891,48 @@ async function daemonStop() {
|
|
|
922
891
|
);
|
|
923
892
|
console.log(JSON.stringify({ stopped: false, pid: existing.pid }, null, 2));
|
|
924
893
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
process.stderr.write("[prim] \u2717 daemon down\n");
|
|
929
|
-
console.log(JSON.stringify({ running: false }, null, 2));
|
|
930
|
-
if (!process.exitCode) {
|
|
931
|
-
process.exitCode = EXIT_NOT_RUNNING;
|
|
932
|
-
}
|
|
933
|
-
return;
|
|
894
|
+
function classifyStatus(pidAlive, responding, snapshot, pid) {
|
|
895
|
+
if (!pidAlive) {
|
|
896
|
+
return { json: { running: false }, exitCode: EXIT_NOT_RUNNING };
|
|
934
897
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
if (!process.exitCode) {
|
|
941
|
-
process.exitCode = EXIT_NOT_RUNNING;
|
|
942
|
-
}
|
|
943
|
-
return;
|
|
898
|
+
if (!responding) {
|
|
899
|
+
return {
|
|
900
|
+
json: { running: true, responding: false, state: "starting", pid },
|
|
901
|
+
exitCode: EXIT_BOOTING
|
|
902
|
+
};
|
|
944
903
|
}
|
|
945
|
-
|
|
904
|
+
if (!snapshot) {
|
|
905
|
+
return { json: { running: true, responding: true }, exitCode: EXIT_OK };
|
|
906
|
+
}
|
|
907
|
+
return { json: { running: true, responding: true, ...snapshot }, exitCode: EXIT_OK };
|
|
908
|
+
}
|
|
909
|
+
async function daemonStatus() {
|
|
910
|
+
const pid = readPidfile();
|
|
911
|
+
const pidAlive = pid?.alive ?? false;
|
|
912
|
+
const responding = pidAlive ? await daemonIsLive(STATUS_PROBE_TIMEOUT_MS) : false;
|
|
913
|
+
const snapshot = responding ? await daemonRequest(
|
|
946
914
|
"status_snapshot",
|
|
947
915
|
{},
|
|
948
916
|
{ timeoutMs: STATUS_PROBE_TIMEOUT_MS }
|
|
949
|
-
);
|
|
950
|
-
|
|
917
|
+
) : null;
|
|
918
|
+
const { json, exitCode } = classifyStatus(pidAlive, responding, snapshot, pid?.pid);
|
|
919
|
+
if (!pidAlive) {
|
|
920
|
+
process.stderr.write("[prim] \u2717 daemon down\n");
|
|
921
|
+
} else if (!responding) {
|
|
922
|
+
process.stderr.write(`[prim] \u25CC daemon pid=${pid?.pid} starting (socket not responding yet)
|
|
923
|
+
`);
|
|
924
|
+
} else if (!snapshot) {
|
|
951
925
|
process.stderr.write("[prim] \u2713 daemon live (no snapshot)\n");
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
process.stderr.write(
|
|
956
|
-
`[prim] \u2713 daemon live \xB7 pid=${snapshot.pid} \xB7 uptime=${Math.round(snapshot.uptimeMs / 1e3)}s \xB7 session=${snapshot.sessionId}
|
|
926
|
+
} else {
|
|
927
|
+
process.stderr.write(
|
|
928
|
+
`[prim] \u2713 daemon live \xB7 pid=${snapshot.pid} \xB7 uptime=${Math.round(snapshot.uptimeMs / 1e3)}s \xB7 session=${snapshot.sessionId}
|
|
957
929
|
`
|
|
958
|
-
|
|
959
|
-
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
console.log(JSON.stringify(json, null, 2));
|
|
933
|
+
if (exitCode !== EXIT_OK && !process.exitCode) {
|
|
934
|
+
process.exitCode = exitCode;
|
|
935
|
+
}
|
|
960
936
|
}
|
|
961
937
|
async function daemonRestart(opts) {
|
|
962
938
|
await daemonStop();
|
|
@@ -1574,7 +1550,7 @@ function registerDecisionsCommands(program2) {
|
|
|
1574
1550
|
|
|
1575
1551
|
// src/commands/hooks.ts
|
|
1576
1552
|
import { execSync } from "child_process";
|
|
1577
|
-
import { existsSync as
|
|
1553
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1578
1554
|
import { resolve } from "path";
|
|
1579
1555
|
import { Option } from "commander";
|
|
1580
1556
|
var PRE_COMMIT = { hookName: "pre-commit", binName: "prim-pre-commit" };
|
|
@@ -1617,11 +1593,11 @@ function getGitRoot() {
|
|
|
1617
1593
|
}
|
|
1618
1594
|
function detectHusky(gitRoot) {
|
|
1619
1595
|
const huskyDir = resolve(gitRoot, ".husky");
|
|
1620
|
-
if (!
|
|
1621
|
-
if (
|
|
1622
|
-
if (
|
|
1596
|
+
if (!existsSync5(huskyDir)) return false;
|
|
1597
|
+
if (existsSync5(resolve(huskyDir, "_"))) return true;
|
|
1598
|
+
if (existsSync5(resolve(huskyDir, "pre-commit"))) return true;
|
|
1623
1599
|
const pkgPath = resolve(gitRoot, "package.json");
|
|
1624
|
-
if (
|
|
1600
|
+
if (existsSync5(pkgPath)) {
|
|
1625
1601
|
try {
|
|
1626
1602
|
const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
1627
1603
|
const scripts = pkg2.scripts ?? {};
|
|
@@ -1650,7 +1626,7 @@ async function askConfirmation(question) {
|
|
|
1650
1626
|
}
|
|
1651
1627
|
function installToHusky(gitRoot, spec = PRE_COMMIT) {
|
|
1652
1628
|
const hookPath = resolve(gitRoot, ".husky", spec.hookName);
|
|
1653
|
-
if (
|
|
1629
|
+
if (existsSync5(hookPath)) {
|
|
1654
1630
|
const existing = readFileSync5(hookPath, "utf-8");
|
|
1655
1631
|
if (containsPrimHook(existing, spec.binName)) {
|
|
1656
1632
|
console.log(`Prim ${spec.hookName} hook is already installed in .husky/${spec.hookName}.`);
|
|
@@ -1675,10 +1651,10 @@ ${huskyBlock(spec)}
|
|
|
1675
1651
|
function installToDotGit(gitRoot, spec = PRE_COMMIT) {
|
|
1676
1652
|
const hooksDir = resolve(gitRoot, ".git", "hooks");
|
|
1677
1653
|
const hookPath = resolve(hooksDir, spec.hookName);
|
|
1678
|
-
if (!
|
|
1654
|
+
if (!existsSync5(hooksDir)) {
|
|
1679
1655
|
mkdirSync3(hooksDir, { recursive: true });
|
|
1680
1656
|
}
|
|
1681
|
-
if (
|
|
1657
|
+
if (existsSync5(hookPath)) {
|
|
1682
1658
|
const existing = readFileSync5(hookPath, "utf-8");
|
|
1683
1659
|
if (containsPrimHook(existing, spec.binName)) {
|
|
1684
1660
|
console.log(`Prim ${spec.hookName} hook is already installed at ${hookPath}.`);
|
|
@@ -1742,7 +1718,7 @@ function registerHooksCommands(program2) {
|
|
|
1742
1718
|
const gitRoot = getGitRoot();
|
|
1743
1719
|
for (const spec of HOOKS) {
|
|
1744
1720
|
const hookPath = resolve(gitRoot, ".git", "hooks", spec.hookName);
|
|
1745
|
-
if (!
|
|
1721
|
+
if (!existsSync5(hookPath)) {
|
|
1746
1722
|
console.log(`No ${spec.hookName} hook found.`);
|
|
1747
1723
|
continue;
|
|
1748
1724
|
}
|
|
@@ -1757,8 +1733,8 @@ function registerHooksCommands(program2) {
|
|
|
1757
1733
|
}
|
|
1758
1734
|
|
|
1759
1735
|
// src/commands/moves.ts
|
|
1760
|
-
import { existsSync as
|
|
1761
|
-
import { join as
|
|
1736
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
1737
|
+
import { join as join5 } from "path";
|
|
1762
1738
|
|
|
1763
1739
|
// src/flusher.ts
|
|
1764
1740
|
import { renameSync as renameSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
@@ -1867,19 +1843,19 @@ function registerMovesCommands(program2) {
|
|
|
1867
1843
|
}
|
|
1868
1844
|
});
|
|
1869
1845
|
moves.command("bind").description("Pin the current directory to an org via .prim/workspace.json").requiredOption("--orgId <orgId>", "Convex organization id").action((opts) => {
|
|
1870
|
-
const dir =
|
|
1871
|
-
if (!
|
|
1846
|
+
const dir = join5(process.cwd(), ".prim");
|
|
1847
|
+
if (!existsSync6(dir)) {
|
|
1872
1848
|
mkdirSync4(dir, { recursive: true, mode: DIR_MODE });
|
|
1873
1849
|
}
|
|
1874
|
-
const file =
|
|
1850
|
+
const file = join5(process.cwd(), WORKSPACE_FILE);
|
|
1875
1851
|
writeFileSync4(file, JSON.stringify({ orgId: opts.orgId, boundAt: Date.now() }, null, 2), {
|
|
1876
1852
|
mode: FILE_MODE2
|
|
1877
1853
|
});
|
|
1878
1854
|
console.log(`[prim] bound ${process.cwd()} to org ${opts.orgId}`);
|
|
1879
1855
|
});
|
|
1880
1856
|
moves.command("drop").description("Remove the .prim/workspace.json binding from the cwd").action(() => {
|
|
1881
|
-
const file =
|
|
1882
|
-
if (!
|
|
1857
|
+
const file = join5(process.cwd(), WORKSPACE_FILE);
|
|
1858
|
+
if (!existsSync6(file)) {
|
|
1883
1859
|
console.log("[prim] no workspace binding in cwd");
|
|
1884
1860
|
return;
|
|
1885
1861
|
}
|
|
@@ -1888,30 +1864,8 @@ function registerMovesCommands(program2) {
|
|
|
1888
1864
|
});
|
|
1889
1865
|
}
|
|
1890
1866
|
|
|
1891
|
-
// src/commands/project.ts
|
|
1892
|
-
function registerProjectCommands(program2) {
|
|
1893
|
-
const project = program2.command("project").description("Manage projects");
|
|
1894
|
-
project.command("create").description("Create a new project").requiredOption("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").option("--spec <contextId>", "Link an existing spec as this project's spec").option("--json", "Output as JSON").action(async (opts) => {
|
|
1895
|
-
const client = getClient();
|
|
1896
|
-
const result = await client.post("/api/cli/tasks", {
|
|
1897
|
-
name: opts.name,
|
|
1898
|
-
description: opts.description,
|
|
1899
|
-
specContextId: opts.spec
|
|
1900
|
-
});
|
|
1901
|
-
if (opts.json) {
|
|
1902
|
-
printJson(opts.spec ? { _id: result._id, spec: opts.spec } : { _id: result._id });
|
|
1903
|
-
return;
|
|
1904
|
-
}
|
|
1905
|
-
console.error(`Created project: ${result._id}`);
|
|
1906
|
-
if (opts.spec) {
|
|
1907
|
-
console.error(`Linked spec: ${opts.spec}`);
|
|
1908
|
-
}
|
|
1909
|
-
console.log(result._id);
|
|
1910
|
-
});
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
1867
|
// src/commands/reconcile.ts
|
|
1914
|
-
var
|
|
1868
|
+
var EXIT_OK2 = 0;
|
|
1915
1869
|
var EXIT_USAGE = 2;
|
|
1916
1870
|
var EXIT_SERVER = 3;
|
|
1917
1871
|
var HTTP_CLIENT_ERROR_MIN = 400;
|
|
@@ -1974,7 +1928,7 @@ async function performReconcile(idOrShortId, opts = {}) {
|
|
|
1974
1928
|
`
|
|
1975
1929
|
);
|
|
1976
1930
|
console.log(JSON.stringify(response, null, 2));
|
|
1977
|
-
process.exitCode =
|
|
1931
|
+
process.exitCode = EXIT_OK2;
|
|
1978
1932
|
return;
|
|
1979
1933
|
}
|
|
1980
1934
|
process.stderr.write("[prim] reconcile: malformed server response\n");
|
|
@@ -1994,23 +1948,23 @@ function registerReconcileCommands(program2) {
|
|
|
1994
1948
|
|
|
1995
1949
|
// src/commands/session.ts
|
|
1996
1950
|
import {
|
|
1997
|
-
existsSync as
|
|
1951
|
+
existsSync as existsSync7,
|
|
1998
1952
|
mkdirSync as mkdirSync5,
|
|
1999
1953
|
readFileSync as readFileSync6,
|
|
2000
1954
|
readdirSync,
|
|
2001
1955
|
unlinkSync as unlinkSync5,
|
|
2002
1956
|
writeFileSync as writeFileSync5
|
|
2003
1957
|
} from "fs";
|
|
2004
|
-
import { join as
|
|
1958
|
+
import { join as join6 } from "path";
|
|
2005
1959
|
var DIR_MODE2 = 448;
|
|
2006
1960
|
var FILE_MODE3 = 384;
|
|
2007
1961
|
function ensureDir() {
|
|
2008
|
-
if (!
|
|
1962
|
+
if (!existsSync7(SESSIONS_DIR)) {
|
|
2009
1963
|
mkdirSync5(SESSIONS_DIR, { recursive: true, mode: DIR_MODE2 });
|
|
2010
1964
|
}
|
|
2011
1965
|
}
|
|
2012
1966
|
function markerPath(sessionId) {
|
|
2013
|
-
return
|
|
1967
|
+
return join6(SESSIONS_DIR, `${sessionId}.json`);
|
|
2014
1968
|
}
|
|
2015
1969
|
function registerSessionCommands(program2) {
|
|
2016
1970
|
const session = program2.command("session").description("Decision Event Pipeline \u2014 session binding markers");
|
|
@@ -2026,7 +1980,7 @@ function registerSessionCommands(program2) {
|
|
|
2026
1980
|
console.log(`[prim] session ${sessionId} bound to org ${opts.orgId}`);
|
|
2027
1981
|
});
|
|
2028
1982
|
session.command("list").description("List active session markers").action(() => {
|
|
2029
|
-
if (!
|
|
1983
|
+
if (!existsSync7(SESSIONS_DIR)) {
|
|
2030
1984
|
console.log("[prim] no session markers");
|
|
2031
1985
|
return;
|
|
2032
1986
|
}
|
|
@@ -2038,7 +1992,7 @@ function registerSessionCommands(program2) {
|
|
|
2038
1992
|
for (const f of files) {
|
|
2039
1993
|
const sessionId = f.replace(/\.json$/, "");
|
|
2040
1994
|
try {
|
|
2041
|
-
const m = JSON.parse(readFileSync6(
|
|
1995
|
+
const m = JSON.parse(readFileSync6(join6(SESSIONS_DIR, f), "utf-8"));
|
|
2042
1996
|
console.log(`${sessionId} org=${m.orgId}`);
|
|
2043
1997
|
} catch {
|
|
2044
1998
|
}
|
|
@@ -2046,7 +2000,7 @@ function registerSessionCommands(program2) {
|
|
|
2046
2000
|
});
|
|
2047
2001
|
session.command("drop <sessionId>").description("Remove a session marker").action((sessionId) => {
|
|
2048
2002
|
const p = markerPath(sessionId);
|
|
2049
|
-
if (!
|
|
2003
|
+
if (!existsSync7(p)) {
|
|
2050
2004
|
console.log(`[prim] no marker for session ${sessionId}`);
|
|
2051
2005
|
return;
|
|
2052
2006
|
}
|
|
@@ -2058,17 +2012,17 @@ function registerSessionCommands(program2) {
|
|
|
2058
2012
|
// src/commands/skill.ts
|
|
2059
2013
|
import {
|
|
2060
2014
|
closeSync as closeSync2,
|
|
2061
|
-
existsSync as
|
|
2015
|
+
existsSync as existsSync8,
|
|
2062
2016
|
fsyncSync as fsyncSync2,
|
|
2063
2017
|
openSync as openSync2,
|
|
2064
2018
|
readFileSync as readFileSync7,
|
|
2065
2019
|
renameSync as renameSync3,
|
|
2066
2020
|
writeFileSync as writeFileSync6
|
|
2067
2021
|
} from "fs";
|
|
2068
|
-
import { dirname as
|
|
2069
|
-
import { fileURLToPath } from "url";
|
|
2022
|
+
import { dirname as dirname4, resolve as resolve2 } from "path";
|
|
2023
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2070
2024
|
import { createPatch } from "diff";
|
|
2071
|
-
var __dirname =
|
|
2025
|
+
var __dirname = dirname4(fileURLToPath2(import.meta.url));
|
|
2072
2026
|
var SKILL_BEGIN = "<!-- BEGIN PRIM SKILL v1 -->";
|
|
2073
2027
|
var SKILL_END = "<!-- END PRIM SKILL v1 -->";
|
|
2074
2028
|
var TARGET_CANDIDATES = [
|
|
@@ -2081,15 +2035,15 @@ var TARGET_CANDIDATES = [
|
|
|
2081
2035
|
var DEFAULT_TARGET = "CLAUDE.md";
|
|
2082
2036
|
function loadSkill() {
|
|
2083
2037
|
let dir = __dirname;
|
|
2084
|
-
while (dir !==
|
|
2038
|
+
while (dir !== dirname4(dir)) {
|
|
2085
2039
|
const p = resolve2(dir, "SKILL.md");
|
|
2086
|
-
if (
|
|
2087
|
-
dir =
|
|
2040
|
+
if (existsSync8(p)) return readFileSync7(p, "utf-8");
|
|
2041
|
+
dir = dirname4(dir);
|
|
2088
2042
|
}
|
|
2089
2043
|
throw new Error("SKILL.md not found in package");
|
|
2090
2044
|
}
|
|
2091
2045
|
function detectTargets(cwd) {
|
|
2092
|
-
return TARGET_CANDIDATES.filter((p) =>
|
|
2046
|
+
return TARGET_CANDIDATES.filter((p) => existsSync8(resolve2(cwd, p)));
|
|
2093
2047
|
}
|
|
2094
2048
|
function detectNewline(content) {
|
|
2095
2049
|
return content.includes("\r\n") ? "\r\n" : "\n";
|
|
@@ -2138,7 +2092,7 @@ function resolveTarget(cwd, override) {
|
|
|
2138
2092
|
function runInstall(cwd, opts) {
|
|
2139
2093
|
const target = resolveTarget(cwd, opts.target);
|
|
2140
2094
|
if (target === null) return 1;
|
|
2141
|
-
const existing =
|
|
2095
|
+
const existing = existsSync8(target) ? readFileSync7(target, "utf-8") : "";
|
|
2142
2096
|
const eol = existing ? detectNewline(existing) : "\n";
|
|
2143
2097
|
const block = composeBlock(loadSkill(), eol);
|
|
2144
2098
|
const next = applyBlock(existing, block, eol);
|
|
@@ -2157,7 +2111,7 @@ function runInstall(cwd, opts) {
|
|
|
2157
2111
|
function runUninstall(cwd, opts) {
|
|
2158
2112
|
const target = resolveTarget(cwd, opts.target);
|
|
2159
2113
|
if (target === null) return 1;
|
|
2160
|
-
if (!
|
|
2114
|
+
if (!existsSync8(target)) {
|
|
2161
2115
|
console.log(`Skill block not present at ${target}`);
|
|
2162
2116
|
return 0;
|
|
2163
2117
|
}
|
|
@@ -2174,7 +2128,7 @@ function runUninstall(cwd, opts) {
|
|
|
2174
2128
|
function runStatus(cwd, opts) {
|
|
2175
2129
|
const target = resolveTarget(cwd, opts.target);
|
|
2176
2130
|
if (target === null) return 1;
|
|
2177
|
-
const fileExists =
|
|
2131
|
+
const fileExists = existsSync8(target);
|
|
2178
2132
|
let installed = false;
|
|
2179
2133
|
if (fileExists) {
|
|
2180
2134
|
const content = readFileSync7(target, "utf-8");
|
|
@@ -2213,284 +2167,18 @@ function registerSkillCommands(program2) {
|
|
|
2213
2167
|
});
|
|
2214
2168
|
}
|
|
2215
2169
|
|
|
2216
|
-
// src/commands/spec.ts
|
|
2217
|
-
import { readFileSync as readFileSync8 } from "fs";
|
|
2218
|
-
function registerSpecCommands(program2) {
|
|
2219
|
-
const spec = program2.command("spec").description("Manage spec documents");
|
|
2220
|
-
spec.command("list").description("List spec documents").option("-t, --project-id <projectId>", "List spec for a specific root project").option("--json", "Output as JSON").action(async (opts) => {
|
|
2221
|
-
const client = getClient();
|
|
2222
|
-
if (opts.projectId) {
|
|
2223
|
-
const specs = await client.get(`/api/cli/specs?rootTaskId=${opts.projectId}`);
|
|
2224
|
-
if (opts.json) {
|
|
2225
|
-
printJson(specs[0] ?? null);
|
|
2226
|
-
return;
|
|
2227
|
-
}
|
|
2228
|
-
if (specs.length === 0) {
|
|
2229
|
-
console.error("No spec document found for this project.");
|
|
2230
|
-
return;
|
|
2231
|
-
}
|
|
2232
|
-
printSpec(specs[0]);
|
|
2233
|
-
return;
|
|
2234
|
-
}
|
|
2235
|
-
const contexts = await client.get("/api/cli/specs");
|
|
2236
|
-
if (opts.json) {
|
|
2237
|
-
printJson(contexts);
|
|
2238
|
-
return;
|
|
2239
|
-
}
|
|
2240
|
-
if (contexts.length === 0) {
|
|
2241
|
-
console.error("No spec documents found.");
|
|
2242
|
-
return;
|
|
2243
|
-
}
|
|
2244
|
-
for (const ctx of contexts) {
|
|
2245
|
-
const scope = ctx.scope === "task" ? "project" : ctx.scope ?? "project";
|
|
2246
|
-
const review = ctx.specReviewStatus ?? "\u2014";
|
|
2247
|
-
const name = ctx.name ?? "(unnamed)";
|
|
2248
|
-
console.log(`${ctx._id} ${scope.padEnd(8)} ${String(review).padEnd(10)} ${name}`);
|
|
2249
|
-
}
|
|
2250
|
-
console.error(`
|
|
2251
|
-
${contexts.length} spec(s)`);
|
|
2252
|
-
});
|
|
2253
|
-
spec.command("get <contextId>").description("Get a spec document by ID").option("--text-only", "Print only the text content (no metadata)").option("--json", "Output as JSON (overrides --text-only)").action(async (contextId, opts) => {
|
|
2254
|
-
const client = getClient();
|
|
2255
|
-
const ctx = await client.get(`/api/cli/contexts/${contextId}`);
|
|
2256
|
-
if (opts.json) {
|
|
2257
|
-
printJson(ctx);
|
|
2258
|
-
return;
|
|
2259
|
-
}
|
|
2260
|
-
if (opts.textOnly) {
|
|
2261
|
-
console.log(ctx.text ?? "");
|
|
2262
|
-
return;
|
|
2263
|
-
}
|
|
2264
|
-
printSpec(ctx);
|
|
2265
|
-
});
|
|
2266
|
-
spec.command("create").description("Create a new spec document").requiredOption("-s, --scope <scope>", "Scope: project, global, external").requiredOption("-n, --name <name>", "Spec name").option("-t, --text <text>", "Spec text content").option("-f, --file <path>", "Read text content from file").option("--project-id <projectId>", "Link to project(s), comma-separated").option("--branch <branch>", "Link spec to this branch on the current repo").option("--pr <prNumber>", "Optional PR number to attach to the link").option("--json", "Output as JSON").action(
|
|
2267
|
-
async (opts) => {
|
|
2268
|
-
const client = getClient();
|
|
2269
|
-
let text = opts.text;
|
|
2270
|
-
if (opts.file) {
|
|
2271
|
-
text = readFileSync8(opts.file, "utf-8");
|
|
2272
|
-
}
|
|
2273
|
-
const taskIds = opts.projectId ? opts.projectId.split(",").map((id) => id.trim()) : void 0;
|
|
2274
|
-
let linkedBranch;
|
|
2275
|
-
if (opts.branch) {
|
|
2276
|
-
const { repoFullName } = getGitContext();
|
|
2277
|
-
if (!repoFullName) {
|
|
2278
|
-
console.warn(
|
|
2279
|
-
"[prim] --branch supplied but origin remote is not GitHub; skipping link."
|
|
2280
|
-
);
|
|
2281
|
-
} else {
|
|
2282
|
-
linkedBranch = { repoFullName, branch: opts.branch };
|
|
2283
|
-
if (opts.pr) {
|
|
2284
|
-
const n = Number.parseInt(opts.pr, 10);
|
|
2285
|
-
if (Number.isFinite(n)) linkedBranch.prNumber = n;
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2289
|
-
const result = await client.post("/api/cli/contexts", {
|
|
2290
|
-
scope: opts.scope === "project" ? "task" : opts.scope,
|
|
2291
|
-
name: opts.name,
|
|
2292
|
-
text,
|
|
2293
|
-
taskIds,
|
|
2294
|
-
isSpecDocument: true,
|
|
2295
|
-
linkedBranch
|
|
2296
|
-
});
|
|
2297
|
-
if (opts.json) {
|
|
2298
|
-
printJson({ _id: result._id });
|
|
2299
|
-
return;
|
|
2300
|
-
}
|
|
2301
|
-
console.error(
|
|
2302
|
-
`Created spec: ${result._id}${linkedBranch ? ` (linked to ${linkedBranch.branch})` : ""}`
|
|
2303
|
-
);
|
|
2304
|
-
console.log(result._id);
|
|
2305
|
-
}
|
|
2306
|
-
);
|
|
2307
|
-
spec.command("update <contextId>").description("Update a spec document's text content").option("-t, --text <text>", "New text content").option("-f, --file <path>", "Read text content from file").option("-n, --name <name>", "New name").option("--json", "Output as JSON").action(
|
|
2308
|
-
async (contextId, opts) => {
|
|
2309
|
-
const client = getClient();
|
|
2310
|
-
let text = opts.text;
|
|
2311
|
-
if (opts.file) {
|
|
2312
|
-
text = readFileSync8(opts.file, "utf-8");
|
|
2313
|
-
}
|
|
2314
|
-
if (!(text || opts.name)) {
|
|
2315
|
-
console.error("Provide --text, --file, or --name to update.");
|
|
2316
|
-
process.exit(1);
|
|
2317
|
-
}
|
|
2318
|
-
await client.patch(`/api/cli/contexts/${contextId}`, {
|
|
2319
|
-
name: opts.name,
|
|
2320
|
-
text,
|
|
2321
|
-
skipTiptapLifecycle: !!text
|
|
2322
|
-
});
|
|
2323
|
-
if (text) {
|
|
2324
|
-
await client.post(`/api/cli/contexts/${contextId}/inject`);
|
|
2325
|
-
}
|
|
2326
|
-
if (opts.json) {
|
|
2327
|
-
printJson({ _id: contextId });
|
|
2328
|
-
return;
|
|
2329
|
-
}
|
|
2330
|
-
console.error(`Updated spec: ${contextId}`);
|
|
2331
|
-
console.log(contextId);
|
|
2332
|
-
}
|
|
2333
|
-
);
|
|
2334
|
-
spec.command("sync <contextId>").description("Trigger spec \u2194 project DAG synchronization").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
2335
|
-
const client = getClient();
|
|
2336
|
-
const ctx = await client.get(`/api/cli/contexts/${contextId}`);
|
|
2337
|
-
if (!ctx.isSpecDocument) {
|
|
2338
|
-
console.error("Context is not a spec document. Use `prim context` instead.");
|
|
2339
|
-
process.exit(1);
|
|
2340
|
-
}
|
|
2341
|
-
await client.post(`/api/cli/contexts/${contextId}/sync`);
|
|
2342
|
-
if (opts.json) {
|
|
2343
|
-
printJson(
|
|
2344
|
-
ctx.specRootTaskId ? { _id: contextId, specRootTaskId: ctx.specRootTaskId } : { _id: contextId }
|
|
2345
|
-
);
|
|
2346
|
-
return;
|
|
2347
|
-
}
|
|
2348
|
-
console.error(`Triggered sync for spec: ${contextId}`);
|
|
2349
|
-
if (ctx.specRootTaskId) {
|
|
2350
|
-
console.error(`Root project: ${ctx.specRootTaskId}`);
|
|
2351
|
-
}
|
|
2352
|
-
console.log(contextId);
|
|
2353
|
-
});
|
|
2354
|
-
spec.command("review <contextId>").description("Manually trigger the PR Intent Review bot for a spec").requiredOption("--pr <prNumber>", "PR number to review against").option("--sha <headSha>", "Commit SHA the review runs against (defaults to current HEAD)").action(async (contextId, opts) => {
|
|
2355
|
-
const prNumber = Number.parseInt(opts.pr, 10);
|
|
2356
|
-
if (!Number.isFinite(prNumber)) {
|
|
2357
|
-
console.error("--pr must be an integer.");
|
|
2358
|
-
process.exit(1);
|
|
2359
|
-
}
|
|
2360
|
-
const headSha = opts.sha ?? getGitContext().sha;
|
|
2361
|
-
if (!headSha) {
|
|
2362
|
-
console.error("Could not determine head SHA \u2014 pass --sha or run inside a git checkout.");
|
|
2363
|
-
process.exit(1);
|
|
2364
|
-
}
|
|
2365
|
-
const client = getClient();
|
|
2366
|
-
await client.post(`/api/cli/contexts/${contextId}/review`, {
|
|
2367
|
-
prNumber,
|
|
2368
|
-
headSha
|
|
2369
|
-
});
|
|
2370
|
-
console.log(
|
|
2371
|
-
`Scheduled review: ${contextId} against PR #${String(prNumber)} @ ${headSha.slice(0, 7)}`
|
|
2372
|
-
);
|
|
2373
|
-
});
|
|
2374
|
-
spec.command("drift <contextId>").description("Dispatch the Claude Code drift-fix workflow against a PR").requiredOption("--pr <prNumber>", "PR number to dispatch the drift-fix workflow against").action(async (contextId, opts) => {
|
|
2375
|
-
const prNumber = Number.parseInt(opts.pr, 10);
|
|
2376
|
-
if (!Number.isFinite(prNumber)) {
|
|
2377
|
-
console.error("--pr must be an integer.");
|
|
2378
|
-
process.exit(1);
|
|
2379
|
-
}
|
|
2380
|
-
const client = getClient();
|
|
2381
|
-
const result = await client.post(`/api/cli/contexts/${contextId}/drift`, {
|
|
2382
|
-
prNumber
|
|
2383
|
-
});
|
|
2384
|
-
if (result.dispatched) {
|
|
2385
|
-
const ref = result.runUrl ? `: ${result.runUrl}` : "";
|
|
2386
|
-
console.log(`Dispatched drift-fix workflow${ref}`);
|
|
2387
|
-
} else {
|
|
2388
|
-
console.error(
|
|
2389
|
-
"Drift-fix dispatch failed. Likely causes: actions:write App scope not granted, primitive-drift-fix.yml workflow file missing, or no findings on the latest review."
|
|
2390
|
-
);
|
|
2391
|
-
process.exit(1);
|
|
2392
|
-
}
|
|
2393
|
-
});
|
|
2394
|
-
spec.command("status <taskId>").description(
|
|
2395
|
-
"Show task status, auto-complete suppression flag, and the most-recent bot auto-completion"
|
|
2396
|
-
).action(async (taskId) => {
|
|
2397
|
-
const client = getClient();
|
|
2398
|
-
const result = await client.get(`/api/cli/tasks/${taskId}/status`);
|
|
2399
|
-
console.log(`status: ${result.status}`);
|
|
2400
|
-
console.log(`auto-complete suppressed: ${result.autoCompleteSuppressed ? "yes" : "no"}`);
|
|
2401
|
-
const last = result.lastAutoCompleteActivity;
|
|
2402
|
-
if (last) {
|
|
2403
|
-
const when = last.createdAt ? new Date(last.createdAt).toISOString() : "\u2014";
|
|
2404
|
-
const pr = last.prNumber ? `#${String(last.prNumber)}` : "\u2014";
|
|
2405
|
-
console.log(`last auto-complete: ${when} (PR ${pr})`);
|
|
2406
|
-
if (last.explanation) {
|
|
2407
|
-
console.log(` ${last.explanation}`);
|
|
2408
|
-
}
|
|
2409
|
-
} else {
|
|
2410
|
-
console.log("last auto-complete: \u2014");
|
|
2411
|
-
}
|
|
2412
|
-
});
|
|
2413
|
-
spec.command("map <contextId>").description("Map file patterns to a spec (used by pre-commit hook to detect affected specs)").requiredOption(
|
|
2414
|
-
"-p, --pattern <patterns...>",
|
|
2415
|
-
'Glob pattern(s) to associate, e.g. "src/auth/**"'
|
|
2416
|
-
).option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
2417
|
-
const client = getClient();
|
|
2418
|
-
const result = await client.post(`/api/cli/contexts/${contextId}/map`, {
|
|
2419
|
-
patterns: opts.pattern
|
|
2420
|
-
});
|
|
2421
|
-
if (opts.json) {
|
|
2422
|
-
printJson({ _id: contextId, filePatterns: result.filePatterns });
|
|
2423
|
-
return;
|
|
2424
|
-
}
|
|
2425
|
-
console.error(`Mapped patterns to spec ${contextId}:`);
|
|
2426
|
-
for (const p of result.filePatterns) {
|
|
2427
|
-
console.error(` ${p}`);
|
|
2428
|
-
}
|
|
2429
|
-
console.log(contextId);
|
|
2430
|
-
});
|
|
2431
|
-
spec.command("unmap <contextId>").description("Remove file pattern mappings from a spec (omit --pattern to clear all)").option("-p, --pattern <patterns...>", "Specific pattern(s) to remove (omit to clear all)").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
2432
|
-
const client = getClient();
|
|
2433
|
-
const result = await client.post(`/api/cli/contexts/${contextId}/unmap`, {
|
|
2434
|
-
patterns: opts.pattern
|
|
2435
|
-
});
|
|
2436
|
-
if (opts.json) {
|
|
2437
|
-
printJson({ _id: contextId, filePatterns: result.filePatterns });
|
|
2438
|
-
return;
|
|
2439
|
-
}
|
|
2440
|
-
if (result.filePatterns.length === 0) {
|
|
2441
|
-
console.error(`Cleared all file patterns from spec ${contextId}`);
|
|
2442
|
-
} else {
|
|
2443
|
-
console.error(`Updated patterns for spec ${contextId}:`);
|
|
2444
|
-
for (const p of result.filePatterns) {
|
|
2445
|
-
console.error(` ${p}`);
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
|
-
console.log(contextId);
|
|
2449
|
-
});
|
|
2450
|
-
spec.command("auto-map <contextId>").description("Trigger auto-mapping of file patterns for a spec").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
2451
|
-
const client = getClient();
|
|
2452
|
-
await client.post(`/api/cli/contexts/${contextId}/auto-map`);
|
|
2453
|
-
if (opts.json) {
|
|
2454
|
-
printJson({ _id: contextId });
|
|
2455
|
-
return;
|
|
2456
|
-
}
|
|
2457
|
-
console.error(`Auto-mapping triggered for spec: ${contextId}`);
|
|
2458
|
-
console.log(contextId);
|
|
2459
|
-
});
|
|
2460
|
-
}
|
|
2461
|
-
function printSpec(ctx) {
|
|
2462
|
-
const name = ctx.name ?? ctx.title ?? "(unnamed)";
|
|
2463
|
-
const review = ctx.specReviewStatus ?? "\u2014";
|
|
2464
|
-
const patterns = ctx.filePatterns;
|
|
2465
|
-
console.log(`ID: ${ctx._id}`);
|
|
2466
|
-
console.log(`Name: ${name}`);
|
|
2467
|
-
console.log(`Scope: ${ctx.scope === "task" ? "project" : ctx.scope ?? "project"}`);
|
|
2468
|
-
console.log(`Review Status: ${review}`);
|
|
2469
|
-
console.log(`Root Project: ${ctx.specRootTaskId ?? "\u2014"}`);
|
|
2470
|
-
console.log(`Sync Version: ${ctx.syncVersion ?? 0}`);
|
|
2471
|
-
console.log(`Index Status: ${ctx.indexStatus ?? "\u2014"}`);
|
|
2472
|
-
console.log(`File Patterns: ${patterns?.length ? patterns.join(", ") : "\u2014"}`);
|
|
2473
|
-
if (ctx.text) {
|
|
2474
|
-
const text = ctx.text;
|
|
2475
|
-
const preview = text.length > 500 ? `${text.slice(0, 500)}\u2026` : text;
|
|
2476
|
-
console.log(`
|
|
2477
|
-
--- Text ---
|
|
2478
|
-
${preview}`);
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
|
|
2482
2170
|
// src/commands/statusline.ts
|
|
2483
|
-
import { readFileSync as
|
|
2484
|
-
import { dirname as
|
|
2485
|
-
import { fileURLToPath as
|
|
2171
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
2172
|
+
import { dirname as dirname5, resolve as resolve3 } from "path";
|
|
2173
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2486
2174
|
var STATUSLINE_TIMEOUT_MS = 200;
|
|
2487
2175
|
function readPackageVersion() {
|
|
2488
2176
|
try {
|
|
2489
|
-
const here =
|
|
2177
|
+
const here = dirname5(fileURLToPath3(import.meta.url));
|
|
2490
2178
|
const candidates = [resolve3(here, "../../package.json"), resolve3(here, "../package.json")];
|
|
2491
2179
|
for (const path of candidates) {
|
|
2492
2180
|
try {
|
|
2493
|
-
const pkg2 = JSON.parse(
|
|
2181
|
+
const pkg2 = JSON.parse(readFileSync8(path, "utf-8"));
|
|
2494
2182
|
if (pkg2.version) {
|
|
2495
2183
|
return pkg2.version;
|
|
2496
2184
|
}
|
|
@@ -2535,18 +2223,15 @@ function registerStatuslineCommands(program2) {
|
|
|
2535
2223
|
}
|
|
2536
2224
|
|
|
2537
2225
|
// src/index.ts
|
|
2538
|
-
var __dirname2 =
|
|
2539
|
-
var pkg = JSON.parse(
|
|
2226
|
+
var __dirname2 = dirname6(fileURLToPath4(import.meta.url));
|
|
2227
|
+
var pkg = JSON.parse(readFileSync9(resolve4(__dirname2, "../package.json"), "utf-8"));
|
|
2540
2228
|
updateNotifier({ pkg }).notify();
|
|
2541
2229
|
var program = new Command();
|
|
2542
|
-
program.name("prim").description("CLI for
|
|
2230
|
+
program.name("prim").description("CLI for Primitive's decision graph").version(pkg.version).option("-y, --yes", "auto-confirm prompts").option(
|
|
2543
2231
|
"--non-interactive",
|
|
2544
2232
|
"fail fast instead of prompting (also: CI=1, PRIM_NON_INTERACTIVE=1)"
|
|
2545
2233
|
);
|
|
2546
2234
|
registerAuthCommands(program);
|
|
2547
|
-
registerContextCommands(program);
|
|
2548
|
-
registerSpecCommands(program);
|
|
2549
|
-
registerProjectCommands(program);
|
|
2550
2235
|
registerHooksCommands(program);
|
|
2551
2236
|
registerSkillCommands(program);
|
|
2552
2237
|
registerMovesCommands(program);
|