@primitive.ai/prim 0.1.0-alpha.23 → 0.1.0-alpha.25

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 +27 -1
  2. package/dist/index.js +119 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -24,6 +24,15 @@ npx @primitive.ai/prim
24
24
 
25
25
  ## Quick Start
26
26
 
27
+ One command does the whole install — auth, session hooks, daemon, git hooks,
28
+ skill, and the welcome:
29
+
30
+ ```bash
31
+ prim setup # add --agent codex for Codex, --no-daemon to skip the daemon
32
+ ```
33
+
34
+ Or run the steps individually:
35
+
27
36
  ```bash
28
37
  # 1. Authenticate via browser (WorkOS OAuth)
29
38
  prim auth login
@@ -38,10 +47,27 @@ prim daemon start
38
47
  prim hooks install
39
48
  ```
40
49
 
41
- An AI coding agent can drive the entire setup itself — see [`setup.md`](./setup.md).
50
+ `prim claude install` also writes a scoped `Bash(npx --yes @primitive.ai/prim:*)`
51
+ allow-rule into `.claude/settings.json` (covering both the `@latest` onboarding form
52
+ and the bare day-to-day form), so an agent's prim calls don't stall on a permission
53
+ prompt.
54
+
55
+ An AI coding agent can drive the setup itself — see [`setup.md`](./setup.md).
42
56
 
43
57
  ## Commands
44
58
 
59
+ ### Setup
60
+
61
+ ```bash
62
+ prim setup # Run the whole install in one shot
63
+ prim setup --agent codex # Same, for OpenAI Codex
64
+ prim setup --no-daemon # Skip the companion daemon
65
+ ```
66
+
67
+ Orchestrates auth → session hooks → daemon → git hooks → skill → welcome,
68
+ re-running each underlying command so every step behaves exactly as if run by
69
+ hand (including the browser login). Idempotent — safe to re-run.
70
+
45
71
  ### Auth
46
72
 
