@shakecodeslikecray/npc-cli 0.4.3 → 0.4.5

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.
@@ -0,0 +1,145 @@
1
+ import { existsSync, readFileSync, writeFileSync, copyFileSync, chmodSync, mkdirSync, } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { createInterface } from "node:readline";
4
+ import { execFileSync } from "node:child_process";
5
+ import * as api from "../client/api.js";
6
+ import { CREDENTIALS_PATH } from "../config.js";
7
+ import { fetchAndCache } from "./fetch.js";
8
+ function ask(question) {
9
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
10
+ return new Promise((resolve) => {
11
+ rl.question(question, (answer) => {
12
+ rl.close();
13
+ resolve(answer.trim());
14
+ });
15
+ });
16
+ }
17
+ export async function init(opts) {
18
+ // 1. Check auth
19
+ if (!existsSync(CREDENTIALS_PATH)) {
20
+ console.error("Not authenticated. Run: npc auth login");
21
+ process.exit(1);
22
+ }
23
+ // 2. Check .git
24
+ if (!existsSync(".git")) {
25
+ throw new Error("Not a git repository. Run this from a git repo root.");
26
+ }
27
+ // 3. Pick project
28
+ let projectId = opts.project;
29
+ let projectName = "";
30
+ if (!projectId) {
31
+ const projects = await api.listProjects();
32
+ if (projects.length === 0) {
33
+ throw new Error("No projects found. Create one first in the NPC dashboard.");
34
+ }
35
+ console.log("\nProjects:");
36
+ for (let i = 0; i < projects.length; i++) {
37
+ console.log(` ${i + 1}) ${projects[i].name}`);
38
+ }
39
+ const choice = await ask(`\nSelect project [1-${projects.length}]: `);
40
+ const idx = parseInt(choice, 10) - 1;
41
+ if (isNaN(idx) || idx < 0 || idx >= projects.length) {
42
+ throw new Error("Invalid selection.");
43
+ }
44
+ projectId = projects[idx].id;
45
+ projectName = projects[idx].name;
46
+ }
47
+ // 4. Pick repo
48
+ let repoId = opts.repo;
49
+ let repoName = "";
50
+ if (!repoId) {
51
+ const repos = await api.getProjectRepos(projectId);
52
+ if (repos.length === 0) {
53
+ throw new Error("No repos found for this project. Create one first.");
54
+ }
55
+ console.log("\nRepos:");
56
+ for (let i = 0; i < repos.length; i++) {
57
+ console.log(` ${i + 1}) ${repos[i].name}`);
58
+ }
59
+ const choice = await ask(`\nSelect repo [1-${repos.length}]: `);
60
+ const idx = parseInt(choice, 10) - 1;
61
+ if (isNaN(idx) || idx < 0 || idx >= repos.length) {
62
+ throw new Error("Invalid selection.");
63
+ }
64
+ repoId = repos[idx].id;
65
+ repoName = repos[idx].name;
66
+ }
67
+ else {
68
+ // If repo ID was provided directly, fetch repos to get the name
69
+ const repos = await api.getProjectRepos(projectId);
70
+ const match = repos.find((r) => r.id === repoId);
71
+ repoName = match?.name ?? repoId;
72
+ }
73
+ // 5. Write .npcrc.json
74
+ const npcrc = { project_id: projectId, repo_id: repoId, repo_name: repoName };
75
+ writeFileSync(".npcrc.json", JSON.stringify(npcrc, null, 2) + "\n");
76
+ console.log("\n[ok] .npcrc.json written");
77
+ // 6. Add .npc/ to .gitignore
78
+ const gitignorePath = ".gitignore";
79
+ let gitignore = existsSync(gitignorePath)
80
+ ? readFileSync(gitignorePath, "utf-8")
81
+ : "";
82
+ if (!gitignore.includes(".npc/")) {
83
+ gitignore = gitignore.trimEnd() + "\n.npc/\n";
84
+ writeFileSync(gitignorePath, gitignore);
85
+ console.log("[ok] .npc/ added to .gitignore");
86
+ }
87
+ else {
88
+ console.log("[ok] .npc/ already in .gitignore");
89
+ }
90
+ // 7. Install git hooks
91
+ if (opts.hooks !== false) {
92
+ const hooksDir = join(".git", "hooks");
93
+ mkdirSync(hooksDir, { recursive: true });
94
+ const hooks = {
95
+ "pre-push": "#!/bin/sh\nexec npc hook pre-push \"$@\"\n",
96
+ "pre-commit": "#!/bin/sh\nexec npc hook pre-commit \"$@\"\n",
97
+ };
98
+ for (const [name, content] of Object.entries(hooks)) {
99
+ const hookPath = join(hooksDir, name);
100
+ // Skip if already installed
101
+ if (existsSync(hookPath)) {
102
+ const existing = readFileSync(hookPath, "utf-8");
103
+ if (existing.includes("npc hook")) {
104
+ console.log(`[ok] ${name} hook already installed`);
105
+ continue;
106
+ }
107
+ // Back up existing hook
108
+ const bakPath = `${hookPath}.bak`;
109
+ copyFileSync(hookPath, bakPath);
110
+ console.log(`[ok] backed up existing ${name} hook to ${name}.bak`);
111
+ }
112
+ writeFileSync(hookPath, content);
113
+ chmodSync(hookPath, 0o755);
114
+ console.log(`[ok] ${name} hook installed`);
115
+ }
116
+ }
117
+ else {
118
+ console.log("[--] hooks skipped (--no-hooks)");
119
+ }
120
+ // 8. Run npc fetch
121
+ if (opts.fetch !== false) {
122
+ console.log("\nFetching work items...");
123
+ try {
124
+ const result = await fetchAndCache(repoId);
125
+ console.log(`[ok] Cached ${result.item_count} items, ${result.edge_count} edges, ${result.cr_count} CRs`);
126
+ }
127
+ catch (err) {
128
+ console.warn(`[warn] fetch failed: ${err.message || err}`);
129
+ }
130
+ }
131
+ else {
132
+ console.log("[--] fetch skipped (--no-fetch)");
133
+ }
134
+ // 9. Detect Claude Code
135
+ try {
136
+ execFileSync("which", ["claude"], { stdio: "pipe" });
137
+ console.log("\nClaude Code detected. Install NPC plugins:\n" +
138
+ " claude plugins:add-marketplace shakecodeslikecray/fordel-plugins");
139
+ }
140
+ catch {
141
+ // claude not found, skip
142
+ }
143
+ console.log("\nDone. Repository initialized for NPC.");
144
+ }
145
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
package/dist/config.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export declare const NPC_DIR: string;
2
+ export declare const NPC_LOCAL_DIR = ".npc";
3
+ export declare const NPC_CACHE_DIR: string;
2
4
  export declare const CREDENTIALS_PATH: string;
