@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.
Files changed (3) hide show
  1. package/README.md +7 -2
  2. package/dist/index.js +107 -43
  3. 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 # Install Claude Code hooks (uninstall / status)
63
- prim codex install # Install OpenAI Codex hooks (uninstall / status)
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
- var PROJECT_SCOPE_PATH = join2(process.cwd(), ".claude", "settings.json");
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 : PROJECT_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(PROJECT_SCOPE_PATH) };
562
+ return { user: statusFor(USER_SCOPE_PATH), project: statusFor(projectScopePath()) };
552
563
  }
553
564
  function resolveScope(input) {
554
- if (input === void 0 || input === "user") {
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
- "user (default, ~/.claude/settings.json) or project (./.claude/settings.json)"
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
- "user (default, ~/.claude/settings.json) or project (./.claude/settings.json)"
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 PROJECT_SCOPE_PATH2 = join3(process.cwd(), ".codex", "hooks.json");
643
+ var projectScopePath2 = () => join3(projectRoot(), ".codex", "hooks.json");
633
644
  function settingsPathFor2(scope) {
634
- return scope === "user" ? USER_SCOPE_PATH2 : PROJECT_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(PROJECT_SCOPE_PATH2) };
710
+ return { user: statusFor(USER_SCOPE_PATH2), project: statusFor(projectScopePath2()) };
700
711
  }
701
712
  function resolveScope2(input) {
702
- if (input === void 0 || input === "user") {
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 ~/.codex/hooks.json").option(
725
+ codex.command("install").description("Register the prim hooks in Codex's hooks.json (project scope by default)").option(
715
726
  "--scope <scope>",
716
- "user (default, ~/.codex/hooks.json) or project (./.codex/hooks.json)"
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 ~/.codex/hooks.json").option(
739
+ codex.command("uninstall").description("Remove all prim hooks from Codex's hooks.json").option(
729
740
  "--scope <scope>",
730
- "user (default, ~/.codex/hooks.json) or project (./.codex/hooks.json)"
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
- const clock = formatClock(row.classifiedAt);
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 execSync("git rev-parse --show-toplevel", {
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
- function formatWelcome() {
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
- return [
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
- bold("Get started"),
2347
- cmd("prim decisions recent", "what your team has decided lately"),
2348
- cmd("prim decisions check --files <files>", "what governs files you're about to change"),
2349
- cmd("prim --help", "everything else"),
2350
- "",
2351
- dim("App: https://app.getprimitive.ai")
2352
- ].join("\n");
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
- process.stderr.write(`${formatWelcome()}
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({ welcomed: true });
2422
+ printJson(welcomeJson(state));
2359
2423
  });
2360
2424
  }
2361
2425
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitive.ai/prim",
3
- "version": "0.1.0-alpha.21",
3
+ "version": "0.1.0-alpha.22",
4
4
  "description": "CLI for Primitive's decision graph — passive decision capture, conflict gate, and team presence",
5
5
  "type": "module",
6
6
  "license": "MIT",