@shakecodeslikecray/npc-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +60 -0
  2. package/dist/adapters/claude-code.d.ts +21 -0
  3. package/dist/adapters/claude-code.d.ts.map +1 -0
  4. package/dist/adapters/claude-code.js +83 -0
  5. package/dist/adapters/claude-code.js.map +1 -0
  6. package/dist/adapters/index.d.ts +36 -0
  7. package/dist/adapters/index.d.ts.map +1 -0
  8. package/dist/adapters/index.js +50 -0
  9. package/dist/adapters/index.js.map +1 -0
  10. package/dist/client/api.d.ts +50 -0
  11. package/dist/client/api.d.ts.map +1 -0
  12. package/dist/client/api.js +325 -0
  13. package/dist/client/api.js.map +1 -0
  14. package/dist/client/auth.d.ts +47 -0
  15. package/dist/client/auth.d.ts.map +1 -0
  16. package/dist/client/auth.js +129 -0
  17. package/dist/client/auth.js.map +1 -0
  18. package/dist/config.d.ts +9 -0
  19. package/dist/config.d.ts.map +1 -0
  20. package/dist/config.js +11 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/index.d.ts +3 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +1055 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/mcp.d.ts +3 -0
  27. package/dist/mcp.d.ts.map +1 -0
  28. package/dist/mcp.js +22 -0
  29. package/dist/mcp.js.map +1 -0
  30. package/dist/tools/execution.d.ts +3 -0
  31. package/dist/tools/execution.d.ts.map +1 -0
  32. package/dist/tools/execution.js +255 -0
  33. package/dist/tools/execution.js.map +1 -0
  34. package/dist/tools/ingestion.d.ts +3 -0
  35. package/dist/tools/ingestion.d.ts.map +1 -0
  36. package/dist/tools/ingestion.js +131 -0
  37. package/dist/tools/ingestion.js.map +1 -0
  38. package/dist/tools/references.d.ts +3 -0
  39. package/dist/tools/references.d.ts.map +1 -0
  40. package/dist/tools/references.js +101 -0
  41. package/dist/tools/references.js.map +1 -0
  42. package/dist/tools/reporting.d.ts +3 -0
  43. package/dist/tools/reporting.d.ts.map +1 -0
  44. package/dist/tools/reporting.js +285 -0
  45. package/dist/tools/reporting.js.map +1 -0
  46. package/dist/tools/telemetry.d.ts +3 -0
  47. package/dist/tools/telemetry.d.ts.map +1 -0
  48. package/dist/tools/telemetry.js +73 -0
  49. package/dist/tools/telemetry.js.map +1 -0
  50. package/dist/types.d.ts +266 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +3 -0
  53. package/dist/types.js.map +1 -0
  54. package/package.json +36 -0