3
5
  export declare const DEFAULT_API_URL = "https://npc-api.fordelstudios.com";
4
6
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,OAAO,QAA0B,CAAC;AAC/C,eAAO,MAAM,gBAAgB,QAAoC,CAAC;AAClE,eAAO,MAAM,eAAe,sCAAsC,CAAC;AAEnE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAkC,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,OAAO,QAA0B,CAAC;AAC/C,eAAO,MAAM,aAAa,SAAS,CAAC;AACpC,eAAO,MAAM,aAAa,QAAyB,CAAC;AACpD,eAAO,MAAM,gBAAgB,QAAoC,CAAC;AAClE,eAAO,MAAM,eAAe,sCAAsC,CAAC;AAEnE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAkC,CAAC"}
package/dist/config.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { homedir } from "node:os";
2
2
  import { join } from "node:path";
3
3
  export const NPC_DIR = join(homedir(), ".npc");
4
+ export const NPC_LOCAL_DIR = ".npc";
5
+ export const NPC_CACHE_DIR = join(NPC_DIR, "cache");
4
6
  export const CREDENTIALS_PATH = join(NPC_DIR, "credentials.json");
5
7
  export const DEFAULT_API_URL = "https://npc-api.fordelstudios.com";
6
8
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;AAC/C,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;AAClE,MAAM,CAAC,MAAM,eAAe,GAAG,mCAAmC,CAAC;AAEnE;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;AAC/C,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC;AACpC,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;AAClE,MAAM,CAAC,MAAM,eAAe,GAAG,mCAAmC,CAAC;AAEnE;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC"}
package/dist/index.js CHANGED
@@ -7,6 +7,9 @@ import { fileURLToPath } from "node:url";
7
7
  import * as auth from "./client/auth.js";
8
8
  import * as api from "./client/api.js";
9
9
  import { CREDENTIALS_PATH, DEFAULT_API_URL } from "./config.js";
