@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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildPrompt
3
- } from "./chunk-AV5RB3N2.js";
3
+ } from "./chunk-76D3BYJD.js";
4
4
  import {
5
5
  external_exports
6
6
  } from "./chunk-KIKPIH6N.js";
@@ -38,12 +38,13 @@ var JobConfigSchema = external_exports.object({
38
38
  repoUrl: external_exports.string().url(),
39
39
  ref: external_exports.string(),
40
40
  baseBranch: external_exports.string(),
41
- prNumber: external_exports.number().int().positive(),
41
+ prNumber: external_exports.number().int().positive().optional(),
42
42
  repoFullName: external_exports.string(),
43
43
  instruction: external_exports.string(),
44
44
  jiraContext: external_exports.string().optional(),
45
45
  timeoutMinutes: external_exports.number().int().positive().default(1440),
46
- envVars: external_exports.record(external_exports.string()).optional()
46
+ envVars: external_exports.record(external_exports.string()).optional(),
47
+ autoCommitIgnore: external_exports.array(external_exports.string()).default([".pnpm-store/", "node_modules/", ".claude/"])
47
48
  });
48
49
  var JobResultSchema = external_exports.object({
49
50
  jobId: external_exports.string(),
@@ -69,8 +70,8 @@ var JobSchema = external_exports.object({
69
70
  startedAt: external_exports.string().datetime().optional(),
70
71
  completedAt: external_exports.string().datetime().optional(),
71
72
  cancelledBy: external_exports.string().optional(),
72
- commentId: external_exports.number().int().positive(),
73
- commentAuthor: external_exports.string()
73
+ commentId: external_exports.number().int().positive().optional(),
74
+ commentAuthor: external_exports.string().optional()
74
75
  });
75
76
  var GitHubAppAuthSchema = external_exports.object({
76
77
  appId: external_exports.string(),
@@ -157,6 +158,26 @@ var JiraReferenceSchema = external_exports.object({
157
158
  key: external_exports.string(),
158
159
  url: external_exports.string().url()
159
160
  });
161
+ var JiraSprintSchema = external_exports.object({
162
+ id: external_exports.number().int(),
163
+ name: external_exports.string(),
164
+ state: external_exports.string(),
165
+ // "active", "closed", "future"
166
+ startDate: external_exports.string().optional(),
167
+ endDate: external_exports.string().optional()
168
+ });
169
+ var SprintImportConfigSchema = external_exports.object({
170
+ jiraBaseUrl: external_exports.string().url(),
171
+ email: external_exports.string().email(),
172
+ apiToken: external_exports.string(),
173
+ sprintId: external_exports.number().int().positive()
174
+ });
175
+ var JiraSprintImportSchema = external_exports.object({
176
+ sprintId: external_exports.number().int(),
177
+ tasks: external_exports.array(external_exports.unknown()),
178
+ // TrackerTask[] — imported at runtime
179
+ warnings: external_exports.array(external_exports.string())
180
+ });
160
181
 
161
182
  // src/commands/ci.ts
162
183
  async function runCi(args, projectDir) {
@@ -185,7 +206,7 @@ async function runCiJob(args) {
185
206
  let jobId = "";
186
207
  let configJson = "";
187
208
  let outputPath = "";
188
- let model = "claude-sonnet-4-20250514";
209
+ let model = process.env.ULPI_CLAUDE_MODEL ?? "claude-opus-4-6";
189
210
  for (let i = 0; i < args.length; i++) {
190
211
  switch (args[i]) {
191
212
  case "--job-id":
@@ -250,6 +271,7 @@ async function runCiJob(args) {
250
271
  process.exit(1);
251
272
  }
252
273
  console.log(chalk.blue(`[ulpi-ci] Using claude at: ${claudePath}`));
274
+ logClaudeMdFiles(projectDir);
253
275
  const claudeArgs = [
254
276
  "--print",
255
277
  "--verbose",
@@ -261,8 +283,9 @@ async function runCiJob(args) {
261
283
  ];
262
284
  console.log(chalk.blue("[ulpi-ci] Spawning Claude Code..."));
263
285
  const result = await spawnClaude(claudePath, claudeArgs, prompt, config.timeoutMinutes * 60 * 1e3);
264
- commitUnstagedWork(process.cwd());
286
+ commitUnstagedWork(process.cwd(), config.autoCommitIgnore);
265
287
  await postSessionFinalize(process.cwd());
288
+ await exportShadowBranches(process.cwd());
266
289
  const baseBranch = config.baseBranch;
267
290
  let diff = "";
268
291
  let filesChanged = [];
@@ -327,7 +350,7 @@ async function initUlpiSubsystems(projectDir) {
327
350
  if (fs.existsSync(guardsPath)) {
328
351
  console.log(chalk.blue("[ulpi-ci] guards.yml found \u2014 rules will be enforced"));
329
352
  } else {
330
- const { loadBundledTemplates, composeTemplates, resolveTemplate } = await import("./dist-XG2GG5SD.js");
353
+ const { loadBundledTemplates, composeTemplates, resolveTemplate } = await import("./dist-HU5RZAON.js");
331
354
  const stackConfig = {
332
355
  name: path.basename(projectDir),
333
356
  runtime: stack.runtime?.id ?? "unknown",
@@ -369,14 +392,14 @@ async function initUlpiSubsystems(projectDir) {
369
392
  console.log(chalk.yellow(`[ulpi-ci] Warning: Could not detect stack/generate guards.yml: ${err instanceof Error ? err.message : String(err)}`));
370
393
  }
371
394
  try {
372
- const { installHooks } = await import("./hooks-installer-K2JXEBNN.js");
395
+ const { installHooks } = await import("./hooks-installer-YEYTYA6Q.js");
373
396
  installHooks(projectDir);
374
397
  console.log(chalk.blue("[ulpi-ci] Hooks installed"));
375
398
  } catch (err) {
376
399
  console.log(chalk.yellow(`[ulpi-ci] Warning: Could not install hooks: ${err instanceof Error ? err.message : String(err)}`));
377
400
  }
378
401
  try {
379
- const { installMcpServer, installMemoryMcpServer } = await import("./mcp-installer-TOYDP77X.js");
402
+ const { installMcpServer, installMemoryMcpServer } = await import("./mcp-installer-PQU3XOGO.js");
380
403
  installMcpServer(projectDir);
381
404
  installMemoryMcpServer(projectDir);
382
405
  console.log(chalk.blue("[ulpi-ci] MCP servers registered"));
@@ -389,10 +412,10 @@ async function initUlpiSubsystems(projectDir) {
389
412
  } catch {
390
413
  }
391
414
  try {
392
- const { historyBranchExists, initHistoryBranch } = await import("./dist-5R4RYNQO.js");
415
+ const { historyBranchExists, initHistoryBranch } = await import("./dist-YCNWHSLN.js");
393
416
  if (!historyBranchExists(projectDir)) {
394
417
  const projectName = path.basename(projectDir);
395
- const version = true ? "0.1.5" : "0.0.0";
418
+ const version = true ? "0.1.7" : "0.0.0";
396
419
  initHistoryBranch(projectDir, projectName, version);
397
420
  console.log(chalk.blue("[ulpi-ci] History branch initialized"));
398
421
  } else {
@@ -402,8 +425,8 @@ async function initUlpiSubsystems(projectDir) {
402
425
  console.log(chalk.yellow(`[ulpi-ci] Warning: Could not init history: ${err instanceof Error ? err.message : String(err)}`));
403
426
  }
404
427
  try {
405
- const { isMemoryInitialized, saveMemoryConfig, DEFAULT_MEMORY_CONFIG, initMemoryBranch } = await import("./dist-7WLLPWWB.js");
406
- const { projectMemoryDir } = await import("./dist-GWGTAHNM.js");
428
+ const { isMemoryInitialized, saveMemoryConfig, DEFAULT_MEMORY_CONFIG, initMemoryBranch } = await import("./dist-JLU26AB6.js");
429
+ const { projectMemoryDir } = await import("./dist-NUXMDXZ3.js");
407
430
  if (!isMemoryInitialized(projectDir)) {
408
431
  const memDir = projectMemoryDir(projectDir);
409
432
  fs.mkdirSync(memDir, { recursive: true });
@@ -418,11 +441,11 @@ async function initUlpiSubsystems(projectDir) {
418
441
  console.log(chalk.yellow(`[ulpi-ci] Warning: Could not init memory: ${err instanceof Error ? err.message : String(err)}`));
419
442
  }
420
443
  try {
421
- const { resolveApiKey } = await import("./dist-GWGTAHNM.js");
444
+ const { resolveApiKey } = await import("./dist-NUXMDXZ3.js");
422
445
  const hasOpenAiKey = !!resolveApiKey("openai");
423
446
  const hasUlpiKey = !!resolveApiKey("ulpi");
424
447
  if (hasOpenAiKey || hasUlpiKey) {
425
- const { runInitPipeline } = await import("./dist-6MFVWIFF.js");
448
+ const { runInitPipeline } = await import("./dist-YFFG2ZD6.js");
426
449
  console.log(chalk.blue("[ulpi-ci] Indexing codebase..."));
427
450
  await runInitPipeline(projectDir, (progress) => {
428
451
  if (progress.message) {
@@ -490,8 +513,22 @@ function writeYamlFields(obj, lines, indent) {
490
513
  }
491
514
  }
492
515
  }
493
- function commitUnstagedWork(cwd) {
516
+ function commitUnstagedWork(cwd, ignorePatterns = []) {
494
517
  try {
518
+ if (ignorePatterns.length > 0) {
519
+ const gitignorePath = path.join(cwd, ".gitignore");
520
+ try {
521
+ const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf-8") : "";
522
+ const missing = ignorePatterns.filter((p) => !existing.includes(p));
523
+ if (missing.length > 0) {
524
+ const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
525
+ fs.appendFileSync(gitignorePath, `${separator}# Added by ULPI CI worker
526
+ ${missing.join("\n")}
527
+ `);
528
+ }
529
+ } catch {
530
+ }
531
+ }
495
532
  const status = execFileSync("git", ["status", "--porcelain"], {
496
533
  cwd,
497
534
  encoding: "utf-8",
@@ -500,10 +537,11 @@ function commitUnstagedWork(cwd) {
500
537
  if (!status) return;
501
538
  const fileCount = status.split("\n").filter(Boolean).length;
502
539
  console.log(chalk.blue(`[ulpi-ci] Committing ${fileCount} uncommitted files...`));
503
- execFileSync("git", ["add", "-A"], { cwd, timeout: 3e4 });
504
- execFileSync("git", ["commit", "-m", "wip: auto-commit from ULPI CI worker"], {
540
+ execFileSync("git", ["add", "-A"], { cwd, timeout: 6e5, maxBuffer: 50 * 1024 * 1024 });
541
+ execFileSync("git", ["commit", "--no-verify", "-m", "wip: auto-commit from ULPI CI worker"], {
505
542
  cwd,
506
- timeout: 3e4
543
+ timeout: 6e5,
544
+ maxBuffer: 50 * 1024 * 1024
507
545
  });
508
546
  console.log(chalk.blue("[ulpi-ci] WIP commit created"));
509
547
  } catch (err) {
@@ -522,8 +560,8 @@ async function postSessionFinalize(projectDir) {
522
560
  writeHistoryEntry,
523
561
  readBranchMeta,
524
562
  DEFAULT_HISTORY_CONFIG
525
- } = await import("./dist-5R4RYNQO.js");
526
- const { loadActiveGuards } = await import("./dist-5R4RYNQO.js");
563
+ } = await import("./dist-YCNWHSLN.js");
564
+ const { loadActiveGuards } = await import("./dist-YCNWHSLN.js");
527
565
  if (historyBranchExists(projectDir)) {
528
566
  const commits = listBranchOnlyCommits(projectDir, 50);
529
567
  const meta = readBranchMeta(projectDir);
@@ -563,9 +601,9 @@ async function postSessionFinalize(projectDir) {
563
601
  console.log(chalk.yellow(`[ulpi-ci] Warning: History backfill failed: ${err instanceof Error ? err.message : String(err)}`));
564
602
  }
565
603
  try {
566
- const { isMemoryInitialized, classifySession } = await import("./dist-7WLLPWWB.js");
567
- const { ULPI_GLOBAL_DIR } = await import("./dist-GWGTAHNM.js");
568
- const { JsonSessionStore } = await import("./dist-XD4YI27T.js");
604
+ const { isMemoryInitialized, classifySession } = await import("./dist-JLU26AB6.js");
605
+ const { ULPI_GLOBAL_DIR } = await import("./dist-NUXMDXZ3.js");
606
+ const { JsonSessionStore } = await import("./dist-6M4MZWZW.js");
569
607
  if (isMemoryInitialized(projectDir)) {
570
608
  const store = new JsonSessionStore(ULPI_GLOBAL_DIR, projectDir);
571
609
  const latest = store.getLatestForProject(projectDir);
@@ -586,6 +624,66 @@ async function postSessionFinalize(projectDir) {
586
624
  console.log(chalk.yellow(`[ulpi-ci] Warning: Memory classify failed: ${err instanceof Error ? err.message : String(err)}`));
587
625
  }
588
626
  }
627
+ async function exportShadowBranches(projectDir) {
628
+ try {
629
+ const { getCodemapStatus, exportIndex } = await import("./dist-YFFG2ZD6.js");
630
+ const { getCurrentBranch } = await import("./dist-NUXMDXZ3.js");
631
+ const branch = getCurrentBranch(projectDir);
632
+ const status = getCodemapStatus(projectDir, branch);
633
+ if (status.initialized) {
634
+ const result = await exportIndex(projectDir, branch);
635
+ console.log(chalk.blue(`[ulpi-ci] Codemap exported to ${result.branchName} (${result.filesExported} files)`));
636
+ }
637
+ } catch (err) {
638
+ console.log(chalk.yellow(`[ulpi-ci] Warning: Codemap export failed: ${err instanceof Error ? err.message : String(err)}`));
639
+ }
640
+ try {
641
+ const { isMemoryInitialized, exportMemories } = await import("./dist-JLU26AB6.js");
642
+ if (isMemoryInitialized(projectDir)) {
643
+ const result = await exportMemories(projectDir);
644
+ console.log(chalk.blue(`[ulpi-ci] Memory exported to ${result.branchName} (${result.memoriesExported} memories, ${result.filesExported} files)`));
645
+ }
646
+ } catch (err) {
647
+ console.log(chalk.yellow(`[ulpi-ci] Warning: Memory export failed: ${err instanceof Error ? err.message : String(err)}`));
648
+ }
649
+ }
650
+ function logClaudeMdFiles(projectDir) {
651
+ const rootMd = path.join(projectDir, "CLAUDE.md");
652
+ if (fs.existsSync(rootMd)) {
653
+ const stat = fs.statSync(rootMd);
654
+ console.log(chalk.blue(`[ulpi-ci] Found CLAUDE.md (${stat.size} bytes)`));
655
+ } else {
656
+ console.log(chalk.yellow("[ulpi-ci] No CLAUDE.md in workspace root"));
657
+ }
658
+ const claudeDir = path.join(projectDir, ".claude");
659
+ if (fs.existsSync(claudeDir)) {
660
+ const items = [];
661
+ try {
662
+ for (const entry of fs.readdirSync(claudeDir, { recursive: true })) {
663
+ if (entry.endsWith(".md") || entry === "settings.local.json") {
664
+ items.push(entry);
665
+ }
666
+ }
667
+ } catch {
668
+ }
669
+ console.log(chalk.blue(`[ulpi-ci] .claude/ contents: ${items.length > 0 ? items.join(", ") : "(empty)"}`));
670
+ } else {
671
+ console.log(chalk.yellow("[ulpi-ci] No .claude/ directory in workspace"));
672
+ }
673
+ try {
674
+ const nested = [];
675
+ for (const entry of fs.readdirSync(projectDir, { withFileTypes: true })) {
676
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
677
+ const nestedMd = path.join(projectDir, entry.name, "CLAUDE.md");
678
+ if (fs.existsSync(nestedMd)) nested.push(entry.name);
679
+ }
680
+ }
681
+ if (nested.length > 0) {
682
+ console.log(chalk.blue(`[ulpi-ci] Nested CLAUDE.md: ${nested.join(", ")}`));
683
+ }
684
+ } catch {
685
+ }
686
+ }
589
687
  function findClaudeBinary() {
590
688
  try {
591
689
  const result = execFileSync("which", ["claude"], { encoding: "utf-8", timeout: 5e3 });
@@ -662,7 +760,7 @@ function spawnClaude(claudePath, args, prompt, timeoutMs) {
662
760
  return new Promise((resolve) => {
663
761
  const proc = spawn(claudePath, args, {
664
762
  cwd: process.cwd(),
665
- env: process.env,
763
+ env: { ...process.env, ULPI_RUN_MODE: "ci" },
666
764
  stdio: ["pipe", "pipe", "pipe"]
667
765
  });
668
766
  let stdout = "";
@@ -0,0 +1,274 @@
1
+ import {
2
+ loadCloudGatewayConfig,
3
+ loadUlpiSettings,
4
+ removeCloudGatewayConfig,
5
+ saveCloudGatewayConfig
6
+ } from "./chunk-C7CLUQI6.js";
7
+ import "./chunk-4VNS5WPM.js";
8
+
9
+ // src/commands/cloud.ts
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+ import * as os from "os";
13
+ import * as https from "https";
14
+ import * as http from "http";
15
+ import chalk from "chalk";
16
+ function getFlag(args, flag) {
17
+ const prefix = `--${flag}=`;
18
+ const withEq = args.find((a) => a.startsWith(prefix));
19
+ if (withEq) return withEq.slice(prefix.length);
20
+ const idx = args.indexOf(`--${flag}`);
21
+ if (idx !== -1 && args[idx + 1] && !args[idx + 1].startsWith("--")) return args[idx + 1];
22
+ return void 0;
23
+ }
24
+ function fetchJson(url, apiKey) {
25
+ return new Promise((resolve, reject) => {
26
+ const parsedUrl = new URL(url);
27
+ const mod = parsedUrl.protocol === "https:" ? https : http;
28
+ const req = mod.request(
29
+ {
30
+ hostname: parsedUrl.hostname,
31
+ port: parsedUrl.port || (parsedUrl.protocol === "https:" ? 443 : 80),
32
+ path: parsedUrl.pathname + parsedUrl.search,
33
+ method: "GET",
34
+ headers: {
35
+ "Authorization": `Bearer ${apiKey}`,
36
+ "Accept": "application/json"
37
+ },
38
+ timeout: 1e4
39
+ },
40
+ (res) => {
41
+ let data = "";
42
+ res.on("data", (chunk) => {
43
+ data += chunk;
44
+ });
45
+ res.on("end", () => {
46
+ try {
47
+ if ((res.statusCode ?? 0) >= 400) {
48
+ reject(new Error(`HTTP ${res.statusCode}: ${data}`));
49
+ } else {
50
+ resolve(JSON.parse(data));
51
+ }
52
+ } catch {
53
+ reject(new Error(`Invalid JSON response: ${data}`));
54
+ }
55
+ });
56
+ }
57
+ );
58
+ req.on("error", reject);
59
+ req.on("timeout", () => {
60
+ req.destroy();
61
+ reject(new Error("Request timed out"));
62
+ });
63
+ req.end();
64
+ });
65
+ }
66
+ function mcpJsonPath(projectDir) {
67
+ return path.join(projectDir, ".mcp.json");
68
+ }
69
+ function readMcpJson(projectDir) {
70
+ const filePath = mcpJsonPath(projectDir);
71
+ if (fs.existsSync(filePath)) {
72
+ try {
73
+ const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
74
+ return { mcpServers: data.mcpServers ?? {} };
75
+ } catch {
76
+ }
77
+ }
78
+ return { mcpServers: {} };
79
+ }
80
+ function writeMcpJson(projectDir, mcpServers) {
81
+ const filePath = mcpJsonPath(projectDir);
82
+ fs.writeFileSync(filePath, JSON.stringify({ mcpServers }, null, 2) + "\n", "utf-8");
83
+ }
84
+ var CLOUD_MCP_KEY = "ulpi-cloud";
85
+ function readJsonConfig(filePath) {
86
+ if (fs.existsSync(filePath)) {
87
+ try {
88
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
89
+ } catch {
90
+ }
91
+ }
92
+ return {};
93
+ }
94
+ function writeJsonConfig(filePath, config) {
95
+ const dir = path.dirname(filePath);
96
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
97
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
98
+ }
99
+ function getAgentConfigTargets() {
100
+ const home = os.homedir();
101
+ const targets = [];
102
+ const claudePath = path.join(home, ".claude", "settings.json");
103
+ targets.push({ name: "Claude Code", configPath: claudePath });
104
+ return targets;
105
+ }
106
+ function writeCloudMcpToAgentConfig(configPath, entry) {
107
+ const config = readJsonConfig(configPath);
108
+ const mcpServers = config.mcpServers ?? {};
109
+ if (mcpServers[CLOUD_MCP_KEY]) return false;
110
+ mcpServers[CLOUD_MCP_KEY] = entry;
111
+ config.mcpServers = mcpServers;
112
+ writeJsonConfig(configPath, config);
113
+ return true;
114
+ }
115
+ function removeCloudMcpFromAgentConfig(configPath) {
116
+ if (!fs.existsSync(configPath)) return false;
117
+ const config = readJsonConfig(configPath);
118
+ const mcpServers = config.mcpServers ?? {};
119
+ if (!mcpServers[CLOUD_MCP_KEY]) return false;
120
+ delete mcpServers[CLOUD_MCP_KEY];
121
+ config.mcpServers = mcpServers;
122
+ writeJsonConfig(configPath, config);
123
+ return true;
124
+ }
125
+ async function runConnect(args, projectDir) {
126
+ const settings = loadUlpiSettings();
127
+ const cloudApiUrl = getFlag(args, "url") ?? settings.ulpiUrl ?? process.env.ULPI_CLOUD_URL ?? "https://api.ulpi.io";
128
+ const apiKey = getFlag(args, "key") ?? settings.apiKeys?.ulpi ?? process.env.ULPI_API_KEY;
129
+ if (!apiKey) {
130
+ console.error(chalk.red("Error: API key required."));
131
+ console.error(chalk.dim("Provide via --key=<apiKey>, settings.json apiKeys.ulpi, or ULPI_API_KEY env var."));
132
+ console.error(chalk.dim("Run: ulpi config set apiKeys.ulpi <your-key>"));
133
+ process.exit(1);
134
+ }
135
+ const orgId = getFlag(args, "org");
136
+ if (!orgId) {
137
+ console.error(chalk.red("Error: Organization ID required."));
138
+ console.error(chalk.dim("Provide via --org=<orgId>"));
139
+ process.exit(1);
140
+ }
141
+ const connectionUrl = `${cloudApiUrl}/api/orgs/${encodeURIComponent(orgId)}/gateway/connection`;
142
+ console.log(chalk.dim(`Fetching gateway info from ${connectionUrl}...`));
143
+ let gatewayInfo;
144
+ try {
145
+ const response = await fetchJson(connectionUrl, apiKey);
146
+ const conn = response.connection;
147
+ if (!conn || typeof conn.url !== "string" || typeof conn.transport !== "string") {
148
+ console.error(chalk.red("Error: Invalid gateway response from cloud API."));
149
+ console.error(chalk.dim(`Response: ${JSON.stringify(response)}`));
150
+ process.exit(1);
151
+ }
152
+ gatewayInfo = { url: conn.url, transport: conn.transport };
153
+ } catch (err) {
154
+ const message = err instanceof Error ? err.message : String(err);
155
+ console.error(chalk.red(`Error: Failed to fetch gateway connection info.`));
156
+ console.error(chalk.dim(message));
157
+ process.exit(1);
158
+ }
159
+ const { mcpServers } = readMcpJson(projectDir);
160
+ mcpServers[CLOUD_MCP_KEY] = {
161
+ url: gatewayInfo.url,
162
+ headers: {
163
+ Authorization: `Bearer ${apiKey}`
164
+ }
165
+ };
166
+ writeMcpJson(projectDir, mcpServers);
167
+ console.log(chalk.green(" Written to .mcp.json"));
168
+ const cloudEntry = {
169
+ url: gatewayInfo.url,
170
+ headers: { Authorization: `Bearer ${apiKey}` }
171
+ };
172
+ const agentTargets = getAgentConfigTargets();
173
+ for (const target of agentTargets) {
174
+ const written = writeCloudMcpToAgentConfig(target.configPath, cloudEntry);
175
+ if (written) {
176
+ console.log(chalk.green(` Written to ${target.name} (${chalk.dim(target.configPath)})`));
177
+ } else {
178
+ console.log(chalk.dim(` ${target.name}: already configured`));
179
+ }
180
+ }
181
+ saveCloudGatewayConfig({
182
+ orgId,
183
+ url: gatewayInfo.url,
184
+ transport: gatewayInfo.transport,
185
+ cloudApiUrl,
186
+ connectedAt: (/* @__PURE__ */ new Date()).toISOString()
187
+ });
188
+ console.log("");
189
+ console.log(chalk.green("Connected to Cloud MCP Gateway."));
190
+ console.log(` Org: ${chalk.cyan(orgId)}`);
191
+ console.log(` Gateway: ${chalk.cyan(gatewayInfo.url)}`);
192
+ console.log(` Transport: ${chalk.cyan(gatewayInfo.transport)}`);
193
+ console.log("");
194
+ console.log(chalk.dim("Restart your agent session for the new MCP server to take effect."));
195
+ }
196
+ function runStatus() {
197
+ const config = loadCloudGatewayConfig();
198
+ if (!config) {
199
+ console.log(chalk.yellow("Not connected to any Cloud MCP Gateway."));
200
+ console.log(chalk.dim("Run: ulpi cloud connect --org=<orgId> --key=<apiKey>"));
201
+ return;
202
+ }
203
+ console.log(chalk.bold("Cloud MCP Gateway Status\n"));
204
+ console.log(` Org: ${chalk.cyan(config.orgId)}`);
205
+ console.log(` Gateway URL: ${chalk.cyan(config.url)}`);
206
+ console.log(` Transport: ${chalk.cyan(config.transport)}`);
207
+ console.log(` Cloud API: ${chalk.dim(config.cloudApiUrl)}`);
208
+ console.log(` Connected: ${chalk.dim(config.connectedAt)}`);
209
+ }
210
+ function runDisconnect(projectDir) {
211
+ let removed = false;
212
+ const { mcpServers } = readMcpJson(projectDir);
213
+ if (mcpServers[CLOUD_MCP_KEY]) {
214
+ delete mcpServers[CLOUD_MCP_KEY];
215
+ writeMcpJson(projectDir, mcpServers);
216
+ console.log(chalk.green(" Removed from .mcp.json"));
217
+ removed = true;
218
+ }
219
+ const agentTargets = getAgentConfigTargets();
220
+ for (const target of agentTargets) {
221
+ const wasRemoved = removeCloudMcpFromAgentConfig(target.configPath);
222
+ if (wasRemoved) {
223
+ console.log(chalk.green(` Removed from ${target.name} (${chalk.dim(target.configPath)})`));
224
+ removed = true;
225
+ }
226
+ }
227
+ const configRemoved = removeCloudGatewayConfig();
228
+ if (configRemoved) {
229
+ removed = true;
230
+ }
231
+ if (removed) {
232
+ console.log("");
233
+ console.log(chalk.green("Disconnected from Cloud MCP Gateway."));
234
+ console.log(chalk.dim("Restart your agent session for changes to take effect."));
235
+ } else {
236
+ console.log(chalk.yellow("No cloud gateway connection found."));
237
+ }
238
+ }
239
+ function printUsage() {
240
+ console.log(`
241
+ Usage: ulpi cloud <subcommand> [options]
242
+
243
+ Subcommands:
244
+ connect Connect local agent to cloud MCP gateway
245
+ status Show current cloud connection status
246
+ disconnect Remove cloud gateway from agent configs
247
+
248
+ Connect Options:
249
+ --url=<url> Cloud API URL (default: https://api.ulpi.io)
250
+ --org=<orgId> Organization ID (required)
251
+ --key=<key> API key (or set via ULPI_API_KEY env / ulpi config)
252
+
253
+ Examples:
254
+ ulpi cloud connect --org=org-abc123 --key=sk-...
255
+ ulpi cloud status
256
+ ulpi cloud disconnect
257
+ `.trim());
258
+ }
259
+ async function runCloud(args, projectDir) {
260
+ const sub = args[0];
261
+ switch (sub) {
262
+ case "connect":
263
+ return runConnect(args.slice(1), projectDir);
264
+ case "status":
265
+ return runStatus();
266
+ case "disconnect":
267
+ return runDisconnect(projectDir);
268
+ default:
269
+ printUsage();
270
+ }
271
+ }
272
+ export {
273
+ runCloud
274
+ };