package/dist/index.js ADDED
@@ -0,0 +1,1055 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
4
+ import { createInterface } from "node:readline";
5
+ import { fileURLToPath } from "node:url";
6
+ import { basename, dirname, join } from "node:path";
7
+ import * as auth from "./client/auth.js";
8
+ import * as api from "./client/api.js";
9
+ import { CREDENTIALS_PATH, DEFAULT_API_URL } from "./config.js";
10
+ import { getAdapter, listSupportedTools, DEFAULT_TOOL } from "./adapters/index.js";
11
+ function prompt(question) {
12
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
13
+ return new Promise((resolve) => {
14
+ rl.question(question, (answer) => { rl.close(); resolve(answer.trim()); });
15
+ });
16
+ }
17
+ function promptPassword(question) {
18
+ return new Promise((resolve) => {
19
+ process.stdout.write(question);
20
+ const stdin = process.stdin;
21
+ stdin.resume();
22
+ if (stdin.isTTY)
23
+ stdin.setRawMode(true);
24
+ stdin.setEncoding("utf8");
25
+ let pw = "";
26
+ const onData = (c) => {
27
+ if (c === "\n" || c === "\r" || c === "\u0004") {
28
+ if (stdin.isTTY)
29
+ stdin.setRawMode(false);
30
+ stdin.removeListener("data", onData);
31
+ stdin.pause();
32
+ process.stdout.write("\n");
33
+ resolve(pw);
34
+ }
35
+ else if (c === "\u007f" || c === "\b") {
36
+ if (pw.length > 0) {
37
+ pw = pw.slice(0, -1);
38
+ process.stdout.write("\b \b");
39
+ }
40
+ }
41
+ else if (c === "\u0003") {
42
+ if (stdin.isTTY)
43
+ stdin.setRawMode(false);
44
+ process.exit(0);
45
+ }
46
+ else {
47
+ pw += c;
48
+ process.stdout.write("*");
49
+ }
50
+ };
51
+ stdin.on("data", onData);
52
+ });
53
+ }
54
+ const program = new Command();
55
+ program
56
+ .name("npc")
57
+ .description("NPC CLI -- agent-first project orchestration")
58
+ .version("0.1.0");
59
+ // ---------------------------------------------------------------------------
60
+ // Auth commands
61
+ // ---------------------------------------------------------------------------
62
+ const authCmd = program.command("auth").description("Authentication");
63
+ authCmd
64
+ .command("login")
65
+ .description("Login to NPC API")
66
+ .option("-e, --email <email>", "Email address")
67
+ .option("-p, --password <password>", "Password (for scripted/non-interactive auth)")
68
+ .option("-t, --tenant <id>", "Tenant UUID (optional)")
69
+ .option("-u, --url <url>", "API URL", DEFAULT_API_URL)
70
+ .action(async (opts) => {
71
+ try {
72
+ const email = opts.email || await prompt("Email: ");
73
+ const password = opts.password || await promptPassword("Password: ");
74
+ const res = await auth.login(email, password, opts.url, opts.tenant);
75
+ console.log(`Logged in as ${res.user.email} (${res.user.role})`);
76
+ console.log(`Tenant: ${res.user.tenant_id}`);
77
+ if (res.must_reset_password) {
78
+ console.log("WARNING: You must reset your password.");
79
+ }
80
+ }
81
+ catch (err) {
82
+ console.error("Login failed:", err instanceof Error ? err.message : err);
83
+ process.exit(1);
84
+ }
85
+ });
86
+ authCmd
87
+ .command("status")
88
+ .description("Show current auth state")
89
+ .action(() => {
90
+ const creds = auth.getCredentials();
91
+ if (!creds) {
92
+ console.log("Not authenticated.");
93
+ return;
94
+ }
95
+ console.log(`Email: ${creds.email}`);
96
+ console.log(`API: ${creds.api_url}`);
97
+ console.log(`Tenant: ${creds.tenant_id}`);
98
+ console.log(`Expires: ${creds.expires_at}`);
99
+ console.log(`Credentials: ${CREDENTIALS_PATH}`);
100
+ });
101
+ authCmd
102
+ .command("logout")
103
+ .description("Clear credentials")
104
+ .action(() => {
105
+ auth.logout();
106
+ console.log("Logged out.");
107
+ });
108
+ authCmd
109
+ .command("agent-key <key>")
110
+ .description("Store an agent API key for queue operations")
111
+ .action((key) => {
112
+ try {
113
+ auth.setApiKey(key);
114
+ console.log("Agent API key stored.");
115
+ }
116
+ catch (err) {
117
+ console.error("Failed:", err instanceof Error ? err.message : err);
118
+ process.exit(1);
119
+ }
120
+ });
121
+ // ---------------------------------------------------------------------------
122
+ // Project commands (project groups)
123
+ // ---------------------------------------------------------------------------
124
+ const projectCmd = program.command("project").description("Project group operations");
125
+ projectCmd
126
+ .command("create")
127
+ .description("Create a new project group")
128
+ .requiredOption("-n, --name <name>", "Project group name")
129
+ .requiredOption("-i, --identifier <id>", "Short identifier")
130
+ .option("-d, --description <desc>", "Description")
131
+ .action(async (opts) => {
132
+ try {
133
+ const project = await api.createProject({
134
+ name: opts.name,
135
+ identifier: opts.identifier,
136
+ description: opts.description,
137
+ });
138
+ console.log(`Created project group: ${project.name} (${project.id})`);
139
+ }
140
+ catch (err) {
141
+ console.error("Error:", err instanceof Error ? err.message : err);
142
+ process.exit(1);
143
+ }
144
+ });
145
+ projectCmd
146
+ .command("list")
147
+ .description("List all project groups")
148
+ .action(async () => {
149
+ try {
150
+ const projects = await api.listProjects();
151
+ if (projects.length === 0) {
152
+ console.log("No project groups found.");
153
+ return;
154
+ }
155
+ for (const p of projects) {
156
+ console.log(` ${p.identifier.padEnd(8)} ${p.name.padEnd(30)} ${p.id}`);
157
+ }
158
+ }
159
+ catch (err) {
160
+ console.error("Error:", err instanceof Error ? err.message : err);
161
+ process.exit(1);
162
+ }
163
+ });
164
+ projectCmd
165
+ .command("get <project-id>")
166
+ .description("Get details for a specific project group")
167
+ .action(async (projectId) => {
168
+ try {
169
+ const project = await api.getProject(projectId);
170
+ console.log(`Name: ${project.name}`);
171
+ console.log(`Identifier: ${project.identifier}`);
172
+ console.log(`ID: ${project.id}`);
173
+ console.log(`Tenant: ${project.tenant_id}`);
174
+ if (project.description)
175
+ console.log(`Description: ${project.description}`);
176
+ console.log(`Created: ${project.created_at}`);
177
+ }
178
+ catch (err) {
179
+ console.error("Error:", err instanceof Error ? err.message : err);
180
+ process.exit(1);
181
+ }
182
+ });
183
+ projectCmd
184
+ .command("stats")
185
+ .description("Show stats for all project groups in the tenant")
186
+ .action(async () => {
187
+ try {
188
+ const stats = await api.getProjectStats();
189
+ for (const [id, s] of Object.entries(stats)) {
190
+ console.log(`\nProject: ${id}`);
191
+ console.log(` Repos: ${s.total_repos} | Items: ${s.total_items} | Done: ${s.done} | In Progress: ${s.in_progress}`);
192
+ console.log(` Not Started: ${s.not_started} | Blocked: ${s.blocked}`);
193
+ }
194
+ }
195
+ catch (err) {
196
+ console.error("Error:", err instanceof Error ? err.message : err);
197
+ process.exit(1);
198
+ }
199
+ });
200
+ // ---------------------------------------------------------------------------
201
+ // Repo commands (individual codebases)
202
+ // ---------------------------------------------------------------------------
203
+ const repoCmd = program.command("repo").description("Repo operations");
204
+ repoCmd
205
+ .command("create")
206
+ .description("Create a new repo")
207
+ .requiredOption("-n, --name <name>", "Repo name")
208
+ .requiredOption("-i, --identifier <id>", "Short identifier")
209
+ .option("-d, --description <desc>", "Description")
210
+ .option("-p, --project <project-id>", "Parent project group UUID")
211
+ .action(async (opts) => {
212
+ try {
213
+ const repo = await api.createRepo({
214
+ name: opts.name,
215
+ identifier: opts.identifier,
216
+ description: opts.description,
217
+ project_id: opts.project,
218
+ });
219
+ console.log(`Created repo: ${repo.name} (${repo.id})`);
220
+ }
221
+ catch (err) {
222
+ console.error("Error:", err instanceof Error ? err.message : err);
223
+ process.exit(1);
224
+ }
225
+ });
226
+ repoCmd
227
+ .command("list")
228
+ .description("List all repos")
229
+ .action(async () => {
230
+ try {
231
+ const repos = await api.listRepos();
232
+ if (repos.length === 0) {
233
+ console.log("No repos found.");
234
+ return;
235
+ }
236
+ for (const r of repos) {
237
+ console.log(` ${r.identifier.padEnd(8)} ${r.name.padEnd(30)} ${r.id}`);
238
+ }
239
+ }
240
+ catch (err) {
241
+ console.error("Error:", err instanceof Error ? err.message : err);
242
+ process.exit(1);
243
+ }
244
+ });
245
+ repoCmd
246
+ .command("get <repo-id>")
247
+ .description("Get details for a specific repo")
248
+ .action(async (repoId) => {
249
+ try {
250
+ const repo = await api.getRepo(repoId);
251
+ console.log(`Name: ${repo.name}`);
252
+ console.log(`Identifier: ${repo.identifier}`);
253
+ console.log(`ID: ${repo.id}`);
254
+ console.log(`Tenant: ${repo.tenant_id}`);
255
+ if (repo.project_id)
256
+ console.log(`Project: ${repo.project_id}`);
257
+ if (repo.description)
258
+ console.log(`Description: ${repo.description}`);
259
+ console.log(`Created: ${repo.created_at}`);
260
+ }
261
+ catch (err) {
262
+ console.error("Error:", err instanceof Error ? err.message : err);
263
+ process.exit(1);
264
+ }
265
+ });
266
+ repoCmd
267
+ .command("stats")
268
+ .description("Show stats for all repos in the tenant")
269
+ .action(async () => {
270
+ try {
271
+ const stats = await api.getRepoStats();
272
+ for (const [id, s] of Object.entries(stats)) {
273
+ const pct = s.total > 0 ? Math.round((s.done / s.total) * 100) : 0;
274
+ console.log(`\nRepo: ${id}`);
275
+ console.log(` Total: ${s.total} | Done: ${s.done} (${pct}%) | In Progress: ${s.in_progress}`);
276
+ console.log(` Blocked: ${s.blocked} | Not Started: ${s.not_started}`);
277
+ }
278
+ }
279
+ catch (err) {
280
+ console.error("Error:", err instanceof Error ? err.message : err);
281
+ process.exit(1);
282
+ }
283
+ });
284
+ // ---------------------------------------------------------------------------
285
+ // Items commands
286
+ // ---------------------------------------------------------------------------
287
+ const itemsCmd = program.command("items").description("Work item operations");
288
+ itemsCmd
289
+ .command("list <repo-id>")
290
+ .description("List work items in a repo")
291
+ .option("-s, --state <state>", "Filter by state")
292
+ .option("--priority <priority>", "Filter by priority")
293
+ .option("--parent <id>", "Filter by parent work item UUID")
294
+ .action(async (repoId, opts) => {
295
+ try {
296
+ const params = {};
297
+ if (opts.state)
298
+ params.state = opts.state;
299
+ if (opts.priority)
300
+ params.priority = opts.priority;
301
+ if (opts.parent)
302
+ params.parent_id = opts.parent;
303
+ const items = await api.getWorkItems(repoId, params);
304
+ if (items.length === 0) {
305
+ console.log("No work items found.");
306
+ return;
307
+ }
308
+ for (const wi of items) {
309
+ console.log(` [${wi.state.padEnd(12)}] ${wi.title} (${wi.priority}) ${wi.id.slice(0, 8)}`);
310
+ }
311
+ }
312
+ catch (err) {
313
+ console.error("Error:", err instanceof Error ? err.message : err);
314
+ process.exit(1);
315
+ }
316
+ });
317
+ itemsCmd
318
+ .command("get <repo-id> <item-id>")
319
+ .description("Get a specific work item")
320
+ .action(async (repoId, itemId) => {
321
+ try {
322
+ const wi = await api.getWorkItem(repoId, itemId);
323
+ console.log(`Title: ${wi.title}`);
324
+ console.log(`ID: ${wi.id}`);
325
+ console.log(`State: ${wi.state}`);
326
+ console.log(`Priority: ${wi.priority}`);
327
+ if (wi.description)
328
+ console.log(`Description: ${wi.description}`);
329
+ if (wi.estimated_hours)
330
+ console.log(`Estimated: ${wi.estimated_hours}h`);
331
+ if (wi.actual_hours)
332
+ console.log(`Actual: ${wi.actual_hours}h`);
333
+ if (wi.labels?.length)
334
+ console.log(`Labels: ${wi.labels.join(", ")}`);
335
+ if (wi.claimed_by)
336
+ console.log(`Claimed by: ${wi.claimed_by}`);
337
+ }
338
+ catch (err) {
339
+ console.error("Error:", err instanceof Error ? err.message : err);
340
+ process.exit(1);
341
+ }
342
+ });
343
+ itemsCmd
344
+ .command("bulk-create <repo-id> <file>")
345
+ .description("Bulk create work items from a JSON file")
346
+ .action(async (repoId, file) => {
347
+ try {
348
+ const raw = readFileSync(file, "utf-8");
349
+ const items = JSON.parse(raw);
350
+ const created = await api.bulkCreateWorkItems(repoId, items);
351
+ console.log(`Created ${created.length} work items.`);
352
+ for (const wi of created) {
353
+ console.log(` [${wi.id.slice(0, 8)}] ${wi.title}`);
354
+ }
355
+ }
356
+ catch (err) {
357
+ console.error("Error:", err instanceof Error ? err.message : err);
358
+ process.exit(1);
359
+ }
360
+ });
361
+ // ---------------------------------------------------------------------------
362
+ // Edge commands
363
+ // ---------------------------------------------------------------------------
364
+ const edgeCmd = program.command("edge").description("DAG edge operations");
365
+ edgeCmd
366
+ .command("list <repo-id>")
367
+ .description("List all edges in a repo")
368
+ .action(async (repoId) => {
369
+ try {
370
+ const edges = await api.listEdges(repoId);
371
+ if (edges.length === 0) {
372
+ console.log("No edges found.");
373
+ return;
374
+ }
375
+ for (const e of edges) {
376
+ console.log(` ${e.from_id.slice(0, 8)} --${e.edge_type}--> ${e.to_id.slice(0, 8)} (${e.id.slice(0, 8)})`);
377
+ }
378
+ }
379
+ catch (err) {
380
+ console.error("Error:", err instanceof Error ? err.message : err);
381
+ process.exit(1);
382
+ }
383
+ });
384
+ edgeCmd
385
+ .command("create")
386
+ .description("Create a dependency edge")
387
+ .requiredOption("--repo <id>", "Repo UUID")
388
+ .requiredOption("--from <id>", "Source work item UUID")
389
+ .requiredOption("--to <id>", "Target work item UUID")
390
+ .option("--type <type>", "Edge type: blocks | relates_to", "blocks")
391
+ .action(async (opts) => {
392
+ try {
393
+ const edge = await api.createEdge(opts.repo, {
394
+ from_id: opts.from,
395
+ to_id: opts.to,
396
+ edge_type: opts.type,
397
+ });
398
+ console.log(`Created edge: ${edge.id}`);
399
+ }
400
+ catch (err) {
401
+ console.error("Error:", err instanceof Error ? err.message : err);
402
+ process.exit(1);
403
+ }
404
+ });
405
+ // ---------------------------------------------------------------------------
406
+ // Task commands
407
+ // ---------------------------------------------------------------------------
408
+ program
409
+ .command("tasks")
410
+ .description("List my assigned tasks")
411
+ .action(async () => {
412
+ try {
413
+ const items = await api.getMyTasks();
414
+ if (items.length === 0) {
415
+ console.log("No tasks assigned.");
416
+ return;
417
+ }
418
+ // Group by state
419
+ const grouped = {};
420
+ for (const item of items) {
421
+ if (!grouped[item.state])
422
+ grouped[item.state] = [];
423
+ grouped[item.state].push(item);
424
+ }
425
+ for (const [state, tasks] of Object.entries(grouped)) {
426
+ console.log(`\n${state} (${tasks.length}):`);
427
+ for (const t of tasks) {
428
+ console.log(` [${t.id.slice(0, 8)}] ${t.title} (${t.priority})`);
429
+ }
430
+ }
431
+ }
432
+ catch (err) {
433
+ console.error("Error:", err instanceof Error ? err.message : err);
434
+ process.exit(1);
435
+ }
436
+ });
437
+ program
438
+ .command("pull")
439
+ .description("Get next unblocked task from the queue")
440
+ .option("-r, --repo <id>", "Filter by repo UUID")
441
+ .action(async (opts) => {
442
+ try {
443
+ const result = await api.queueNext(opts.repo);
444
+ const wi = result.work_item;
445
+ console.log(`Task: ${wi.title}`);
446
+ console.log(`ID: ${wi.id}`);
447
+ console.log(`State: ${wi.state}`);
448
+ console.log(`Priority: ${wi.priority}`);
449
+ if (wi.description)
450
+ console.log(`Description: ${wi.description}`);
451
+ if (result.ancestors.length > 0) {
452
+ console.log(`Context: ${result.ancestors.map((a) => a.title).join(" > ")}`);
453
+ }
454
+ }
455
+ catch (err) {
456
+ console.error("Error:", err instanceof Error ? err.message : err);
457
+ process.exit(1);
458
+ }
459
+ });
460
+ program
461
+ .command("start <task-id>")
462
+ .description("Start working on a task")
463
+ .action(async (taskId) => {
464
+ try {
465
+ const wi = await api.startWorkItem(taskId);
466
+ console.log(`Started: ${wi.title} (${wi.state})`);
467
+ }
468
+ catch (err) {
469
+ console.error("Error:", err instanceof Error ? err.message : err);
470
+ process.exit(1);
471
+ }
472
+ });
473
+ program
474
+ .command("complete <task-id>")
475
+ .description("Mark a task as in_review")
476
+ .option("-s, --summary <text>", "Completion summary", "")
477
+ .option("-h, --hours <n>", "Actual hours spent")
478
+ .action(async (taskId, opts) => {
479
+ try {
480
+ const wi = await api.completeWorkItem(taskId, {
481
+ notes: opts.summary,
482
+ actual_hours: opts.hours ? parseFloat(opts.hours) : undefined,
483
+ });
484
+ console.log(`Completed: ${wi.title} (${wi.state})`);
485
+ }
486
+ catch (err) {
487
+ console.error("Error:", err instanceof Error ? err.message : err);
488
+ process.exit(1);
489
+ }
490
+ });
491
+ program
492
+ .command("block <task-id>")
493
+ .description("Block a task")
494
+ .requiredOption("-r, --reason <text>", "Block reason")
495
+ .action(async (taskId, opts) => {
496
+ try {
497
+ const wi = await api.blockWorkItem(taskId, { reason: opts.reason });
498
+ console.log(`Blocked: ${wi.title}`);
499
+ }
500
+ catch (err) {
501
+ console.error("Error:", err instanceof Error ? err.message : err);
502
+ process.exit(1);
503
+ }
504
+ });
505
+ // ---------------------------------------------------------------------------
506
+ // Signal commands
507
+ // ---------------------------------------------------------------------------
508
+ const signalCmd = program.command("signal").description("Signal operations");
509
+ signalCmd
510
+ .command("create <task-id>")
511
+ .description("Report a signal for a task")
512
+ .requiredOption("-c, --category <cat>", "Signal category: process, quality, scope")
513
+ .requiredOption("-t, --type <type>", "Signal type: tests_passed, lint_clean, build_ok, etc.")
514
+ .requiredOption("-v, --value <json>", "Signal value as JSON")
515
+ .action(async (taskId, opts) => {
516
+ try {
517
+ const value = JSON.parse(opts.value);
518
+ const signal = await api.createSignal(taskId, {
519
+ category: opts.category,
520
+ signal_type: opts.type,
521
+ value,
522
+ });
523
+ console.log(`Signal created: ${signal.id}`);
524
+ }
525
+ catch (err) {
526
+ console.error("Error:", err instanceof Error ? err.message : err);
527
+ process.exit(1);
528
+ }
529
+ });
530
+ signalCmd
531
+ .command("list <task-id>")
532
+ .description("List all signals for a task")
533
+ .action(async (taskId) => {
534
+ try {
535
+ const signals = await api.listSignals(taskId);
536
+ if (signals.length === 0) {
537
+ console.log("No signals found.");
538
+ return;
539
+ }
540
+ for (const s of signals) {
541
+ console.log(` [${s.category}] ${s.signal_type}: ${JSON.stringify(s.value)} (${s.created_at})`);
542
+ }
543
+ }
544
+ catch (err) {
545
+ console.error("Error:", err instanceof Error ? err.message : err);
546
+ process.exit(1);
547
+ }
548
+ });
549
+ signalCmd
550
+ .command("health <task-id>")
551
+ .description("Check signal health for a task")
552
+ .action(async (taskId) => {
553
+ try {
554
+ const health = await api.getSignalHealth(taskId);
555
+ console.log(`Status: ${health.all_green ? "ALL GREEN" : "MISSING SIGNALS"}`);
556
+ console.log(`Required: ${health.required_signals.join(", ") || "none"}`);
557
+ console.log(`Present: ${health.signals.map((s) => s.signal_type).join(", ") || "none"}`);
558
+ if (health.missing.length > 0) {
559
+ console.log(`Missing: ${health.missing.join(", ")}`);
560
+ }
561
+ }
562
+ catch (err) {
563
+ console.error("Error:", err instanceof Error ? err.message : err);
564
+ process.exit(1);
565
+ }
566
+ });
567
+ program
568
+ .command("transition <task-id> <state>")
569
+ .description("Transition a task to a specific state")
570
+ .action(async (taskId, state) => {
571
+ try {
572
+ const wi = await api.transitionWorkItem(taskId, state);
573
+ console.log(`Transitioned: ${wi.title} -> ${wi.state}`);
574
+ }
575
+ catch (err) {
576
+ console.error("Error:", err instanceof Error ? err.message : err);
577
+ process.exit(1);
578
+ }
579
+ });
580
+ program
581
+ .command("context <module-id>")
582
+ .description("Get full context for a module/work item")
583
+ .action(async (moduleId) => {
584
+ try {
585
+ const ctx = await api.getWorkItemContext(moduleId);
586
+ const wi = ctx.work_item;
587
+ console.log(`\nModule: ${wi.title} (${wi.state})`);
588
+ console.log(`ID: ${wi.id}`);
589
+ if (wi.description)
590
+ console.log(`Description: ${wi.description}`);
591
+ if (ctx.ancestors.length > 0) {
592
+ console.log(`\nAncestors:`);
593
+ for (const a of ctx.ancestors) {
594
+ console.log(` ${a.title} (${a.state})`);
595
+ }
596
+ }
597
+ if (ctx.children.length > 0) {
598
+ console.log(`\nChildren (${ctx.children.length}):`);
599
+ for (const c of ctx.children) {
600
+ console.log(` [${c.state.padEnd(12)}] ${c.title} (${c.id.slice(0, 8)})`);
601
+ }
602
+ }
603
+ if (ctx.blockers.length > 0) {
604
+ console.log(`\nBlockers:`);
605
+ for (const b of ctx.blockers) {
606
+ console.log(` ${b.title} (${b.state})`);
607
+ }
608
+ }
609
+ }
610
+ catch (err) {
611
+ console.error("Error:", err instanceof Error ? err.message : err);
612
+ process.exit(1);
613
+ }
614
+ });
615
+ // ---------------------------------------------------------------------------
616
+ // Models commands
617
+ // ---------------------------------------------------------------------------
618
+ const modelsCmd = program.command("models").description("AI model operations");
619
+ modelsCmd
620
+ .command("list")
621
+ .description("List all AI models (global registry)")
622
+ .action(async () => {
623
+ try {
624
+ const models = await api.listAIModels();
625
+ if (models.length === 0) {
626
+ console.log("No AI models found.");
627
+ return;
628
+ }
629
+ // Group by provider
630
+ const grouped = {};
631
+ for (const m of models) {
632
+ if (!grouped[m.provider])
633
+ grouped[m.provider] = [];
634
+ grouped[m.provider].push(m);
635
+ }
636
+ for (const [provider, providerModels] of Object.entries(grouped)) {
637
+ console.log(`\n${provider}:`);
638
+ for (const m of providerModels) {
639
+ const ctx = m.context_window ? `${(m.context_window / 1000).toFixed(0)}k` : "-";
640
+ console.log(` ${m.model_name.padEnd(30)} ${m.model_id.padEnd(30)} T${m.capability_tier} ${ctx.padStart(6)}`);
641
+ }
642
+ }
643
+ }
644
+ catch (err) {
645
+ console.error("Error:", err instanceof Error ? err.message : err);
646
+ process.exit(1);
647
+ }
648
+ });
649
+ modelsCmd
650
+ .command("available")
651
+ .description("List org's available models")
652
+ .action(async () => {
653
+ try {
654
+ const models = await api.listAvailableModels();
655
+ if (models.length === 0) {
656
+ console.log("No available models found.");
657
+ return;
658
+ }
659
+ console.log("Available models:");
660
+ for (const m of models) {
661
+ const ctx = m.context_window ? `${(m.context_window / 1000).toFixed(0)}k` : "-";
662
+ console.log(` ${m.model_name.padEnd(30)} ${m.model_id.padEnd(30)} T${m.capability_tier} ${ctx.padStart(6)}`);
663
+ }
664
+ }
665
+ catch (err) {
666
+ console.error("Error:", err instanceof Error ? err.message : err);
667
+ process.exit(1);
668
+ }
669
+ });
670
+ modelsCmd
671
+ .command("mine")
672
+ .description("List my models")
673
+ .action(async () => {
674
+ try {
675
+ const models = await api.getMyModels();
676
+ if (models.length === 0) {
677
+ console.log("No models set. Use `npc models set` to configure.");
678
+ return;
679
+ }
680
+ console.log("My models:");
681
+ for (const m of models) {
682
+ const ctx = m.context_window ? `${(m.context_window / 1000).toFixed(0)}k` : "-";
683
+ console.log(` ${m.model_name.padEnd(30)} ${m.model_id.padEnd(30)} T${m.capability_tier} ${ctx.padStart(6)}`);
684
+ }
685
+ }
686
+ catch (err) {
687
+ console.error("Error:", err instanceof Error ? err.message : err);
688
+ process.exit(1);
689
+ }
690
+ });
691
+ modelsCmd
692
+ .command("set")
693
+ .description("Set my models")
694
+ .option("-m, --models <ids>", "Comma-separated model_id strings (e.g. claude-opus-4-6,claude-sonnet-4-6)")
695
+ .action(async (opts) => {
696
+ try {
697
+ const modelIdStrings = opts.models
698
+ ? opts.models.split(",").map((s) => s.trim())
699
+ : [];
700
+ if (modelIdStrings.length === 0) {
701
+ console.error("Provide model IDs via --models flag.");
702
+ console.error("Example: npc models set --models claude-opus-4-6,claude-sonnet-4-6");
703
+ process.exit(1);
704
+ }
705
+ // Resolve model_id strings to UUIDs
706
+ const allModels = await api.listAIModels();
707
+ const uuids = [];
708
+ const notFound = [];
709
+ for (const mid of modelIdStrings) {
710
+ const found = allModels.find((m) => m.model_id === mid);
711
+ if (found) {
712
+ uuids.push(found.id);
713
+ }
714
+ else {
715
+ notFound.push(mid);
716
+ }
717
+ }
718
+ if (notFound.length > 0) {
719
+ console.error(`Unknown model IDs: ${notFound.join(", ")}`);
720
+ console.error("Run `npc models list` to see valid model IDs.");
721
+ process.exit(1);
722
+ }
723
+ await api.setMyModels(uuids);
724
+ console.log("Updated your models. Org pool synced.");
725
+ }
726
+ catch (err) {
727
+ console.error("Error:", err instanceof Error ? err.message : err);
728
+ process.exit(1);
729
+ }
730
+ });
731
+ // ---------------------------------------------------------------------------
732
+ // Telemetry commands
733
+ // ---------------------------------------------------------------------------
734
+ const telemetryCmd = program.command("telemetry").description("Telemetry reporting");
735
+ telemetryCmd
736
+ .command("report-session")
737
+ .description("Parse an agent transcript and report telemetry to NPC")
738
+ .option("--transcript <path>", "Path to transcript JSONL file")
739
+ .option("--work-item <id>", "Work item UUID (overrides auto-detection)")
740
+ .option("--tool <name>", `Agent tool that produced the transcript. Supported parsers: claude_code`, "claude_code")
741
+ .action(async (opts) => {
742
+ try {
743
+ // Only claude_code transcript format is parsed today. Other tools can be
744
+ // added by implementing a parser in src/adapters/<tool>.ts and wiring it
745
+ // in below.
746
+ const supportedParsers = ["claude_code"];
747
+ if (!supportedParsers.includes(opts.tool)) {
748
+ console.log(`Transcript parsing not yet supported for ${opts.tool}. Use npc_report_telemetry MCP tool for manual reporting.`);
749
+ process.exit(0);
750
+ }
751
+ // Read hook input from stdin if no --transcript provided
752
+ let transcriptPath = opts.transcript;
753
+ if (!transcriptPath) {
754
+ // Claude Code Stop hook passes JSON on stdin with transcript_path
755
+ const chunks = [];
756
+ for await (const chunk of process.stdin) {
757
+ chunks.push(chunk);
758
+ }
759
+ const stdinData = Buffer.concat(chunks).toString("utf-8").trim();
760
+ if (stdinData) {
761
+ try {
762
+ const hookInput = JSON.parse(stdinData);
763
+ transcriptPath = hookInput.transcript_path;
764
+ }
765
+ catch {
766
+ // Not JSON, maybe it's the path itself
767
+ transcriptPath = stdinData;
768
+ }
769
+ }
770
+ }
771
+ if (!transcriptPath) {
772
+ console.error("No transcript path provided. Use --transcript or pipe hook input on stdin.");
773
+ process.exit(1);
774
+ }
775
+ const { parseClaudeCodeTranscript } = await import("./adapters/claude-code.js");
776
+ const parsed = parseClaudeCodeTranscript(transcriptPath);
777
+ const workItemId = opts.workItem ?? parsed.work_item_id;
778
+ if (!workItemId) {
779
+ console.error("No active work item found in transcript. Use --work-item to specify.");
780
+ process.exit(0); // Exit 0 — not an error, just nothing to report
781
+ }
782
+ const ev = await api.createTelemetry(workItemId, {
783
+ session_id: parsed.session_id,
784
+ agent_tool: opts.tool,
785
+ model: parsed.model,
786
+ tokens_in: parsed.tokens_in,
787
+ tokens_out: parsed.tokens_out,
788
+ cache_read_tokens: parsed.cache_read_tokens,
789
+ cache_creation_tokens: parsed.cache_creation_tokens,
790
+ cost_usd: parsed.cost_usd,
791
+ duration_ms: parsed.duration_ms,
792
+ num_turns: parsed.num_turns,
793
+ tool_calls: parsed.tool_calls,
794
+ tool_failures: parsed.tool_failures,
795
+ metadata: parsed.metadata,
796
+ });
797
+ console.log(`Telemetry reported for work item ${workItemId.slice(0, 8)}: $${ev.cost_usd.toFixed(4)}, ${ev.tokens_in + ev.tokens_out} tokens`);
798
+ }
799
+ catch (err) {
800
+ const msg = err instanceof Error ? err.message : String(err);
801
+ console.error(`Failed to report telemetry: ${msg}`);
802
+ process.exit(1);
803
+ }
804
+ });
805
+ // ---------------------------------------------------------------------------
806
+ // Docs commands
807
+ // ---------------------------------------------------------------------------
808
+ const docsCmd = program.command("docs").description("Document storage operations");
809
+ docsCmd
810
+ .command("list <repo-id>")
811
+ .description("List all documents stored for a repo")
812
+ .action(async (repoId) => {
813
+ try {
814
+ const docs = await api.listDocuments(repoId);
815
+ if (docs.length === 0) {
816
+ console.log("No documents found.");
817
+ return;
818
+ }
819
+ for (const d of docs) {
820
+ const kb = (d.size_bytes / 1024).toFixed(1);
821
+ console.log(` ${d.filename.padEnd(45)} ${kb.padStart(8)}KB ${d.id}`);
822
+ }
823
+ }
824
+ catch (err) {
825
+ console.error("Error:", err instanceof Error ? err.message : err);
826
+ process.exit(1);
827
+ }
828
+ });
829
+ docsCmd
830
+ .command("upload <repo-id> <file>")
831
+ .description("Upload a document file to a repo")
832
+ .option("-n, --name <filename>", "Override filename (default: basename of <file>)")
833
+ .action(async (repoId, file, opts) => {
834
+ try {
835
+ const content = readFileSync(file);
836
+ const filename = opts.name ?? basename(file);
837
+ const contentType = filename.endsWith(".md") ? "text/markdown" : "text/plain";
838
+ const doc = await api.uploadDocument(repoId, filename, content, contentType);
839
+ const baseUrl = auth.getApiUrl().replace(/\/$/, "");
840
+ const contentUrl = `${baseUrl}/api/v1/repos/${repoId}/documents/${doc.id}/content`;
841
+ console.log(`Uploaded: ${doc.filename} (${(doc.size_bytes / 1024).toFixed(1)}KB)`);
842
+ console.log(`ID: ${doc.id}`);
843
+ console.log(`URL: ${contentUrl}`);
844
+ }
845
+ catch (err) {
846
+ console.error("Error:", err instanceof Error ? err.message : err);
847
+ process.exit(1);
848
+ }
849
+ });
850
+ docsCmd
851
+ .command("cat <repo-id> <doc-id>")
852
+ .description("Print document content to stdout")
853
+ .action(async (repoId, docId) => {
854
+ try {
855
+ const content = await api.getDocumentContent(repoId, docId);
856
+ process.stdout.write(content);
857
+ }
858
+ catch (err) {
859
+ console.error("Error:", err instanceof Error ? err.message : err);
860
+ process.exit(1);
861
+ }
862
+ });
863
+ docsCmd
864
+ .command("delete <repo-id> <doc-id>")
865
+ .description("Delete a document from a repo")
866
+ .action(async (repoId, docId) => {
867
+ try {
868
+ await api.deleteDocument(repoId, docId);
869
+ console.log("Deleted.");
870
+ }
871
+ catch (err) {
872
+ console.error("Error:", err instanceof Error ? err.message : err);
873
+ process.exit(1);
874
+ }
875
+ });
876
+ docsCmd
877
+ .command("upload-and-link <repo-id> <file> <work-item-id>")
878
+ .description("Upload a document and immediately attach it as a reference to a work item")
879
+ .option("-n, --name <filename>", "Override filename")
880
+ .option("-l, --label <label>", "Reference label (default: filename)")
881
+ .option("-t, --type <ref-type>", "Reference type: prd, design, screenshot, client_doc, external, user_flow", "user_flow")
882
+ .action(async (repoId, file, workItemId, opts) => {
883
+ try {
884
+ const content = readFileSync(file);
885
+ const filename = opts.name ?? basename(file);
886
+ const contentType = filename.endsWith(".md") ? "text/markdown" : "text/plain";
887
+ // 1. Upload
888
+ const doc = await api.uploadDocument(repoId, filename, content, contentType);
889
+ const baseUrl = auth.getApiUrl().replace(/\/$/, "");
890
+ const contentUrl = `${baseUrl}/api/v1/repos/${repoId}/documents/${doc.id}/content`;
891
+ console.log(`Uploaded: ${doc.filename} (${(doc.size_bytes / 1024).toFixed(1)}KB) → ${doc.id}`);
892
+ // 2. Attach as reference
893
+ const label = opts.label ?? filename.replace(/\.[^.]+$/, "").replace(/[-_]/g, " ");
894
+ const ref = await api.createReference(workItemId, {
895
+ url: contentUrl,
896
+ label,
897
+ ref_type: opts.type,
898
+ });
899
+ console.log(`Linked: [${ref.ref_type}] "${ref.label}" → ${workItemId.slice(0, 8)}`);
900
+ console.log(`Ref ID: ${ref.id}`);
901
+ }
902
+ catch (err) {
903
+ console.error("Error:", err instanceof Error ? err.message : err);
904
+ process.exit(1);
905
+ }
906
+ });
907
+ // ---------------------------------------------------------------------------
908
+ // Refs commands
909
+ // ---------------------------------------------------------------------------
910
+ const refsCmd = program.command("refs").description("Work item reference operations");
911
+ refsCmd
912
+ .command("list <work-item-id>")
913
+ .description("List references for a work item")
914
+ .option("--inherited", "Include references from all ancestor items", false)
915
+ .action(async (workItemId, opts) => {
916
+ try {
917
+ const refs = await api.listReferences(workItemId, opts.inherited);
918
+ if (refs.length === 0) {
919
+ console.log("No references found.");
920
+ return;
921
+ }
922
+ for (const r of refs) {
923
+ const from = r.from_level ? ` (from ${r.from_level})` : "";
924
+ console.log(` [${r.ref_type.padEnd(12)}] ${r.label}${from}`);
925
+ console.log(` ${r.url}`);
926
+ console.log(` id: ${r.id}`);
927
+ }
928
+ }
929
+ catch (err) {
930
+ console.error("Error:", err instanceof Error ? err.message : err);
931
+ process.exit(1);
932
+ }
933
+ });
934
+ refsCmd
935
+ .command("add <work-item-id>")
936
+ .description("Attach a URL reference to a work item")
937
+ .requiredOption("-u, --url <url>", "Reference URL")
938
+ .requiredOption("-l, --label <label>", "Human-readable label")
939
+ .option("-t, --type <type>", "Reference type: prd, design, screenshot, client_doc, external, user_flow", "external")
940
+ .action(async (workItemId, opts) => {
941
+ try {
942
+ const ref = await api.createReference(workItemId, {
943
+ url: opts.url,
944
+ label: opts.label,
945
+ ref_type: opts.type,
946
+ });
947
+ console.log(`Added reference: [${ref.ref_type}] ${ref.label}`);
948
+ console.log(`ID: ${ref.id}`);
949
+ }
950
+ catch (err) {
951
+ console.error("Error:", err instanceof Error ? err.message : err);
952
+ process.exit(1);
953
+ }
954
+ });
955
+ refsCmd
956
+ .command("delete <work-item-id> <ref-id>")
957
+ .description("Delete a reference from a work item")
958
+ .action(async (workItemId, refId) => {
959
+ try {
960
+ await api.deleteReference(workItemId, refId);
961
+ console.log("Deleted.");
962
+ }
963
+ catch (err) {
964
+ console.error("Error:", err instanceof Error ? err.message : err);
965
+ process.exit(1);
966
+ }
967
+ });
968
+ // ---------------------------------------------------------------------------
969
+ // MCP server commands
970
+ // ---------------------------------------------------------------------------
971
+ const mcpCmd = program.command("mcp").description("MCP server operations");
972
+ mcpCmd
973
+ .command("start")
974
+ .description("Start MCP server mode (stdio transport)")
975
+ .action(async () => {
976
+ await import("./mcp.js");
977
+ });
978
+ mcpCmd
979
+ .command("install")
980
+ .description("Register NPC as an MCP server in a supported agent tool")
981
+ .option("--tool <name>", `Agent tool to configure. Supported: ${listSupportedTools().join(", ")}`, DEFAULT_TOOL)
982
+ .action((opts) => {
983
+ const adapter = getAdapter(opts.tool);
984
+ if (!adapter) {
985
+ console.error(`Unknown tool: "${opts.tool}". Supported: ${listSupportedTools().join(", ")}`);
986
+ process.exit(1);
987
+ }
988
+ const __dirname = dirname(fileURLToPath(import.meta.url));
989
+ const mcpEntrypoint = join(__dirname, "mcp.js");
990
+ if (!existsSync(mcpEntrypoint)) {
991
+ console.error(`MCP entrypoint not found at ${mcpEntrypoint}`);
992
+ console.error("Run `npm run build` in npc-cli first.");
993
+ process.exit(1);
994
+ }
995
+ // Read existing config or start fresh
996
+ let config = {};
997
+ if (existsSync(adapter.configPath)) {
998
+ try {
999
+ config = JSON.parse(readFileSync(adapter.configPath, "utf-8"));
1000
+ }
1001
+ catch {
1002
+ // Corrupted file — start fresh
1003
+ }
1004
+ }
1005
+ // Ensure mcpServers key exists
1006
+ const key = adapter.mcpServersKey;
1007
+ if (!config[key] || typeof config[key] !== "object") {
1008
+ config[key] = {};
1009
+ }
1010
+ config[key].npc = {
1011
+ type: "stdio",
1012
+ command: "node",
1013
+ args: [mcpEntrypoint],
1014
+ };
1015
+ // Create parent dirs if needed
1016
+ mkdirSync(dirname(adapter.configPath), { recursive: true });
1017
+ writeFileSync(adapter.configPath, JSON.stringify(config, null, 2) + "\n");
1018
+ console.log(`NPC MCP server registered in ${adapter.displayName}.`);
1019
+ console.log(` Config: ${adapter.configPath}`);
1020
+ console.log(` Entry: node ${mcpEntrypoint}`);
1021
+ console.log(`\n${adapter.restartHint}`);
1022
+ });
1023
+ mcpCmd
1024
+ .command("uninstall")
1025
+ .description("Remove NPC from an agent tool's MCP config")
1026
+ .option("--tool <name>", `Agent tool to remove from. Supported: ${listSupportedTools().join(", ")}`, DEFAULT_TOOL)
1027
+ .action((opts) => {
1028
+ const adapter = getAdapter(opts.tool);
1029
+ if (!adapter) {
1030
+ console.error(`Unknown tool: "${opts.tool}". Supported: ${listSupportedTools().join(", ")}`);
1031
+ process.exit(1);
1032
+ }
1033
+ if (!existsSync(adapter.configPath)) {
1034
+ console.log(`No ${adapter.displayName} config found. Nothing to remove.`);
1035
+ return;
1036
+ }
1037
+ try {
1038
+ const config = JSON.parse(readFileSync(adapter.configPath, "utf-8"));
1039
+ const servers = config[adapter.mcpServersKey];
1040
+ if (!servers?.npc) {
1041
+ console.log(`NPC not found in ${adapter.displayName} MCP config.`);
1042
+ return;
1043
+ }
1044
+ delete servers.npc;
1045
+ writeFileSync(adapter.configPath, JSON.stringify(config, null, 2) + "\n");
1046
+ console.log(`NPC MCP server removed from ${adapter.displayName}.`);
1047
+ console.log(`${adapter.restartHint}`);
1048
+ }
1049
+ catch {
1050
+ console.error(`Failed to read ${adapter.configPath}`);
1051
+ process.exit(1);
1052
+ }
1053
+ });
1054
+ program.parse();
1055
+ //# sourceMappingURL=index.js.map