10
+ import { readNpcrc, fetchAndCache } from "./commands/fetch.js";
11
+ import { prePush, preCommit } from "./commands/hook.js";
12
+ import { init } from "./commands/init.js";
10
13
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
14
  const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf8"));
12
15
  function prompt(question) {
@@ -437,20 +440,71 @@ program
437
440
  });
438
441
  program
439
442
  .command("pull")
440
- .description("Get next unblocked task from the queue")
443
+ .description("Pull next L2 feature from the queue with all tasks")
441
444
  .option("-r, --repo <id>", "Filter by repo UUID")
445
+ .option("--id <uuid>", "Pull a specific L2 by ID")
446
+ .option("--l4", "Legacy: pull a single L4 task instead of L2 feature")
442
447
  .action(async (opts) => {
443
448
  try {
444
- const result = await api.queueNext(opts.repo);
449
+ const level = opts.l4 ? "l4" : "l2";
450
+ const result = await api.queueNext(opts.repo, level, opts.id);
445
451
  const wi = result.work_item;
446
- console.log(`Task: ${wi.title}`);
447
- console.log(`ID: ${wi.id}`);
448
- console.log(`State: ${wi.state}`);
449
- console.log(`Priority: ${wi.priority}`);
450
- if (wi.description)
451
- console.log(`Description: ${wi.description}`);
452
- if (result.ancestors.length > 0) {
453
- console.log(`Context: ${result.ancestors.map((a) => a.title).join(" > ")}`);
452
+ if (level === "l4") {
453
+ // Legacy single-task output
454
+ console.log(`Task: ${wi.title}`);
455
+ console.log(`ID: ${wi.id}`);
456
+ console.log(`State: ${wi.state}`);
457
+ console.log(`Priority: ${wi.priority}`);
458
+ if (wi.description)
459
+ console.log(`Description: ${wi.description}`);
460
+ if (result.ancestors.length > 0) {
461
+ console.log(`Context: ${result.ancestors.map((a) => a.title).join(" > ")}`);
462
+ }
463
+ }
464
+ else {
465
+ // L2 feature tree output
466
+ console.log(`\nFeature: ${wi.title} (${wi.state})`);
467
+ console.log(`ID: ${wi.id}`);
468
+ if (wi.priority && wi.priority !== "none")
469
+ console.log(`Priority: ${wi.priority}`);
470
+ if (result.ancestors.length > 0) {
471
+ console.log(`Context: ${result.ancestors.map((a) => a.title).join(" > ")}`);
472
+ }
473
+ if (wi.description)
474
+ console.log(`\n${wi.description}`);
475
+ // Group descendants into L3 → L4 tree
476
+ const descendants = result.descendants || [];
477
+ const l3s = descendants.filter((d) => d.parent_id === wi.id);
478
+ const l4sByParent = new Map();
479
+ for (const d of descendants) {
480
+ if (d.parent_id && d.parent_id !== wi.id) {
481
+ const arr = l4sByParent.get(d.parent_id) || [];
482
+ arr.push(d);
483
+ l4sByParent.set(d.parent_id, arr);
484
+ }
485
+ }
486
+ if (l3s.length > 0) {
487
+ console.log(`\nTasks:`);
488
+ for (const l3 of l3s) {
489
+ console.log(` ${l3.title} [L3]`);
490
+ const children = l4sByParent.get(l3.id) || [];
491
+ for (const l4 of children) {
492
+ const blocked = l4.state === "todo" && l4.claimed_by ? "blocked" : "unblocked";
493
+ const stateTag = l4.claimed_by && l4.state === "doing" ? "doing" : l4.state;
494
+ console.log(` [${l4.id.slice(0, 8)}] ${l4.title.padEnd(40)} ${stateTag} (${blocked})`);
495
+ }
496
+ }
497
+ }
498
+ // Summary
499
+ const leaves = descendants.filter((d) => !descendants.some((c) => c.parent_id === d.id));
500
+ const doing = leaves.filter((l) => l.state === "doing").length;
501
+ const todo = leaves.filter((l) => l.state === "todo").length;
502
+ const done = leaves.filter((l) => l.state === "done").length;
503
+ console.log(`\n${leaves.length} tasks: ${doing} doing, ${todo} todo, ${done} done`);
504
+ // Auto-refresh local cache after pulling
505
+ const rc = readNpcrc();
506
+ if (rc)
507
+ fetchAndCache(rc.repo_id).catch(() => { });
454
508
  }
455
509
  }
456
510
  catch (err) {
@@ -458,6 +512,19 @@ program
458
512
  process.exit(1);
459
513
  }
460
514
  });
515
+ program
516
+ .command("release <task-id>")
517
+ .description("Release a claimed L2 feature back to the queue")
518
+ .action(async (taskId) => {
519
+ try {
520
+ await api.releaseWorkItem(taskId);
521
+ console.log(`Released: ${taskId}`);
522
+ }
523
+ catch (err) {
524
+ console.error("Error:", err instanceof Error ? err.message : err);
525
+ process.exit(1);
526
+ }
527
+ });
461
528
  program
462
529
  .command("start <task-id>")
463
530
  .description("Start working on a task")
@@ -1063,5 +1130,175 @@ testCaseCmd
1063
1130
  process.exit(1);
1064
1131
  }
