@ulpi/cli 0.1.5 → 0.1.7

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 (109) hide show
  1. package/README.md +143 -214
  2. package/dist/{auth-PN7TMQHV-2W4ICG64.js → auth-FWM7MM4Q-VZC3U2XZ.js} +1 -1
  3. package/dist/{auth-BFFBUJUC.js → auth-HDK7ECJL.js} +2 -1
  4. package/dist/{chunk-RJIRWQJD.js → chunk-3BCW6ABU.js} +402 -142
  5. package/dist/{chunk-L3PWNHSA.js → chunk-3WB5CXH4.js} +180 -5
  6. package/dist/{chunk-K4OVPFY2.js → chunk-4UCJIAOU.js} +2 -2
  7. package/dist/chunk-4XTHZVDS.js +109 -0
  8. package/dist/chunk-4ZPOZULQ.js +6522 -0
  9. package/dist/{chunk-SIAQVRKG.js → chunk-5MI5GIXM.js} +48 -2
  10. package/dist/{chunk-KLEASXUR.js → chunk-6ZL6NXMV.js} +1 -1
  11. package/dist/{chunk-AV5RB3N2.js → chunk-76D3BYJD.js} +48 -0
  12. package/dist/{chunk-DOIKS6C5.js → chunk-AWOSRA5F.js} +1 -1
  13. package/dist/{chunk-UCMT5OKP.js → chunk-BFEKZZHM.js} +274 -57
  14. package/dist/chunk-C7CLUQI6.js +1286 -0
  15. package/dist/{chunk-ELTGWMDE.js → chunk-E3B5NROU.js} +7 -7
  16. package/dist/chunk-EJ7TW77N.js +1418 -0
  17. package/dist/{chunk-P2RESJRN.js → chunk-EWLYVXQ4.js} +2 -2
  18. package/dist/{chunk-6OURRFP7.js → chunk-IV6MWETF.js} +383 -168
  19. package/dist/chunk-IZPJHSPX.js +1478 -0
  20. package/dist/chunk-JLHNLM3C.js +228 -0
  21. package/dist/chunk-PO4NUZUU.js +147 -0
  22. package/dist/chunk-S6ANCSYO.js +1271 -0
  23. package/dist/chunk-SEU7WWNQ.js +1251 -0
  24. package/dist/chunk-SNQ7NAIS.js +453 -0
  25. package/dist/{ulpi-RMMCUAGP-EWYUE7RU.js → chunk-TSLDGT5O.js} +73 -35
  26. package/dist/{chunk-EIWYSP3A.js → chunk-UXHCHOWQ.js} +83 -62
  27. package/dist/chunk-WED4LM5N.js +322 -0
  28. package/dist/chunk-WVOZE25N.js +6757 -0
  29. package/dist/{chunk-5SCG7UYM.js → chunk-XKF4DPUM.js} +7 -7
  30. package/dist/{chunk-74WVVWJ4.js → chunk-YOKL7RB5.js} +184 -15
  31. package/dist/chunk-Z53CAR7G.js +298 -0
  32. package/dist/{ci-JQ56YIKC.js → ci-COZRTPGQ.js} +124 -26
  33. package/dist/cloud-2F3NLVHN.js +274 -0
  34. package/dist/{codemap-HMYBXJL2.js → codemap-XNGMAF3F.js} +37 -37
  35. package/dist/codex-MB5YTMRT.js +132 -0
  36. package/dist/{config-YYWEN7U2.js → config-OOELBYTH.js} +1 -1
  37. package/dist/dist-2BJYR5EI.js +59 -0
  38. package/dist/dist-3EIQTZHT.js +1380 -0
  39. package/dist/{dist-WAMAQVPK.js → dist-4U5L2X2C.js} +2 -2
  40. package/dist/{dist-4XTJ6HLM.js → dist-54KAMNLO.js} +16 -15
  41. package/dist/dist-6M4MZWZW.js +58 -0
  42. package/dist/dist-6X576SU2.js +27 -0
  43. package/dist/dist-7QOEYLFX.js +103 -0
  44. package/dist/dist-AYBGHEDY.js +2541 -0
  45. package/dist/dist-EK45QNEM.js +45 -0
  46. package/dist/{dist-U7ZIJMZD.js → dist-FKFEJRPX.js} +16 -15
  47. package/dist/dist-GTEJUBBT.js +66 -0
  48. package/dist/dist-HA74OKJZ.js +40 -0
  49. package/dist/{dist-XG2GG5SD.js → dist-HU5RZAON.js} +14 -2
  50. package/dist/dist-IYE3OBRB.js +374 -0
  51. package/dist/{dist-7WLLPWWB.js → dist-JLU26AB6.js} +12 -9
  52. package/dist/{dist-6G7JC2RA.js → dist-KUCI6JFE.js} +49 -9
  53. package/dist/dist-NUEMFZFL.js +33 -0
  54. package/dist/{dist-GWGTAHNM.js → dist-NUXMDXZ3.js} +31 -3
  55. package/dist/{dist-5R4RYNQO.js → dist-YCNWHSLN.js} +15 -5
  56. package/dist/{dist-6MFVWIFF.js → dist-YFFG2ZD6.js} +9 -16
  57. package/dist/dist-ZG4OKCSR.js +15 -0
  58. package/dist/doctor-FKYSIHER.js +345 -0
  59. package/dist/{export-import-4A5MWLIA.js → export-import-JFQH4KSJ.js} +1 -1
  60. package/dist/{history-RNUWO4JZ.js → history-UMGQNQQ7.js} +7 -7
  61. package/dist/{hooks-installer-K2JXEBNN.js → hooks-installer-YEYTYA6Q.js} +2 -2
  62. package/dist/index.js +398 -622
  63. package/dist/{init-NQWFZPKO.js → init-TJYW5ROZ.js} +78 -12
  64. package/dist/job-HIDMAFW2.js +376 -0
  65. package/dist/jobs.memory-PLMMSFHB-VBECCTHN.js +33 -0
  66. package/dist/kiro-VMUHDFGK.js +153 -0
  67. package/dist/{launchd-OYXUAVW6.js → launchd-U3MSWBRH.js} +9 -17
  68. package/dist/mcp-PDUD7SGP.js +249 -0
  69. package/dist/mcp-installer-PQU3XOGO.js +259 -0
  70. package/dist/mcp-setup-OA7IB3H3.js +263 -0
  71. package/dist/{memory-D6ZFFCI2.js → memory-ZNAEAK3B.js} +17 -17
  72. package/dist/{ollama-3XCUZMZT-FYKHW4TZ.js → ollama-3XCUZMZT-4JMH6B7P.js} +1 -1
  73. package/dist/{openai-E7G2YAHU-IG33BFYF.js → openai-E7G2YAHU-T3HMBPH7.js} +2 -2
  74. package/dist/portal-JYWVHXDU.js +210 -0
  75. package/dist/prd-Q4J5NVAR.js +408 -0
  76. package/dist/repos-WWZXNN3P.js +271 -0
  77. package/dist/review-integration-RQE4KMAV.js +14 -0
  78. package/dist/{rules-3OFGWHP4.js → rules-Y4VSOY5Y.js} +3 -3
  79. package/dist/run-VPNXEIBY.js +687 -0
  80. package/dist/server-COL4AXKU-P7S7NNF6.js +11 -0
  81. package/dist/server-U7PQ6FTS-MG4MJPTS.js +20 -0
  82. package/dist/{skills-GY2CTPWN.js → skills-QEYU2N27.js} +4 -2
  83. package/dist/start-IJKY5RVT.js +303 -0
  84. package/dist/{status-SE43TIFJ.js → status-BHQYYGAL.js} +2 -2
  85. package/dist/{templates-O2XDKB5R.js → templates-CBRUJ66V.js} +6 -5
  86. package/dist/tui-DP7736EX.js +61 -0
  87. package/dist/ulpi-5EN6JCAS-LFE3WSL4.js +10 -0
  88. package/dist/{uninstall-KWGSGZTI.js → uninstall-BX6FOV77.js} +3 -3
  89. package/dist/{update-QYZA4D23.js → update-AQKTHFVQ.js} +3 -3
  90. package/dist/{version-checker-MVB74DEX.js → version-checker-5L5PUOEX.js} +2 -2
  91. package/package.json +13 -4
  92. package/dist/chunk-26LLDX2T.js +0 -553
  93. package/dist/chunk-DDRLI6JU.js +0 -331
  94. package/dist/chunk-IFATANHR.js +0 -453
  95. package/dist/chunk-JWUUVXIV.js +0 -13694
  96. package/dist/chunk-LD52XG3X.js +0 -4273
  97. package/dist/chunk-MIAQVCFW.js +0 -39
  98. package/dist/chunk-YYZOFYS6.js +0 -415
  99. package/dist/dist-XD4YI27T.js +0 -26
  100. package/dist/mcp-installer-TOYDP77X.js +0 -124
  101. package/dist/projects-COUJP4ZC.js +0 -271
  102. package/dist/review-KMGP2S25.js +0 -152
  103. package/dist/server-USLHY6GH-F4JSXCWA.js +0 -18
  104. package/dist/server-X5P6WH2M-ULZF5WHZ.js +0 -11
  105. package/dist/ui-4SM2SUI6.js +0 -167
  106. package/dist/ui.html +0 -698
  107. /package/dist/skills/{ulpi-generate-guardian → ulpi-generate-guards}/SKILL.md +0 -0
  108. /package/dist/skills/{ulpi-generate-guardian → ulpi-generate-guards}/references/framework-rules.md +0 -0
  109. /package/dist/skills/{ulpi-generate-guardian → ulpi-generate-guards}/references/language-rules.md +0 -0
