@primitive.ai/prim 0.1.0-alpha.21 → 0.1.0-alpha.22
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 +7 -2
- package/dist/index.js +107 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,9 +58,14 @@ graph, conflicting edits are gated, and presence is reported. Each hook
|
|
|
58
58
|
self-resolves the CLI at run time (PATH, then a local install, then
|
|
59
59
|
`npx --yes @latest`), so it keeps working with no global install.
|
|
60
60
|
|
|
61
|
+
Installs into the current project by default — the repo's `.claude/settings.json`
|
|
62
|
+
/ `.codex/hooks.json`, resolved from the git root (so any subdirectory works);
|
|
63
|
+
pass `--scope user` to install machine-wide.
|
|
64
|
+
|
|
61
65
|
```bash
|
|
62
|
-
prim claude install
|
|
63
|
-
prim
|
|
66
|
+
prim claude install # Install Claude Code hooks (project scope; uninstall / status)
|
|
67
|
+
prim claude install --scope user # Install machine-wide instead
|
|
68
|
+
prim codex install # Install OpenAI Codex hooks (project scope)
|
|
64
69
|
```
|
|
65
70
|
|
|
66
71
|
### Daemon
|
package/dist/index.js
CHANGED
|
@@ -281,6 +281,7 @@ async function exchangeCode(siteUrl, code, codeVerifier, redirectUri) {
|
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
// src/commands/claude-install.ts
|
|
284
|
+
import { execSync } from "child_process";
|
|
284
285
|
import {
|
|
285
286
|
closeSync,
|
|
286
287
|
existsSync as existsSync3,
|
|
@@ -370,7 +371,17 @@ var PRIM_BINS = [
|
|
|
370
371
|
];
|
|
371
372
|
var JSON_INDENT = 2;
|
|
372
373
|
var USER_SCOPE_PATH = join2(homedir(), ".claude", "settings.json");
|
|
373
|
-
|
|
374
|
+
function projectRoot() {
|
|
375
|
+
try {
|
|
376
|
+
return execSync("git rev-parse --show-toplevel", {
|
|
377
|
+
encoding: "utf-8",
|
|
378
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
379
|
+
}).trim();
|
|
380
|
+
} catch {
|
|
381
|
+
return process.cwd();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
var projectScopePath = () => join2(projectRoot(), ".claude", "settings.json");
|
|
374
385
|
var CAPTURE_EVENTS = [
|
|
375
386
|
"SessionStart",
|
|
376
387
|
"UserPromptSubmit",
|
|
@@ -391,7 +402,7 @@ var REGISTRATIONS = [
|
|
|
391
402
|
makeRegistration("SessionEnd", "*", SESSION_END_BIN)
|
|
392
403
|
];
|
|
393
404
|
function settingsPathFor(scope) {
|
|
394
|
-
return scope === "user" ? USER_SCOPE_PATH :
|
|
405
|
+
return scope === "user" ? USER_SCOPE_PATH : projectScopePath();
|
|
395
406
|
}
|
|
396
407
|
function readSettings(path) {
|
|
397
408
|
if (!existsSync3(path)) {
|
|
@@ -548,15 +559,15 @@ function performStatus() {
|
|
|
548
559
|
statusline: statuslineInstalled(settings)
|
|
549
560
|
};
|
|
550
561
|
};
|
|
551
|
-
return { user: statusFor(USER_SCOPE_PATH), project: statusFor(
|
|
562
|
+
return { user: statusFor(USER_SCOPE_PATH), project: statusFor(projectScopePath()) };
|
|
552
563
|
}
|
|
553
564
|
function resolveScope(input) {
|
|
554
|
-
if (input === void 0 || input === "
|
|
555
|
-
return "user";
|
|
556
|
-
}
|
|
557
|
-
if (input === "project") {
|
|
565
|
+
if (input === void 0 || input === "project") {
|
|
558
566
|
return "project";
|
|
559
567
|
}
|
|
568
|
+
if (input === "user") {
|
|
569
|
+
return "user";
|
|
570
|
+
}
|
|
560
571
|
console.error(`[prim] unknown --scope "${input}" (expected: user or project)`);
|
|
561
572
|
process.exit(1);
|
|
562
573
|
}
|
|
@@ -564,7 +575,7 @@ function registerClaudeCommands(program2) {
|
|
|
564
575
|
const claude = program2.command("claude").description("Manage the prim Claude Code integration (capture, gate, ingest, presence)");
|
|
565
576
|
claude.command("install").description("Register the prim hooks + statusline in Claude Code's settings.json").option(
|
|
566
577
|
"--scope <scope>",
|
|
567
|
-
"
|
|
578
|
+
"project (default, the repo's .claude/settings.json) or user (~/.claude/settings.json)"
|
|
568
579
|
).option("--force", "Replace any drifted prim hook entries").action((opts) => {
|
|
569
580
|
const scope = resolveScope(opts.scope);
|
|
570
581
|
const result = performInstall(scope, opts.force ?? false);
|
|
@@ -581,7 +592,7 @@ function registerClaudeCommands(program2) {
|
|
|
581
592
|
});
|
|
582
593
|
claude.command("uninstall").description("Remove all prim hooks + the prim statusline from settings.json").option(
|
|
583
594
|
"--scope <scope>",
|
|
584
|
-
"
|
|
595
|
+
"project (default, the repo's .claude/settings.json) or user (~/.claude/settings.json)"
|
|
585
596
|
).action((opts) => {
|
|
586
597
|
const scope = resolveScope(opts.scope);
|
|
587
598
|
const result = performUninstall(scope);
|
|
@@ -629,9 +640,9 @@ var CODEX_REGISTRATIONS = [
|
|
|
629
640
|
makeRegistration("SessionStart", "*", SESSION_START_BIN2, CODEX_ARGS)
|
|
630
641
|
];
|
|
631
642
|
var USER_SCOPE_PATH2 = join3(homedir2(), ".codex", "hooks.json");
|
|
632
|
-
var
|
|
643
|
+
var projectScopePath2 = () => join3(projectRoot(), ".codex", "hooks.json");
|
|
633
644
|
function settingsPathFor2(scope) {
|
|
634
|
-
return scope === "user" ? USER_SCOPE_PATH2 :
|
|
645
|
+
return scope === "user" ? USER_SCOPE_PATH2 : projectScopePath2();
|
|
635
646
|
}
|
|
636
647
|
function applyInstall2(settings, options = {}) {
|
|
637
648
|
const hooks = { ...settings.hooks ?? {} };
|
|
@@ -696,24 +707,24 @@ function performStatus2() {
|
|
|
696
707
|
const settings = readSettings(path);
|
|
697
708
|
return { path, gate: isGateInstalled2(settings), capture: captureInstalled2(settings) };
|
|
698
709
|
};
|
|
699
|
-
return { user: statusFor(USER_SCOPE_PATH2), project: statusFor(
|
|
710
|
+
return { user: statusFor(USER_SCOPE_PATH2), project: statusFor(projectScopePath2()) };
|
|
700
711
|
}
|
|
701
712
|
function resolveScope2(input) {
|
|
702
|
-
if (input === void 0 || input === "
|
|
703
|
-
return "user";
|
|
704
|
-
}
|
|
705
|
-
if (input === "project") {
|
|
713
|
+
if (input === void 0 || input === "project") {
|
|
706
714
|
return "project";
|
|
707
715
|
}
|
|
716
|
+
if (input === "user") {
|
|
717
|
+
return "user";
|
|
718
|
+
}
|
|
708
719
|
console.error(`[prim] unknown --scope "${input}" (expected: user or project)`);
|
|
709
720
|
process.exit(1);
|
|
710
721
|
}
|
|
711
722
|
var TRUST_NOTICE = "[prim] Codex requires hook trust: run `/hooks` in Codex to review and trust these hooks (or start Codex with --dangerously-bypass-hook-trust). Until trusted, the hooks will not fire.";
|
|
712
723
|
function registerCodexCommands(program2) {
|
|
713
724
|
const codex = program2.command("codex").description("Manage the prim Codex integration (capture, gate, ingest, presence)");
|
|
714
|
-
codex.command("install").description("Register the prim hooks in Codex's
|
|
725
|
+
codex.command("install").description("Register the prim hooks in Codex's hooks.json (project scope by default)").option(
|
|
715
726
|
"--scope <scope>",
|
|
716
|
-
"
|
|
727
|
+
"project (default, the repo's .codex/hooks.json) or user (~/.codex/hooks.json)"
|
|
717
728
|
).option("--force", "Replace any drifted prim hook entries").action((opts) => {
|
|
718
729
|
const scope = resolveScope2(opts.scope);
|
|
719
730
|
const result = performInstall2(scope, opts.force ?? false);
|
|
@@ -725,9 +736,9 @@ function registerCodexCommands(program2) {
|
|
|
725
736
|
console.error(TRUST_NOTICE);
|
|
726
737
|
console.log(JSON.stringify(result, null, JSON_INDENT2));
|
|
727
738
|
});
|
|
728
|
-
codex.command("uninstall").description("Remove all prim hooks from
|
|
739
|
+
codex.command("uninstall").description("Remove all prim hooks from Codex's hooks.json").option(
|
|
729
740
|
"--scope <scope>",
|
|
730
|
-
"
|
|
741
|
+
"project (default, the repo's .codex/hooks.json) or user (~/.codex/hooks.json)"
|
|
731
742
|
).action((opts) => {
|
|
732
743
|
const scope = resolveScope2(opts.scope);
|
|
733
744
|
const result = performUninstall2(scope);
|
|
@@ -1208,8 +1219,8 @@ async function fetchRecent(args, deps = defaultDeps2) {
|
|
|
1208
1219
|
if (args.since !== void 0) {
|
|
1209
1220
|
params.set("since", args.since);
|
|
1210
1221
|
}
|
|
1211
|
-
const client = deps.getClient();
|
|
1212
1222
|
try {
|
|
1223
|
+
const client = deps.getClient();
|
|
1213
1224
|
const res = await daemonOrDirectGet(
|
|
1214
1225
|
"decisions_recent",
|
|
1215
1226
|
`/api/cli/decisions/recent?${params.toString()}`,
|
|
@@ -1255,9 +1266,18 @@ function authorLabel(row) {
|
|
|
1255
1266
|
}
|
|
1256
1267
|
}
|
|
1257
1268
|
var AUTHOR_WIDTH = 18;
|
|
1269
|
+
var AREA_WIDTH = 12;
|
|
1258
1270
|
function padRight(s, width) {
|
|
1259
1271
|
return s.length >= width ? `${s.slice(0, width - 1)} ` : s.padEnd(width, " ");
|
|
1260
1272
|
}
|
|
1273
|
+
function formatRecentRow(row) {
|
|
1274
|
+
const clock = formatClock(row.classifiedAt);
|
|
1275
|
+
const author = padRight(authorLabel(row), AUTHOR_WIDTH);
|
|
1276
|
+
const areaText = row.area ? `\u2022 ${row.area}` : "\u2022";
|
|
1277
|
+
const areaPlain = padRight(areaText, AREA_WIDTH);
|
|
1278
|
+
const areaCol = row.area ? areaPlain.replace("\u2022", color("\u2022", colorForArea(row.area))) : areaPlain;
|
|
1279
|
+
return ` ${clock} ${author}${areaCol}${row.intent}`;
|
|
1280
|
+
}
|
|
1261
1281
|
function formatRecentHuman(result) {
|
|
1262
1282
|
if (result.unavailable !== void 0) {
|
|
1263
1283
|
return `[prim] recent \xB7 feed not verified \u2014 ${result.unavailable}`;
|
|
@@ -1267,12 +1287,7 @@ function formatRecentHuman(result) {
|
|
|
1267
1287
|
}
|
|
1268
1288
|
const lines = [`[prim] recent \xB7 ${String(result.decisions.length)} decision(s)`];
|
|
1269
1289
|
for (const row of result.decisions) {
|
|
1270
|
-
|
|
1271
|
-
const author = padRight(authorLabel(row), AUTHOR_WIDTH);
|
|
1272
|
-
const areaText = row.area ? `\u2022 ${row.area}` : "\u2022";
|
|
1273
|
-
const areaPlain = padRight(areaText, 12);
|
|
1274
|
-
const areaCol = row.area ? areaPlain.replace("\u2022", color("\u2022", colorForArea(row.area))) : areaPlain;
|
|
1275
|
-
lines.push(` ${clock} ${author}${areaCol}${row.intent}`);
|
|
1290
|
+
lines.push(formatRecentRow(row));
|
|
1276
1291
|
}
|
|
1277
1292
|
return lines.join("\n");
|
|
1278
1293
|
}
|
|
@@ -1642,7 +1657,7 @@ function registerDecisionsCommands(program2) {
|
|
|
1642
1657
|
}
|
|
1643
1658
|
|
|
1644
1659
|
// src/commands/hooks.ts
|
|
1645
|
-
import { execSync } from "child_process";
|
|
1660
|
+
import { execSync as execSync2 } from "child_process";
|
|
1646
1661
|
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1647
1662
|
import { resolve } from "path";
|
|
1648
1663
|
import { Option } from "commander";
|
|
@@ -1680,7 +1695,7 @@ ${hookShim(spec.binName)}
|
|
|
1680
1695
|
${end}`;
|
|
1681
1696
|
}
|
|
1682
1697
|
function getGitRoot() {
|
|
1683
|
-
return
|
|
1698
|
+
return execSync2("git rev-parse --show-toplevel", {
|
|
1684
1699
|
encoding: "utf-8"
|
|
1685
1700
|
}).trim();
|
|
1686
1701
|
}
|
|
@@ -2325,10 +2340,26 @@ function registerStatuslineCommands(program2) {
|
|
|
2325
2340
|
|
|
2326
2341
|
// src/commands/welcome.ts
|
|
2327
2342
|
var CMD_GUTTER = 38;
|
|
2328
|
-
|
|
2343
|
+
var RECENT_LIMIT = 5;
|
|
2344
|
+
var REVERSE_PROMPT_LINES = [
|
|
2345
|
+
"What are the most important goals in your organization that you're",
|
|
2346
|
+
"responsible for, right now? What are you not focusing on, in order",
|
|
2347
|
+
"to focus on those goals?"
|
|
2348
|
+
];
|
|
2349
|
+
var REVERSE_PROMPT = REVERSE_PROMPT_LINES.join(" ");
|
|
2350
|
+
function welcomeStateFromRecent(result) {
|
|
2351
|
+
if (result.unavailable !== void 0) {
|
|
2352
|
+
return { org: "unknown" };
|
|
2353
|
+
}
|
|
2354
|
+
if (result.decisions.length === 0) {
|
|
2355
|
+
return { org: "empty" };
|
|
2356
|
+
}
|
|
2357
|
+
return { org: "active", recent: result.decisions.slice(0, RECENT_LIMIT) };
|
|
2358
|
+
}
|
|
2359
|
+
function formatWelcome(state) {
|
|
2329
2360
|
const cmd = (command, desc) => ` ${dim(command.padEnd(CMD_GUTTER))}${desc}`;
|
|
2330
2361
|
const bullet = (text) => ` ${color("\u2022", "green")} ${text}`;
|
|
2331
|
-
|
|
2362
|
+
const head = [
|
|
2332
2363
|
bold(color("Welcome to Primitive", "green")),
|
|
2333
2364
|
"",
|
|
2334
2365
|
"Primitive captures the decisions your team makes while coding into a",
|
|
@@ -2342,20 +2373,53 @@ function formatWelcome() {
|
|
|
2342
2373
|
" that decision and retry.",
|
|
2343
2374
|
bullet('Occasional yes/no prompts confirm the "why" behind a decision \u2014'),
|
|
2344
2375
|
" answering keeps the graph trustworthy.",
|
|
2345
|
-
""
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2376
|
+
""
|
|
2377
|
+
];
|
|
2378
|
+
let body;
|
|
2379
|
+
if (state.org === "active") {
|
|
2380
|
+
body = [
|
|
2381
|
+
bold("Recent team decisions"),
|
|
2382
|
+
...state.recent.map(formatRecentRow),
|
|
2383
|
+
"",
|
|
2384
|
+
bold("Get started"),
|
|
2385
|
+
cmd("prim decisions check --files <files>", "what governs files you're about to change"),
|
|
2386
|
+
cmd("prim --help", "everything else")
|
|
2387
|
+
];
|
|
2388
|
+
} else if (state.org === "empty") {
|
|
2389
|
+
body = [
|
|
2390
|
+
bold("Let's seed your decision graph"),
|
|
2391
|
+
"Your team has no decisions recorded yet. Tell me, in your own words:",
|
|
2392
|
+
"",
|
|
2393
|
+
...REVERSE_PROMPT_LINES.map((line) => ` ${line}`),
|
|
2394
|
+
"",
|
|
2395
|
+
"Share your answer and I'll record each goal as a decision."
|
|
2396
|
+
];
|
|
2397
|
+
} else {
|
|
2398
|
+
body = [
|
|
2399
|
+
bold("Get started"),
|
|
2400
|
+
cmd("prim decisions recent", "what your team has decided lately"),
|
|
2401
|
+
cmd("prim decisions check --files <files>", "what governs files you're about to change"),
|
|
2402
|
+
cmd("prim --help", "everything else")
|
|
2403
|
+
];
|
|
2404
|
+
}
|
|
2405
|
+
return [...head, ...body, "", dim("App: https://app.getprimitive.ai")].join("\n");
|
|
2406
|
+
}
|
|
2407
|
+
function welcomeJson(state) {
|
|
2408
|
+
if (state.org === "active") {
|
|
2409
|
+
return { welcomed: true, org: "active", recent: state.recent };
|
|
2410
|
+
}
|
|
2411
|
+
if (state.org === "empty") {
|
|
2412
|
+
return { welcomed: true, org: "empty", reversePrompt: REVERSE_PROMPT };
|
|
2413
|
+
}
|
|
2414
|
+
return { welcomed: true, org: "unknown" };
|
|
2353
2415
|
}
|
|
2354
|
-
function registerWelcomeCommand(program2) {
|
|
2355
|
-
program2.command("welcome").description("Print a brief orientation to Primitive's decision graph").action(() => {
|
|
2356
|
-
|
|
2416
|
+
function registerWelcomeCommand(program2, deps = { getClient }) {
|
|
2417
|
+
program2.command("welcome").description("Print a brief orientation to Primitive's decision graph").action(async () => {
|
|
2418
|
+
const result = await fetchRecent({ limit: RECENT_LIMIT }, deps);
|
|
2419
|
+
const state = welcomeStateFromRecent(result);
|
|
2420
|
+
process.stderr.write(`${formatWelcome(state)}
|
|
2357
2421
|
`);
|
|
2358
|
-
printJson(
|
|
2422
|
+
printJson(welcomeJson(state));
|
|
2359
2423
|
});
|
|
2360
2424
|
}
|
|
2361
2425
|
|
package/package.json
CHANGED