1065
1132
  });
1133
+ // ─── Change Requests ───
1134
+ const crCmd = program.command("cr").description("Change request operations");
1135
+ crCmd
1136
+ .command("list")
1137
+ .description("List change requests")
1138
+ .option("-r, --repo <id>", "Repo ID (reads .npcrc.json if omitted)")
1139
+ .option("-s, --state <state>", "Filter by state")
1140
+ .action(async (opts) => {
1141
+ try {
1142
+ const repoId = opts.repo || (await readNpcrc())?.repo_id;
1143
+ if (!repoId) {
1144
+ console.error("No repo ID. Use --repo or add .npcrc.json");
1145
+ process.exit(1);
1146
+ }
1147
+ const crs = await api.listChangeRequests(repoId, opts.state);
1148
+ if (crs.length === 0) {
1149
+ console.log("No change requests found.");
1150
+ return;
1151
+ }
1152
+ console.table(crs.map((cr) => ({
1153
+ id: cr.id.slice(0, 8),
1154
+ type: cr.cr_type,
1155
+ state: cr.state,
1156
+ title: cr.title.slice(0, 50),
1157
+ hours: cr.estimated_hours ?? "--",
1158
+ submitted: cr.submitter_name || "Unknown",
1159
+ })));
1160
+ }
1161
+ catch (err) {
1162
+ console.error("Error:", err instanceof Error ? err.message : err);
1163
+ process.exit(1);
1164
+ }
1165
+ });
1166
+ crCmd
1167
+ .command("pending-qa")
1168
+ .description("List scheduled CRs that need QA items generated")
1169
+ .option("-r, --repo <id>", "Repo ID (reads .npcrc.json if omitted)")
1170
+ .action(async (opts) => {
1171
+ try {
1172
+ const repoId = opts.repo || (await readNpcrc())?.repo_id;
1173
+ if (!repoId) {
1174
+ console.error("No repo ID. Use --repo or add .npcrc.json");
1175
+ process.exit(1);
1176
+ }
1177
+ // Get scheduled CRs
1178
+ const crs = await api.listChangeRequests(repoId, "scheduled");
1179
+ if (crs.length === 0) {
1180
+ console.log("No scheduled CRs.");
1181
+ return;
1182
+ }
1183
+ // Get user flows and their use cases to check existing QA coverage
1184
+ const flows = await api.listUserFlows(repoId);
1185
+ const existingUCTitles = new Set();
1186
+ for (const flow of flows) {
1187
+ const ucs = await api.listUseCases(repoId, flow.id);
1188
+ for (const uc of ucs)
1189
+ existingUCTitles.add(uc.title);
1190
+ }
1191
+ // Filter CRs that don't have matching use cases
1192
+ const pending = crs.filter((cr) => !existingUCTitles.has(`CR: ${cr.title}`));
1193
+ if (pending.length === 0) {
1194
+ console.log("All scheduled CRs have QA coverage.");
1195
+ return;
1196
+ }
1197
+ console.log(`\n${pending.length} CR(s) need QA items:\n`);
1198
+ for (const cr of pending) {
1199
+ console.log(` [${cr.id.slice(0, 8)}] ${cr.title}`);
1200
+ console.log(` Type: ${cr.cr_type} | Hours: ${cr.estimated_hours ?? "--"} | By: ${cr.submitter_name || "Unknown"}`);
1201
+ if (cr.description)
1202
+ console.log(` ${cr.description.slice(0, 120)}`);
1203
+ console.log();
1204
+ }
1205
+ // Output machine-readable JSON for the cron agent
1206
+ console.log("---JSON---");
1207
+ console.log(JSON.stringify(pending.map((cr) => ({
1208
+ id: cr.id,
1209
+ title: cr.title,
1210
+ description: cr.description,
1211
+ cr_type: cr.cr_type,
1212
+ linked_l1: cr.linked_l1,
1213
+ }))));
1214
+ }
1215
+ catch (err) {
1216
+ console.error("Error:", err instanceof Error ? err.message : err);
1217
+ process.exit(1);
1218
+ }
1219
+ });
1220
+ // ─── Fetch ───
1221
+ program
1222
+ .command("fetch")
1223
+ .description("Fetch and cache work items, edges, and CRs for the current repo")
1224
+ .option("-r, --repo <id>", "Repo ID (reads .npcrc.json if omitted)")
1225
+ .option("--force", "Ignore cache staleness, always fetch")
1226
+ .option("-s, --state <state>", "Filter items by state (todo, doing, done)")
1227
+ .option("-q, --search <query>", "Search items by title")
1228
+ .option("--sort <field>", "Sort by field (created_at, updated_at, sort_order)")
1229
+ .option("--labels <labels>", "Filter by labels (comma-separated)")
1230
+ .option("--per-page <n>", "Items per page (default: all)")
1231
+ .action(async (opts) => {
1232
+ try {
1233
+ const repoId = opts.repo || readNpcrc()?.repo_id;
1234
+ if (!repoId) {
1235
+ console.error("No repo ID. Use --repo or add .npcrc.json");
1236
+ process.exit(1);
1237
+ }
1238
+ const params = {};
1239
+ if (opts.state)
1240
+ params.state = opts.state;
1241
+ if (opts.search)
1242
+ params.q = opts.search;
1243
+ if (opts.sort)
1244
+ params.sort = opts.sort;
1245
+ if (opts.labels)
1246
+ params.labels = opts.labels;
1247
+ if (opts.perPage)
1248
+ params.per_page = opts.perPage;
1249
+ console.log("Fetching data from NPC API...");
1250
+ const result = await fetchAndCache(repoId, params);
1251
+ console.log(`Cached ${result.item_count} items, ${result.edge_count} edges, ${result.cr_count} CRs`);
1252
+ console.log(`Cache dir: ${result.cache_dir}`);
1253
+ }
1254
+ catch (err) {
1255
+ console.error("Error:", err instanceof Error ? err.message : err);
1256
+ process.exit(1);
1257
+ }
1258
+ });
1259
+ // ---------------------------------------------------------------------------
1260
+ // Hook commands (called by git hooks)
1261
+ // ---------------------------------------------------------------------------
1262
+ const hookCmd = program.command("hook").description("Git hook handlers (called by git hooks)");
1263
+ hookCmd
1264
+ .command("pre-push")
1265
+ .description("Pre-push hook: signals, CR detection, telemetry")
1266
+ .action(async () => {
1267
+ try {
1268
+ await prePush();
1269
+ }
1270
+ catch (e) {
1271
+ console.log("NPC: Hook error (push continues):", e.message || e);
1272
+ }
1273
+ process.exit(0);
1274
+ });
1275
+ hookCmd
1276
+ .command("pre-commit")
1277
+ .description("Pre-commit hook: task tracking enforcement")
1278
+ .action(async () => {
1279
+ try {
1280
+ await preCommit();
1281
+ }
1282
+ catch (e) {
1283
+ console.error("NPC:", e.message || e);
1284
+ process.exit(1);
1285
+ }
1286
+ });
1287
+ program
1288
+ .command("init")
1289
+ .description("Initialize NPC in current repo: auth, config, hooks, cache")
1290
+ .option("-p, --project <id>", "Project UUID (skip interactive)")
1291
+ .option("-r, --repo <id>", "Repo UUID (skip interactive)")
1292
+ .option("--no-hooks", "Skip git hook installation")
1293
+ .option("--no-fetch", "Skip initial data fetch")
1294
+ .action(async (opts) => {
1295
+ try {
1296
+ await init(opts);
1297
+ }
1298
+ catch (err) {
1299
+ console.error("Error:", err.message || err);
1300
+ process.exit(1);
1301
+ }
1302
+ });
1066
1303
  program.parse();
1067
1304
  //# sourceMappingURL=index.js.map