@@ -0,0 +1,210 @@
1
+ import {
2
+ API_LOCK_FILE,
3
+ ULPI_GLOBAL_DIR
4
+ } from "./chunk-C7CLUQI6.js";
5
+ import "./chunk-4VNS5WPM.js";
6
+
7
+ // src/commands/portal.ts
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ import { fileURLToPath } from "url";
11
+ import { execFileSync, spawn } from "child_process";
12
+ import chalk from "chalk";
13
+ var __filename = fileURLToPath(import.meta.url);
14
+ var __dirname = path.dirname(__filename);
15
+ var PID_FILE = path.join(ULPI_GLOBAL_DIR, "portal.pid");
16
+ function findNextBinary(portalDir) {
17
+ const localNext = path.join(portalDir, "node_modules", ".bin", "next");
18
+ if (fs.existsSync(localNext)) return localNext;
19
+ try {
20
+ const whichResult = execFileSync("which", ["next"], {
21
+ encoding: "utf-8",
22
+ timeout: 5e3
23
+ }).trim();
24
+ if (whichResult) return whichResult;
25
+ } catch {
26
+ }
27
+ return null;
28
+ }
29
+ function findPortalDir() {
30
+ const candidates = [
31
+ // apps/cli/dist/ → ../../portal (sibling app in apps/)
32
+ path.resolve(__dirname, "..", "..", "portal"),
33
+ // From cwd
34
+ path.join(process.cwd(), "apps", "portal")
35
+ ];
36
+ for (const candidate of candidates) {
37
+ if (fs.existsSync(path.join(candidate, "package.json")) && fs.existsSync(path.join(candidate, "src"))) {
38
+ try {
39
+ const pkg = JSON.parse(
40
+ fs.readFileSync(path.join(candidate, "package.json"), "utf-8")
41
+ );
42
+ if (pkg.name === "@ulpi/portal") return candidate;
43
+ } catch {
44
+ }
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ function writePidFile(pid) {
50
+ fs.mkdirSync(path.dirname(PID_FILE), { recursive: true });
51
+ fs.writeFileSync(PID_FILE, String(pid), "utf-8");
52
+ }
53
+ function readPidFile() {
54
+ try {
55
+ const content = fs.readFileSync(PID_FILE, "utf-8").trim();
56
+ const pid = parseInt(content, 10);
57
+ if (isNaN(pid)) return null;
58
+ try {
59
+ process.kill(pid, 0);
60
+ return pid;
61
+ } catch {
62
+ fs.unlinkSync(PID_FILE);
63
+ return null;
64
+ }
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+ function removePidFile() {
70
+ try {
71
+ fs.unlinkSync(PID_FILE);
72
+ } catch {
73
+ }
74
+ }
75
+ async function startPortal(args, projectDir) {
76
+ const portIndex = args.indexOf("--port");
77
+ let port = 9801;
78
+ if (portIndex !== -1 && args[portIndex + 1]) {
79
+ const parsed = parseInt(args[portIndex + 1], 10);
80
+ if (isNaN(parsed) || parsed < 1 || parsed > 65535) {
81
+ console.log(chalk.red("Invalid port number. Must be between 1 and 65535."));
82
+ process.exit(1);
83
+ }
84
+ port = parsed;
85
+ }
86
+ const devMode = args.includes("--dev");
87
+ const existingPid = readPidFile();
88
+ if (existingPid) {
89
+ console.log(chalk.yellow(`Portal is already running (PID ${existingPid}).`));
90
+ console.log(chalk.dim("Run 'ulpi portal stop' to stop it first."));
91
+ return;
92
+ }
93
+ const portalDir = findPortalDir();
94
+ if (!portalDir) {
95
+ console.log(chalk.red("Could not find the @ulpi/portal app directory."));
96
+ console.log(chalk.dim("Make sure you are in the ULPI monorepo, or the portal package is installed."));
97
+ process.exit(1);
98
+ }
99
+ const nextBin = findNextBinary(portalDir);
100
+ if (!nextBin) {
101
+ console.log(chalk.red("Could not find the 'next' binary."));
102
+ console.log(chalk.dim(`Install dependencies: cd ${portalDir} && pnpm install`));
103
+ process.exit(1);
104
+ }
105
+ const hasBuild = fs.existsSync(path.join(portalDir, ".next"));
106
+ if (!devMode && !hasBuild) {
107
+ console.log(chalk.yellow("No build output found. Starting in dev mode."));
108
+ console.log(chalk.dim(`Build first with: pnpm --filter @ulpi/portal build`));
109
+ console.log("");
110
+ }
111
+ const useDevMode = devMode || !hasBuild;
112
+ const nextCommand = useDevMode ? "dev" : "start";
113
+ let apiSecret = "";
114
+ let apiPort = 9800;
115
+ try {
116
+ const lock = JSON.parse(fs.readFileSync(API_LOCK_FILE, "utf-8"));
117
+ if (lock.secret) apiSecret = lock.secret;
118
+ if (lock.port) apiPort = lock.port;
119
+ } catch {
120
+ console.log(chalk.yellow("Warning: API server not running or lock file missing."));
121
+ console.log(chalk.dim("Start the API server first: ulpi ui\n"));
122
+ }
123
+ console.log(chalk.bold("\nULPI Portal \u2014 Local Mode\n"));
124
+ console.log(` Mode: ${useDevMode ? chalk.yellow("development") : chalk.green("production")}`);
125
+ console.log(` Port: ${chalk.cyan(String(port))}`);
126
+ console.log(` API: ${chalk.cyan(`http://localhost:${apiPort}`)}`);
127
+ console.log(` Portal: ${chalk.cyan(`apps/portal`)}`);
128
+ console.log("");
129
+ const env = {
130
+ ...process.env,
131
+ ULPI_MODE: "local",
132
+ NEXT_PUBLIC_ULPI_MODE: "local",
133
+ NEXT_PUBLIC_API_URL: `http://localhost:${apiPort}`,
134
+ NEXT_PUBLIC_ULPI_API_SECRET: apiSecret,
135
+ PORT: String(port)
136
+ };
137
+ const child = spawn(nextBin, [nextCommand, "--port", String(port)], {
138
+ cwd: portalDir,
139
+ env,
140
+ stdio: "inherit"
141
+ });
142
+ if (child.pid) {
143
+ writePidFile(child.pid);
144
+ }
145
+ const cleanup = () => {
146
+ removePidFile();
147
+ child.kill("SIGTERM");
148
+ };
149
+ process.on("SIGINT", cleanup);
150
+ process.on("SIGTERM", cleanup);
151
+ child.on("error", (err) => {
152
+ console.error(chalk.red(`Failed to start portal: ${err.message}`));
153
+ removePidFile();
154
+ process.exit(1);
155
+ });
156
+ child.on("close", (code) => {
157
+ removePidFile();
158
+ if (code !== 0 && code !== null) {
159
+ console.error(chalk.red(`Portal exited with code ${code}`));
160
+ process.exit(code);
161
+ }
162
+ });
163
+ console.log(chalk.green(`Portal starting at http://localhost:${port}`));
164
+ console.log(chalk.dim("Press Ctrl+C to stop.\n"));
165
+ }
166
+ function stopPortal() {
167
+ const pid = readPidFile();
168
+ if (!pid) {
169
+ console.log(chalk.yellow("No running portal instance found."));
170
+ return;
171
+ }
172
+ try {
173
+ process.kill(pid, "SIGTERM");
174
+ removePidFile();
175
+ console.log(chalk.green(`Portal stopped (PID ${pid}).`));
176
+ } catch (err) {
177
+ const message = err instanceof Error ? err.message : String(err);
178
+ console.log(chalk.red(`Failed to stop portal: ${message}`));
179
+ removePidFile();
180
+ }
181
+ }
182
+ function runPortalCommand(args, projectDir) {
183
+ const subcommand = args[0];
184
+ switch (subcommand) {
185
+ case "stop":
186
+ return stopPortal();
187
+ case "start":
188
+ return startPortal(args.slice(1), projectDir);
189
+ case void 0:
190
+ return startPortal(args, projectDir);
191
+ default:
192
+ if (subcommand?.startsWith("--")) {
193
+ return startPortal(args, projectDir);
194
+ }
195
+ console.log(`
196
+ Usage: ulpi portal [subcommand] [options]
197
+
198
+ Subcommands:
199
+ start Start Portal in local mode (default)
200
+ stop Stop running Portal instance
201
+
202
+ Options:
203
+ --port <number> Port to run on (default: 3000)
204
+ --dev Force development mode
205
+ `.trim());
206
+ }
207
+ }
208
+ export {
209
+ runPortalCommand
210
+ };
@@ -0,0 +1,408 @@
1
+ import "./chunk-4VNS5WPM.js";
2
+
3
+ // src/commands/prd.ts
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import chalk from "chalk";
7
+ async function runPrd(args, projectDir) {
8
+ const subcommand = args[0];
9
+ switch (subcommand) {
10
+ case "create":
11
+ return await createSubcommand(args.slice(1), projectDir);
12
+ case "convert":
13
+ return await convertSubcommand(args.slice(1), projectDir);
14
+ case "show":
15
+ return await showSubcommand(args.slice(1), projectDir);
16
+ default:
17
+ console.log(`
18
+ Usage: ulpi prd <subcommand> [options]
19
+
20
+ Subcommands:
21
+ create [file] [--chat] Create or parse a PRD
22
+ convert <file> [--output] Convert markdown PRD to JSON tasks
23
+ show <file> Show parsed PRD summary
24
+
25
+ Options:
26
+ --chat Interactive AI mode for PRD creation
27
+ --agent <name> AI agent to use (default: claude)
28
+ --model <id> Model override for the agent
29
+ --output, -o Output file path for conversion
30
+ --prefix Task ID prefix (default: "TASK-")
31
+ `.trim());
32
+ }
33
+ }
34
+ async function createSubcommand(args, projectDir) {
35
+ const isChat = args.includes("--chat");
36
+ const fileArg = args.find((a) => !a.startsWith("--"));
37
+ if (isChat) {
38
+ return await runChatMode(args, projectDir);
39
+ }
40
+ if (!fileArg) {
41
+ console.log(chalk.yellow("Provide a markdown file to parse, or use --chat for interactive mode."));
42
+ console.log(chalk.dim(" ulpi prd create <file.md>"));
43
+ console.log(chalk.dim(" ulpi prd create --chat"));
44
+ return;
45
+ }
46
+ const filePath = path.isAbsolute(fileArg) ? fileArg : path.resolve(projectDir, fileArg);
47
+ if (!fs.existsSync(filePath)) {
48
+ console.error(chalk.red(`File not found: ${filePath}`));
49
+ process.exit(1);
50
+ }
51
+ const markdown = fs.readFileSync(filePath, "utf-8");
52
+ const { parsePrdMarkdown } = await import("./dist-3EIQTZHT.js");
53
+ const result = parsePrdMarkdown(markdown);
54
+ console.log(chalk.bold(`
55
+ PRD: ${result.document.title}
56
+ `));
57
+ console.log(chalk.dim(`Description: ${result.document.description}
58
+ `));
59
+ if (result.document.tasks.length > 0) {
60
+ console.log(chalk.bold(`Tasks (${result.document.tasks.length}):`));
61
+ for (const task of result.document.tasks) {
62
+ const priorityColor = task.priority <= 1 ? "red" : task.priority <= 2 ? "yellow" : "green";
63
+ const priorityStr = chalk[priorityColor](`P${task.priority}`);
64
+ console.log(` ${chalk.cyan(task.id)} ${task.title} ${priorityStr}`);
65
+ if (task.acceptanceCriteria.length > 0) {
66
+ console.log(chalk.dim(` ${task.acceptanceCriteria.length} acceptance criteria`));
67
+ }
68
+ if (task.dependsOn.length > 0) {
69
+ console.log(chalk.dim(` depends on: ${task.dependsOn.join(", ")}`));
70
+ }
71
+ }
72
+ } else {
73
+ console.log(chalk.yellow("No tasks found in PRD."));
74
+ }
75
+ if (result.warnings.length > 0) {
76
+ console.log(chalk.yellow(`
77
+ Warnings (${result.warnings.length}):`));
78
+ for (const warning of result.warnings) {
79
+ console.log(chalk.yellow(` - ${warning}`));
80
+ }
81
+ }
82
+ console.log("");
83
+ }
84
+ async function convertSubcommand(args, projectDir) {
85
+ const fileArg = args.find((a) => !a.startsWith("--") && !a.startsWith("-"));
86
+ const outputIdx = args.findIndex((a) => a === "--output" || a === "-o");
87
+ const outputArg = outputIdx !== -1 ? args[outputIdx + 1] : void 0;
88
+ const prefixIdx = args.findIndex((a) => a === "--prefix");
89
+ const prefix = prefixIdx !== -1 ? args[prefixIdx + 1] : void 0;
90
+ if (!fileArg) {
91
+ console.error(chalk.red("Provide a markdown PRD file to convert."));
92
+ console.log(chalk.dim(" ulpi prd convert <file.md> [--output tasks.json]"));
93
+ process.exit(1);
94
+ }
95
+ const filePath = path.isAbsolute(fileArg) ? fileArg : path.resolve(projectDir, fileArg);
96
+ if (!fs.existsSync(filePath)) {
97
+ console.error(chalk.red(`File not found: ${filePath}`));
98
+ process.exit(1);
99
+ }
100
+ const markdown = fs.readFileSync(filePath, "utf-8");
101
+ const { parsePrdMarkdown, convertPrdToTasks, convertPrdToJson } = await import("./dist-3EIQTZHT.js");
102
+ const result = parsePrdMarkdown(markdown, { taskPrefix: prefix });
103
+ if (result.document.tasks.length === 0) {
104
+ console.error(chalk.red("No tasks found in PRD. Cannot convert."));
105
+ if (result.warnings.length > 0) {
106
+ for (const warning of result.warnings) {
107
+ console.error(chalk.yellow(` - ${warning}`));
108
+ }
109
+ }
110
+ process.exit(1);
111
+ }
112
+ const tasks = convertPrdToTasks(result.document, true);
113
+ const jsonOutput = convertPrdToJson(result.document);
114
+ const outputPath = outputArg ? path.isAbsolute(outputArg) ? outputArg : path.resolve(projectDir, outputArg) : path.resolve(projectDir, ".ulpi", "prds", `${path.basename(filePath, path.extname(filePath))}.json`);
115
+ const outputDir = path.dirname(outputPath);
116
+ if (!fs.existsSync(outputDir)) {
117
+ fs.mkdirSync(outputDir, { recursive: true });
118
+ }
119
+ fs.writeFileSync(outputPath, JSON.stringify(jsonOutput, null, 2) + "\n", "utf-8");
120
+ console.log(chalk.green(`Converted ${tasks.length} tasks to: ${outputPath}`));
121
+ for (const task of tasks) {
122
+ console.log(chalk.dim(` ${task.id}: ${task.title} (P${task.priority})`));
123
+ }
124
+ }
125
+ async function showSubcommand(args, projectDir) {
126
+ const fileArg = args.find((a) => !a.startsWith("--"));
127
+ if (!fileArg) {
128
+ console.error(chalk.red("Provide a markdown PRD file to show."));
129
+ console.log(chalk.dim(" ulpi prd show <file.md>"));
130
+ process.exit(1);
131
+ }
132
+ const filePath = path.isAbsolute(fileArg) ? fileArg : path.resolve(projectDir, fileArg);
133
+ if (!fs.existsSync(filePath)) {
134
+ console.error(chalk.red(`File not found: ${filePath}`));
135
+ process.exit(1);
136
+ }
137
+ const markdown = fs.readFileSync(filePath, "utf-8");
138
+ const { parsePrdMarkdown } = await import("./dist-3EIQTZHT.js");
139
+ const result = parsePrdMarkdown(markdown);
140
+ const doc = result.document;
141
+ console.log(chalk.bold(`
142
+ ${doc.title}`));
143
+ console.log(chalk.dim("=".repeat(doc.title.length)));
144
+ console.log("");
145
+ if (doc.metadata.branchName) {
146
+ console.log(`Branch: ${chalk.cyan(String(doc.metadata.branchName))}`);
147
+ }
148
+ if (doc.metadata.createdAt) {
149
+ console.log(`Created: ${chalk.dim(String(doc.metadata.createdAt))}`);
150
+ }
151
+ console.log("");
152
+ console.log(doc.description);
153
+ console.log("");
154
+ if (doc.sections.length > 0) {
155
+ console.log(chalk.bold("Sections:"));
156
+ for (const section of doc.sections) {
157
+ const indent = " ".repeat(Math.max(0, section.level - 1));
158
+ console.log(`${indent}${"#".repeat(section.level)} ${section.heading}`);
159
+ }
160
+ console.log("");
161
+ }
162
+ if (doc.tasks.length > 0) {
163
+ console.log(chalk.bold(`Tasks (${doc.tasks.length}):`));
164
+ console.log("");
165
+ for (const task of doc.tasks) {
166
+ const priorityColor = task.priority <= 1 ? "red" : task.priority <= 2 ? "yellow" : "green";
167
+ console.log(` ${chalk.bold.cyan(task.id)}: ${chalk.bold(task.title)} ${chalk[priorityColor](`P${task.priority}`)}`);
168
+ if (task.description !== task.title) {
169
+ console.log(chalk.dim(` ${task.description.slice(0, 120)}${task.description.length > 120 ? "..." : ""}`));
170
+ }
171
+ if (task.acceptanceCriteria.length > 0) {
172
+ console.log(` ${chalk.dim("Criteria:")} ${task.acceptanceCriteria.length} items`);
173
+ for (const ac of task.acceptanceCriteria.slice(0, 3)) {
174
+ console.log(chalk.dim(` - ${ac}`));
175
+ }
176
+ if (task.acceptanceCriteria.length > 3) {
177
+ console.log(chalk.dim(` ... and ${task.acceptanceCriteria.length - 3} more`));
178
+ }
179
+ }
180
+ if (task.dependsOn.length > 0) {
181
+ console.log(` ${chalk.dim("Depends on:")} ${task.dependsOn.join(", ")}`);
182
+ }
183
+ if (task.labels.length > 0) {
184
+ console.log(` ${chalk.dim("Labels:")} ${task.labels.map((l) => `[${l}]`).join(" ")}`);
185
+ }
186
+ console.log("");
187
+ }
188
+ }
189
+ if (result.warnings.length > 0) {
190
+ console.log(chalk.yellow(`Warnings (${result.warnings.length}):`));
191
+ for (const warning of result.warnings) {
192
+ console.log(chalk.yellow(` - ${warning}`));
193
+ }
194
+ console.log("");
195
+ }
196
+ }
197
+ async function runChatMode(args, projectDir) {
198
+ console.log(chalk.bold("\n PRD Generator -- AI-Powered\n"));
199
+ console.log(chalk.dim(" Describe a feature and the AI will help you build a structured PRD."));
200
+ console.log(chalk.dim(" Type 'quit' or press Ctrl+C to exit.\n"));
201
+ const agentIdx = args.findIndex((a) => a === "--agent");
202
+ const agentName = agentIdx !== -1 && args[agentIdx + 1] ? args[agentIdx + 1] : "claude";
203
+ const modelIdx = args.findIndex((a) => a === "--model");
204
+ const model = modelIdx !== -1 && args[modelIdx + 1] ? args[modelIdx + 1] : "";
205
+ const {
206
+ getPrdSystemPrompt,
207
+ buildConversationPrompt,
208
+ detectPrd,
209
+ parsePrdMarkdown,
210
+ slugify
211
+ } = await import("./dist-3EIQTZHT.js");
212
+ const { getAgentRegistry } = await import("./dist-7QOEYLFX.js");
213
+ const readline = await import("readline/promises");
214
+ const registry = getAgentRegistry();
215
+ await registry.initialize();
216
+ if (!registry.hasPlugin(agentName)) {
217
+ console.error(chalk.red(`Unknown agent: ${agentName}`));
218
+ console.log(chalk.dim(`Available agents: ${registry.getRegisteredPlugins().map((p) => p.name).join(", ")}`));
219
+ return;
220
+ }
221
+ const plugin = await registry.getInstance({
222
+ name: `prd-chat-${agentName}`,
223
+ plugin: agentName,
224
+ options: model ? { model } : {}
225
+ });
226
+ const detection = await plugin.detect();
227
+ if (!detection.available) {
228
+ console.error(chalk.red(`Agent ${agentName} is not available: ${detection.error ?? "not found in PATH"}`));
229
+ return;
230
+ }
231
+ console.log(chalk.dim(` Agent: ${agentName}${model ? ` (model: ${model})` : ""}${detection.executablePath ? ` (${detection.executablePath})` : ""}`));
232
+ console.log("");
233
+ const history = [];
234
+ const rl = readline.createInterface({
235
+ input: process.stdin,
236
+ output: process.stdout
237
+ });
238
+ rl.on("close", () => {
239
+ console.log(chalk.yellow("\n PRD creation cancelled."));
240
+ });
241
+ try {
242
+ while (true) {
243
+ let userInput;
244
+ try {
245
+ userInput = await rl.question(chalk.cyan(" You: "));
246
+ } catch {
247
+ break;
248
+ }
249
+ const trimmed = userInput.trim();
250
+ if (!trimmed) continue;
251
+ if (trimmed.toLowerCase() === "quit" || trimmed.toLowerCase() === "exit") {
252
+ console.log(chalk.dim("\n Goodbye!"));
253
+ break;
254
+ }
255
+ history.push({
256
+ role: "user",
257
+ content: trimmed,
258
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
259
+ });
260
+ const prompt = buildConversationPrompt(history, trimmed);
261
+ process.stdout.write(chalk.dim(" AI: thinking..."));
262
+ try {
263
+ let responseText = "";
264
+ const handle = plugin.execute(prompt, {
265
+ cwd: projectDir,
266
+ timeout: 12e4,
267
+ onStdout: (data) => {
268
+ responseText += data;
269
+ }
270
+ });
271
+ const result = await handle.promise;
272
+ process.stdout.write("\r" + " ".repeat(40) + "\r");
273
+ if (result.status !== "completed") {
274
+ const errorDetail = result.error ?? result.stderr?.slice(0, 200) ?? "unknown error";
275
+ console.log(chalk.red(` AI: Error - ${errorDetail}`));
276
+ history.pop();
277
+ continue;
278
+ }
279
+ const aiResponse = extractTextFromAgentOutput(responseText);
280
+ if (!aiResponse.trim()) {
281
+ console.log(chalk.red(" AI: (empty response)"));
282
+ history.pop();
283
+ continue;
284
+ }
285
+ history.push({
286
+ role: "assistant",
287
+ content: aiResponse,
288
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
289
+ });
290
+ const lines = aiResponse.split("\n");
291
+ console.log(chalk.white(` AI: ${lines[0]}`));
292
+ for (const line of lines.slice(1)) {
293
+ console.log(chalk.white(` ${line}`));
294
+ }
295
+ console.log("");
296
+ const prdDetection = detectPrd(aiResponse);
297
+ if (prdDetection.found && prdDetection.content) {
298
+ const parsed = parsePrdMarkdown(prdDetection.content);
299
+ const doc = parsed.document;
300
+ console.log(chalk.green.bold(" PRD detected!"));
301
+ console.log(chalk.green(` Title: ${doc.title}`));
302
+ console.log(chalk.green(` Tasks: ${doc.tasks.length}`));
303
+ if (doc.tasks.length > 0) {
304
+ for (const task of doc.tasks) {
305
+ const priorityColor = task.priority <= 1 ? "red" : task.priority <= 2 ? "yellow" : "green";
306
+ console.log(` ${chalk.cyan(task.id)} ${task.title} ${chalk[priorityColor](`P${task.priority}`)}`);
307
+ }
308
+ }
309
+ console.log("");
310
+ let saveAnswer;
311
+ try {
312
+ saveAnswer = await rl.question(chalk.cyan(" Save this PRD? [Y/n] "));
313
+ } catch {
314
+ break;
315
+ }
316
+ if (saveAnswer.trim().toLowerCase() !== "n") {
317
+ const slug = slugify(doc.title || "untitled");
318
+ const filename = `prd-${slug}.md`;
319
+ const prdsDir = path.join(projectDir, ".ulpi", "prds");
320
+ if (!fs.existsSync(prdsDir)) {
321
+ fs.mkdirSync(prdsDir, { recursive: true });
322
+ }
323
+ const outputPath = path.join(prdsDir, filename);
324
+ fs.writeFileSync(outputPath, prdDetection.content, "utf-8");
325
+ console.log(chalk.green(` Saved to: ${outputPath}`));
326
+ console.log("");
327
+ let runAnswer;
328
+ try {
329
+ runAnswer = await rl.question(chalk.cyan(" Run this PRD? [y/N] "));
330
+ } catch {
331
+ break;
332
+ }
333
+ if (runAnswer.trim().toLowerCase() === "y") {
334
+ const modelArgs = model ? ` --model ${model}` : "";
335
+ console.log(chalk.dim(`
336
+ Starting: ulpi run --prd ${outputPath} --agent ${agentName}${modelArgs}
337
+ `));
338
+ rl.close();
339
+ try {
340
+ const { runRunCommand } = await import("./run-VPNXEIBY.js");
341
+ const runArgs = ["--prd", outputPath, "--agent", agentName];
342
+ if (model) runArgs.push("--model", model);
343
+ await runRunCommand(runArgs, projectDir);
344
+ } catch (runErr) {
345
+ const msg = runErr instanceof Error ? runErr.message : String(runErr);
346
+ console.error(chalk.red(` Failed to start run: ${msg}`));
347
+ }
348
+ return;
349
+ }
350
+ }
351
+ break;
352
+ }
353
+ } catch (err) {
354
+ process.stdout.write("\r" + " ".repeat(40) + "\r");
355
+ const message = err instanceof Error ? err.message : String(err);
356
+ console.log(chalk.red(` Error: ${message}`));
357
+ history.pop();
358
+ }
359
+ }
360
+ } finally {
361
+ try {
362
+ rl.close();
363
+ } catch {
364
+ }
365
+ try {
366
+ await registry.disposeInstance(`prd-chat-${agentName}`);
367
+ } catch {
368
+ }
369
+ }
370
+ }
371
+ function extractTextFromAgentOutput(raw) {
372
+ const lines = raw.split("\n").filter((l) => l.trim());
373
+ const textParts = [];
374
+ let isStreamJson = false;
375
+ for (const line of lines) {
376
+ try {
377
+ const parsed = JSON.parse(line);
378
+ isStreamJson = true;
379
+ if (parsed.type === "assistant" && parsed.message) {
380
+ const msg = parsed.message;
381
+ if (Array.isArray(msg.content)) {
382
+ for (const block of msg.content) {
383
+ if (block.type === "text") {
384
+ textParts.push(block.text);
385
+ }
386
+ }
387
+ }
388
+ }
389
+ if (parsed.type === "content_block_delta") {
390
+ const delta = parsed.delta;
391
+ if (delta && delta.type === "text_delta" && typeof delta.text === "string") {
392
+ textParts.push(delta.text);
393
+ }
394
+ }
395
+ if (parsed.type === "result" && typeof parsed.result === "string") {
396
+ textParts.push(parsed.result);
397
+ }
398
+ } catch {
399
+ if (!isStreamJson) {
400
+ textParts.push(line);
401
+ }
402
+ }
403
+ }
404
+ return textParts.join("").trim() || raw.trim();
405
+ }
406
+ export {
407
+ runPrd
408
+ };