@primitive.ai/prim 0.1.0-alpha.48 → 0.1.0-alpha.50
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 +37 -4
- package/SKILL.md +39 -10
- package/dist/{chunk-5K725K35.js → chunk-BOTKPLTI.js} +1 -1
- package/dist/{chunk-TPROV45L.js → chunk-DWDEQRWN.js} +12 -1
- package/dist/{chunk-F7O7V6ZM.js → chunk-HSZPTVKU.js} +12 -88
- package/dist/chunk-R5ZJRSLV.js +82 -0
- package/dist/{chunk-WRPKAPVE.js → chunk-VDLAAUWV.js} +1 -1
- package/dist/{chunk-CK75QBCB.js → chunk-WELBNODJ.js} +1 -1
- package/dist/daemon/server.js +46 -18
- package/dist/hooks/post-commit.js +23 -9
- package/dist/hooks/post-tool-use.js +6 -4
- package/dist/hooks/pre-commit.js +2 -2
- package/dist/hooks/pre-tool-use.js +5 -3
- package/dist/hooks/prim-hook.js +7 -5
- package/dist/hooks/session-start.js +27 -5
- package/dist/index.js +131 -82
- package/dist/statusline-main.js +76 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
checkAffectedDecisions,
|
|
13
13
|
daemonOrDirectGet,
|
|
14
14
|
formatDecisionsWarning
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-VDLAAUWV.js";
|
|
16
16
|
import {
|
|
17
17
|
JOURNAL_DIR,
|
|
18
18
|
SESSIONS_DIR,
|
|
@@ -21,23 +21,27 @@ import {
|
|
|
21
21
|
listFlushing,
|
|
22
22
|
pendingJournalStats,
|
|
23
23
|
readMovesFromPath
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-BOTKPLTI.js";
|
|
25
25
|
import {
|
|
26
26
|
fetchFeedbackCapability
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-WELBNODJ.js";
|
|
28
28
|
import {
|
|
29
29
|
inspectWorkspaceId
|
|
30
30
|
} from "./chunk-IMAIBPUC.js";
|
|
31
31
|
import {
|
|
32
|
-
activateRepoBestEffort,
|
|
33
32
|
binFile,
|
|
34
33
|
commandMatchesBin,
|
|
35
34
|
detachedHookShimCommand,
|
|
36
|
-
gitToplevel,
|
|
37
35
|
hookShimCommand,
|
|
38
|
-
setRepoActive,
|
|
39
36
|
warmBinCache
|
|
40
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-HSZPTVKU.js";
|
|
38
|
+
import {
|
|
39
|
+
activateRepoBestEffort,
|
|
40
|
+
decisionIngestionStatus,
|
|
41
|
+
gitToplevel,
|
|
42
|
+
isRepoActiveForCapture,
|
|
43
|
+
setRepoActive
|
|
44
|
+
} from "./chunk-R5ZJRSLV.js";
|
|
41
45
|
import {
|
|
42
46
|
HttpError,
|
|
43
47
|
REFRESH_TOKEN_PATH,
|
|
@@ -52,7 +56,7 @@ import {
|
|
|
52
56
|
resolveAuthCredential,
|
|
53
57
|
setStoredToken,
|
|
54
58
|
withFileLock
|
|
55
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-DWDEQRWN.js";
|
|
56
60
|
import {
|
|
57
61
|
daemonIsLive,
|
|
58
62
|
daemonRequest
|
|
@@ -60,7 +64,7 @@ import {
|
|
|
60
64
|
|
|
61
65
|
// src/index.ts
|
|
62
66
|
import { readFileSync as readFileSync12 } from "fs";
|
|
63
|
-
import { dirname as
|
|
67
|
+
import { dirname as dirname9, resolve as resolve6 } from "path";
|
|
64
68
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
65
69
|
import { Command } from "commander";
|
|
66
70
|
import updateNotifier from "update-notifier";
|
|
@@ -1703,6 +1707,12 @@ var HEALTHY_POLL_MS = 250;
|
|
|
1703
1707
|
var EXIT_OK2 = 0;
|
|
1704
1708
|
var EXIT_NOT_RUNNING = 2;
|
|
1705
1709
|
var EXIT_BOOTING = 3;
|
|
1710
|
+
function formatDaemonLifecycleMessage(message, decisionIngestion) {
|
|
1711
|
+
return `${message} \xB7 Decision ingestion ${decisionIngestion}`;
|
|
1712
|
+
}
|
|
1713
|
+
function formatCurrentDaemonLifecycleMessage(message) {
|
|
1714
|
+
return formatDaemonLifecycleMessage(message, decisionIngestionStatus(process.cwd()));
|
|
1715
|
+
}
|
|
1706
1716
|
var MAX_HEALTH_ERROR_LENGTH = 240;
|
|
1707
1717
|
function boundedHealthError(value) {
|
|
1708
1718
|
if (!value) return void 0;
|
|
@@ -1819,8 +1829,10 @@ async function detachedDaemonStart(opts) {
|
|
|
1819
1829
|
if (existing?.alive) {
|
|
1820
1830
|
const snapshot = await verifiedPid(existing);
|
|
1821
1831
|
if (snapshot) {
|
|
1822
|
-
process.stderr.write(
|
|
1823
|
-
`)
|
|
1832
|
+
process.stderr.write(
|
|
1833
|
+
`${formatCurrentDaemonLifecycleMessage(`[prim] daemon already running (pid=${existing.pid})`)}
|
|
1834
|
+
`
|
|
1835
|
+
);
|
|
1824
1836
|
console.log(JSON.stringify({ started: false, pid: existing.pid }, null, 2));
|
|
1825
1837
|
return;
|
|
1826
1838
|
}
|
|
@@ -1876,7 +1888,7 @@ async function detachedDaemonStart(opts) {
|
|
|
1876
1888
|
if (live) {
|
|
1877
1889
|
const after = readPidfile();
|
|
1878
1890
|
process.stderr.write(
|
|
1879
|
-
`[prim] \u2713 daemon started (pid=${after?.pid ?? "?"}, socket=${SOCK_PATH})
|
|
1891
|
+
`${formatCurrentDaemonLifecycleMessage(`[prim] \u2713 daemon started (pid=${after?.pid ?? "?"}, socket=${SOCK_PATH})`)}
|
|
1880
1892
|
`
|
|
1881
1893
|
);
|
|
1882
1894
|
console.log(JSON.stringify({ started: true, pid: after?.pid }, null, 2));
|
|
@@ -1966,7 +1978,7 @@ async function macDaemonStart(forceRestart = false) {
|
|
|
1966
1978
|
if (healthy) {
|
|
1967
1979
|
const verb = result.action === "none" ? "already running" : "started";
|
|
1968
1980
|
process.stderr.write(
|
|
1969
|
-
`[prim] \u2713 daemon ${verb} under launchd (pid=${snapshot?.pid ?? result.service.pid ?? "?"})
|
|
1981
|
+
`${formatCurrentDaemonLifecycleMessage(`[prim] \u2713 daemon ${verb} under launchd (pid=${snapshot?.pid ?? result.service.pid ?? "?"})`)}
|
|
1970
1982
|
`
|
|
1971
1983
|
);
|
|
1972
1984
|
} else {
|
|
@@ -2140,28 +2152,23 @@ function classifyLaunchdStatus(service, responding, snapshot, disabled) {
|
|
|
2140
2152
|
exitCode: EXIT_OK2
|
|
2141
2153
|
};
|
|
2142
2154
|
}
|
|
2143
|
-
function
|
|
2155
|
+
function formatDaemonSnapshotMessage(snapshot, supervised, decisionIngestion) {
|
|
2144
2156
|
if (!snapshot) {
|
|
2145
|
-
|
|
2146
|
-
supervised ? "[prim] \u2713 daemon live under launchd (no snapshot)\n" : "[prim] \u2713 daemon live\n"
|
|
2147
|
-
);
|
|
2148
|
-
return;
|
|
2157
|
+
return supervised ? `[prim] \u2713 daemon live, Decision ingestion ${decisionIngestion} under launchd (no snapshot)` : `[prim] \u2713 daemon live, Decision ingestion ${decisionIngestion}`;
|
|
2149
2158
|
}
|
|
2150
2159
|
const team = snapshot.onlineNames !== void 0 ? ` \xB7 team: ${formatTeammates(snapshot.onlineNames, Number.POSITIVE_INFINITY)}` : "";
|
|
2151
2160
|
if (snapshot.healthy === false) {
|
|
2152
2161
|
const reason = daemonDegradedReason(snapshot);
|
|
2153
|
-
|
|
2154
|
-
`[prim] \u2717 daemon unhealthy${supervised ? " under launchd" : ""} \xB7 pid=${snapshot.pid}${team}${reason ? ` \xB7 ${reason}` : ""}
|
|
2155
|
-
`
|
|
2156
|
-
);
|
|
2157
|
-
return;
|
|
2162
|
+
return `[prim] \u2717 daemon unhealthy${supervised ? " under launchd" : ""} \xB7 pid=${snapshot.pid}${team}${reason ? ` \xB7 ${reason}` : ""}`;
|
|
2158
2163
|
}
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
);
|
|
2164
|
+
return `[prim] \u2713 daemon live, Decision ingestion ${decisionIngestion}${supervised ? " under launchd" : ""} \xB7 pid=${snapshot.pid} \xB7 uptime=${Math.round(
|
|
2165
|
+
snapshot.uptimeMs / 1e3
|
|
2166
|
+
)}s \xB7 session=${snapshot.sessionId}${team}`;
|
|
2167
|
+
}
|
|
2168
|
+
function writeLiveSnapshot(snapshot, supervised = false) {
|
|
2169
|
+
const decisionIngestion = snapshot?.healthy === false ? "disabled" : decisionIngestionStatus(process.cwd());
|
|
2170
|
+
process.stderr.write(`${formatDaemonSnapshotMessage(snapshot, supervised, decisionIngestion)}
|
|
2171
|
+
`);
|
|
2165
2172
|
}
|
|
2166
2173
|
async function detachedDaemonStatus() {
|
|
2167
2174
|
const pid = readPidfile();
|
|
@@ -2262,8 +2269,10 @@ async function daemonEnsure() {
|
|
|
2262
2269
|
if (result.state === "disabled") {
|
|
2263
2270
|
process.stderr.write("[prim] daemon remains explicitly disabled\n");
|
|
2264
2271
|
} else if (result.state === "running") {
|
|
2265
|
-
process.stderr.write(
|
|
2266
|
-
`)
|
|
2272
|
+
process.stderr.write(
|
|
2273
|
+
`${formatCurrentDaemonLifecycleMessage(`[prim] \u2713 daemon ensured under launchd (${result.action})`)}
|
|
2274
|
+
`
|
|
2275
|
+
);
|
|
2267
2276
|
} else {
|
|
2268
2277
|
process.stderr.write(`[prim] \u2717 daemon ensure failed; see ${LOG_PATH}
|
|
2269
2278
|
`);
|
|
@@ -2999,9 +3008,29 @@ function formatShowJson(result) {
|
|
|
2999
3008
|
return JSON.stringify(result, null, 2);
|
|
3000
3009
|
}
|
|
3001
3010
|
|
|
3011
|
+
// src/lib/confirmation.ts
|
|
3012
|
+
function isNonInteractive(globals) {
|
|
3013
|
+
return Boolean(globals.nonInteractive || process.env.CI || process.env.PRIM_NON_INTERACTIVE);
|
|
3014
|
+
}
|
|
3015
|
+
async function askConfirmation(question, output = process.stdout) {
|
|
3016
|
+
if (!process.stdin.isTTY) return false;
|
|
3017
|
+
const { createInterface: createInterface2 } = await import("readline/promises");
|
|
3018
|
+
const rl = createInterface2({ input: process.stdin, output });
|
|
3019
|
+
try {
|
|
3020
|
+
const answer = await rl.question(`${question} [y/N] `);
|
|
3021
|
+
const normalized = answer.trim().toLowerCase();
|
|
3022
|
+
return normalized === "y" || normalized === "yes";
|
|
3023
|
+
} finally {
|
|
3024
|
+
rl.close();
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3002
3028
|
// src/commands/decisions.ts
|
|
3003
3029
|
var EXIT_NOT_FOUND = 4;
|
|
3004
3030
|
var EXIT_USAGE = 2;
|
|
3031
|
+
var CREATE_INACTIVE_PROMPT = "[prim] Decision ingestion is disabled here. Create this one Decision without enabling passive ingestion?";
|
|
3032
|
+
var CREATE_INACTIVE_APPROVED = "[prim] one-time Decision creation approved; passive ingestion remains disabled here";
|
|
3033
|
+
var CREATE_INACTIVE_REJECTED = "[prim] decision not created: Decision ingestion is disabled here; rerun with prim's --yes to approve this one Decision, or run `prim enable` in a Git project";
|
|
3005
3034
|
var splitList = (value) => (value ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
3006
3035
|
function registerDecisionsCommands(program2) {
|
|
3007
3036
|
const decisions = program2.command("decisions").description("Inspect the project Decision Graph");
|
|
@@ -3081,7 +3110,19 @@ function registerDecisionsCommands(program2) {
|
|
|
3081
3110
|
).option("--decided <items>", "Comma-separated option(s) chosen").option("--alternatives <items>", "Comma-separated options considered but rejected").option("--confidence <level>", "high | medium | low (default high)").option("--reversibility <level>", "high | low (default high)").option(
|
|
3082
3111
|
"--files <paths>",
|
|
3083
3112
|
"Comma-separated repo-relative paths this decision governs (the files Conflict Gates would check \u2014 not currently enabled)"
|
|
3084
|
-
).action(async (opts) => {
|
|
3113
|
+
).action(async (opts, command) => {
|
|
3114
|
+
if (!isRepoActiveForCapture(process.cwd())) {
|
|
3115
|
+
const globals = command.optsWithGlobals();
|
|
3116
|
+
const nonInteractive = isNonInteractive(globals);
|
|
3117
|
+
const approved = Boolean(globals.yes) || !nonInteractive && await askConfirmation(CREATE_INACTIVE_PROMPT, process.stderr);
|
|
3118
|
+
if (!approved) {
|
|
3119
|
+
console.error(CREATE_INACTIVE_REJECTED);
|
|
3120
|
+
console.log(JSON.stringify({ ok: false, error: "prim_inactive" }, null, 2));
|
|
3121
|
+
process.exitCode = EXIT_USAGE;
|
|
3122
|
+
return;
|
|
3123
|
+
}
|
|
3124
|
+
console.error(CREATE_INACTIVE_APPROVED);
|
|
3125
|
+
}
|
|
3085
3126
|
const request = {
|
|
3086
3127
|
intent: opts.intent,
|
|
3087
3128
|
kind: opts.kind,
|
|
@@ -3243,10 +3284,11 @@ function classifyDaemonHealth(snapshot, options = {}) {
|
|
|
3243
3284
|
if (snapshot.healthy !== true) {
|
|
3244
3285
|
return { name: "daemon", status: "fail", detail: "health state is not ready" };
|
|
3245
3286
|
}
|
|
3287
|
+
const ingestionStatus = options.ingestionStatus ?? decisionIngestionStatus(process.cwd());
|
|
3246
3288
|
return {
|
|
3247
3289
|
name: "daemon",
|
|
3248
3290
|
status: "ok",
|
|
3249
|
-
detail: `supervised and healthy${snapshot.version ? ` \xB7 v${snapshot.version}` : ""}`
|
|
3291
|
+
detail: `supervised and healthy${snapshot.version ? ` \xB7 v${snapshot.version}` : ""} \xB7 Decision ingestion ${ingestionStatus}`
|
|
3250
3292
|
};
|
|
3251
3293
|
}
|
|
3252
3294
|
async function checkDaemon() {
|
|
@@ -3925,18 +3967,6 @@ function detectHusky(gitRoot) {
|
|
|
3925
3967
|
function containsPrimHook(content, binName = PRE_COMMIT.binName) {
|
|
3926
3968
|
return content.includes(binName);
|
|
3927
3969
|
}
|
|
3928
|
-
async function askConfirmation(question) {
|
|
3929
|
-
if (!process.stdin.isTTY) return false;
|
|
3930
|
-
const { createInterface: createInterface2 } = await import("readline/promises");
|
|
3931
|
-
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
3932
|
-
try {
|
|
3933
|
-
const answer = await rl.question(`${question} [y/N] `);
|
|
3934
|
-
const normalized = answer.trim().toLowerCase();
|
|
3935
|
-
return normalized === "y" || normalized === "yes";
|
|
3936
|
-
} finally {
|
|
3937
|
-
rl.close();
|
|
3938
|
-
}
|
|
3939
|
-
}
|
|
3940
3970
|
function installToHusky(gitRoot, spec = PRE_COMMIT) {
|
|
3941
3971
|
const hookPath = resolve2(gitRoot, ".husky", spec.hookName);
|
|
3942
3972
|
const existed = existsSync7(hookPath);
|
|
@@ -4154,9 +4184,7 @@ function registerHooksCommands(program2) {
|
|
|
4154
4184
|
return;
|
|
4155
4185
|
}
|
|
4156
4186
|
const globals = command.optsWithGlobals();
|
|
4157
|
-
const nonInteractive =
|
|
4158
|
-
globals.nonInteractive || process.env.CI || process.env.PRIM_NON_INTERACTIVE
|
|
4159
|
-
);
|
|
4187
|
+
const nonInteractive = isNonInteractive(globals);
|
|
4160
4188
|
const gitRoot = getGitRoot();
|
|
4161
4189
|
if (opts.target === "husky") return installHooks(gitRoot, "husky");
|
|
4162
4190
|
if (opts.target === "git-hooks") return installHooks(gitRoot, "git-hooks");
|
|
@@ -4213,10 +4241,11 @@ function registerHooksCommands(program2) {
|
|
|
4213
4241
|
|
|
4214
4242
|
// src/commands/moves.ts
|
|
4215
4243
|
import { existsSync as existsSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
4216
|
-
import { join as
|
|
4244
|
+
import { join as join8 } from "path";
|
|
4217
4245
|
|
|
4218
4246
|
// src/flusher.ts
|
|
4219
4247
|
import { createReadStream, renameSync as renameSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
4248
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
4220
4249
|
import { createInterface } from "readline";
|
|
4221
4250
|
var BATCH_SIZE = 500;
|
|
4222
4251
|
var HTTP_TIMEOUT_MS = 1e4;
|
|
@@ -4340,11 +4369,23 @@ async function flushOnce() {
|
|
|
4340
4369
|
return { flushed: total };
|
|
4341
4370
|
}
|
|
4342
4371
|
var flushInFlight;
|
|
4372
|
+
var FLUSH_LOCK_PATH = join7(dirname5(JOURNAL_DIR), ".flush.lock");
|
|
4373
|
+
var FLUSH_LOCK_TIMEOUT_MS = 250;
|
|
4374
|
+
function isFlushLockContended(error) {
|
|
4375
|
+
return error instanceof Error && error.message.startsWith("timed out waiting for file lock");
|
|
4376
|
+
}
|
|
4343
4377
|
function flush() {
|
|
4344
4378
|
if (flushInFlight) {
|
|
4345
4379
|
return flushInFlight;
|
|
4346
4380
|
}
|
|
4347
|
-
const attempt =
|
|
4381
|
+
const attempt = withFileLock(FLUSH_LOCK_PATH, flushOnce, {
|
|
4382
|
+
timeoutMs: FLUSH_LOCK_TIMEOUT_MS
|
|
4383
|
+
}).catch((error) => {
|
|
4384
|
+
if (isFlushLockContended(error)) {
|
|
4385
|
+
return { flushed: 0, skipped: true };
|
|
4386
|
+
}
|
|
4387
|
+
throw error;
|
|
4388
|
+
}).finally(() => {
|
|
4348
4389
|
if (flushInFlight === attempt) {
|
|
4349
4390
|
flushInFlight = void 0;
|
|
4350
4391
|
}
|
|
@@ -4382,7 +4423,11 @@ var WORKSPACE_FILE = ".prim/workspace.json";
|
|
|
4382
4423
|
function registerMovesCommands(program2) {
|
|
4383
4424
|
const moves = program2.command("moves").description("Decision Event Pipeline \u2014 local journal");
|
|
4384
4425
|
moves.command("flush").description("Drain all local move journals to the server").action(async () => {
|
|
4385
|
-
const { flushed } = await flush();
|
|
4426
|
+
const { flushed, skipped } = await flush();
|
|
4427
|
+
if (skipped) {
|
|
4428
|
+
console.log("[prim] another flush is already draining; moves left journaled");
|
|
4429
|
+
return;
|
|
4430
|
+
}
|
|
4386
4431
|
console.log(`[prim] flushed ${String(flushed)} move${flushed === 1 ? "" : "s"}`);
|
|
4387
4432
|
});
|
|
4388
4433
|
moves.command("status").description("Show per-bucket pending stats").action(() => {
|
|
@@ -4441,18 +4486,18 @@ function registerMovesCommands(program2) {
|
|
|
4441
4486
|
}
|
|
4442
4487
|
});
|
|
4443
4488
|
moves.command("bind").description("Pin the current directory to an org via .prim/workspace.json").requiredOption("--orgId <orgId>", "Convex organization id").action((opts) => {
|
|
4444
|
-
const dir =
|
|
4489
|
+
const dir = join8(process.cwd(), ".prim");
|
|
4445
4490
|
if (!existsSync8(dir)) {
|
|
4446
4491
|
mkdirSync6(dir, { recursive: true, mode: DIR_MODE });
|
|
4447
4492
|
}
|
|
4448
|
-
const file =
|
|
4493
|
+
const file = join8(process.cwd(), WORKSPACE_FILE);
|
|
4449
4494
|
writeFileSync5(file, JSON.stringify({ orgId: opts.orgId, boundAt: Date.now() }, null, 2), {
|
|
4450
4495
|
mode: FILE_MODE
|
|
4451
4496
|
});
|
|
4452
4497
|
console.log(`[prim] bound ${process.cwd()} to org ${opts.orgId}`);
|
|
4453
4498
|
});
|
|
4454
4499
|
moves.command("drop").description("Remove the .prim/workspace.json binding from the cwd").action(() => {
|
|
4455
|
-
const file =
|
|
4500
|
+
const file = join8(process.cwd(), WORKSPACE_FILE);
|
|
4456
4501
|
if (!existsSync8(file)) {
|
|
4457
4502
|
console.log("[prim] no workspace binding in cwd");
|
|
4458
4503
|
return;
|
|
@@ -4553,7 +4598,7 @@ import {
|
|
|
4553
4598
|
unlinkSync as unlinkSync5,
|
|
4554
4599
|
writeFileSync as writeFileSync6
|
|
4555
4600
|
} from "fs";
|
|
4556
|
-
import { join as
|
|
4601
|
+
import { join as join9 } from "path";
|
|
4557
4602
|
var DIR_MODE2 = 448;
|
|
4558
4603
|
var FILE_MODE2 = 384;
|
|
4559
4604
|
function ensureDir() {
|
|
@@ -4562,7 +4607,7 @@ function ensureDir() {
|
|
|
4562
4607
|
}
|
|
4563
4608
|
}
|
|
4564
4609
|
function markerPath(sessionId) {
|
|
4565
|
-
return
|
|
4610
|
+
return join9(SESSIONS_DIR, `${sessionId}.json`);
|
|
4566
4611
|
}
|
|
4567
4612
|
function registerSessionCommands(program2) {
|
|
4568
4613
|
const session = program2.command("session").description("Decision Event Pipeline \u2014 session binding markers");
|
|
@@ -4590,7 +4635,7 @@ function registerSessionCommands(program2) {
|
|
|
4590
4635
|
for (const f of files) {
|
|
4591
4636
|
const sessionId = f.replace(/\.json$/, "");
|
|
4592
4637
|
try {
|
|
4593
|
-
const m = JSON.parse(readFileSync7(
|
|
4638
|
+
const m = JSON.parse(readFileSync7(join9(SESSIONS_DIR, f), "utf-8"));
|
|
4594
4639
|
console.log(`${sessionId} org=${m.orgId}`);
|
|
4595
4640
|
} catch {
|
|
4596
4641
|
}
|
|
@@ -4610,7 +4655,7 @@ function registerSessionCommands(program2) {
|
|
|
4610
4655
|
// src/commands/setup.ts
|
|
4611
4656
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
4612
4657
|
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
4613
|
-
import { join as
|
|
4658
|
+
import { join as join10 } from "path";
|
|
4614
4659
|
var EXIT_INCOMPLETE = 1;
|
|
4615
4660
|
var EXIT_USAGE3 = 2;
|
|
4616
4661
|
var SESSION_LABELS = {
|
|
@@ -4686,7 +4731,7 @@ function detectProjectConflicts(agent, run) {
|
|
|
4686
4731
|
}
|
|
4687
4732
|
try {
|
|
4688
4733
|
const root = gitToplevel();
|
|
4689
|
-
const preCommit = root &&
|
|
4734
|
+
const preCommit = root && join10(root, ".git", "hooks", "pre-commit");
|
|
4690
4735
|
if (preCommit && existsSync10(preCommit) && readFileSync8(preCommit, "utf-8").includes("prim-pre-commit")) {
|
|
4691
4736
|
conflicts.push(CONFLICT_HOOKS);
|
|
4692
4737
|
}
|
|
@@ -4837,31 +4882,31 @@ import {
|
|
|
4837
4882
|
writeFileSync as writeFileSync7
|
|
4838
4883
|
} from "fs";
|
|
4839
4884
|
import { homedir as homedir8 } from "os";
|
|
4840
|
-
import { dirname as
|
|
4885
|
+
import { dirname as dirname7, join as join12, resolve as resolve4 } from "path";
|
|
4841
4886
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4842
4887
|
import { createPatch } from "diff";
|
|
4843
4888
|
|
|
4844
4889
|
// src/commands/claude-plugin.ts
|
|
4845
4890
|
import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync9, readdirSync as readdirSync2, rmSync as rmSync3, rmdirSync } from "fs";
|
|
4846
4891
|
import { homedir as homedir7 } from "os";
|
|
4847
|
-
import { dirname as
|
|
4892
|
+
import { dirname as dirname6, join as join11, resolve as resolve3 } from "path";
|
|
4848
4893
|
import { fileURLToPath } from "url";
|
|
4849
|
-
var __dirname =
|
|
4894
|
+
var __dirname = dirname6(fileURLToPath(import.meta.url));
|
|
4850
4895
|
var PLUGIN_DESCRIPTION = "Primitive decision-graph guidance for the prim CLI \u2014 a model-invoked skill installed by prim skill install.";
|
|
4851
4896
|
function resolvePluginDir(cwd, scope) {
|
|
4852
4897
|
if (scope && scope !== "user" && scope !== "project") {
|
|
4853
4898
|
console.error(`Unknown --scope "${scope}" (expected user or project)`);
|
|
4854
4899
|
return null;
|
|
4855
4900
|
}
|
|
4856
|
-
const base = scope === "user" ?
|
|
4857
|
-
return
|
|
4901
|
+
const base = scope === "user" ? join11(homedir7(), ".claude") : join11(gitToplevel(cwd) ?? cwd, ".claude");
|
|
4902
|
+
return join11(base, "skills", "prim");
|
|
4858
4903
|
}
|
|
4859
4904
|
function packageVersion() {
|
|
4860
4905
|
let dir = __dirname;
|
|
4861
|
-
while (dir !==
|
|
4906
|
+
while (dir !== dirname6(dir)) {
|
|
4862
4907
|
const p = resolve3(dir, "package.json");
|
|
4863
4908
|
if (existsSync11(p)) return JSON.parse(readFileSync9(p, "utf-8")).version;
|
|
4864
|
-
dir =
|
|
4909
|
+
dir = dirname6(dir);
|
|
4865
4910
|
}
|
|
4866
4911
|
return "0.0.0";
|
|
4867
4912
|
}
|
|
@@ -4875,8 +4920,8 @@ function removeDirIfEmpty(dir) {
|
|
|
4875
4920
|
}
|
|
4876
4921
|
function pluginPaths(dir) {
|
|
4877
4922
|
return {
|
|
4878
|
-
manifestPath:
|
|
4879
|
-
skillPath:
|
|
4923
|
+
manifestPath: join11(dir, ".claude-plugin", "plugin.json"),
|
|
4924
|
+
skillPath: join11(dir, "SKILL.md")
|
|
4880
4925
|
};
|
|
4881
4926
|
}
|
|
4882
4927
|
function installClaudePlugin(cwd, opts) {
|
|
@@ -4895,7 +4940,7 @@ function installClaudePlugin(cwd, opts) {
|
|
|
4895
4940
|
console.log(`Would write plugin to ${dir} (.claude-plugin/plugin.json + SKILL.md)`);
|
|
4896
4941
|
return 0;
|
|
4897
4942
|
}
|
|
4898
|
-
mkdirSync8(
|
|
4943
|
+
mkdirSync8(join11(dir, ".claude-plugin"), { recursive: true });
|
|
4899
4944
|
atomicWrite3(manifestPath, manifest);
|
|
4900
4945
|
atomicWrite3(skillPath, skill);
|
|
4901
4946
|
console.log(`Installed prim skill plugin at ${dir}`);
|
|
@@ -4915,7 +4960,7 @@ function uninstallClaudePlugin(cwd, opts) {
|
|
|
4915
4960
|
}
|
|
4916
4961
|
rmSync3(manifestPath, { force: true });
|
|
4917
4962
|
rmSync3(skillPath, { force: true });
|
|
4918
|
-
removeDirIfEmpty(
|
|
4963
|
+
removeDirIfEmpty(join11(dir, ".claude-plugin"));
|
|
4919
4964
|
removeDirIfEmpty(dir);
|
|
4920
4965
|
console.log(`Removed prim skill plugin from ${dir}`);
|
|
4921
4966
|
return 0;
|
|
@@ -4938,7 +4983,7 @@ function statusClaudePlugin(cwd, opts) {
|
|
|
4938
4983
|
}
|
|
4939
4984
|
|
|
4940
4985
|
// src/commands/skill.ts
|
|
4941
|
-
var __dirname2 =
|
|
4986
|
+
var __dirname2 = dirname7(fileURLToPath2(import.meta.url));
|
|
4942
4987
|
var SKILL_BEGIN = "<!-- BEGIN PRIM SKILL v1 -->";
|
|
4943
4988
|
var SKILL_END = "<!-- END PRIM SKILL v1 -->";
|
|
4944
4989
|
var TARGET_CANDIDATES = [
|
|
@@ -4956,19 +5001,19 @@ var AGENT_TARGET = {
|
|
|
4956
5001
|
hermes: ".hermes.md"
|
|
4957
5002
|
};
|
|
4958
5003
|
function userTargetFor(agent) {
|
|
4959
|
-
if (agent === "claude") return
|
|
4960
|
-
if (agent === "codex") return
|
|
5004
|
+
if (agent === "claude") return join12(homedir8(), ".claude", "CLAUDE.md");
|
|
5005
|
+
if (agent === "codex") return join12(homedir8(), ".codex", "AGENTS.md");
|
|
4961
5006
|
if (agent === "hermes") {
|
|
4962
|
-
return
|
|
5007
|
+
return join12(process.env.HERMES_HOME ?? join12(homedir8(), ".hermes"), ".hermes.md");
|
|
4963
5008
|
}
|
|
4964
5009
|
return null;
|
|
4965
5010
|
}
|
|
4966
5011
|
function loadSkill() {
|
|
4967
5012
|
let dir = __dirname2;
|
|
4968
|
-
while (dir !==
|
|
5013
|
+
while (dir !== dirname7(dir)) {
|
|
4969
5014
|
const p = resolve4(dir, "SKILL.md");
|
|
4970
5015
|
if (existsSync12(p)) return readFileSync10(p, "utf-8");
|
|
4971
|
-
dir =
|
|
5016
|
+
dir = dirname7(dir);
|
|
4972
5017
|
}
|
|
4973
5018
|
throw new Error("SKILL.md not found in package");
|
|
4974
5019
|
}
|
|
@@ -5122,13 +5167,13 @@ function registerSkillCommands(program2) {
|
|
|
5122
5167
|
|
|
5123
5168
|
// src/commands/statusline.ts
|
|
5124
5169
|
import { readFileSync as readFileSync11 } from "fs";
|
|
5125
|
-
import { dirname as
|
|
5170
|
+
import { dirname as dirname8, resolve as resolve5 } from "path";
|
|
5126
5171
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5127
5172
|
var STATUSLINE_TIMEOUT_MS = 200;
|
|
5128
5173
|
var STATUSLINE_NAME_CAP = 3;
|
|
5129
5174
|
function readPackageVersion() {
|
|
5130
5175
|
try {
|
|
5131
|
-
const here =
|
|
5176
|
+
const here = dirname8(fileURLToPath3(import.meta.url));
|
|
5132
5177
|
const candidates = [
|
|
5133
5178
|
// The supervised runtime stages a tiny manifest beside the standalone
|
|
5134
5179
|
// statusline bundle, so it remains versioned without loading the package.
|
|
@@ -5169,6 +5214,9 @@ async function renderStatusline() {
|
|
|
5169
5214
|
return `primitive ${version} (daemon: down)`;
|
|
5170
5215
|
}
|
|
5171
5216
|
if (snapshot.healthy === false) {
|
|
5217
|
+
if (snapshot.needsReauth) {
|
|
5218
|
+
return `primitive ${version} (daemon: paused \xB7 run \`prim auth login\`)`;
|
|
5219
|
+
}
|
|
5172
5220
|
if (snapshot.ingestion?.healthy === false) {
|
|
5173
5221
|
const pending = snapshot.ingestion.pendingCount;
|
|
5174
5222
|
const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
|
|
@@ -5179,11 +5227,12 @@ async function renderStatusline() {
|
|
|
5179
5227
|
}
|
|
5180
5228
|
return `primitive ${version} (daemon: starting)`;
|
|
5181
5229
|
}
|
|
5230
|
+
const ingestionStatus = decisionIngestionStatus(process.cwd());
|
|
5182
5231
|
if (snapshot.envMismatch) {
|
|
5183
|
-
return `primitive ${version} (daemon: live \xB7 presence: other env)`;
|
|
5232
|
+
return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: other env)`;
|
|
5184
5233
|
}
|
|
5185
5234
|
if (snapshot.presenceStale) {
|
|
5186
|
-
return `primitive ${version} (daemon: live \xB7 presence: stale)`;
|
|
5235
|
+
return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: stale)`;
|
|
5187
5236
|
}
|
|
5188
5237
|
let team;
|
|
5189
5238
|
if (snapshot.onlineTeammates !== void 0) {
|
|
@@ -5195,7 +5244,7 @@ async function renderStatusline() {
|
|
|
5195
5244
|
} else {
|
|
5196
5245
|
team = "team: \u2014";
|
|
5197
5246
|
}
|
|
5198
|
-
return `primitive ${version} (daemon: live \xB7 ${team})`;
|
|
5247
|
+
return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 ${team})`;
|
|
5199
5248
|
}
|
|
5200
5249
|
function registerStatuslineCommands(program2) {
|
|
5201
5250
|
program2.command("statusline").description("Render the Claude Code statusLine for the prim companion daemon").action(async () => {
|
|
@@ -5321,7 +5370,7 @@ function registerWelcomeCommand(program2, deps = { getClient }) {
|
|
|
5321
5370
|
}
|
|
5322
5371
|
|
|
5323
5372
|
// src/index.ts
|
|
5324
|
-
var __dirname3 =
|
|
5373
|
+
var __dirname3 = dirname9(fileURLToPath4(import.meta.url));
|
|
5325
5374
|
var pkg = JSON.parse(readFileSync12(resolve6(__dirname3, "../package.json"), "utf-8"));
|
|
5326
5375
|
updateNotifier({ pkg }).notify();
|
|
5327
5376
|
var program = new Command();
|
package/dist/statusline-main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/commands/statusline.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
5
5
|
import { dirname as dirname4, resolve as resolve2 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
7
|
|
|
@@ -132,14 +132,77 @@ function daemonRequest(method, params = {}, opts = {}) {
|
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
// src/lib/activation.ts
|
|
136
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
137
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
138
|
+
import { homedir as homedir3 } from "os";
|
|
139
|
+
import { join as join4 } from "path";
|
|
140
|
+
|
|
141
|
+
// src/lib/git.ts
|
|
142
|
+
import { execFileSync } from "child_process";
|
|
143
|
+
function gitToplevel(cwd) {
|
|
144
|
+
try {
|
|
145
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
146
|
+
cwd,
|
|
147
|
+
encoding: "utf-8",
|
|
148
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
149
|
+
}).trim();
|
|
150
|
+
} catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/lib/activation.ts
|
|
156
|
+
var PRIM_ACTIVE_KEY = "prim.active";
|
|
157
|
+
var PROJECT_INSTALL_FILES = [".claude/settings.json", ".codex/hooks.json"];
|
|
158
|
+
var PRIM_HOOK_BINS = ["prim-hook", "prim-pre-tool-use", "prim-post-tool-use"];
|
|
159
|
+
function repoActiveFlag(cwd) {
|
|
160
|
+
try {
|
|
161
|
+
const value = execFileSync2("git", ["config", "--get", PRIM_ACTIVE_KEY], {
|
|
162
|
+
cwd,
|
|
163
|
+
encoding: "utf-8",
|
|
164
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
165
|
+
}).trim();
|
|
166
|
+
if (value === "true") return "true";
|
|
167
|
+
if (value === "false") return "false";
|
|
168
|
+
return void 0;
|
|
169
|
+
} catch {
|
|
170
|
+
return void 0;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function hasProjectPrimInstall(cwd) {
|
|
174
|
+
const root = gitToplevel(cwd);
|
|
175
|
+
if (root === null || root === homedir3()) return false;
|
|
176
|
+
for (const rel of PROJECT_INSTALL_FILES) {
|
|
177
|
+
const path = join4(root, rel);
|
|
178
|
+
if (existsSync3(path)) {
|
|
179
|
+
try {
|
|
180
|
+
const content = readFileSync3(path, "utf-8");
|
|
181
|
+
if (PRIM_HOOK_BINS.some((bin) => content.includes(bin))) return true;
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
function isRepoActiveForCapture(cwd) {
|
|
189
|
+
const flag = repoActiveFlag(cwd);
|
|
190
|
+
if (flag === "true") return true;
|
|
191
|
+
if (flag === "false") return false;
|
|
192
|
+
return hasProjectPrimInstall(cwd);
|
|
193
|
+
}
|
|
194
|
+
function decisionIngestionStatus(cwd) {
|
|
195
|
+
return isRepoActiveForCapture(cwd) ? "enabled" : "disabled";
|
|
196
|
+
}
|
|
197
|
+
|
|
135
198
|
// src/lib/bin-cache.ts
|
|
136
199
|
import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
137
|
-
import { homedir as
|
|
138
|
-
import { join as
|
|
200
|
+
import { homedir as homedir4 } from "os";
|
|
201
|
+
import { join as join6 } from "path";
|
|
139
202
|
|
|
140
203
|
// src/lib/bin-path.ts
|
|
141
|
-
import { existsSync as
|
|
142
|
-
import { dirname as dirname3, isAbsolute, join as
|
|
204
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
205
|
+
import { dirname as dirname3, isAbsolute, join as join5 } from "path";
|
|
143
206
|
import { fileURLToPath } from "url";
|
|
144
207
|
var PKG_NAME = "@primitive.ai/prim";
|
|
145
208
|
var NPX_FALLBACK = `npx --yes -p ${PKG_NAME}@latest`;
|
|
@@ -218,7 +281,7 @@ function readPackageVersion() {
|
|
|
218
281
|
];
|
|
219
282
|
for (const path of candidates) {
|
|
220
283
|
try {
|
|
221
|
-
const pkg = JSON.parse(
|
|
284
|
+
const pkg = JSON.parse(readFileSync5(path, "utf-8"));
|
|
222
285
|
if (pkg.version) {
|
|
223
286
|
return pkg.version;
|
|
224
287
|
}
|
|
@@ -249,6 +312,9 @@ async function renderStatusline() {
|
|
|
249
312
|
return `primitive ${version} (daemon: down)`;
|
|
250
313
|
}
|
|
251
314
|
if (snapshot.healthy === false) {
|
|
315
|
+
if (snapshot.needsReauth) {
|
|
316
|
+
return `primitive ${version} (daemon: paused \xB7 run \`prim auth login\`)`;
|
|
317
|
+
}
|
|
252
318
|
if (snapshot.ingestion?.healthy === false) {
|
|
253
319
|
const pending = snapshot.ingestion.pendingCount;
|
|
254
320
|
const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
|
|
@@ -259,11 +325,12 @@ async function renderStatusline() {
|
|
|
259
325
|
}
|
|
260
326
|
return `primitive ${version} (daemon: starting)`;
|
|
261
327
|
}
|
|
328
|
+
const ingestionStatus = decisionIngestionStatus(process.cwd());
|
|
262
329
|
if (snapshot.envMismatch) {
|
|
263
|
-
return `primitive ${version} (daemon: live \xB7 presence: other env)`;
|
|
330
|
+
return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: other env)`;
|
|
264
331
|
}
|
|
265
332
|
if (snapshot.presenceStale) {
|
|
266
|
-
return `primitive ${version} (daemon: live \xB7 presence: stale)`;
|
|
333
|
+
return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: stale)`;
|
|
267
334
|
}
|
|
268
335
|
let team;
|
|
269
336
|
if (snapshot.onlineTeammates !== void 0) {
|
|
@@ -275,7 +342,7 @@ async function renderStatusline() {
|
|
|
275
342
|
} else {
|
|
276
343
|
team = "team: \u2014";
|
|
277
344
|
}
|
|
278
|
-
return `primitive ${version} (daemon: live \xB7 ${team})`;
|
|
345
|
+
return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 ${team})`;
|
|
279
346
|
}
|
|
280
347
|
|
|
281
348
|
// src/statusline-main.ts
|
package/package.json
CHANGED