@primitive.ai/prim 0.1.0-alpha.13 → 0.1.0-alpha.14
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/SKILL.md +24 -11
- package/dist/index.js +207 -76
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -83,9 +83,13 @@ npx --yes @primitive.ai/prim project create -n "<name>" --spec <contextId> #
|
|
|
83
83
|
|
|
84
84
|
### Install the pre-commit hook
|
|
85
85
|
```
|
|
86
|
-
npx --yes @primitive.ai/prim hooks install
|
|
86
|
+
npx --yes @primitive.ai/prim hooks install # auto-detects Husky and prompts
|
|
87
|
+
npx --yes @primitive.ai/prim hooks install --yes # confirm Husky (non-interactive)
|
|
88
|
+
npx --yes @primitive.ai/prim hooks install --target=git-hooks # force .git/hooks (skip Husky detection)
|
|
87
89
|
npx --yes @primitive.ai/prim hooks uninstall
|
|
88
90
|
```
|
|
91
|
+
Under `CI=1` (or with `--non-interactive`), `hooks install` fails fast in a Husky repo unless `--yes` or `--target` is set. The error message names both escapes.
|
|
92
|
+
|
|
89
93
|
**Note:** `hooks uninstall` only removes `.git/hooks/pre-commit`. If the hook was installed into `.husky/pre-commit`, you must remove the prim block from that file manually.
|
|
90
94
|
|
|
91
95
|
## How the pre-commit hook behaves
|
|
@@ -106,17 +110,26 @@ What that means:
|
|
|
106
110
|
|
|
107
111
|
## Output formats
|
|
108
112
|
|
|
109
|
-
|
|
113
|
+
Every data-returning command accepts `--json`. With `--json` set, stdout is a single JSON document — pipe to `jq` instead of parsing text:
|
|
114
|
+
|
|
115
|
+
- `id=$(npx --yes @primitive.ai/prim context create -s global -n foo --text "x" --json | jq -r ._id)` — capture an ID
|
|
116
|
+
- `npx --yes @primitive.ai/prim spec list --json | jq -r '.[]._id'` — list every spec ID
|
|
117
|
+
- `npx --yes @primitive.ai/prim auth status --json | jq -r .authenticated` — boolean; the exit code remains the authoritative signal
|
|
118
|
+
|
|
119
|
+
Without `--json`, mutating commands (`context create/update/delete/link/unlink`, `spec update/sync/map/unmap/auto-map`, `project create`) emit the bare resource `_id` to **stdout** (one line, no prefix) and human-readable diagnostics to **stderr**. So this also works as a one-liner without `jq`:
|
|
120
|
+
|
|
121
|
+
- `id=$(npx --yes @primitive.ai/prim context create -s global -n foo --text "x")`
|
|
122
|
+
|
|
123
|
+
| Command | Without `--json` | With `--json` |
|
|
110
124
|
|---|---|---|
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `npx --yes @primitive.ai/prim spec get <id> --text-only` | Raw spec markdown, nothing else | n/a |
|
|
125
|
+
| Mutators above | stdout: bare `_id`; stderr: `Created/Updated/...` prefix (plus secondary lines: `Root project:`, `Linked spec:`, pattern lists) | stdout: `{ "_id": "<id>", … }` with extras where applicable (`spec sync` adds `specRootTaskId`; `context link/unlink` add `project`; `project create --spec` adds `spec`; `spec map/unmap` add `filePatterns`) |
|
|
126
|
+
| `context list`, `spec list` (non-empty) | stdout: rows (first token = `_id`); stderr: `N context(s)` / `N spec(s)` summary | stdout: JSON array |
|
|
127
|
+
| `context list`, `spec list` (empty) | stdout: (empty); stderr: `No contexts found.` / `No spec documents found.` | stdout: `[]` |
|
|
128
|
+
| `spec list --project-id <pid>` | stdout: key:value block (or stdout empty + stderr `No spec document found for this project.` if none) | stdout: single object or `null` |
|
|
129
|
+
| `context get <id>` | stdout: pretty-printed JSON (always JSON; `--json` accepted for symmetry) | stdout: pretty-printed JSON |
|
|
130
|
+
| `spec get <id>` | stdout: human-readable key:value block (`ID:` line first) | stdout: JSON object |
|
|
131
|
+
| `spec get <id> --text-only` | stdout: raw spec markdown, nothing else | stdout: JSON object (`--json` wins over `--text-only`) |
|
|
132
|
+
| `auth status` | stdout: human readout; **exit code is the authoritative signal** (0 = authed) | stdout: JSON; exit code unchanged |
|
|
120
133
|
|
|
121
134
|
## Pitfalls
|
|
122
135
|
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,13 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
|
24
24
|
import { createServer } from "http";
|
|
25
25
|
import { platform } from "os";
|
|
26
26
|
import { dirname } from "path";
|
|
27
|
+
|
|
28
|
+
// src/output.ts
|
|
29
|
+
function printJson(data) {
|
|
30
|
+
console.log(JSON.stringify(data, null, 2));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/commands/auth.ts
|
|
27
34
|
var FILE_MODE = 384;
|
|
28
35
|
var LOCALHOST = "127.0.0.1";
|
|
29
36
|
var CALLBACK_PORT = 19876;
|
|
@@ -174,8 +181,22 @@ ${authUrl.toString()}
|
|
|
174
181
|
console.log("No saved tokens found.");
|
|
175
182
|
}
|
|
176
183
|
});
|
|
177
|
-
auth.command("status").description("Check authentication status and token expiry").action(() => {
|
|
184
|
+
auth.command("status").description("Check authentication status and token expiry").option("--json", "Output as JSON").action((opts) => {
|
|
178
185
|
const token = getAuthToken();
|
|
186
|
+
if (opts.json) {
|
|
187
|
+
const expiresAt2 = getTokenExpiresAt();
|
|
188
|
+
const expiresInMs = expiresAt2 ? expiresAt2 - Date.now() : null;
|
|
189
|
+
const refreshPresent = existsSync(REFRESH_TOKEN_PATH);
|
|
190
|
+
printJson({
|
|
191
|
+
authenticated: !!token,
|
|
192
|
+
tokenFile: token ? TOKEN_FILE_PATH : null,
|
|
193
|
+
accessTokenExpiresInMs: expiresInMs,
|
|
194
|
+
accessTokenExpired: expiresInMs !== null && expiresInMs <= 0,
|
|
195
|
+
refreshTokenPresent: refreshPresent,
|
|
196
|
+
warnings: !token || refreshPresent ? [] : ["no refresh token"]
|
|
197
|
+
});
|
|
198
|
+
process.exit(token ? 0 : 1);
|
|
199
|
+
}
|
|
179
200
|
if (!token) {
|
|
180
201
|
console.log("Not authenticated. Run `prim auth login` to authenticate.");
|
|
181
202
|
process.exit(1);
|
|
@@ -238,7 +259,7 @@ async function exchangeCode(siteUrl, code, codeVerifier, redirectUri) {
|
|
|
238
259
|
import { readFileSync as readFileSync2 } from "fs";
|
|
239
260
|
function registerContextCommands(program2) {
|
|
240
261
|
const context = program2.command("context").description("Manage contexts");
|
|
241
|
-
context.command("list").description("List contexts").option("-s, --scope <scope>", "Filter by scope: project, global, external").option("-t, --project-id <projectId>", "List contexts linked to a specific project").action(async (opts) => {
|
|
262
|
+
context.command("list").description("List contexts").option("-s, --scope <scope>", "Filter by scope: project, global, external").option("-t, --project-id <projectId>", "List contexts linked to a specific project").option("--json", "Output as JSON").action(async (opts) => {
|
|
242
263
|
const client = getClient();
|
|
243
264
|
const params = new URLSearchParams();
|
|
244
265
|
if (opts.projectId) {
|
|
@@ -248,14 +269,18 @@ function registerContextCommands(program2) {
|
|
|
248
269
|
params.set("scope", opts.scope === "project" ? "task" : opts.scope);
|
|
249
270
|
}
|
|
250
271
|
const contexts = await client.get(`/api/cli/contexts?${params.toString()}`);
|
|
272
|
+
if (opts.json) {
|
|
273
|
+
printJson(contexts);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
251
276
|
printContextList(contexts);
|
|
252
277
|
});
|
|
253
|
-
context.command("get <contextId>").description("Get a context by ID").action(async (contextId) => {
|
|
278
|
+
context.command("get <contextId>").description("Get a context by ID").option("--json", "Output as JSON (default behavior; accepted for symmetry)").action(async (contextId) => {
|
|
254
279
|
const client = getClient();
|
|
255
280
|
const ctx = await client.get(`/api/cli/contexts/${contextId}`);
|
|
256
|
-
|
|
281
|
+
printJson(ctx);
|
|
257
282
|
});
|
|
258
|
-
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").action(
|
|
283
|
+
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(
|
|
259
284
|
async (opts) => {
|
|
260
285
|
const client = getClient();
|
|
261
286
|
let text = opts.text;
|
|
@@ -270,44 +295,71 @@ function registerContextCommands(program2) {
|
|
|
270
295
|
taskIds,
|
|
271
296
|
isSpecDocument: opts.spec ?? false
|
|
272
297
|
});
|
|
273
|
-
|
|
298
|
+
if (opts.json) {
|
|
299
|
+
printJson({ _id: result._id });
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
console.error(`Created context: ${result._id}`);
|
|
303
|
+
console.log(result._id);
|
|
274
304
|
}
|
|
275
305
|
);
|
|
276
|
-
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").
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
306
|
+
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(
|
|
307
|
+
async (contextId, opts) => {
|
|
308
|
+
const client = getClient();
|
|
309
|
+
let text = opts.text;
|
|
310
|
+
if (opts.file) {
|
|
311
|
+
text = readFileSync2(opts.file, "utf-8");
|
|
312
|
+
}
|
|
313
|
+
await client.patch(`/api/cli/contexts/${contextId}`, {
|
|
314
|
+
name: opts.name,
|
|
315
|
+
text
|
|
316
|
+
});
|
|
317
|
+
if (opts.json) {
|
|
318
|
+
printJson({ _id: contextId });
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
console.error(`Updated context: ${contextId}`);
|
|
322
|
+
console.log(contextId);
|
|
281
323
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
text
|
|
285
|
-
});
|
|
286
|
-
console.log(`Updated context: ${contextId}`);
|
|
287
|
-
});
|
|
288
|
-
context.command("delete <contextId>").description("Delete a context").action(async (contextId) => {
|
|
324
|
+
);
|
|
325
|
+
context.command("delete <contextId>").description("Delete a context").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
289
326
|
const client = getClient();
|
|
290
327
|
await client.delete(`/api/cli/contexts/${contextId}`);
|
|
291
|
-
|
|
328
|
+
if (opts.json) {
|
|
329
|
+
printJson({ _id: contextId });
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
console.error(`Deleted context: ${contextId}`);
|
|
333
|
+
console.log(contextId);
|
|
292
334
|
});
|
|
293
|
-
context.command("link <contextId>").description("Link a context to a project").requiredOption("--project <projectId>", "Project ID to link to").action(async (contextId, opts) => {
|
|
335
|
+
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) => {
|
|
294
336
|
const client = getClient();
|
|
295
337
|
await client.post(`/api/cli/contexts/${contextId}/link`, {
|
|
296
338
|
taskId: opts.project
|
|
297
339
|
});
|
|
298
|
-
|
|
340
|
+
if (opts.json) {
|
|
341
|
+
printJson({ _id: contextId, project: opts.project });
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
console.error(`Linked context ${contextId} to project ${opts.project}`);
|
|
345
|
+
console.log(contextId);
|
|
299
346
|
});
|
|
300
|
-
context.command("unlink <contextId>").description("Unlink a context from a project").requiredOption("--project <projectId>", "Project ID to unlink from").action(async (contextId, opts) => {
|
|
347
|
+
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) => {
|
|
301
348
|
const client = getClient();
|
|
302
349
|
await client.post(`/api/cli/contexts/${contextId}/unlink`, {
|
|
303
350
|
taskId: opts.project
|
|
304
351
|
});
|
|
305
|
-
|
|
352
|
+
if (opts.json) {
|
|
353
|
+
printJson({ _id: contextId, project: opts.project });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
console.error(`Unlinked context ${contextId} from project ${opts.project}`);
|
|
357
|
+
console.log(contextId);
|
|
306
358
|
});
|
|
307
359
|
}
|
|
308
360
|
function printContextList(contexts) {
|
|
309
361
|
if (contexts.length === 0) {
|
|
310
|
-
console.
|
|
362
|
+
console.error("No contexts found.");
|
|
311
363
|
return;
|
|
312
364
|
}
|
|
313
365
|
for (const ctx of contexts) {
|
|
@@ -316,7 +368,7 @@ function printContextList(contexts) {
|
|
|
316
368
|
const name = ctx.name ?? ctx.title ?? "(unnamed)";
|
|
317
369
|
console.log(`${ctx._id} ${scope.padEnd(8)} ${name}${spec}`);
|
|
318
370
|
}
|
|
319
|
-
console.
|
|
371
|
+
console.error(`
|
|
320
372
|
${contexts.length} context(s)`);
|
|
321
373
|
}
|
|
322
374
|
|
|
@@ -324,6 +376,7 @@ ${contexts.length} context(s)`);
|
|
|
324
376
|
import { execSync } from "child_process";
|
|
325
377
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
326
378
|
import { resolve } from "path";
|
|
379
|
+
import { Option } from "commander";
|
|
327
380
|
var HOOK_SCRIPT = `#!/bin/sh
|
|
328
381
|
# prim pre-commit hook \u2014 auto-syncs affected specs on commit
|
|
329
382
|
# Installed by: prim hooks install
|
|
@@ -431,17 +484,37 @@ function installToDotGit(gitRoot) {
|
|
|
431
484
|
}
|
|
432
485
|
function registerHooksCommands(program2) {
|
|
433
486
|
const hooks = program2.command("hooks").description("Manage git hooks");
|
|
434
|
-
hooks.command("install").description("Install the prim pre-commit hook").
|
|
487
|
+
hooks.command("install").description("Install the prim pre-commit hook (auto-detects Husky; use --target to override)").addOption(
|
|
488
|
+
new Option("--target <where>", "install destination; bypasses Husky detection").choices([
|
|
489
|
+
"husky",
|
|
490
|
+
"git-hooks"
|
|
491
|
+
])
|
|
492
|
+
).action(async (opts, command) => {
|
|
493
|
+
const globals = command.optsWithGlobals();
|
|
494
|
+
const nonInteractive = Boolean(
|
|
495
|
+
globals.nonInteractive || process.env.CI || process.env.PRIM_NON_INTERACTIVE
|
|
496
|
+
);
|
|
435
497
|
const gitRoot = getGitRoot();
|
|
498
|
+
if (opts.target === "husky") return installToHusky(gitRoot);
|
|
499
|
+
if (opts.target === "git-hooks") return installToDotGit(gitRoot);
|
|
436
500
|
if (detectHusky(gitRoot)) {
|
|
437
|
-
|
|
501
|
+
if (globals.yes) return installToHusky(gitRoot);
|
|
502
|
+
if (nonInteractive) {
|
|
503
|
+
throw new Error(
|
|
504
|
+
"--non-interactive set, refusing to prompt for Husky-hook installation. Pass --yes to confirm or --target=git-hooks to choose."
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
if (!process.stdin.isTTY) {
|
|
508
|
+
console.error(
|
|
509
|
+
"Note: Husky detected but stdin is not a TTY \u2014 falling back to .git/hooks. Pass --yes for Husky or --non-interactive to fail fast."
|
|
510
|
+
);
|
|
511
|
+
} else if (await askConfirmation(
|
|
438
512
|
"Husky detected. Install prim hook into .husky/pre-commit instead of .git/hooks/pre-commit?"
|
|
439
|
-
)
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
513
|
+
)) {
|
|
514
|
+
return installToHusky(gitRoot);
|
|
515
|
+
} else {
|
|
516
|
+
console.log("Falling back to .git/hooks/pre-commit install.");
|
|
443
517
|
}
|
|
444
|
-
console.log("Falling back to .git/hooks/pre-commit install.");
|
|
445
518
|
}
|
|
446
519
|
installToDotGit(gitRoot);
|
|
447
520
|
});
|
|
@@ -460,17 +533,22 @@ function registerHooksCommands(program2) {
|
|
|
460
533
|
// src/commands/project.ts
|
|
461
534
|
function registerProjectCommands(program2) {
|
|
462
535
|
const project = program2.command("project").description("Manage projects");
|
|
463
|
-
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").action(async (opts) => {
|
|
536
|
+
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) => {
|
|
464
537
|
const client = getClient();
|
|
465
538
|
const result = await client.post("/api/cli/tasks", {
|
|
466
539
|
name: opts.name,
|
|
467
540
|
description: opts.description,
|
|
468
541
|
specContextId: opts.spec
|
|
469
542
|
});
|
|
470
|
-
|
|
543
|
+
if (opts.json) {
|
|
544
|
+
printJson(opts.spec ? { _id: result._id, spec: opts.spec } : { _id: result._id });
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
console.error(`Created project: ${result._id}`);
|
|
471
548
|
if (opts.spec) {
|
|
472
|
-
console.
|
|
549
|
+
console.error(`Linked spec: ${opts.spec}`);
|
|
473
550
|
}
|
|
551
|
+
console.log(result._id);
|
|
474
552
|
});
|
|
475
553
|
}
|
|
476
554
|
|
|
@@ -592,12 +670,21 @@ function runUninstall(cwd, opts) {
|
|
|
592
670
|
function runStatus(cwd, opts) {
|
|
593
671
|
const target = resolveTarget(cwd, opts.target);
|
|
594
672
|
if (target === null) return 1;
|
|
595
|
-
|
|
673
|
+
const fileExists = existsSync3(target);
|
|
674
|
+
let installed = false;
|
|
675
|
+
if (fileExists) {
|
|
676
|
+
const content = readFileSync4(target, "utf-8");
|
|
677
|
+
installed = content.includes(SKILL_BEGIN) && content.includes(SKILL_END);
|
|
678
|
+
}
|
|
679
|
+
if (opts.json) {
|
|
680
|
+
printJson({ installed, target });
|
|
681
|
+
return installed ? 0 : 1;
|
|
682
|
+
}
|
|
683
|
+
if (!fileExists) {
|
|
596
684
|
console.log(`No rules file at ${target}`);
|
|
597
685
|
return 1;
|
|
598
686
|
}
|
|
599
|
-
|
|
600
|
-
if (content.includes(SKILL_BEGIN) && content.includes(SKILL_END)) {
|
|
687
|
+
if (installed) {
|
|
601
688
|
console.log(`PRIM SKILL v1 installed at ${target}`);
|
|
602
689
|
return 0;
|
|
603
690
|
}
|
|
@@ -617,7 +704,7 @@ function registerSkillCommands(program2) {
|
|
|
617
704
|
skill.command("uninstall").description("Remove the prim skill block from your project rules file").option("--target <path>", "Path to the rules file (overrides auto-detection)").action((opts) => {
|
|
618
705
|
process.exit(runUninstall(process.cwd(), opts));
|
|
619
706
|
});
|
|
620
|
-
skill.command("status").description("Report whether the prim skill block is installed").option("--target <path>", "Path to the rules file (overrides auto-detection)").action((opts) => {
|
|
707
|
+
skill.command("status").description("Report whether the prim skill block is installed").option("--target <path>", "Path to the rules file (overrides auto-detection)").option("--json", "Output as JSON").action((opts) => {
|
|
621
708
|
process.exit(runStatus(process.cwd(), opts));
|
|
622
709
|
});
|
|
623
710
|
}
|
|
@@ -626,20 +713,28 @@ function registerSkillCommands(program2) {
|
|
|
626
713
|
import { readFileSync as readFileSync5 } from "fs";
|
|
627
714
|
function registerSpecCommands(program2) {
|
|
628
715
|
const spec = program2.command("spec").description("Manage spec documents");
|
|
629
|
-
spec.command("list").description("List spec documents").option("-t, --project-id <projectId>", "List spec for a specific root project").action(async (opts) => {
|
|
716
|
+
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) => {
|
|
630
717
|
const client = getClient();
|
|
631
718
|
if (opts.projectId) {
|
|
632
719
|
const specs = await client.get(`/api/cli/specs?rootTaskId=${opts.projectId}`);
|
|
720
|
+
if (opts.json) {
|
|
721
|
+
printJson(specs[0] ?? null);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
633
724
|
if (specs.length === 0) {
|
|
634
|
-
console.
|
|
725
|
+
console.error("No spec document found for this project.");
|
|
635
726
|
return;
|
|
636
727
|
}
|
|
637
728
|
printSpec(specs[0]);
|
|
638
729
|
return;
|
|
639
730
|
}
|
|
640
731
|
const contexts = await client.get("/api/cli/specs");
|
|
732
|
+
if (opts.json) {
|
|
733
|
+
printJson(contexts);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
641
736
|
if (contexts.length === 0) {
|
|
642
|
-
console.
|
|
737
|
+
console.error("No spec documents found.");
|
|
643
738
|
return;
|
|
644
739
|
}
|
|
645
740
|
for (const ctx of contexts) {
|
|
@@ -648,39 +743,50 @@ function registerSpecCommands(program2) {
|
|
|
648
743
|
const name = ctx.name ?? "(unnamed)";
|
|
649
744
|
console.log(`${ctx._id} ${scope.padEnd(8)} ${String(review).padEnd(10)} ${name}`);
|
|
650
745
|
}
|
|
651
|
-
console.
|
|
746
|
+
console.error(`
|
|
652
747
|
${contexts.length} spec(s)`);
|
|
653
748
|
});
|
|
654
|
-
spec.command("get <contextId>").description("Get a spec document by ID").option("--text-only", "Print only the text content (no metadata)").action(async (contextId, opts) => {
|
|
749
|
+
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) => {
|
|
655
750
|
const client = getClient();
|
|
656
751
|
const ctx = await client.get(`/api/cli/contexts/${contextId}`);
|
|
752
|
+
if (opts.json) {
|
|
753
|
+
printJson(ctx);
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
657
756
|
if (opts.textOnly) {
|
|
658
757
|
console.log(ctx.text ?? "");
|
|
659
758
|
return;
|
|
660
759
|
}
|
|
661
760
|
printSpec(ctx);
|
|
662
761
|
});
|
|
663
|
-
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").
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
762
|
+
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(
|
|
763
|
+
async (contextId, opts) => {
|
|
764
|
+
const client = getClient();
|
|
765
|
+
let text = opts.text;
|
|
766
|
+
if (opts.file) {
|
|
767
|
+
text = readFileSync5(opts.file, "utf-8");
|
|
768
|
+
}
|
|
769
|
+
if (!(text || opts.name)) {
|
|
770
|
+
console.error("Provide --text, --file, or --name to update.");
|
|
771
|
+
process.exit(1);
|
|
772
|
+
}
|
|
773
|
+
await client.patch(`/api/cli/contexts/${contextId}`, {
|
|
774
|
+
name: opts.name,
|
|
775
|
+
text,
|
|
776
|
+
skipTiptapLifecycle: !!text
|
|
777
|
+
});
|
|
778
|
+
if (text) {
|
|
779
|
+
await client.post(`/api/cli/contexts/${contextId}/inject`);
|
|
780
|
+
}
|
|
781
|
+
if (opts.json) {
|
|
782
|
+
printJson({ _id: contextId });
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
console.error(`Updated spec: ${contextId}`);
|
|
786
|
+
console.log(contextId);
|
|
680
787
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
spec.command("sync <contextId>").description("Trigger spec \u2194 project DAG synchronization").action(async (contextId) => {
|
|
788
|
+
);
|
|
789
|
+
spec.command("sync <contextId>").description("Trigger spec \u2194 project DAG synchronization").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
684
790
|
const client = getClient();
|
|
685
791
|
const ctx = await client.get(`/api/cli/contexts/${contextId}`);
|
|
686
792
|
if (!ctx.isSpecDocument) {
|
|
@@ -688,42 +794,64 @@ ${contexts.length} spec(s)`);
|
|
|
688
794
|
process.exit(1);
|
|
689
795
|
}
|
|
690
796
|
await client.post(`/api/cli/contexts/${contextId}/sync`);
|
|
691
|
-
|
|
797
|
+
if (opts.json) {
|
|
798
|
+
printJson(
|
|
799
|
+
ctx.specRootTaskId ? { _id: contextId, specRootTaskId: ctx.specRootTaskId } : { _id: contextId }
|
|
800
|
+
);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
console.error(`Triggered sync for spec: ${contextId}`);
|
|
692
804
|
if (ctx.specRootTaskId) {
|
|
693
|
-
console.
|
|
805
|
+
console.error(`Root project: ${ctx.specRootTaskId}`);
|
|
694
806
|
}
|
|
807
|
+
console.log(contextId);
|
|
695
808
|
});
|
|
696
809
|
spec.command("map <contextId>").description("Map file patterns to a spec (used by pre-commit hook to detect affected specs)").requiredOption(
|
|
697
810
|
"-p, --pattern <patterns...>",
|
|
698
811
|
'Glob pattern(s) to associate, e.g. "src/auth/**"'
|
|
699
|
-
).action(async (contextId, opts) => {
|
|
812
|
+
).option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
700
813
|
const client = getClient();
|
|
701
814
|
const result = await client.post(`/api/cli/contexts/${contextId}/map`, {
|
|
702
815
|
patterns: opts.pattern
|
|
703
816
|
});
|
|
704
|
-
|
|
817
|
+
if (opts.json) {
|
|
818
|
+
printJson({ _id: contextId, filePatterns: result.filePatterns });
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
console.error(`Mapped patterns to spec ${contextId}:`);
|
|
705
822
|
for (const p of result.filePatterns) {
|
|
706
|
-
console.
|
|
823
|
+
console.error(` ${p}`);
|
|
707
824
|
}
|
|
825
|
+
console.log(contextId);
|
|
708
826
|
});
|
|
709
|
-
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)").action(async (contextId, opts) => {
|
|
827
|
+
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) => {
|
|
710
828
|
const client = getClient();
|
|
711
829
|
const result = await client.post(`/api/cli/contexts/${contextId}/unmap`, {
|
|
712
830
|
patterns: opts.pattern
|
|
713
831
|
});
|
|
832
|
+
if (opts.json) {
|
|
833
|
+
printJson({ _id: contextId, filePatterns: result.filePatterns });
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
714
836
|
if (result.filePatterns.length === 0) {
|
|
715
|
-
console.
|
|
837
|
+
console.error(`Cleared all file patterns from spec ${contextId}`);
|
|
716
838
|
} else {
|
|
717
|
-
console.
|
|
839
|
+
console.error(`Updated patterns for spec ${contextId}:`);
|
|
718
840
|
for (const p of result.filePatterns) {
|
|
719
|
-
console.
|
|
841
|
+
console.error(` ${p}`);
|
|
720
842
|
}
|
|
721
843
|
}
|
|
844
|
+
console.log(contextId);
|
|
722
845
|
});
|
|
723
|
-
spec.command("auto-map <contextId>").description("Trigger auto-mapping of file patterns for a spec").action(async (contextId) => {
|
|
846
|
+
spec.command("auto-map <contextId>").description("Trigger auto-mapping of file patterns for a spec").option("--json", "Output as JSON").action(async (contextId, opts) => {
|
|
724
847
|
const client = getClient();
|
|
725
848
|
await client.post(`/api/cli/contexts/${contextId}/auto-map`);
|
|
726
|
-
|
|
849
|
+
if (opts.json) {
|
|
850
|
+
printJson({ _id: contextId });
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
console.error(`Auto-mapping triggered for spec: ${contextId}`);
|
|
854
|
+
console.log(contextId);
|
|
727
855
|
});
|
|
728
856
|
}
|
|
729
857
|
function printSpec(ctx) {
|
|
@@ -752,7 +880,10 @@ var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
|
752
880
|
var pkg = JSON.parse(readFileSync6(resolve3(__dirname2, "../package.json"), "utf-8"));
|
|
753
881
|
updateNotifier({ pkg }).notify();
|
|
754
882
|
var program = new Command();
|
|
755
|
-
program.name("prim").description("CLI for managing Primitive specs and contexts").version(pkg.version)
|
|
883
|
+
program.name("prim").description("CLI for managing Primitive specs and contexts").version(pkg.version).option("-y, --yes", "auto-confirm prompts").option(
|
|
884
|
+
"--non-interactive",
|
|
885
|
+
"fail fast instead of prompting (also: CI=1, PRIM_NON_INTERACTIVE=1)"
|
|
886
|
+
);
|
|
756
887
|
registerAuthCommands(program);
|
|
757
888
|
registerContextCommands(program);
|
|
758
889
|
registerSpecCommands(program);
|