47
73
  ```bash
package/dist/index.js CHANGED
@@ -401,6 +401,12 @@ var REGISTRATIONS = [
401
401
  makeRegistration("SessionStart", "*", SESSION_START_BIN),
402
402
  makeRegistration("SessionEnd", "*", SESSION_END_BIN)
403
403
  ];
404
+ var PRIM_PERMISSION_RULE = "Bash(npx --yes @primitive.ai/prim:*)";
405
+ var LEGACY_PRIM_PERMISSION_RULES = ["Bash(npx --yes @primitive.ai/prim@latest:*)"];
406
+ var ALL_PRIM_PERMISSION_RULES = /* @__PURE__ */ new Set([
407
+ PRIM_PERMISSION_RULE,
408
+ ...LEGACY_PRIM_PERMISSION_RULES
409
+ ]);
404
410
  function settingsPathFor(scope) {
405
411
  return scope === "user" ? USER_SCOPE_PATH : projectScopePath();
406
412
  }
@@ -463,12 +469,36 @@ function applyStatusLine(settings) {
463
469
  }
464
470
  };
465
471
  }
472
+ function applyPermissions(settings) {
473
+ const permissions = settings.permissions ?? {};
474
+ const allow = permissions.allow ?? [];
475
+ const withoutPrim = allow.filter((rule) => !ALL_PRIM_PERMISSION_RULES.has(rule));
476
+ const nextAllow = [...withoutPrim, PRIM_PERMISSION_RULE];
477
+ const unchanged = allow.length === nextAllow.length && allow.every((rule, i) => rule === nextAllow[i]);
478
+ if (unchanged) {
479
+ return settings;
480
+ }
481
+ return { ...settings, permissions: { ...permissions, allow: nextAllow } };
482
+ }
483
+ function removePrimPermission(settings) {
484
+ const permissions = settings.permissions;
485
+ if (!permissions?.allow?.some((rule) => ALL_PRIM_PERMISSION_RULES.has(rule))) {
486
+ return settings;
487
+ }
488
+ const allow = permissions.allow.filter((rule) => !ALL_PRIM_PERMISSION_RULES.has(rule));
489
+ const nextPermissions = {
490
+ ...permissions,
491
+ allow: allow.length > 0 ? allow : void 0
492
+ };
493
+ const hasOtherPerms = Object.values(nextPermissions).some((v) => v !== void 0);
494
+ return { ...settings, permissions: hasOtherPerms ? nextPermissions : void 0 };
495
+ }
466
496
  function applyInstall(settings, options = {}) {
467
497
  const hooks = { ...settings.hooks ?? {} };
468
498
  for (const reg of REGISTRATIONS) {
469
499
  hooks[reg.event] = ensureRegistration(hooks[reg.event] ?? [], reg, options.force ?? false);
470
500
  }
471
- return applyStatusLine({ ...settings, hooks });
501
+ return applyPermissions(applyStatusLine({ ...settings, hooks }));
472
502
  }
473
503
  function applyUninstall(settings) {
474
504
  const source = settings.hooks ?? {};
@@ -486,7 +516,7 @@ function applyUninstall(settings) {
486
516
  if (isPrimStatusLine(next)) {
487
517
  next.statusLine = void 0;
488
518
  }
489
- return next;
519
+ return removePrimPermission(next);
490
520
  }
491
521
  function captureInstalled(settings) {
492
522
  return CAPTURE_EVENTS.some(
@@ -2231,6 +2261,92 @@ function registerSessionCommands(program2) {
2231
2261
  });
2232
2262
  }
2233
2263
 
2264
+ // src/commands/setup.ts
2265
+ import { spawnSync } from "child_process";
2266
+ var EXIT_INCOMPLETE = 1;
2267
+ var EXIT_USAGE3 = 2;
2268
+ function planSetupSteps(opts) {
2269
+ const scopeArgs = opts.scope === "user" ? ["--scope", "user"] : [];
2270
+ const steps = [
2271
+ {
2272
+ key: "session",
2273
+ label: opts.agent === "codex" ? "Codex integration" : "Claude Code integration",
2274
+ args: [opts.agent, "install", ...scopeArgs],
2275
+ required: true
2276
+ }
2277
+ ];
2278
+ if (opts.daemon) {
2279
+ steps.push({
2280
+ key: "daemon",
2281
+ label: "Companion daemon",
2282
+ args: ["daemon", "start"],
2283
+ required: false
2284
+ });
2285
+ }
2286
+ steps.push({ key: "hooks", label: "Git hooks", args: ["hooks", "install"], required: true });
2287
+ steps.push({ key: "skill", label: "Agent skill", args: ["skill", "install"], required: true });
2288
+ return steps;
2289
+ }
2290
+ function registerSetupCommand(program2) {
2291
+ program2.command("setup").description(
2292
+ "Install everything in one shot (auth, session + git hooks, daemon, skill, welcome)"
2293
+ ).option("--agent <agent>", "claude or codex", "claude").option("--scope <scope>", "project or user (session integration)", "project").option("--no-daemon", "skip starting the companion daemon").action((opts) => {
2294
+ if (opts.agent !== "claude" && opts.agent !== "codex") {
2295
+ process.stderr.write(`[prim] unknown --agent "${opts.agent}" (expected claude or codex)
2296
+ `);
2297
+ process.exit(EXIT_USAGE3);
2298
+ }
2299
+ if (opts.scope !== "project" && opts.scope !== "user") {
2300
+ process.stderr.write(`[prim] unknown --scope "${opts.scope}" (expected project or user)
2301
+ `);
2302
+ process.exit(EXIT_USAGE3);
2303
+ }
2304
+ const agent = opts.agent;
2305
+ const scope = opts.scope;
2306
+ const self = process.argv[1];
2307
+ const run = (args, capture = false) => {
2308
+ const r = spawnSync(process.execPath, [self, ...args], {
2309
+ stdio: capture ? ["inherit", "pipe", "inherit"] : "inherit",
2310
+ encoding: "utf-8"
2311
+ });
2312
+ return { code: r.status ?? 1, stdout: capture ? r.stdout ?? "" : "" };
2313
+ };
2314
+ const results = {};
2315
+ const note = (msg) => {
2316
+ process.stderr.write(`[prim] ${msg}
2317
+ `);
2318
+ };
2319
+ const isAuthed = (json) => {
2320
+ try {
2321
+ return JSON.parse(json || "{}").authenticated === true;
2322
+ } catch {
2323
+ return false;
2324
+ }
2325
+ };
2326
+ if (isAuthed(run(["auth", "status", "--json"], true).stdout)) {
2327
+ note("auth \xB7 already authenticated");
2328
+ results.auth = "ok";
2329
+ } else {
2330
+ note("auth \xB7 opening browser to authenticate\u2026");
2331
+ run(["auth", "login"]);
2332
+ results.auth = isAuthed(run(["auth", "status", "--json"], true).stdout) ? "ok" : "failed";
2333
+ }
2334
+ for (const step of planSetupSteps({ agent, daemon: opts.daemon, scope })) {
2335
+ note(`${step.label} \xB7 installing\u2026`);
2336
+ const { code } = run(step.args);
2337
+ results[step.key] = code === 0 ? "ok" : step.required ? "failed" : "skipped";
2338
+ }
2339
+ note("welcome");
2340
+ results.welcome = run(["welcome"]).code === 0 ? "ok" : "failed";
2341
+ const failed = Object.entries(results).filter(([, v]) => v === "failed").map(([k]) => k);
2342
+ const trail = Object.entries(results).map(([k, v]) => `${k}:${v}`).join(" \xB7 ");
2343
+ note(
2344
+ `setup ${failed.length === 0 ? "complete" : `incomplete (failed: ${failed.join(", ")})`} \u2014 ${trail}`
2345
+ );
2346
+ process.exit(failed.length === 0 ? 0 : EXIT_INCOMPLETE);
2347
+ });
2348
+ }
2349
+
2234
2350
  // src/commands/skill.ts
2235
2351
  import {
2236
2352
  closeSync as closeSync2,
@@ -2583,6 +2699,7 @@ registerDaemonCommands(program);
2583
2699
  registerReconcileCommands(program);
2584
2700
  registerStatuslineCommands(program);
2585
2701
  registerWelcomeCommand(program);
2702
+ registerSetupCommand(program);
2586
2703
  process.on("unhandledRejection", (err) => {
2587
2704
  const msg = err instanceof Error ? err.message : String(err);
2588
2705
  console.error(msg);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitive.ai/prim",
3
- "version": "0.1.0-alpha.23",
3
+ "version": "0.1.0-alpha.25",
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",