@primitive.ai/prim 0.1.0-alpha.19 → 0.1.0-alpha.21
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 +86 -37
- package/SKILL.md +44 -177
- package/dist/{chunk-6SIEWWUL.js → chunk-26VA3ADF.js} +1 -3
- package/dist/{chunk-BEEGFDGU.js → chunk-4QJOQIY6.js} +8 -0
- package/dist/{chunk-TPQ3X244.js → chunk-E5UZXMZL.js} +2 -31
- package/dist/daemon/server.js +7 -6
- package/dist/hooks/post-tool-use.js +2 -2
- package/dist/hooks/pre-commit.js +4 -157
- package/dist/hooks/pre-tool-use.js +1 -1
- package/dist/index.js +167 -438
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
bold,
|
|
3
4
|
color,
|
|
4
5
|
colorForArea,
|
|
6
|
+
dim,
|
|
5
7
|
stripAnsi
|
|
6
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-4QJOQIY6.js";
|
|
7
9
|
import {
|
|
8
10
|
checkAffectedDecisions,
|
|
9
11
|
daemonOrDirectGet,
|
|
10
|
-
formatDecisionsWarning
|
|
11
|
-
|
|
12
|
-
} from "./chunk-TPQ3X244.js";
|
|
12
|
+
formatDecisionsWarning
|
|
13
|
+
} from "./chunk-E5UZXMZL.js";
|
|
13
14
|
import {
|
|
14
15
|
HttpError,
|
|
15
16
|
REFRESH_TOKEN_PATH,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
getSiteUrl,
|
|
21
22
|
getTokenExpiresAt,
|
|
22
23
|
saveTokenExpiry
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-26VA3ADF.js";
|
|
24
25
|
import {
|
|
25
26
|
JOURNAL_DIR,
|
|
26
27
|
SESSIONS_DIR,
|
|
@@ -34,7 +35,7 @@ import {
|
|
|
34
35
|
} from "./chunk-UTKQTZHL.js";
|
|
35
36
|
|
|
36
37
|
// src/index.ts
|
|
37
|
-
import { readFileSync as
|
|
38
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
38
39
|
import { dirname as dirname6, resolve as resolve4 } from "path";
|
|
39
40
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
40
41
|
import { Command } from "commander";
|
|
@@ -747,128 +748,27 @@ ${line("project", result.project)}`);
|
|
|
747
748
|
});
|
|
748
749
|
}
|
|
749
750
|
|
|
750
|
-
// src/commands/
|
|
751
|
-
import {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
if (opts.scope) {
|
|
761
|
-
params.set("scope", opts.scope === "project" ? "task" : opts.scope);
|
|
762
|
-
}
|
|
763
|
-
const contexts = await client.get(`/api/cli/contexts?${params.toString()}`);
|
|
764
|
-
if (opts.json) {
|
|
765
|
-
printJson(contexts);
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
|
-
printContextList(contexts);
|
|
769
|
-
});
|
|
770
|
-
context.command("get <contextId>").description("Get a context by ID").option("--json", "Output as JSON (default behavior; accepted for symmetry)").action(async (contextId) => {
|
|
771
|
-
const client = getClient();
|
|
772
|
-
const ctx = await client.get(`/api/cli/contexts/${contextId}`);
|
|
773
|
-
printJson(ctx);
|
|
774
|
-
});
|
|
775
|
-
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(
|
|
776
|
-
async (opts) => {
|
|
777
|
-
const client = getClient();
|
|
778
|
-
let text = opts.text;
|
|
779
|
-
if (opts.file) {
|
|
780
|
-
text = readFileSync4(opts.file, "utf-8");
|
|
781
|
-
}
|
|
782
|
-
const taskIds = opts.projectId ? opts.projectId.split(",").map((id) => id.trim()) : void 0;
|
|
783
|
-
const result = await client.post("/api/cli/contexts", {
|
|
784
|
-
scope: opts.scope === "project" ? "task" : opts.scope,
|
|
785
|
-
name: opts.name,
|
|
786
|
-
text,
|
|
787
|
-
taskIds,
|
|
788
|
-
isSpecDocument: opts.spec ?? false
|
|
789
|
-
});
|
|
790
|
-
if (opts.json) {
|
|
791
|
-
printJson({ _id: result._id });
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
console.error(`Created context: ${result._id}`);
|
|
795
|
-
console.log(result._id);
|
|
796
|
-
}
|
|
797
|
-
);
|
|
798
|
-
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(
|
|
799
|
-
async (contextId, opts) => {
|
|
800
|
-
const client = getClient();
|
|
801
|
-
let text = opts.text;
|
|
802
|
-
if (opts.file) {
|
|
803
|
-
text = readFileSync4(opts.file, "utf-8");
|
|
804
|
-
}
|
|
805
|
-
await client.patch(`/api/cli/contexts/${contextId}`, {
|
|
806
|
-
name: opts.name,
|
|
807
|
-
text
|
|
808
|
-
});
|
|
809
|
-
if (opts.json) {
|
|
810
|
-
printJson({ _id: contextId });
|
|
811
|
-
return;
|
|
812
|
-
}
|
|
813
|
-
console.error(`Updated context: ${contextId}`);
|
|
814
|
-
console.log(contextId);
|
|
815
|
-
}
|
|
816
|
-
);
|
|
817
|
-
context.command("delete <contextId>").description("Delete a context").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
818
|
-
const client = getClient();
|
|
819
|
-
await client.delete(`/api/cli/contexts/${contextId}`);
|
|
820
|
-
if (opts.json) {
|
|
821
|
-
printJson({ _id: contextId });
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
console.error(`Deleted context: ${contextId}`);
|
|
825
|
-
console.log(contextId);
|
|
826
|
-
});
|
|
827
|
-
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) => {
|
|
828
|
-
const client = getClient();
|
|
829
|
-
await client.post(`/api/cli/contexts/${contextId}/link`, {
|
|
830
|
-
taskId: opts.project
|
|
831
|
-
});
|
|
832
|
-
if (opts.json) {
|
|
833
|
-
printJson({ _id: contextId, project: opts.project });
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
console.error(`Linked context ${contextId} to project ${opts.project}`);
|
|
837
|
-
console.log(contextId);
|
|
838
|
-
});
|
|
839
|
-
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) => {
|
|
840
|
-
const client = getClient();
|
|
841
|
-
await client.post(`/api/cli/contexts/${contextId}/unlink`, {
|
|
842
|
-
taskId: opts.project
|
|
843
|
-
});
|
|
844
|
-
if (opts.json) {
|
|
845
|
-
printJson({ _id: contextId, project: opts.project });
|
|
846
|
-
return;
|
|
847
|
-
}
|
|
848
|
-
console.error(`Unlinked context ${contextId} from project ${opts.project}`);
|
|
849
|
-
console.log(contextId);
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
function printContextList(contexts) {
|
|
853
|
-
if (contexts.length === 0) {
|
|
854
|
-
console.error("No contexts found.");
|
|
855
|
-
return;
|
|
751
|
+
// src/commands/daemon.ts
|
|
752
|
+
import { spawn } from "child_process";
|
|
753
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
754
|
+
import { homedir as homedir3 } from "os";
|
|
755
|
+
import { join as join4 } from "path";
|
|
756
|
+
|
|
757
|
+
// src/lib/presence.ts
|
|
758
|
+
function formatTeammates(names, cap) {
|
|
759
|
+
if (names === void 0) {
|
|
760
|
+
return "\u2014";
|
|
856
761
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
762
|
+
if (names.length === 0) {
|
|
763
|
+
return "just you";
|
|
764
|
+
}
|
|
765
|
+
if (names.length <= cap) {
|
|
766
|
+
return names.join(", ");
|
|
862
767
|
}
|
|
863
|
-
|
|
864
|
-
${contexts.length} context(s)`);
|
|
768
|
+
return `${names.slice(0, cap).join(", ")} +${String(names.length - cap)}`;
|
|
865
769
|
}
|
|
866
770
|
|
|
867
771
|
// src/commands/daemon.ts
|
|
868
|
-
import { spawn } from "child_process";
|
|
869
|
-
import { existsSync as existsSync4, readFileSync as readFileSync5, unlinkSync } from "fs";
|
|
870
|
-
import { homedir as homedir3 } from "os";
|
|
871
|
-
import { join as join4 } from "path";
|
|
872
772
|
var DAEMON_BIN = "prim-daemon-server";
|
|
873
773
|
var PID_PATH = join4(homedir3(), ".config", "prim", "daemon.pid");
|
|
874
774
|
var SOCK_PATH = join4(homedir3(), ".config", "prim", "sock");
|
|
@@ -885,7 +785,7 @@ function readPidfile() {
|
|
|
885
785
|
if (!existsSync4(PID_PATH)) {
|
|
886
786
|
return null;
|
|
887
787
|
}
|
|
888
|
-
const raw =
|
|
788
|
+
const raw = readFileSync4(PID_PATH, "utf-8").trim();
|
|
889
789
|
const pid = Number(raw);
|
|
890
790
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
891
791
|
return null;
|
|
@@ -1042,8 +942,9 @@ async function daemonStatus() {
|
|
|
1042
942
|
} else if (!snapshot) {
|
|
1043
943
|
process.stderr.write("[prim] \u2713 daemon live (no snapshot)\n");
|
|
1044
944
|
} else {
|
|
945
|
+
const team = snapshot.onlineNames !== void 0 ? ` \xB7 team: ${formatTeammates(snapshot.onlineNames, Number.POSITIVE_INFINITY)}` : "";
|
|
1045
946
|
process.stderr.write(
|
|
1046
|
-
`[prim] \u2713 daemon live \xB7 pid=${snapshot.pid} \xB7 uptime=${Math.round(snapshot.uptimeMs / 1e3)}s \xB7 session=${snapshot.sessionId}
|
|
947
|
+
`[prim] \u2713 daemon live \xB7 pid=${snapshot.pid} \xB7 uptime=${Math.round(snapshot.uptimeMs / 1e3)}s \xB7 session=${snapshot.sessionId}${team}
|
|
1047
948
|
`
|
|
1048
949
|
);
|
|
1049
950
|
}
|
|
@@ -1460,6 +1361,44 @@ function formatConfirmJson(result) {
|
|
|
1460
1361
|
return JSON.stringify(result.outcome, null, 2);
|
|
1461
1362
|
}
|
|
1462
1363
|
|
|
1364
|
+
// src/decisions/create.ts
|
|
1365
|
+
var CREATE_TIMEOUT_MS = 1e4;
|
|
1366
|
+
var defaultDeps4 = { getClient };
|
|
1367
|
+
function toRequestBody(request) {
|
|
1368
|
+
const candidate = {
|
|
1369
|
+
intent: request.intent,
|
|
1370
|
+
kind: request.kind,
|
|
1371
|
+
rationale: request.rationale,
|
|
1372
|
+
area: request.area,
|
|
1373
|
+
decided: request.decided,
|
|
1374
|
+
alternatives: request.alternatives,
|
|
1375
|
+
confidence: request.confidence,
|
|
1376
|
+
reversibility: request.reversibility,
|
|
1377
|
+
files: request.files
|
|
1378
|
+
};
|
|
1379
|
+
const body = {};
|
|
1380
|
+
for (const [key, value] of Object.entries(candidate)) {
|
|
1381
|
+
const isEmpty = value === void 0 || Array.isArray(value) && value.length === 0;
|
|
1382
|
+
if (!isEmpty) {
|
|
1383
|
+
body[key] = value;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
return body;
|
|
1387
|
+
}
|
|
1388
|
+
async function fetchCreate(request, deps = defaultDeps4) {
|
|
1389
|
+
const client = deps.getClient();
|
|
1390
|
+
return await client.post("/api/cli/decisions/create", toRequestBody(request), {
|
|
1391
|
+
signal: AbortSignal.timeout(CREATE_TIMEOUT_MS)
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
function formatCreateHuman(outcome) {
|
|
1395
|
+
const id = renderIdentifier({ shortId: outcome.shortId, id: outcome.decisionId });
|
|
1396
|
+
return `[prim] created ${id}.`;
|
|
1397
|
+
}
|
|
1398
|
+
function formatCreateJson(outcome) {
|
|
1399
|
+
return JSON.stringify(outcome, null, 2);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1463
1402
|
// src/decisions/show.ts
|
|
1464
1403
|
var NOT_FOUND_RE3 = /not found/i;
|
|
1465
1404
|
function colorStatus(status) {
|
|
@@ -1472,14 +1411,14 @@ function colorStatus(status) {
|
|
|
1472
1411
|
return color(status, "gray");
|
|
1473
1412
|
}
|
|
1474
1413
|
var SHOW_TIMEOUT_MS = 1e4;
|
|
1475
|
-
var
|
|
1414
|
+
var defaultDeps5 = { getClient };
|
|
1476
1415
|
var DecisionNotFoundError = class extends Error {
|
|
1477
1416
|
constructor(idOrShortId) {
|
|
1478
1417
|
super(`Decision not found: ${idOrShortId}`);
|
|
1479
1418
|
this.name = "DecisionNotFoundError";
|
|
1480
1419
|
}
|
|
1481
1420
|
};
|
|
1482
|
-
async function fetchShow(idOrShortId, deps =
|
|
1421
|
+
async function fetchShow(idOrShortId, deps = defaultDeps5) {
|
|
1483
1422
|
const params = new URLSearchParams({ id: idOrShortId });
|
|
1484
1423
|
const client = deps.getClient();
|
|
1485
1424
|
try {
|
|
@@ -1596,13 +1535,15 @@ function formatShowJson(result) {
|
|
|
1596
1535
|
|
|
1597
1536
|
// src/commands/decisions.ts
|
|
1598
1537
|
var EXIT_NOT_FOUND = 4;
|
|
1538
|
+
var EXIT_USAGE = 2;
|
|
1539
|
+
var splitList = (value) => (value ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
1599
1540
|
function registerDecisionsCommands(program2) {
|
|
1600
1541
|
const decisions = program2.command("decisions").description("Inspect the project Decision Graph");
|
|
1601
1542
|
decisions.command("check").description("Look up active decisions that reference one or more file paths").requiredOption(
|
|
1602
1543
|
"--files <files>",
|
|
1603
1544
|
"Comma-separated file paths to check against the Decision Graph"
|
|
1604
1545
|
).action(async (opts) => {
|
|
1605
|
-
const filePaths = opts.files
|
|
1546
|
+
const filePaths = splitList(opts.files);
|
|
1606
1547
|
const result = await checkAffectedDecisions(filePaths);
|
|
1607
1548
|
const warning = formatDecisionsWarning(result);
|
|
1608
1549
|
if (warning) {
|
|
@@ -1664,11 +1605,45 @@ function registerDecisionsCommands(program2) {
|
|
|
1664
1605
|
throw err;
|
|
1665
1606
|
}
|
|
1666
1607
|
});
|
|
1608
|
+
decisions.command("create").description("Author a decision directly \u2014 the deliberate manual path around automatic capture").requiredOption("--intent <text>", "What was decided (required)").option("--kind <kind>", "change | exploration | task_execution | unclear (default change)").option("--rationale <text>", "Why the decision was made").option(
|
|
1609
|
+
"--area <area>",
|
|
1610
|
+
"Functional area (auth, data, infra, ui, api, billing, mobile, docs, testing)"
|
|
1611
|
+
).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(
|
|
1612
|
+
"--files <paths>",
|
|
1613
|
+
"Comma-separated repo-relative paths this decision governs (gates edits to them)"
|
|
1614
|
+
).action(async (opts) => {
|
|
1615
|
+
const request = {
|
|
1616
|
+
intent: opts.intent,
|
|
1617
|
+
kind: opts.kind,
|
|
1618
|
+
rationale: opts.rationale,
|
|
1619
|
+
area: opts.area,
|
|
1620
|
+
decided: splitList(opts.decided),
|
|
1621
|
+
alternatives: splitList(opts.alternatives),
|
|
1622
|
+
confidence: opts.confidence,
|
|
1623
|
+
reversibility: opts.reversibility,
|
|
1624
|
+
files: splitList(opts.files)
|
|
1625
|
+
};
|
|
1626
|
+
try {
|
|
1627
|
+
const outcome = await fetchCreate(request);
|
|
1628
|
+
console.error(formatCreateHuman(outcome));
|
|
1629
|
+
console.log(formatCreateJson(outcome));
|
|
1630
|
+
} catch (err) {
|
|
1631
|
+
if (err instanceof HttpError && err.status >= 400 && err.status < 500) {
|
|
1632
|
+
console.error(`[prim] create rejected: ${err.message}`);
|
|
1633
|
+
console.log(
|
|
1634
|
+
JSON.stringify({ ok: false, status: err.status, error: err.message }, null, 2)
|
|
1635
|
+
);
|
|
1636
|
+
process.exitCode = EXIT_USAGE;
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
throw err;
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1667
1642
|
}
|
|
1668
1643
|
|
|
1669
1644
|
// src/commands/hooks.ts
|
|
1670
1645
|
import { execSync } from "child_process";
|
|
1671
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as
|
|
1646
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1672
1647
|
import { resolve } from "path";
|
|
1673
1648
|
import { Option } from "commander";
|
|
1674
1649
|
var PRE_COMMIT = { hookName: "pre-commit", binName: "prim-pre-commit" };
|
|
@@ -1717,7 +1692,7 @@ function detectHusky(gitRoot) {
|
|
|
1717
1692
|
const pkgPath = resolve(gitRoot, "package.json");
|
|
1718
1693
|
if (existsSync5(pkgPath)) {
|
|
1719
1694
|
try {
|
|
1720
|
-
const pkg2 = JSON.parse(
|
|
1695
|
+
const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
1721
1696
|
const scripts = pkg2.scripts ?? {};
|
|
1722
1697
|
if (/husky/i.test(scripts.prepare ?? "") || /husky/i.test(scripts.postinstall ?? "")) {
|
|
1723
1698
|
return true;
|
|
@@ -1745,7 +1720,7 @@ async function askConfirmation(question) {
|
|
|
1745
1720
|
function installToHusky(gitRoot, spec = PRE_COMMIT) {
|
|
1746
1721
|
const hookPath = resolve(gitRoot, ".husky", spec.hookName);
|
|
1747
1722
|
if (existsSync5(hookPath)) {
|
|
1748
|
-
const existing =
|
|
1723
|
+
const existing = readFileSync5(hookPath, "utf-8");
|
|
1749
1724
|
if (containsPrimHook(existing, spec.binName)) {
|
|
1750
1725
|
console.log(`Prim ${spec.hookName} hook is already installed in .husky/${spec.hookName}.`);
|
|
1751
1726
|
return;
|
|
@@ -1773,7 +1748,7 @@ function installToDotGit(gitRoot, spec = PRE_COMMIT) {
|
|
|
1773
1748
|
mkdirSync3(hooksDir, { recursive: true });
|
|
1774
1749
|
}
|
|
1775
1750
|
if (existsSync5(hookPath)) {
|
|
1776
|
-
const existing =
|
|
1751
|
+
const existing = readFileSync5(hookPath, "utf-8");
|
|
1777
1752
|
if (containsPrimHook(existing, spec.binName)) {
|
|
1778
1753
|
console.log(`Prim ${spec.hookName} hook is already installed at ${hookPath}.`);
|
|
1779
1754
|
return;
|
|
@@ -1840,7 +1815,7 @@ function registerHooksCommands(program2) {
|
|
|
1840
1815
|
console.log(`No ${spec.hookName} hook found.`);
|
|
1841
1816
|
continue;
|
|
1842
1817
|
}
|
|
1843
|
-
if (containsPrimHook(
|
|
1818
|
+
if (containsPrimHook(readFileSync5(hookPath, "utf-8"), spec.binName)) {
|
|
1844
1819
|
unlinkSync2(hookPath);
|
|
1845
1820
|
console.log(`Removed ${spec.hookName} hook at ${hookPath}`);
|
|
1846
1821
|
} else {
|
|
@@ -1982,31 +1957,9 @@ function registerMovesCommands(program2) {
|
|
|
1982
1957
|
});
|
|
1983
1958
|
}
|
|
1984
1959
|
|
|
1985
|
-
// src/commands/project.ts
|
|
1986
|
-
function registerProjectCommands(program2) {
|
|
1987
|
-
const project = program2.command("project").description("Manage projects");
|
|
1988
|
-
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) => {
|
|
1989
|
-
const client = getClient();
|
|
1990
|
-
const result = await client.post("/api/cli/tasks", {
|
|
1991
|
-
name: opts.name,
|
|
1992
|
-
description: opts.description,
|
|
1993
|
-
specContextId: opts.spec
|
|
1994
|
-
});
|
|
1995
|
-
if (opts.json) {
|
|
1996
|
-
printJson(opts.spec ? { _id: result._id, spec: opts.spec } : { _id: result._id });
|
|
1997
|
-
return;
|
|
1998
|
-
}
|
|
1999
|
-
console.error(`Created project: ${result._id}`);
|
|
2000
|
-
if (opts.spec) {
|
|
2001
|
-
console.error(`Linked spec: ${opts.spec}`);
|
|
2002
|
-
}
|
|
2003
|
-
console.log(result._id);
|
|
2004
|
-
});
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
1960
|
// src/commands/reconcile.ts
|
|
2008
1961
|
var EXIT_OK2 = 0;
|
|
2009
|
-
var
|
|
1962
|
+
var EXIT_USAGE2 = 2;
|
|
2010
1963
|
var EXIT_SERVER = 3;
|
|
2011
1964
|
var HTTP_CLIENT_ERROR_MIN = 400;
|
|
2012
1965
|
var HTTP_SERVER_ERROR_MIN = 500;
|
|
@@ -2050,7 +2003,7 @@ async function performReconcile(idOrShortId, opts = {}) {
|
|
|
2050
2003
|
process.stderr.write(`[prim] reconcile rejected: ${err.message}
|
|
2051
2004
|
`);
|
|
2052
2005
|
console.log(JSON.stringify({ ok: false, status: err.status, error: err.message }, null, 2));
|
|
2053
|
-
process.exitCode =
|
|
2006
|
+
process.exitCode = EXIT_USAGE2;
|
|
2054
2007
|
return;
|
|
2055
2008
|
}
|
|
2056
2009
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -2090,7 +2043,7 @@ function registerReconcileCommands(program2) {
|
|
|
2090
2043
|
import {
|
|
2091
2044
|
existsSync as existsSync7,
|
|
2092
2045
|
mkdirSync as mkdirSync5,
|
|
2093
|
-
readFileSync as
|
|
2046
|
+
readFileSync as readFileSync6,
|
|
2094
2047
|
readdirSync,
|
|
2095
2048
|
unlinkSync as unlinkSync5,
|
|
2096
2049
|
writeFileSync as writeFileSync5
|
|
@@ -2132,7 +2085,7 @@ function registerSessionCommands(program2) {
|
|
|
2132
2085
|
for (const f of files) {
|
|
2133
2086
|
const sessionId = f.replace(/\.json$/, "");
|
|
2134
2087
|
try {
|
|
2135
|
-
const m = JSON.parse(
|
|
2088
|
+
const m = JSON.parse(readFileSync6(join6(SESSIONS_DIR, f), "utf-8"));
|
|
2136
2089
|
console.log(`${sessionId} org=${m.orgId}`);
|
|
2137
2090
|
} catch {
|
|
2138
2091
|
}
|
|
@@ -2155,7 +2108,7 @@ import {
|
|
|
2155
2108
|
existsSync as existsSync8,
|
|
2156
2109
|
fsyncSync as fsyncSync2,
|
|
2157
2110
|
openSync as openSync2,
|
|
2158
|
-
readFileSync as
|
|
2111
|
+
readFileSync as readFileSync7,
|
|
2159
2112
|
renameSync as renameSync3,
|
|
2160
2113
|
writeFileSync as writeFileSync6
|
|
2161
2114
|
} from "fs";
|
|
@@ -2177,7 +2130,7 @@ function loadSkill() {
|
|
|
2177
2130
|
let dir = __dirname;
|
|
2178
2131
|
while (dir !== dirname4(dir)) {
|
|
2179
2132
|
const p = resolve2(dir, "SKILL.md");
|
|
2180
|
-
if (existsSync8(p)) return
|
|
2133
|
+
if (existsSync8(p)) return readFileSync7(p, "utf-8");
|
|
2181
2134
|
dir = dirname4(dir);
|
|
2182
2135
|
}
|
|
2183
2136
|
throw new Error("SKILL.md not found in package");
|
|
@@ -2232,7 +2185,7 @@ function resolveTarget(cwd, override) {
|
|
|
2232
2185
|
function runInstall(cwd, opts) {
|
|
2233
2186
|
const target = resolveTarget(cwd, opts.target);
|
|
2234
2187
|
if (target === null) return 1;
|
|
2235
|
-
const existing = existsSync8(target) ?
|
|
2188
|
+
const existing = existsSync8(target) ? readFileSync7(target, "utf-8") : "";
|
|
2236
2189
|
const eol = existing ? detectNewline(existing) : "\n";
|
|
2237
2190
|
const block = composeBlock(loadSkill(), eol);
|
|
2238
2191
|
const next = applyBlock(existing, block, eol);
|
|
@@ -2255,7 +2208,7 @@ function runUninstall(cwd, opts) {
|
|
|
2255
2208
|
console.log(`Skill block not present at ${target}`);
|
|
2256
2209
|
return 0;
|
|
2257
2210
|
}
|
|
2258
|
-
const existing =
|
|
2211
|
+
const existing = readFileSync7(target, "utf-8");
|
|
2259
2212
|
const next = removeBlock(existing);
|
|
2260
2213
|
if (next === null) {
|
|
2261
2214
|
console.log(`Skill block not present at ${target}`);
|
|
@@ -2271,7 +2224,7 @@ function runStatus(cwd, opts) {
|
|
|
2271
2224
|
const fileExists = existsSync8(target);
|
|
2272
2225
|
let installed = false;
|
|
2273
2226
|
if (fileExists) {
|
|
2274
|
-
const content =
|
|
2227
|
+
const content = readFileSync7(target, "utf-8");
|
|
2275
2228
|
installed = content.includes(SKILL_BEGIN) && content.includes(SKILL_END);
|
|
2276
2229
|
}
|
|
2277
2230
|
if (opts.json) {
|
|
@@ -2307,284 +2260,19 @@ function registerSkillCommands(program2) {
|
|
|
2307
2260
|
});
|
|
2308
2261
|
}
|
|
2309
2262
|
|
|
2310
|
-
// src/commands/spec.ts
|
|
2311
|
-
import { readFileSync as readFileSync9 } from "fs";
|
|
2312
|
-
function registerSpecCommands(program2) {
|
|
2313
|
-
const spec = program2.command("spec").description("Manage spec documents");
|
|
2314
|
-
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) => {
|
|
2315
|
-
const client = getClient();
|
|
2316
|
-
if (opts.projectId) {
|
|
2317
|
-
const specs = await client.get(`/api/cli/specs?rootTaskId=${opts.projectId}`);
|
|
2318
|
-
if (opts.json) {
|
|
2319
|
-
printJson(specs[0] ?? null);
|
|
2320
|
-
return;
|
|
2321
|
-
}
|
|
2322
|
-
if (specs.length === 0) {
|
|
2323
|
-
console.error("No spec document found for this project.");
|
|
2324
|
-
return;
|
|
2325
|
-
}
|
|
2326
|
-
printSpec(specs[0]);
|
|
2327
|
-
return;
|
|
2328
|
-
}
|
|
2329
|
-
const contexts = await client.get("/api/cli/specs");
|
|
2330
|
-
if (opts.json) {
|
|
2331
|
-
printJson(contexts);
|
|
2332
|
-
return;
|
|
2333
|
-
}
|
|
2334
|
-
if (contexts.length === 0) {
|
|
2335
|
-
console.error("No spec documents found.");
|
|
2336
|
-
return;
|
|
2337
|
-
}
|
|
2338
|
-
for (const ctx of contexts) {
|
|
2339
|
-
const scope = ctx.scope === "task" ? "project" : ctx.scope ?? "project";
|
|
2340
|
-
const review = ctx.specReviewStatus ?? "\u2014";
|
|
2341
|
-
const name = ctx.name ?? "(unnamed)";
|
|
2342
|
-
console.log(`${ctx._id} ${scope.padEnd(8)} ${String(review).padEnd(10)} ${name}`);
|
|
2343
|
-
}
|
|
2344
|
-
console.error(`
|
|
2345
|
-
${contexts.length} spec(s)`);
|
|
2346
|
-
});
|
|
2347
|
-
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) => {
|
|
2348
|
-
const client = getClient();
|
|
2349
|
-
const ctx = await client.get(`/api/cli/contexts/${contextId}`);
|
|
2350
|
-
if (opts.json) {
|
|
2351
|
-
printJson(ctx);
|
|
2352
|
-
return;
|
|
2353
|
-
}
|
|
2354
|
-
if (opts.textOnly) {
|
|
2355
|
-
console.log(ctx.text ?? "");
|
|
2356
|
-
return;
|
|
2357
|
-
}
|
|
2358
|
-
printSpec(ctx);
|
|
2359
|
-
});
|
|
2360
|
-
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(
|
|
2361
|
-
async (opts) => {
|
|
2362
|
-
const client = getClient();
|
|
2363
|
-
let text = opts.text;
|
|
2364
|
-
if (opts.file) {
|
|
2365
|
-
text = readFileSync9(opts.file, "utf-8");
|
|
2366
|
-
}
|
|
2367
|
-
const taskIds = opts.projectId ? opts.projectId.split(",").map((id) => id.trim()) : void 0;
|
|
2368
|
-
let linkedBranch;
|
|
2369
|
-
if (opts.branch) {
|
|
2370
|
-
const { repoFullName } = getGitContext();
|
|
2371
|
-
if (!repoFullName) {
|
|
2372
|
-
console.warn(
|
|
2373
|
-
"[prim] --branch supplied but origin remote is not GitHub; skipping link."
|
|
2374
|
-
);
|
|
2375
|
-
} else {
|
|
2376
|
-
linkedBranch = { repoFullName, branch: opts.branch };
|
|
2377
|
-
if (opts.pr) {
|
|
2378
|
-
const n = Number.parseInt(opts.pr, 10);
|
|
2379
|
-
if (Number.isFinite(n)) linkedBranch.prNumber = n;
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
const result = await client.post("/api/cli/contexts", {
|
|
2384
|
-
scope: opts.scope === "project" ? "task" : opts.scope,
|
|
2385
|
-
name: opts.name,
|
|
2386
|
-
text,
|
|
2387
|
-
taskIds,
|
|
2388
|
-
isSpecDocument: true,
|
|
2389
|
-
linkedBranch
|
|
2390
|
-
});
|
|
2391
|
-
if (opts.json) {
|
|
2392
|
-
printJson({ _id: result._id });
|
|
2393
|
-
return;
|
|
2394
|
-
}
|
|
2395
|
-
console.error(
|
|
2396
|
-
`Created spec: ${result._id}${linkedBranch ? ` (linked to ${linkedBranch.branch})` : ""}`
|
|
2397
|
-
);
|
|
2398
|
-
console.log(result._id);
|
|
2399
|
-
}
|
|
2400
|
-
);
|
|
2401
|
-
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(
|
|
2402
|
-
async (contextId, opts) => {
|
|
2403
|
-
const client = getClient();
|
|
2404
|
-
let text = opts.text;
|
|
2405
|
-
if (opts.file) {
|
|
2406
|
-
text = readFileSync9(opts.file, "utf-8");
|
|
2407
|
-
}
|
|
2408
|
-
if (!(text || opts.name)) {
|
|
2409
|
-
console.error("Provide --text, --file, or --name to update.");
|
|
2410
|
-
process.exit(1);
|
|
2411
|
-
}
|
|
2412
|
-
await client.patch(`/api/cli/contexts/${contextId}`, {
|
|
2413
|
-
name: opts.name,
|
|
2414
|
-
text,
|
|
2415
|
-
skipTiptapLifecycle: !!text
|
|
2416
|
-
});
|
|
2417
|
-
if (text) {
|
|
2418
|
-
await client.post(`/api/cli/contexts/${contextId}/inject`);
|
|
2419
|
-
}
|
|
2420
|
-
if (opts.json) {
|
|
2421
|
-
printJson({ _id: contextId });
|
|
2422
|
-
return;
|
|
2423
|
-
}
|
|
2424
|
-
console.error(`Updated spec: ${contextId}`);
|
|
2425
|
-
console.log(contextId);
|
|
2426
|
-
}
|
|
2427
|
-
);
|
|
2428
|
-
spec.command("sync <contextId>").description("Trigger spec \u2194 project DAG synchronization").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
2429
|
-
const client = getClient();
|
|
2430
|
-
const ctx = await client.get(`/api/cli/contexts/${contextId}`);
|
|
2431
|
-
if (!ctx.isSpecDocument) {
|
|
2432
|
-
console.error("Context is not a spec document. Use `prim context` instead.");
|
|
2433
|
-
process.exit(1);
|
|
2434
|
-
}
|
|
2435
|
-
await client.post(`/api/cli/contexts/${contextId}/sync`);
|
|
2436
|
-
if (opts.json) {
|
|
2437
|
-
printJson(
|
|
2438
|
-
ctx.specRootTaskId ? { _id: contextId, specRootTaskId: ctx.specRootTaskId } : { _id: contextId }
|
|
2439
|
-
);
|
|
2440
|
-
return;
|
|
2441
|
-
}
|
|
2442
|
-
console.error(`Triggered sync for spec: ${contextId}`);
|
|
2443
|
-
if (ctx.specRootTaskId) {
|
|
2444
|
-
console.error(`Root project: ${ctx.specRootTaskId}`);
|
|
2445
|
-
}
|
|
2446
|
-
console.log(contextId);
|
|
2447
|
-
});
|
|
2448
|
-
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) => {
|
|
2449
|
-
const prNumber = Number.parseInt(opts.pr, 10);
|
|
2450
|
-
if (!Number.isFinite(prNumber)) {
|
|
2451
|
-
console.error("--pr must be an integer.");
|
|
2452
|
-
process.exit(1);
|
|
2453
|
-
}
|
|
2454
|
-
const headSha = opts.sha ?? getGitContext().sha;
|
|
2455
|
-
if (!headSha) {
|
|
2456
|
-
console.error("Could not determine head SHA \u2014 pass --sha or run inside a git checkout.");
|
|
2457
|
-
process.exit(1);
|
|
2458
|
-
}
|
|
2459
|
-
const client = getClient();
|
|
2460
|
-
await client.post(`/api/cli/contexts/${contextId}/review`, {
|
|
2461
|
-
prNumber,
|
|
2462
|
-
headSha
|
|
2463
|
-
});
|
|
2464
|
-
console.log(
|
|
2465
|
-
`Scheduled review: ${contextId} against PR #${String(prNumber)} @ ${headSha.slice(0, 7)}`
|
|
2466
|
-
);
|
|
2467
|
-
});
|
|
2468
|
-
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) => {
|
|
2469
|
-
const prNumber = Number.parseInt(opts.pr, 10);
|
|
2470
|
-
if (!Number.isFinite(prNumber)) {
|
|
2471
|
-
console.error("--pr must be an integer.");
|
|
2472
|
-
process.exit(1);
|
|
2473
|
-
}
|
|
2474
|
-
const client = getClient();
|
|
2475
|
-
const result = await client.post(`/api/cli/contexts/${contextId}/drift`, {
|
|
2476
|
-
prNumber
|
|
2477
|
-
});
|
|
2478
|
-
if (result.dispatched) {
|
|
2479
|
-
const ref = result.runUrl ? `: ${result.runUrl}` : "";
|
|
2480
|
-
console.log(`Dispatched drift-fix workflow${ref}`);
|
|
2481
|
-
} else {
|
|
2482
|
-
console.error(
|
|
2483
|
-
"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."
|
|
2484
|
-
);
|
|
2485
|
-
process.exit(1);
|
|
2486
|
-
}
|
|
2487
|
-
});
|
|
2488
|
-
spec.command("status <taskId>").description(
|
|
2489
|
-
"Show task status, auto-complete suppression flag, and the most-recent bot auto-completion"
|
|
2490
|
-
).action(async (taskId) => {
|
|
2491
|
-
const client = getClient();
|
|
2492
|
-
const result = await client.get(`/api/cli/tasks/${taskId}/status`);
|
|
2493
|
-
console.log(`status: ${result.status}`);
|
|
2494
|
-
console.log(`auto-complete suppressed: ${result.autoCompleteSuppressed ? "yes" : "no"}`);
|
|
2495
|
-
const last = result.lastAutoCompleteActivity;
|
|
2496
|
-
if (last) {
|
|
2497
|
-
const when = last.createdAt ? new Date(last.createdAt).toISOString() : "\u2014";
|
|
2498
|
-
const pr = last.prNumber ? `#${String(last.prNumber)}` : "\u2014";
|
|
2499
|
-
console.log(`last auto-complete: ${when} (PR ${pr})`);
|
|
2500
|
-
if (last.explanation) {
|
|
2501
|
-
console.log(` ${last.explanation}`);
|
|
2502
|
-
}
|
|
2503
|
-
} else {
|
|
2504
|
-
console.log("last auto-complete: \u2014");
|
|
2505
|
-
}
|
|
2506
|
-
});
|
|
2507
|
-
spec.command("map <contextId>").description("Map file patterns to a spec (used by pre-commit hook to detect affected specs)").requiredOption(
|
|
2508
|
-
"-p, --pattern <patterns...>",
|
|
2509
|
-
'Glob pattern(s) to associate, e.g. "src/auth/**"'
|
|
2510
|
-
).option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
2511
|
-
const client = getClient();
|
|
2512
|
-
const result = await client.post(`/api/cli/contexts/${contextId}/map`, {
|
|
2513
|
-
patterns: opts.pattern
|
|
2514
|
-
});
|
|
2515
|
-
if (opts.json) {
|
|
2516
|
-
printJson({ _id: contextId, filePatterns: result.filePatterns });
|
|
2517
|
-
return;
|
|
2518
|
-
}
|
|
2519
|
-
console.error(`Mapped patterns to spec ${contextId}:`);
|
|
2520
|
-
for (const p of result.filePatterns) {
|
|
2521
|
-
console.error(` ${p}`);
|
|
2522
|
-
}
|
|
2523
|
-
console.log(contextId);
|
|
2524
|
-
});
|
|
2525
|
-
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) => {
|
|
2526
|
-
const client = getClient();
|
|
2527
|
-
const result = await client.post(`/api/cli/contexts/${contextId}/unmap`, {
|
|
2528
|
-
patterns: opts.pattern
|
|
2529
|
-
});
|
|
2530
|
-
if (opts.json) {
|
|
2531
|
-
printJson({ _id: contextId, filePatterns: result.filePatterns });
|
|
2532
|
-
return;
|
|
2533
|
-
}
|
|
2534
|
-
if (result.filePatterns.length === 0) {
|
|
2535
|
-
console.error(`Cleared all file patterns from spec ${contextId}`);
|
|
2536
|
-
} else {
|
|
2537
|
-
console.error(`Updated patterns for spec ${contextId}:`);
|
|
2538
|
-
for (const p of result.filePatterns) {
|
|
2539
|
-
console.error(` ${p}`);
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2542
|
-
console.log(contextId);
|
|
2543
|
-
});
|
|
2544
|
-
spec.command("auto-map <contextId>").description("Trigger auto-mapping of file patterns for a spec").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
2545
|
-
const client = getClient();
|
|
2546
|
-
await client.post(`/api/cli/contexts/${contextId}/auto-map`);
|
|
2547
|
-
if (opts.json) {
|
|
2548
|
-
printJson({ _id: contextId });
|
|
2549
|
-
return;
|
|
2550
|
-
}
|
|
2551
|
-
console.error(`Auto-mapping triggered for spec: ${contextId}`);
|
|
2552
|
-
console.log(contextId);
|
|
2553
|
-
});
|
|
2554
|
-
}
|
|
2555
|
-
function printSpec(ctx) {
|
|
2556
|
-
const name = ctx.name ?? ctx.title ?? "(unnamed)";
|
|
2557
|
-
const review = ctx.specReviewStatus ?? "\u2014";
|
|
2558
|
-
const patterns = ctx.filePatterns;
|
|
2559
|
-
console.log(`ID: ${ctx._id}`);
|
|
2560
|
-
console.log(`Name: ${name}`);
|
|
2561
|
-
console.log(`Scope: ${ctx.scope === "task" ? "project" : ctx.scope ?? "project"}`);
|
|
2562
|
-
console.log(`Review Status: ${review}`);
|
|
2563
|
-
console.log(`Root Project: ${ctx.specRootTaskId ?? "\u2014"}`);
|
|
2564
|
-
console.log(`Sync Version: ${ctx.syncVersion ?? 0}`);
|
|
2565
|
-
console.log(`Index Status: ${ctx.indexStatus ?? "\u2014"}`);
|
|
2566
|
-
console.log(`File Patterns: ${patterns?.length ? patterns.join(", ") : "\u2014"}`);
|
|
2567
|
-
if (ctx.text) {
|
|
2568
|
-
const text = ctx.text;
|
|
2569
|
-
const preview = text.length > 500 ? `${text.slice(0, 500)}\u2026` : text;
|
|
2570
|
-
console.log(`
|
|
2571
|
-
--- Text ---
|
|
2572
|
-
${preview}`);
|
|
2573
|
-
}
|
|
2574
|
-
}
|
|
2575
|
-
|
|
2576
2263
|
// src/commands/statusline.ts
|
|
2577
|
-
import { readFileSync as
|
|
2264
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
2578
2265
|
import { dirname as dirname5, resolve as resolve3 } from "path";
|
|
2579
2266
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2580
2267
|
var STATUSLINE_TIMEOUT_MS = 200;
|
|
2268
|
+
var STATUSLINE_NAME_CAP = 3;
|
|
2581
2269
|
function readPackageVersion() {
|
|
2582
2270
|
try {
|
|
2583
2271
|
const here = dirname5(fileURLToPath3(import.meta.url));
|
|
2584
2272
|
const candidates = [resolve3(here, "../../package.json"), resolve3(here, "../package.json")];
|
|
2585
2273
|
for (const path of candidates) {
|
|
2586
2274
|
try {
|
|
2587
|
-
const pkg2 = JSON.parse(
|
|
2275
|
+
const pkg2 = JSON.parse(readFileSync8(path, "utf-8"));
|
|
2588
2276
|
if (pkg2.version) {
|
|
2589
2277
|
return pkg2.version;
|
|
2590
2278
|
}
|
|
@@ -2615,7 +2303,14 @@ async function renderStatusline() {
|
|
|
2615
2303
|
if (snapshot.presenceStale) {
|
|
2616
2304
|
return `primitive ${version} (daemon: live \xB7 presence: stale)`;
|
|
2617
2305
|
}
|
|
2618
|
-
|
|
2306
|
+
let team;
|
|
2307
|
+
if (snapshot.onlineNames !== void 0) {
|
|
2308
|
+
team = `team: ${formatTeammates(snapshot.onlineNames, STATUSLINE_NAME_CAP)}`;
|
|
2309
|
+
} else if (typeof snapshot.onlineCount === "number") {
|
|
2310
|
+
team = `team: ${String(snapshot.onlineCount)} online`;
|
|
2311
|
+
} else {
|
|
2312
|
+
team = "team: \u2014";
|
|
2313
|
+
}
|
|
2619
2314
|
return `primitive ${version} (daemon: live \xB7 ${team})`;
|
|
2620
2315
|
}
|
|
2621
2316
|
function registerStatuslineCommands(program2) {
|
|
@@ -2628,19 +2323,52 @@ function registerStatuslineCommands(program2) {
|
|
|
2628
2323
|
});
|
|
2629
2324
|
}
|
|
2630
2325
|
|
|
2326
|
+
// src/commands/welcome.ts
|
|
2327
|
+
var CMD_GUTTER = 38;
|
|
2328
|
+
function formatWelcome() {
|
|
2329
|
+
const cmd = (command, desc) => ` ${dim(command.padEnd(CMD_GUTTER))}${desc}`;
|
|
2330
|
+
const bullet = (text) => ` ${color("\u2022", "green")} ${text}`;
|
|
2331
|
+
return [
|
|
2332
|
+
bold(color("Welcome to Primitive", "green")),
|
|
2333
|
+
"",
|
|
2334
|
+
"Primitive captures the decisions your team makes while coding into a",
|
|
2335
|
+
"shared graph \u2014 and flags edits that conflict with earlier ones before",
|
|
2336
|
+
"they land.",
|
|
2337
|
+
"",
|
|
2338
|
+
bold("How it works"),
|
|
2339
|
+
bullet("Capture is automatic \u2014 keep coding; your decisions are recorded for you."),
|
|
2340
|
+
bullet("The conflict gate has your back: when an edit conflicts with a"),
|
|
2341
|
+
" load-bearing decision, prim surfaces it. Run `prim reconcile dec_<id>` to clear",
|
|
2342
|
+
" that decision and retry.",
|
|
2343
|
+
bullet('Occasional yes/no prompts confirm the "why" behind a decision \u2014'),
|
|
2344
|
+
" 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");
|
|
2353
|
+
}
|
|
2354
|
+
function registerWelcomeCommand(program2) {
|
|
2355
|
+
program2.command("welcome").description("Print a brief orientation to Primitive's decision graph").action(() => {
|
|
2356
|
+
process.stderr.write(`${formatWelcome()}
|
|
2357
|
+
`);
|
|
2358
|
+
printJson({ welcomed: true });
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2631
2362
|
// src/index.ts
|
|
2632
2363
|
var __dirname2 = dirname6(fileURLToPath4(import.meta.url));
|
|
2633
|
-
var pkg = JSON.parse(
|
|
2364
|
+
var pkg = JSON.parse(readFileSync9(resolve4(__dirname2, "../package.json"), "utf-8"));
|
|
2634
2365
|
updateNotifier({ pkg }).notify();
|
|
2635
2366
|
var program = new Command();
|
|
2636
|
-
program.name("prim").description("CLI for
|
|
2367
|
+
program.name("prim").description("CLI for Primitive's decision graph").version(pkg.version).option("-y, --yes", "auto-confirm prompts").option(
|
|
2637
2368
|
"--non-interactive",
|
|
2638
2369
|
"fail fast instead of prompting (also: CI=1, PRIM_NON_INTERACTIVE=1)"
|
|
2639
2370
|
);
|
|
2640
2371
|
registerAuthCommands(program);
|
|
2641
|
-
registerContextCommands(program);
|
|
2642
|
-
registerSpecCommands(program);
|
|
2643
|
-
registerProjectCommands(program);
|
|
2644
2372
|
registerHooksCommands(program);
|
|
2645
2373
|
registerSkillCommands(program);
|
|
2646
2374
|
registerMovesCommands(program);
|
|
@@ -2651,6 +2379,7 @@ registerCodexCommands(program);
|
|
|
2651
2379
|
registerDaemonCommands(program);
|
|
2652
2380
|
registerReconcileCommands(program);
|
|
2653
2381
|
registerStatuslineCommands(program);
|
|
2382
|
+
registerWelcomeCommand(program);
|
|
2654
2383
|
process.on("unhandledRejection", (err) => {
|
|
2655
2384
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2656
2385
|
console.error(msg);
|