@caliber-ai/cli 0.2.1 → 0.3.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 (3) hide show
  1. package/dist/bin.js +1280 -305
  2. package/dist/bin.js.map +1 -1
  3. package/package.json +2 -1
package/dist/bin.js CHANGED
@@ -32,8 +32,8 @@ __export(constants_exports, {
32
32
  FRONTEND_URL: () => FRONTEND_URL,
33
33
  MANIFEST_FILE: () => MANIFEST_FILE
34
34
  });
35
- import path from "path";
36
- import os from "os";
35
+ import path2 from "path";
36
+ import os2 from "os";
37
37
  var API_URL, FRONTEND_URL, CLI_CALLBACK_PORT, AUTH_DIR, AUTH_FILE, CALIBER_DIR, MANIFEST_FILE, BACKUPS_DIR;
38
38
  var init_constants = __esm({
39
39
  "src/constants.ts"() {
@@ -41,39 +41,65 @@ var init_constants = __esm({
41
41
  API_URL = process.env.CALIBER_API_URL || "https://caliber-backend.up.railway.app";
42
42
  FRONTEND_URL = process.env.CALIBER_FRONTEND_URL || "https://caliber-app.up.railway.app";
43
43
  CLI_CALLBACK_PORT = 19284;
44
- AUTH_DIR = path.join(os.homedir(), ".caliber");
45
- AUTH_FILE = path.join(AUTH_DIR, "auth.json");
44
+ AUTH_DIR = path2.join(os2.homedir(), ".caliber");
45
+ AUTH_FILE = path2.join(AUTH_DIR, "auth.json");
46
46
  CALIBER_DIR = ".caliber";
47
- MANIFEST_FILE = path.join(CALIBER_DIR, "manifest.json");
48
- BACKUPS_DIR = path.join(CALIBER_DIR, "backups");
47
+ MANIFEST_FILE = path2.join(CALIBER_DIR, "manifest.json");
48
+ BACKUPS_DIR = path2.join(CALIBER_DIR, "backups");
49
49
  }
50
50
  });
51
51
 
52
+ // src/lib/sentry.ts
53
+ import * as Sentry from "@sentry/node";
54
+ import os from "os";
55
+ import fs from "fs";
56
+ import path from "path";
57
+ import { fileURLToPath } from "url";
58
+ var dsn = process.env.CALIBER_SENTRY_DSN || "https://e988132485fced78d7d60202f97942b3@o4510975175622656.ingest.us.sentry.io/4510975226347520";
59
+ var __dirname = path.dirname(fileURLToPath(import.meta.url));
60
+ var pkg = JSON.parse(
61
+ fs.readFileSync(path.resolve(__dirname, "..", "package.json"), "utf-8")
62
+ );
63
+ if (dsn) {
64
+ Sentry.init({
65
+ dsn,
66
+ release: `caliber-cli@${pkg.version}`,
67
+ environment: process.env.NODE_ENV || "production",
68
+ tracesSampleRate: 0,
69
+ beforeSend(event) {
70
+ event.tags = { ...event.tags, source: "cli", os: os.platform() };
71
+ return event;
72
+ }
73
+ });
74
+ }
75
+
52
76
  // src/cli.ts
53
77
  import { Command } from "commander";
54
- import fs12 from "fs";
78
+ import fs16 from "fs";
79
+ import path13 from "path";
80
+ import { fileURLToPath as fileURLToPath3 } from "url";
55
81
 
56
82
  // src/commands/init.ts
57
83
  import chalk2 from "chalk";
58
84
  import ora2 from "ora";
59
85
  import readline from "readline";
60
- import path10 from "path";
86
+ import fs13 from "fs";
61
87
 
62
88
  // src/auth/token-store.ts
63
89
  init_constants();
64
- import fs from "fs";
90
+ import fs2 from "fs";
65
91
  function getStoredAuth() {
66
92
  try {
67
- if (!fs.existsSync(AUTH_FILE)) return null;
68
- const data = JSON.parse(fs.readFileSync(AUTH_FILE, "utf-8"));
93
+ if (!fs2.existsSync(AUTH_FILE)) return null;
94
+ const data = JSON.parse(fs2.readFileSync(AUTH_FILE, "utf-8"));
69
95
  return data;
70
96
  } catch {
71
97
  return null;
72
98
  }
73
99
  }
74
100
  function storeAuth(auth2) {
75
- if (!fs.existsSync(AUTH_DIR)) {
76
- fs.mkdirSync(AUTH_DIR, { recursive: true });
101
+ if (!fs2.existsSync(AUTH_DIR)) {
102
+ fs2.mkdirSync(AUTH_DIR, { recursive: true });
77
103
  }
78
104
  const data = {
79
105
  accessToken: auth2.accessToken,
@@ -82,11 +108,11 @@ function storeAuth(auth2) {
82
108
  userId: auth2.user.id,
83
109
  email: auth2.user.email
84
110
  };
85
- fs.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
111
+ fs2.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
86
112
  }
87
113
  function clearAuth() {
88
114
  try {
89
- if (fs.existsSync(AUTH_FILE)) fs.unlinkSync(AUTH_FILE);
115
+ if (fs2.existsSync(AUTH_FILE)) fs2.unlinkSync(AUTH_FILE);
90
116
  } catch {
91
117
  }
92
118
  }
@@ -113,7 +139,7 @@ function generatePKCE() {
113
139
  // src/auth/callback-server.ts
114
140
  init_constants();
115
141
  import http from "http";
116
- import { URL as URL2 } from "url";
142
+ import { URL } from "url";
117
143
  function startCallbackServer(expectedState) {
118
144
  return new Promise((resolve2, reject) => {
119
145
  const server = http.createServer((req, res) => {
@@ -122,7 +148,7 @@ function startCallbackServer(expectedState) {
122
148
  res.end();
123
149
  return;
124
150
  }
125
- const url = new URL2(req.url, `http://127.0.0.1:${CLI_CALLBACK_PORT}`);
151
+ const url = new URL(req.url, `http://127.0.0.1:${CLI_CALLBACK_PORT}`);
126
152
  const error = url.searchParams.get("error");
127
153
  if (error) {
128
154
  res.writeHead(200, { "Content-Type": "text/html" });
@@ -177,12 +203,13 @@ init_constants();
177
203
  // src/telemetry.ts
178
204
  init_constants();
179
205
  import { PostHog } from "posthog-node";
180
- import fs2 from "fs";
181
- import path2 from "path";
182
- import os2 from "os";
206
+ import fs3 from "fs";
207
+ import path3 from "path";
208
+ import os3 from "os";
183
209
  import { randomUUID } from "crypto";
210
+ import { fileURLToPath as fileURLToPath2 } from "url";
184
211
  var POSTHOG_API_KEY = process.env.CALIBER_POSTHOG_KEY || "phc_XXrV0pSX4s2QVxVoOaeuyXDvtlRwPAjovt1ttMGVMPp";
185
- var DEVICE_ID_FILE = path2.join(AUTH_DIR, "device-id");
212
+ var DEVICE_ID_FILE = path3.join(AUTH_DIR, "device-id");
186
213
  var client = null;
187
214
  function getClient() {
188
215
  if (!client) {
@@ -192,36 +219,37 @@ function getClient() {
192
219
  }
193
220
  function getDeviceId() {
194
221
  try {
195
- if (fs2.existsSync(DEVICE_ID_FILE)) {
196
- return fs2.readFileSync(DEVICE_ID_FILE, "utf-8").trim();
222
+ if (fs3.existsSync(DEVICE_ID_FILE)) {
223
+ return fs3.readFileSync(DEVICE_ID_FILE, "utf-8").trim();
197
224
  }
198
225
  } catch {
199
226
  }
200
227
  const id = randomUUID();
201
228
  try {
202
- if (!fs2.existsSync(AUTH_DIR)) {
203
- fs2.mkdirSync(AUTH_DIR, { recursive: true });
229
+ if (!fs3.existsSync(AUTH_DIR)) {
230
+ fs3.mkdirSync(AUTH_DIR, { recursive: true });
204
231
  }
205
- fs2.writeFileSync(DEVICE_ID_FILE, id, { mode: 384 });
232
+ fs3.writeFileSync(DEVICE_ID_FILE, id, { mode: 384 });
206
233
  } catch {
207
234
  }
208
235
  return id;
209
236
  }
210
237
  function isFirstRun() {
211
- return !fs2.existsSync(DEVICE_ID_FILE);
238
+ return !fs3.existsSync(DEVICE_ID_FILE);
212
239
  }
213
240
  function getDistinctId() {
214
241
  const auth2 = getStoredAuth();
215
242
  return auth2?.userId || getDeviceId();
216
243
  }
217
- var pkg = JSON.parse(
218
- fs2.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
244
+ var __dirname_telemetry = path3.dirname(fileURLToPath2(import.meta.url));
245
+ var pkg2 = JSON.parse(
246
+ fs3.readFileSync(path3.resolve(__dirname_telemetry, "..", "package.json"), "utf-8")
219
247
  );
220
248
  function baseProperties() {
221
249
  return {
222
250
  source: "cli",
223
- cli_version: pkg.version,
224
- os: os2.platform()
251
+ cli_version: pkg2.version,
252
+ os: os3.platform()
225
253
  };
226
254
  }
227
255
  function trackEvent(event, properties = {}) {
@@ -245,24 +273,6 @@ function identifyUser(userId, email) {
245
273
  } catch {
246
274
  }
247
275
  }
248
- function countSuggestedFiles(setup) {
249
- let count = 0;
250
- const claude = setup.claude;
251
- const cursor = setup.cursor;
252
- if (claude) {
253
- if (claude.claudeMd) count++;
254
- if (claude.settings) count++;
255
- if (claude.settingsLocal) count++;
256
- if (Array.isArray(claude.skills)) count += claude.skills.length;
257
- if (claude.mcpServers && Object.keys(claude.mcpServers).length > 0) count++;
258
- }
259
- if (cursor) {
260
- if (cursor.cursorrules) count++;
261
- if (Array.isArray(cursor.rules)) count += cursor.rules.length;
262
- if (cursor.mcpServers && Object.keys(cursor.mcpServers).length > 0) count++;
263
- }
264
- return count;
265
- }
266
276
  async function shutdownTelemetry() {
267
277
  try {
268
278
  await Promise.race([
@@ -303,8 +313,9 @@ ${authUrl}
303
313
  trackEvent("signin", { method: "cli_pkce" });
304
314
  spinner.succeed(chalk.green(`Logged in as ${result.user.email}`));
305
315
  } catch (err) {
316
+ trackEvent("error_occurred", { error_type: "auth_failed", error_message: err instanceof Error ? err.message : "Unknown error", command: "login" });
306
317
  spinner.fail(chalk.red(`Authentication failed: ${err instanceof Error ? err.message : "Unknown error"}`));
307
- process.exit(1);
318
+ throw new Error("__exit__");
308
319
  }
309
320
  }
310
321
 
@@ -334,8 +345,8 @@ function getGitRemoteUrl() {
334
345
  }
335
346
 
336
347
  // src/fingerprint/package-json.ts
337
- import fs3 from "fs";
338
- import path3 from "path";
348
+ import fs4 from "fs";
349
+ import path4 from "path";
339
350
  import { globSync } from "glob";
340
351
  var NODE_FRAMEWORK_DEPS = {
341
352
  react: "React",
@@ -386,8 +397,8 @@ var WORKSPACE_GLOBS = [
386
397
  ];
387
398
  function detectNodeFrameworks(pkgPath) {
388
399
  try {
389
- const pkg4 = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
390
- const allDeps = { ...pkg4.dependencies, ...pkg4.devDependencies };
400
+ const pkg5 = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
401
+ const allDeps = { ...pkg5.dependencies, ...pkg5.devDependencies };
391
402
  const frameworks = [];
392
403
  for (const [dep, framework] of Object.entries(NODE_FRAMEWORK_DEPS)) {
393
404
  if (allDeps[dep]) frameworks.push(framework);
@@ -400,16 +411,16 @@ function detectNodeFrameworks(pkgPath) {
400
411
  function detectPythonFrameworks(dir) {
401
412
  const frameworks = [];
402
413
  const candidates = [
403
- path3.join(dir, "pyproject.toml"),
404
- path3.join(dir, "requirements.txt"),
414
+ path4.join(dir, "pyproject.toml"),
415
+ path4.join(dir, "requirements.txt"),
405
416
  ...globSync("apps/*/pyproject.toml", { cwd: dir, absolute: true }),
406
417
  ...globSync("apps/*/requirements.txt", { cwd: dir, absolute: true }),
407
418
  ...globSync("services/*/pyproject.toml", { cwd: dir, absolute: true })
408
419
  ];
409
420
  for (const filePath of candidates) {
410
- if (!fs3.existsSync(filePath)) continue;
421
+ if (!fs4.existsSync(filePath)) continue;
411
422
  try {
412
- const content = fs3.readFileSync(filePath, "utf-8").toLowerCase();
423
+ const content = fs4.readFileSync(filePath, "utf-8").toLowerCase();
413
424
  for (const [dep, framework] of Object.entries(PYTHON_FRAMEWORK_DEPS)) {
414
425
  if (content.includes(dep)) frameworks.push(framework);
415
426
  }
@@ -419,15 +430,15 @@ function detectPythonFrameworks(dir) {
419
430
  return frameworks;
420
431
  }
421
432
  function analyzePackageJson(dir) {
422
- const rootPkgPath = path3.join(dir, "package.json");
433
+ const rootPkgPath = path4.join(dir, "package.json");
423
434
  let name;
424
435
  const allFrameworks = [];
425
436
  const languages = [];
426
- if (fs3.existsSync(rootPkgPath)) {
437
+ if (fs4.existsSync(rootPkgPath)) {
427
438
  try {
428
- const pkg4 = JSON.parse(fs3.readFileSync(rootPkgPath, "utf-8"));
429
- name = pkg4.name;
430
- const allDeps = { ...pkg4.dependencies, ...pkg4.devDependencies };
439
+ const pkg5 = JSON.parse(fs4.readFileSync(rootPkgPath, "utf-8"));
440
+ name = pkg5.name;
441
+ const allDeps = { ...pkg5.dependencies, ...pkg5.devDependencies };
431
442
  allFrameworks.push(...detectNodeFrameworks(rootPkgPath));
432
443
  if (allDeps.typescript || allDeps["@types/node"]) {
433
444
  languages.push("TypeScript");
@@ -441,8 +452,8 @@ function analyzePackageJson(dir) {
441
452
  for (const pkgPath of matches) {
442
453
  allFrameworks.push(...detectNodeFrameworks(pkgPath));
443
454
  try {
444
- const pkg4 = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
445
- const deps = { ...pkg4.dependencies, ...pkg4.devDependencies };
455
+ const pkg5 = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
456
+ const deps = { ...pkg5.dependencies, ...pkg5.devDependencies };
446
457
  if (deps.typescript || deps["@types/node"]) {
447
458
  languages.push("TypeScript");
448
459
  }
@@ -459,8 +470,8 @@ function analyzePackageJson(dir) {
459
470
  }
460
471
 
461
472
  // src/fingerprint/file-tree.ts
462
- import fs4 from "fs";
463
- import path4 from "path";
473
+ import fs5 from "fs";
474
+ import path5 from "path";
464
475
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
465
476
  "node_modules",
466
477
  ".git",
@@ -483,10 +494,10 @@ function getFileTree(dir, maxDepth = 3) {
483
494
  }
484
495
  function scan(base, rel, depth, maxDepth, result) {
485
496
  if (depth > maxDepth) return;
486
- const fullPath = path4.join(base, rel);
497
+ const fullPath = path5.join(base, rel);
487
498
  let entries;
488
499
  try {
489
- entries = fs4.readdirSync(fullPath, { withFileTypes: true });
500
+ entries = fs5.readdirSync(fullPath, { withFileTypes: true });
490
501
  } catch {
491
502
  return;
492
503
  }
@@ -504,7 +515,7 @@ function scan(base, rel, depth, maxDepth, result) {
504
515
  }
505
516
 
506
517
  // src/fingerprint/languages.ts
507
- import path5 from "path";
518
+ import path6 from "path";
508
519
  var EXT_TO_LANG = {
509
520
  ".ts": "TypeScript",
510
521
  ".tsx": "TypeScript",
@@ -530,7 +541,7 @@ var EXT_TO_LANG = {
530
541
  function detectLanguages(fileTree) {
531
542
  const langs = /* @__PURE__ */ new Set();
532
543
  for (const file of fileTree) {
533
- const ext = path5.extname(file).toLowerCase();
544
+ const ext = path6.extname(file).toLowerCase();
534
545
  if (EXT_TO_LANG[ext]) {
535
546
  langs.add(EXT_TO_LANG[ext]);
536
547
  }
@@ -539,43 +550,43 @@ function detectLanguages(fileTree) {
539
550
  }
540
551
 
541
552
  // src/fingerprint/existing-config.ts
542
- import fs5 from "fs";
543
- import path6 from "path";
553
+ import fs6 from "fs";
554
+ import path7 from "path";
544
555
  function readExistingConfigs(dir) {
545
556
  const configs = {};
546
- const claudeMdPath = path6.join(dir, "CLAUDE.md");
547
- if (fs5.existsSync(claudeMdPath)) {
548
- configs.claudeMd = fs5.readFileSync(claudeMdPath, "utf-8");
557
+ const claudeMdPath = path7.join(dir, "CLAUDE.md");
558
+ if (fs6.existsSync(claudeMdPath)) {
559
+ configs.claudeMd = fs6.readFileSync(claudeMdPath, "utf-8");
549
560
  }
550
- const claudeSettingsPath = path6.join(dir, ".claude", "settings.json");
551
- if (fs5.existsSync(claudeSettingsPath)) {
561
+ const claudeSettingsPath = path7.join(dir, ".claude", "settings.json");
562
+ if (fs6.existsSync(claudeSettingsPath)) {
552
563
  try {
553
- configs.claudeSettings = JSON.parse(fs5.readFileSync(claudeSettingsPath, "utf-8"));
564
+ configs.claudeSettings = JSON.parse(fs6.readFileSync(claudeSettingsPath, "utf-8"));
554
565
  } catch {
555
566
  }
556
567
  }
557
- const skillsDir = path6.join(dir, ".claude", "skills");
558
- if (fs5.existsSync(skillsDir)) {
568
+ const skillsDir = path7.join(dir, ".claude", "skills");
569
+ if (fs6.existsSync(skillsDir)) {
559
570
  try {
560
- const files = fs5.readdirSync(skillsDir).filter((f) => f.endsWith(".md"));
571
+ const files = fs6.readdirSync(skillsDir).filter((f) => f.endsWith(".md"));
561
572
  configs.claudeSkills = files.map((f) => ({
562
573
  filename: f,
563
- content: fs5.readFileSync(path6.join(skillsDir, f), "utf-8")
574
+ content: fs6.readFileSync(path7.join(skillsDir, f), "utf-8")
564
575
  }));
565
576
  } catch {
566
577
  }
567
578
  }
568
- const cursorrulesPath = path6.join(dir, ".cursorrules");
569
- if (fs5.existsSync(cursorrulesPath)) {
570
- configs.cursorrules = fs5.readFileSync(cursorrulesPath, "utf-8");
579
+ const cursorrulesPath = path7.join(dir, ".cursorrules");
580
+ if (fs6.existsSync(cursorrulesPath)) {
581
+ configs.cursorrules = fs6.readFileSync(cursorrulesPath, "utf-8");
571
582
  }
572
- const cursorRulesDir = path6.join(dir, ".cursor", "rules");
573
- if (fs5.existsSync(cursorRulesDir)) {
583
+ const cursorRulesDir = path7.join(dir, ".cursor", "rules");
584
+ if (fs6.existsSync(cursorRulesDir)) {
574
585
  try {
575
- const files = fs5.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"));
586
+ const files = fs6.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"));
576
587
  configs.cursorRules = files.map((f) => ({
577
588
  filename: f,
578
- content: fs5.readFileSync(path6.join(cursorRulesDir, f), "utf-8")
589
+ content: fs6.readFileSync(path7.join(cursorRulesDir, f), "utf-8")
579
590
  }));
580
591
  } catch {
581
592
  }
@@ -583,6 +594,277 @@ function readExistingConfigs(dir) {
583
594
  return configs;
584
595
  }
585
596
 
597
+ // src/fingerprint/code-analysis.ts
598
+ import fs7 from "fs";
599
+ import path8 from "path";
600
+ var IGNORE_DIRS2 = /* @__PURE__ */ new Set([
601
+ "node_modules",
602
+ ".git",
603
+ ".next",
604
+ "dist",
605
+ "build",
606
+ ".cache",
607
+ ".turbo",
608
+ "coverage",
609
+ ".caliber",
610
+ "__pycache__",
611
+ ".venv",
612
+ "vendor",
613
+ "target"
614
+ ]);
615
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".py"]);
616
+ var CONFIG_FILE_NAMES = /* @__PURE__ */ new Set([
617
+ "Dockerfile",
618
+ "docker-compose.yml",
619
+ "docker-compose.yaml",
620
+ "Makefile",
621
+ "tsconfig.json",
622
+ "pyproject.toml",
623
+ "turbo.json",
624
+ "next.config.js",
625
+ "next.config.mjs",
626
+ "next.config.ts",
627
+ "vite.config.ts",
628
+ "vite.config.js",
629
+ "vite.config.mjs",
630
+ "drizzle.config.ts",
631
+ "drizzle.config.js",
632
+ "jest.config.ts",
633
+ "jest.config.js",
634
+ "jest.config.mjs",
635
+ "vitest.config.ts",
636
+ "vitest.config.js",
637
+ "vitest.config.mts",
638
+ "alembic.ini",
639
+ "setup.cfg",
640
+ "tox.ini"
641
+ ]);
642
+ var CONFIG_GLOBS_DIRS = [
643
+ { dir: ".github/workflows", pattern: /\.ya?ml$/ }
644
+ ];
645
+ var TOTAL_BUDGET = 5e4;
646
+ var CONFIG_BUDGET = Math.floor(TOTAL_BUDGET * 0.3);
647
+ var SOURCE_BUDGET = Math.floor(TOTAL_BUDGET * 0.7);
648
+ function analyzeCode(dir) {
649
+ const sourceFiles = [];
650
+ const configFiles = [];
651
+ walkDir(dir, "", 0, 10, sourceFiles, configFiles, dir);
652
+ sortByPriority(sourceFiles);
653
+ let configChars = 0;
654
+ const trimmedConfigs = [];
655
+ for (const cfg of configFiles) {
656
+ const size = cfg.path.length + cfg.content.length;
657
+ if (configChars + size > CONFIG_BUDGET) break;
658
+ trimmedConfigs.push(cfg);
659
+ configChars += size;
660
+ }
661
+ let sourceChars = 0;
662
+ let truncated = false;
663
+ const fileSummaries = [];
664
+ for (const relPath of sourceFiles) {
665
+ const fullPath = path8.join(dir, relPath);
666
+ let content;
667
+ try {
668
+ content = fs7.readFileSync(fullPath, "utf-8");
669
+ } catch {
670
+ continue;
671
+ }
672
+ const lineCount = content.split("\n").length;
673
+ if (lineCount > 500) continue;
674
+ const ext = path8.extname(relPath);
675
+ const language = resolveLanguage(ext);
676
+ if (!language) continue;
677
+ const summary = language === "py" ? extractPython(relPath, content) : extractTypeScriptJavaScript(relPath, content, language);
678
+ const summarySize = estimateSummarySize(summary);
679
+ if (sourceChars + summarySize > SOURCE_BUDGET) {
680
+ truncated = true;
681
+ break;
682
+ }
683
+ fileSummaries.push(summary);
684
+ sourceChars += summarySize;
685
+ }
686
+ return { fileSummaries, configFiles: trimmedConfigs, truncated };
687
+ }
688
+ function walkDir(base, rel, depth, maxDepth, sourceFiles, configFiles, rootDir) {
689
+ if (depth > maxDepth) return;
690
+ const fullPath = path8.join(base, rel);
691
+ let entries;
692
+ try {
693
+ entries = fs7.readdirSync(fullPath, { withFileTypes: true });
694
+ } catch {
695
+ return;
696
+ }
697
+ for (const entry of entries) {
698
+ if (IGNORE_DIRS2.has(entry.name)) continue;
699
+ if (entry.name.startsWith(".") && depth === 0 && entry.isDirectory()) continue;
700
+ const relPath = rel ? `${rel}/${entry.name}` : entry.name;
701
+ if (entry.isDirectory()) {
702
+ const matchedGlobDir = CONFIG_GLOBS_DIRS.find(
703
+ (g) => relPath === g.dir || relPath.endsWith(`/${g.dir}`)
704
+ );
705
+ if (matchedGlobDir) {
706
+ collectConfigsFromDir(base, relPath, matchedGlobDir.pattern, configFiles);
707
+ }
708
+ walkDir(base, relPath, depth + 1, maxDepth, sourceFiles, configFiles, rootDir);
709
+ } else {
710
+ if (CONFIG_FILE_NAMES.has(entry.name)) {
711
+ try {
712
+ const content = fs7.readFileSync(path8.join(base, relPath), "utf-8");
713
+ configFiles.push({ path: relPath, content });
714
+ } catch {
715
+ }
716
+ }
717
+ const ext = path8.extname(entry.name);
718
+ if (SOURCE_EXTENSIONS.has(ext) && !entry.name.endsWith(".d.ts")) {
719
+ sourceFiles.push(relPath);
720
+ }
721
+ }
722
+ }
723
+ }
724
+ function collectConfigsFromDir(base, relDir, pattern, configFiles) {
725
+ const fullDir = path8.join(base, relDir);
726
+ let entries;
727
+ try {
728
+ entries = fs7.readdirSync(fullDir, { withFileTypes: true });
729
+ } catch {
730
+ return;
731
+ }
732
+ for (const entry of entries) {
733
+ if (entry.isFile() && pattern.test(entry.name)) {
734
+ const relPath = `${relDir}/${entry.name}`;
735
+ try {
736
+ const content = fs7.readFileSync(path8.join(base, relPath), "utf-8");
737
+ configFiles.push({ path: relPath, content });
738
+ } catch {
739
+ }
740
+ }
741
+ }
742
+ }
743
+ function sortByPriority(files) {
744
+ const entryPointNames = /* @__PURE__ */ new Set(["index.ts", "index.js", "main.py", "app.ts", "app.js", "server.ts", "server.js"]);
745
+ const routePattern = /(route|api|controller)/i;
746
+ const schemaPattern = /(types|schema|models)/i;
747
+ const servicePattern = /(service|lib|utils)/i;
748
+ const testPattern = /(test|spec|__tests__)/i;
749
+ function priority(filePath) {
750
+ const base = path8.basename(filePath);
751
+ if (entryPointNames.has(base)) return 0;
752
+ if (routePattern.test(filePath)) return 1;
753
+ if (schemaPattern.test(filePath)) return 2;
754
+ if (servicePattern.test(filePath)) return 3;
755
+ if (testPattern.test(filePath)) return 5;
756
+ return 4;
757
+ }
758
+ files.sort((a, b) => priority(a) - priority(b));
759
+ }
760
+ function resolveLanguage(ext) {
761
+ if (ext === ".ts" || ext === ".tsx") return "ts";
762
+ if (ext === ".js" || ext === ".jsx") return "js";
763
+ if (ext === ".py") return "py";
764
+ return null;
765
+ }
766
+ function extractTypeScriptJavaScript(filePath, content, language) {
767
+ const lines = content.split("\n");
768
+ const imports = [];
769
+ const exports = [];
770
+ const functions = [];
771
+ const classes = [];
772
+ const types = [];
773
+ const routes = [];
774
+ for (const line of lines) {
775
+ const trimmed = line.trim();
776
+ if (/^import\s+/.test(trimmed)) {
777
+ imports.push(trimmed);
778
+ continue;
779
+ }
780
+ const exportMatch = trimmed.match(
781
+ /^export\s+(?:default\s+)?(?:async\s+)?(function|const|class|interface|type|enum)\s+(\w+)/
782
+ );
783
+ if (exportMatch) {
784
+ exports.push(`${exportMatch[1]} ${exportMatch[2]}`);
785
+ if (exportMatch[1] === "class") classes.push(exportMatch[2]);
786
+ if (exportMatch[1] === "interface" || exportMatch[1] === "type") types.push(exportMatch[2]);
787
+ if (exportMatch[1] === "function") functions.push(exportMatch[2]);
788
+ continue;
789
+ }
790
+ const fnMatch = trimmed.match(/^(?:async\s+)?function\s+(\w+)/);
791
+ if (fnMatch && !trimmed.startsWith("export")) {
792
+ functions.push(fnMatch[1]);
793
+ continue;
794
+ }
795
+ const arrowMatch = trimmed.match(/^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s*)?\(/);
796
+ if (arrowMatch && !exports.some((e) => e.endsWith(arrowMatch[1]))) {
797
+ functions.push(arrowMatch[1]);
798
+ }
799
+ const classMatch = trimmed.match(/^class\s+(\w+)/);
800
+ if (classMatch && !trimmed.startsWith("export")) {
801
+ classes.push(classMatch[1]);
802
+ continue;
803
+ }
804
+ const ifaceMatch = trimmed.match(/^(?:interface|type)\s+(\w+)/);
805
+ if (ifaceMatch && !trimmed.startsWith("export")) {
806
+ types.push(ifaceMatch[1]);
807
+ continue;
808
+ }
809
+ const routeMatch = trimmed.match(
810
+ /(?:router|app|server)\.(get|post|put|patch|delete|all|use)\s*\(\s*['"`]([^'"`]+)['"`]/
811
+ );
812
+ if (routeMatch) {
813
+ routes.push(`${routeMatch[1].toUpperCase()} ${routeMatch[2]}`);
814
+ }
815
+ }
816
+ return { path: filePath, language, imports, exports, functions, classes, types, routes };
817
+ }
818
+ function extractPython(filePath, content) {
819
+ const lines = content.split("\n");
820
+ const imports = [];
821
+ const functions = [];
822
+ const classes = [];
823
+ const routes = [];
824
+ let prevLine = "";
825
+ for (const line of lines) {
826
+ const trimmed = line.trim();
827
+ if (/^import\s+/.test(trimmed) || /^from\s+/.test(trimmed)) {
828
+ imports.push(trimmed);
829
+ continue;
830
+ }
831
+ const defMatch = trimmed.match(/^def\s+(\w+)\s*\(([^)]*)\)/);
832
+ if (defMatch) {
833
+ functions.push(`${defMatch[1]}(${defMatch[2]})`);
834
+ }
835
+ const classMatch = trimmed.match(/^class\s+(\w+)/);
836
+ if (classMatch) {
837
+ classes.push(classMatch[1]);
838
+ }
839
+ const decoratorRoute = prevLine.match(
840
+ /@(?:app|router)\.(get|post|put|patch|delete|options|head)\s*\(\s*['"]([^'"]+)['"]/
841
+ );
842
+ if (decoratorRoute && defMatch) {
843
+ routes.push(`${decoratorRoute[1].toUpperCase()} ${decoratorRoute[2]}`);
844
+ }
845
+ const includeRouter = trimmed.match(
846
+ /include_router\s*\([^,]+,\s*prefix\s*=\s*['"]([^'"]+)['"]/
847
+ );
848
+ if (includeRouter) {
849
+ routes.push(`ROUTER ${includeRouter[1]}`);
850
+ }
851
+ prevLine = trimmed;
852
+ }
853
+ return {
854
+ path: filePath,
855
+ language: "py",
856
+ imports,
857
+ exports: [],
858
+ functions,
859
+ classes,
860
+ types: [],
861
+ routes
862
+ };
863
+ }
864
+ function estimateSummarySize(summary) {
865
+ return summary.path.length + summary.imports.reduce((s, i) => s + i.length, 0) + summary.exports.reduce((s, e) => s + e.length, 0) + summary.functions.reduce((s, f) => s + f.length, 0) + summary.classes.reduce((s, c) => s + c.length, 0) + summary.types.reduce((s, t) => s + t.length, 0) + summary.routes.reduce((s, r) => s + r.length, 0);
866
+ }
867
+
586
868
  // src/fingerprint/index.ts
587
869
  function collectFingerprint(dir) {
588
870
  const gitRemoteUrl = getGitRemoteUrl();
@@ -590,6 +872,7 @@ function collectFingerprint(dir) {
590
872
  const fileTree = getFileTree(dir);
591
873
  const fileLangs = detectLanguages(fileTree);
592
874
  const existingConfigs = readExistingConfigs(dir);
875
+ const codeAnalysis = analyzeCode(dir);
593
876
  const languages = [.../* @__PURE__ */ new Set([...pkgInfo.languages, ...fileLangs])];
594
877
  return {
595
878
  gitRemoteUrl,
@@ -597,7 +880,8 @@ function collectFingerprint(dir) {
597
880
  languages,
598
881
  frameworks: pkgInfo.frameworks,
599
882
  fileTree,
600
- existingConfigs
883
+ existingConfigs,
884
+ codeAnalysis
601
885
  };
602
886
  }
603
887
  function computeFingerprintHash(fingerprint) {
@@ -610,10 +894,9 @@ function computeFingerprintHash(fingerprint) {
610
894
 
611
895
  // src/api/client.ts
612
896
  init_constants();
613
- async function refreshTokenIfNeeded() {
897
+ async function forceRefreshToken() {
614
898
  const auth2 = getStoredAuth();
615
899
  if (!auth2) return null;
616
- if (!isTokenExpired()) return auth2.accessToken;
617
900
  try {
618
901
  const resp = await fetch(`${API_URL}/api/auth/refresh`, {
619
902
  method: "POST",
@@ -633,12 +916,21 @@ async function refreshTokenIfNeeded() {
633
916
  return null;
634
917
  }
635
918
  }
636
- async function apiRequest(path11, options = {}) {
637
- const token = await refreshTokenIfNeeded();
638
- if (!token) {
919
+ async function getValidToken() {
920
+ const auth2 = getStoredAuth();
921
+ if (!auth2) {
639
922
  throw new Error("Not authenticated. Run `caliber login` first.");
640
923
  }
641
- const resp = await fetch(`${API_URL}${path11}`, {
924
+ if (!isTokenExpired()) return auth2.accessToken;
925
+ const refreshed = await forceRefreshToken();
926
+ if (!refreshed) {
927
+ throw new Error("Session expired. Run `caliber login` to re-authenticate.");
928
+ }
929
+ return refreshed;
930
+ }
931
+ async function apiRequest(path15, options = {}) {
932
+ let token = await getValidToken();
933
+ let resp = await fetch(`${API_URL}${path15}`, {
642
934
  method: options.method || "GET",
643
935
  headers: {
644
936
  "Content-Type": "application/json",
@@ -646,6 +938,21 @@ async function apiRequest(path11, options = {}) {
646
938
  },
647
939
  body: options.body ? JSON.stringify(options.body) : void 0
648
940
  });
941
+ if (resp.status === 401) {
942
+ const refreshed = await forceRefreshToken();
943
+ if (!refreshed) {
944
+ throw new Error("Session expired. Run `caliber login` to re-authenticate.");
945
+ }
946
+ token = refreshed;
947
+ resp = await fetch(`${API_URL}${path15}`, {
948
+ method: options.method || "GET",
949
+ headers: {
950
+ "Content-Type": "application/json",
951
+ Authorization: `Bearer ${token}`
952
+ },
953
+ body: options.body ? JSON.stringify(options.body) : void 0
954
+ });
955
+ }
649
956
  if (!resp.ok) {
650
957
  const error = await resp.json().catch(() => ({ error: resp.statusText }));
651
958
  throw new Error(error.error || `API error: ${resp.status}`);
@@ -653,12 +960,9 @@ async function apiRequest(path11, options = {}) {
653
960
  const json = await resp.json();
654
961
  return json.data;
655
962
  }
656
- async function apiStream(path11, body, onChunk, onComplete, onError, onStatus) {
657
- const token = await refreshTokenIfNeeded();
658
- if (!token) {
659
- throw new Error("Not authenticated. Run `caliber login` first.");
660
- }
661
- const resp = await fetch(`${API_URL}${path11}`, {
963
+ async function apiStream(path15, body, onChunk, onComplete, onError, onStatus) {
964
+ let token = await getValidToken();
965
+ let resp = await fetch(`${API_URL}${path15}`, {
662
966
  method: "POST",
663
967
  headers: {
664
968
  "Content-Type": "application/json",
@@ -666,6 +970,21 @@ async function apiStream(path11, body, onChunk, onComplete, onError, onStatus) {
666
970
  },
667
971
  body: JSON.stringify(body)
668
972
  });
973
+ if (resp.status === 401) {
974
+ const refreshed = await forceRefreshToken();
975
+ if (!refreshed) {
976
+ throw new Error("Session expired. Run `caliber login` to re-authenticate.");
977
+ }
978
+ token = refreshed;
979
+ resp = await fetch(`${API_URL}${path15}`, {
980
+ method: "POST",
981
+ headers: {
982
+ "Content-Type": "application/json",
983
+ Authorization: `Bearer ${token}`
984
+ },
985
+ body: JSON.stringify(body)
986
+ });
987
+ }
669
988
  if (!resp.ok || !resp.body) {
670
989
  throw new Error(`API error: ${resp.status}`);
671
990
  }
@@ -686,7 +1005,7 @@ async function apiStream(path11, body, onChunk, onComplete, onError, onStatus) {
686
1005
  const parsed = JSON.parse(data);
687
1006
  if (parsed.type === "chunk") onChunk(parsed.content);
688
1007
  else if (parsed.type === "status" && onStatus) onStatus(parsed.message);
689
- else if (parsed.type === "complete") onComplete({ setup: parsed.setup, explanation: parsed.explanation });
1008
+ else if (parsed.type === "complete") onComplete({ setup: parsed.setup, explanation: parsed.explanation, raw: parsed.raw });
690
1009
  else if (parsed.type === "error") onError(parsed.message);
691
1010
  } catch {
692
1011
  }
@@ -695,132 +1014,132 @@ async function apiStream(path11, body, onChunk, onComplete, onError, onStatus) {
695
1014
  }
696
1015
 
697
1016
  // src/writers/index.ts
698
- import fs10 from "fs";
1017
+ import fs12 from "fs";
699
1018
 
700
1019
  // src/writers/claude/index.ts
701
- import fs6 from "fs";
702
- import path7 from "path";
1020
+ import fs8 from "fs";
1021
+ import path9 from "path";
703
1022
  function writeClaudeConfig(config) {
704
1023
  const written = [];
705
- fs6.writeFileSync("CLAUDE.md", config.claudeMd);
1024
+ fs8.writeFileSync("CLAUDE.md", config.claudeMd);
706
1025
  written.push("CLAUDE.md");
707
1026
  const claudeDir = ".claude";
708
- if (!fs6.existsSync(claudeDir)) fs6.mkdirSync(claudeDir, { recursive: true });
709
- fs6.writeFileSync(
710
- path7.join(claudeDir, "settings.json"),
1027
+ if (!fs8.existsSync(claudeDir)) fs8.mkdirSync(claudeDir, { recursive: true });
1028
+ fs8.writeFileSync(
1029
+ path9.join(claudeDir, "settings.json"),
711
1030
  JSON.stringify(config.settings, null, 2)
712
1031
  );
713
- written.push(path7.join(claudeDir, "settings.json"));
714
- fs6.writeFileSync(
715
- path7.join(claudeDir, "settings.local.json"),
1032
+ written.push(path9.join(claudeDir, "settings.json"));
1033
+ fs8.writeFileSync(
1034
+ path9.join(claudeDir, "settings.local.json"),
716
1035
  JSON.stringify(config.settingsLocal, null, 2)
717
1036
  );
718
- written.push(path7.join(claudeDir, "settings.local.json"));
1037
+ written.push(path9.join(claudeDir, "settings.local.json"));
719
1038
  if (config.skills?.length) {
720
- const skillsDir = path7.join(claudeDir, "skills");
721
- if (!fs6.existsSync(skillsDir)) fs6.mkdirSync(skillsDir, { recursive: true });
1039
+ const skillsDir = path9.join(claudeDir, "skills");
1040
+ if (!fs8.existsSync(skillsDir)) fs8.mkdirSync(skillsDir, { recursive: true });
722
1041
  for (const skill of config.skills) {
723
1042
  const filename = `${skill.name.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}.md`;
724
- const skillPath = path7.join(skillsDir, filename);
725
- fs6.writeFileSync(skillPath, skill.content);
1043
+ const skillPath = path9.join(skillsDir, filename);
1044
+ fs8.writeFileSync(skillPath, skill.content);
726
1045
  written.push(skillPath);
727
1046
  }
728
1047
  }
729
1048
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
730
1049
  const mcpConfig = { mcpServers: config.mcpServers };
731
- fs6.writeFileSync(".mcp.json", JSON.stringify(mcpConfig, null, 2));
1050
+ fs8.writeFileSync(".mcp.json", JSON.stringify(mcpConfig, null, 2));
732
1051
  written.push(".mcp.json");
733
1052
  }
734
1053
  return written;
735
1054
  }
736
1055
 
737
1056
  // src/writers/cursor/index.ts
738
- import fs7 from "fs";
739
- import path8 from "path";
1057
+ import fs9 from "fs";
1058
+ import path10 from "path";
740
1059
  function writeCursorConfig(config) {
741
1060
  const written = [];
742
1061
  if (config.cursorrules) {
743
- fs7.writeFileSync(".cursorrules", config.cursorrules);
1062
+ fs9.writeFileSync(".cursorrules", config.cursorrules);
744
1063
  written.push(".cursorrules");
745
1064
  }
746
1065
  if (config.rules?.length) {
747
- const rulesDir = path8.join(".cursor", "rules");
748
- if (!fs7.existsSync(rulesDir)) fs7.mkdirSync(rulesDir, { recursive: true });
1066
+ const rulesDir = path10.join(".cursor", "rules");
1067
+ if (!fs9.existsSync(rulesDir)) fs9.mkdirSync(rulesDir, { recursive: true });
749
1068
  for (const rule of config.rules) {
750
- const rulePath = path8.join(rulesDir, rule.filename);
751
- fs7.writeFileSync(rulePath, rule.content);
1069
+ const rulePath = path10.join(rulesDir, rule.filename);
1070
+ fs9.writeFileSync(rulePath, rule.content);
752
1071
  written.push(rulePath);
753
1072
  }
754
1073
  }
755
1074
  if (config.mcpServers && Object.keys(config.mcpServers).length > 0) {
756
1075
  const cursorDir = ".cursor";
757
- if (!fs7.existsSync(cursorDir)) fs7.mkdirSync(cursorDir, { recursive: true });
1076
+ if (!fs9.existsSync(cursorDir)) fs9.mkdirSync(cursorDir, { recursive: true });
758
1077
  const mcpConfig = { mcpServers: config.mcpServers };
759
- fs7.writeFileSync(
760
- path8.join(cursorDir, "mcp.json"),
1078
+ fs9.writeFileSync(
1079
+ path10.join(cursorDir, "mcp.json"),
761
1080
  JSON.stringify(mcpConfig, null, 2)
762
1081
  );
763
- written.push(path8.join(cursorDir, "mcp.json"));
1082
+ written.push(path10.join(cursorDir, "mcp.json"));
764
1083
  }
765
1084
  return written;
766
1085
  }
767
1086
 
768
1087
  // src/writers/backup.ts
769
1088
  init_constants();
770
- import fs8 from "fs";
771
- import path9 from "path";
1089
+ import fs10 from "fs";
1090
+ import path11 from "path";
772
1091
  function createBackup(files) {
773
1092
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
774
- const backupDir = path9.join(BACKUPS_DIR, timestamp);
1093
+ const backupDir = path11.join(BACKUPS_DIR, timestamp);
775
1094
  for (const file of files) {
776
- if (!fs8.existsSync(file)) continue;
777
- const dest = path9.join(backupDir, file);
778
- const destDir = path9.dirname(dest);
779
- if (!fs8.existsSync(destDir)) {
780
- fs8.mkdirSync(destDir, { recursive: true });
1095
+ if (!fs10.existsSync(file)) continue;
1096
+ const dest = path11.join(backupDir, file);
1097
+ const destDir = path11.dirname(dest);
1098
+ if (!fs10.existsSync(destDir)) {
1099
+ fs10.mkdirSync(destDir, { recursive: true });
781
1100
  }
782
- fs8.copyFileSync(file, dest);
1101
+ fs10.copyFileSync(file, dest);
783
1102
  }
784
1103
  return backupDir;
785
1104
  }
786
1105
  function restoreBackup(backupDir, file) {
787
- const backupFile = path9.join(backupDir, file);
788
- if (!fs8.existsSync(backupFile)) return false;
789
- const destDir = path9.dirname(file);
790
- if (!fs8.existsSync(destDir)) {
791
- fs8.mkdirSync(destDir, { recursive: true });
1106
+ const backupFile = path11.join(backupDir, file);
1107
+ if (!fs10.existsSync(backupFile)) return false;
1108
+ const destDir = path11.dirname(file);
1109
+ if (!fs10.existsSync(destDir)) {
1110
+ fs10.mkdirSync(destDir, { recursive: true });
792
1111
  }
793
- fs8.copyFileSync(backupFile, file);
1112
+ fs10.copyFileSync(backupFile, file);
794
1113
  return true;
795
1114
  }
796
1115
 
797
1116
  // src/writers/manifest.ts
798
1117
  init_constants();
799
- import fs9 from "fs";
1118
+ import fs11 from "fs";
800
1119
  import crypto3 from "crypto";
801
1120
  function readManifest() {
802
1121
  try {
803
- if (!fs9.existsSync(MANIFEST_FILE)) return null;
804
- return JSON.parse(fs9.readFileSync(MANIFEST_FILE, "utf-8"));
1122
+ if (!fs11.existsSync(MANIFEST_FILE)) return null;
1123
+ return JSON.parse(fs11.readFileSync(MANIFEST_FILE, "utf-8"));
805
1124
  } catch {
806
1125
  return null;
807
1126
  }
808
1127
  }
809
1128
  function writeManifest(manifest) {
810
- if (!fs9.existsSync(CALIBER_DIR)) {
811
- fs9.mkdirSync(CALIBER_DIR, { recursive: true });
1129
+ if (!fs11.existsSync(CALIBER_DIR)) {
1130
+ fs11.mkdirSync(CALIBER_DIR, { recursive: true });
812
1131
  }
813
- fs9.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
1132
+ fs11.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
814
1133
  }
815
1134
  function fileChecksum(filePath) {
816
- const content = fs9.readFileSync(filePath);
1135
+ const content = fs11.readFileSync(filePath);
817
1136
  return crypto3.createHash("sha256").update(content).digest("hex");
818
1137
  }
819
1138
 
820
1139
  // src/writers/index.ts
821
1140
  function writeSetup(setup) {
822
1141
  const filesToWrite = getFilesToWrite(setup);
823
- const existingFiles = filesToWrite.filter((f) => fs10.existsSync(f));
1142
+ const existingFiles = filesToWrite.filter((f) => fs12.existsSync(f));
824
1143
  const backupDir = existingFiles.length > 0 ? createBackup(existingFiles) : void 0;
825
1144
  const written = [];
826
1145
  if ((setup.targetAgent === "claude" || setup.targetAgent === "both") && setup.claude) {
@@ -848,8 +1167,8 @@ function undoSetup() {
848
1167
  const removed = [];
849
1168
  for (const entry of manifest.entries) {
850
1169
  if (entry.action === "created") {
851
- if (fs10.existsSync(entry.path)) {
852
- fs10.unlinkSync(entry.path);
1170
+ if (fs12.existsSync(entry.path)) {
1171
+ fs12.unlinkSync(entry.path);
853
1172
  removed.push(entry.path);
854
1173
  }
855
1174
  } else if (entry.action === "modified" && manifest.backupDir) {
@@ -859,8 +1178,8 @@ function undoSetup() {
859
1178
  }
860
1179
  }
861
1180
  const { MANIFEST_FILE: MANIFEST_FILE2 } = (init_constants(), __toCommonJS(constants_exports));
862
- if (fs10.existsSync(MANIFEST_FILE2)) {
863
- fs10.unlinkSync(MANIFEST_FILE2);
1181
+ if (fs12.existsSync(MANIFEST_FILE2)) {
1182
+ fs12.unlinkSync(MANIFEST_FILE2);
864
1183
  }
865
1184
  return { restored, removed };
866
1185
  }
@@ -886,13 +1205,13 @@ function getFilesToWrite(setup) {
886
1205
  }
887
1206
  function ensureGitignore() {
888
1207
  const gitignorePath = ".gitignore";
889
- if (fs10.existsSync(gitignorePath)) {
890
- const content = fs10.readFileSync(gitignorePath, "utf-8");
1208
+ if (fs12.existsSync(gitignorePath)) {
1209
+ const content = fs12.readFileSync(gitignorePath, "utf-8");
891
1210
  if (!content.includes(".caliber/")) {
892
- fs10.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
1211
+ fs12.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
893
1212
  }
894
1213
  } else {
895
- fs10.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
1214
+ fs12.writeFileSync(gitignorePath, "# Caliber local state\n.caliber/\n");
896
1215
  }
897
1216
  }
898
1217
 
@@ -970,7 +1289,7 @@ async function initCommand(options) {
970
1289
  auth2 = getStoredAuth();
971
1290
  if (!auth2) {
972
1291
  console.log(chalk2.red("Authentication required. Exiting."));
973
- process.exit(1);
1292
+ throw new Error("__exit__");
974
1293
  }
975
1294
  }
976
1295
  console.log(chalk2.dim(`Authenticated as ${auth2.email}
@@ -996,14 +1315,23 @@ async function initCommand(options) {
996
1315
  `));
997
1316
  const matchSpinner = ora2("Checking for existing setup...").start();
998
1317
  let existingSetup = null;
1318
+ let existingProjectId = null;
999
1319
  try {
1000
1320
  const match = await apiRequest("/api/projects/match", {
1001
1321
  method: "POST",
1002
1322
  body: { fingerprintHash: hash }
1003
1323
  });
1324
+ if (match.project) {
1325
+ existingProjectId = match.project.id;
1326
+ }
1004
1327
  if (match.setup) {
1005
1328
  existingSetup = match.setup;
1006
- trackEvent("existing_config_detected");
1329
+ trackEvent("existing_config_detected", {
1330
+ has_claude_md: !!fingerprint.existingConfigs.claudeMd,
1331
+ has_cursorrules: !!fingerprint.existingConfigs.cursorrules,
1332
+ has_cursor_rules: (fingerprint.existingConfigs.cursorRules?.length ?? 0) > 0,
1333
+ has_skills: (fingerprint.existingConfigs.claudeSkills?.length ?? 0) > 0
1334
+ });
1007
1335
  matchSpinner.succeed("Found existing setup");
1008
1336
  } else {
1009
1337
  matchSpinner.info("No existing setup found");
@@ -1019,55 +1347,63 @@ async function initCommand(options) {
1019
1347
  }
1020
1348
  let generatedSetup = null;
1021
1349
  let setupExplanation;
1350
+ let rawOutput;
1022
1351
  trackEvent("generation_started", { target_agent: targetAgent });
1023
- const generationStart = Date.now();
1024
1352
  const genSpinner = ora2("Generating setup...").start();
1025
1353
  const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES);
1026
1354
  genMessages.start();
1027
- await apiStream(
1028
- "/api/setups/generate",
1029
- {
1030
- fingerprint,
1031
- targetAgent,
1032
- prompt: fingerprint.description
1033
- },
1034
- () => {
1035
- },
1036
- (payload) => {
1037
- generatedSetup = payload.setup;
1038
- setupExplanation = payload.explanation;
1039
- },
1040
- (error) => {
1041
- genMessages.stop();
1042
- trackEvent("error_occurred", { error_type: "generation_failed", error_message: error });
1043
- genSpinner.fail(`Generation error: ${error}`);
1044
- },
1045
- (status) => {
1046
- genMessages.handleServerStatus(status);
1047
- }
1048
- );
1355
+ try {
1356
+ await apiStream(
1357
+ "/api/setups/generate",
1358
+ {
1359
+ fingerprint,
1360
+ targetAgent,
1361
+ prompt: fingerprint.description
1362
+ },
1363
+ () => {
1364
+ },
1365
+ (payload) => {
1366
+ generatedSetup = payload.setup;
1367
+ setupExplanation = payload.explanation;
1368
+ rawOutput = payload.raw;
1369
+ },
1370
+ (error) => {
1371
+ genMessages.stop();
1372
+ trackEvent("error_occurred", { error_type: "generation_failed", error_message: error, command: "init" });
1373
+ genSpinner.fail(`Generation error: ${error}`);
1374
+ },
1375
+ (status) => {
1376
+ genMessages.handleServerStatus(status);
1377
+ }
1378
+ );
1379
+ } catch (err) {
1380
+ genMessages.stop();
1381
+ const msg = err instanceof Error ? err.message : "Unknown error";
1382
+ trackEvent("error_occurred", { error_type: "generation_request_failed", error_message: msg, command: "init" });
1383
+ genSpinner.fail(`Generation failed: ${msg}`);
1384
+ throw new Error("__exit__");
1385
+ }
1049
1386
  genMessages.stop();
1050
1387
  if (!generatedSetup) {
1051
1388
  genSpinner.fail("Failed to generate setup.");
1052
- process.exit(1);
1389
+ if (rawOutput) {
1390
+ console.log(chalk2.dim("\nRaw LLM output (JSON parse failed):"));
1391
+ console.log(chalk2.dim(rawOutput.slice(0, 500)));
1392
+ }
1393
+ throw new Error("__exit__");
1053
1394
  }
1054
- trackEvent("generation_completed", {
1055
- target_agent: targetAgent,
1056
- duration_ms: Date.now() - generationStart,
1057
- files_suggested: countSuggestedFiles(generatedSetup)
1058
- });
1059
1395
  genSpinner.succeed("Setup generated");
1060
1396
  printSetupSummary(generatedSetup);
1061
- let action = await promptAction();
1397
+ let explained = false;
1398
+ let action = await promptAction(explained);
1062
1399
  while (action === "explain") {
1063
1400
  if (setupExplanation) {
1064
- console.log(chalk2.bold("\nWhy this setup?\n"));
1065
- console.log(chalk2.dim(setupExplanation));
1066
- console.log("");
1401
+ printExplanation(setupExplanation);
1067
1402
  } else {
1068
1403
  console.log(chalk2.dim("\nNo explanation available for this setup.\n"));
1069
1404
  }
1070
- action = await promptAction();
1405
+ explained = true;
1406
+ action = await promptAction(explained);
1071
1407
  }
1072
1408
  if (action === "decline") {
1073
1409
  trackEvent("setup_declined");
@@ -1077,7 +1413,7 @@ async function initCommand(options) {
1077
1413
  if (action === "refine") {
1078
1414
  generatedSetup = await refineLoop(generatedSetup, targetAgent);
1079
1415
  if (!generatedSetup) {
1080
- trackEvent("generation_cancelled");
1416
+ trackEvent("refinement_cancelled");
1081
1417
  console.log(chalk2.dim("Refinement cancelled. No files were modified."));
1082
1418
  return;
1083
1419
  }
@@ -1092,9 +1428,6 @@ async function initCommand(options) {
1092
1428
  const result = writeSetup(generatedSetup);
1093
1429
  writeSpinner.succeed("Config files written");
1094
1430
  trackEvent("setup_applied", { files_written: result.written.length, target_agent: targetAgent });
1095
- for (const file of result.written) {
1096
- trackEvent("config_file_written", { file_type: path10.extname(file) || path10.basename(file) });
1097
- }
1098
1431
  console.log(chalk2.bold("\nFiles created/updated:"));
1099
1432
  for (const file of result.written) {
1100
1433
  console.log(` ${chalk2.green("\u2713")} ${file}`);
@@ -1106,20 +1439,23 @@ async function initCommand(options) {
1106
1439
  } catch (err) {
1107
1440
  writeSpinner.fail("Failed to write files");
1108
1441
  console.error(chalk2.red(err instanceof Error ? err.message : "Unknown error"));
1109
- process.exit(1);
1442
+ throw new Error("__exit__");
1110
1443
  }
1111
1444
  try {
1112
- const project = await apiRequest("/api/projects", {
1113
- method: "POST",
1114
- body: {
1115
- fingerprintHash: hash,
1116
- name: fingerprint.packageName || "untitled",
1117
- gitRemoteUrl: fingerprint.gitRemoteUrl,
1118
- description: fingerprint.description
1119
- }
1120
- });
1121
- trackEvent("project_created", { project_name: fingerprint.packageName || "untitled" });
1122
- await apiRequest(`/api/setups/project/${project.id}`, {
1445
+ let projectId = existingProjectId;
1446
+ if (!projectId) {
1447
+ const project = await apiRequest("/api/projects", {
1448
+ method: "POST",
1449
+ body: {
1450
+ fingerprintHash: hash,
1451
+ name: fingerprint.packageName || "untitled",
1452
+ gitRemoteUrl: fingerprint.gitRemoteUrl,
1453
+ description: fingerprint.description
1454
+ }
1455
+ });
1456
+ projectId = project.id;
1457
+ }
1458
+ await apiRequest(`/api/setups/project/${projectId}`, {
1123
1459
  method: "POST",
1124
1460
  body: {
1125
1461
  targetAgent,
@@ -1204,75 +1540,117 @@ function promptAgent() {
1204
1540
  });
1205
1541
  });
1206
1542
  }
1207
- function promptAction() {
1543
+ function promptAction(explained) {
1208
1544
  return new Promise((resolve2) => {
1209
1545
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1210
1546
  console.log(chalk2.bold("What would you like to do?"));
1211
- console.log(" 1. Accept and apply");
1212
- console.log(" 2. Refine via chat");
1213
- console.log(" 3. Explain recommendations");
1214
- console.log(" 4. Decline");
1215
- rl.question(chalk2.cyan("\nChoose (1-4): "), (answer) => {
1216
- rl.close();
1217
- const map = {
1218
- "1": "accept",
1219
- "2": "refine",
1220
- "3": "explain",
1221
- "4": "decline"
1222
- };
1223
- resolve2(map[answer.trim()] || "accept");
1224
- });
1547
+ if (explained) {
1548
+ console.log(" 1. Accept and apply");
1549
+ console.log(" 2. Refine via chat");
1550
+ console.log(" 3. Decline");
1551
+ rl.question(chalk2.cyan("\nChoose (1-3): "), (answer) => {
1552
+ rl.close();
1553
+ const map = {
1554
+ "1": "accept",
1555
+ "2": "refine",
1556
+ "3": "decline"
1557
+ };
1558
+ resolve2(map[answer.trim()] || "accept");
1559
+ });
1560
+ } else {
1561
+ console.log(" 1. Accept and apply");
1562
+ console.log(" 2. Refine via chat");
1563
+ console.log(" 3. Explain recommendations");
1564
+ console.log(" 4. Decline");
1565
+ rl.question(chalk2.cyan("\nChoose (1-4): "), (answer) => {
1566
+ rl.close();
1567
+ const map = {
1568
+ "1": "accept",
1569
+ "2": "refine",
1570
+ "3": "explain",
1571
+ "4": "decline"
1572
+ };
1573
+ resolve2(map[answer.trim()] || "accept");
1574
+ });
1575
+ }
1225
1576
  });
1226
1577
  }
1578
+ function fileEntry(filePath, desc) {
1579
+ const icon = fs13.existsSync(filePath) ? chalk2.yellow("~") : chalk2.green("+");
1580
+ const description = desc ? chalk2.dim(`\u2014 ${desc}`) : "";
1581
+ return ` ${icon} ${filePath} ${description}`;
1582
+ }
1227
1583
  function printSetupSummary(setup) {
1228
1584
  const claude = setup.claude;
1229
1585
  const cursor = setup.cursor;
1586
+ const descriptions = setup.fileDescriptions || {};
1230
1587
  console.log("");
1231
1588
  if (claude) {
1232
1589
  if (claude.claudeMd) {
1233
- console.log(` ${chalk2.green("+")} CLAUDE.md ${chalk2.dim("\u2014 project guidelines, commands, conventions")}`);
1590
+ console.log(fileEntry("CLAUDE.md", descriptions["CLAUDE.md"]));
1234
1591
  }
1235
1592
  if (claude.settings) {
1236
- console.log(` ${chalk2.green("+")} .claude/settings.json ${chalk2.dim("\u2014 tool permissions & hooks")}`);
1593
+ console.log(fileEntry(".claude/settings.json", descriptions[".claude/settings.json"]));
1237
1594
  }
1238
1595
  if (claude.settingsLocal) {
1239
- console.log(` ${chalk2.green("+")} .claude/settings.local.json ${chalk2.dim("\u2014 local dev permissions")}`);
1596
+ console.log(fileEntry(".claude/settings.local.json", descriptions[".claude/settings.local.json"]));
1240
1597
  }
1241
1598
  const skills = claude.skills;
1242
1599
  if (Array.isArray(skills) && skills.length > 0) {
1243
- console.log(` ${chalk2.green("+")} ${skills.length} skill${skills.length > 1 ? "s" : ""}:`);
1244
1600
  for (const skill of skills) {
1245
- console.log(` ${chalk2.dim("\u2022")} ${skill.name}`);
1601
+ const skillPath = `.claude/skills/${skill.name.replace(/[^a-z0-9-]/gi, "-").toLowerCase()}.md`;
1602
+ console.log(fileEntry(skillPath, descriptions[skillPath] || skill.name));
1246
1603
  }
1247
1604
  }
1248
1605
  const mcpServers = claude.mcpServers;
1249
1606
  if (mcpServers && Object.keys(mcpServers).length > 0) {
1250
- const names = Object.keys(mcpServers);
1251
- console.log(` ${chalk2.green("+")} ${names.length} MCP server${names.length > 1 ? "s" : ""}:`);
1252
- for (const name of names) {
1253
- console.log(` ${chalk2.dim("\u2022")} ${name}`);
1254
- }
1607
+ console.log(fileEntry(".mcp.json", descriptions[".mcp.json"] || Object.keys(mcpServers).join(", ")));
1255
1608
  }
1256
1609
  }
1257
1610
  if (cursor) {
1258
1611
  if (cursor.cursorrules) {
1259
- console.log(` ${chalk2.green("+")} .cursorrules ${chalk2.dim("\u2014 coding rules for Cursor")}`);
1612
+ console.log(fileEntry(".cursorrules", descriptions[".cursorrules"]));
1260
1613
  }
1261
1614
  const rules = cursor.rules;
1262
1615
  if (Array.isArray(rules) && rules.length > 0) {
1263
- console.log(` ${chalk2.green("+")} ${rules.length} Cursor rule${rules.length > 1 ? "s" : ""}:`);
1264
1616
  for (const rule of rules) {
1265
- console.log(` ${chalk2.dim("\u2022")} ${rule.filename}`);
1617
+ const rulePath = `.cursor/rules/${rule.filename}`;
1618
+ console.log(fileEntry(rulePath, descriptions[rulePath]));
1266
1619
  }
1267
1620
  }
1268
1621
  const mcpServers = cursor.mcpServers;
1269
1622
  if (mcpServers && Object.keys(mcpServers).length > 0) {
1270
- const names = Object.keys(mcpServers);
1271
- console.log(` ${chalk2.green("+")} ${names.length} MCP server${names.length > 1 ? "s" : ""}:`);
1272
- for (const name of names) {
1273
- console.log(` ${chalk2.dim("\u2022")} ${name}`);
1274
- }
1623
+ console.log(fileEntry(".cursor/mcp.json", descriptions[".cursor/mcp.json"] || Object.keys(mcpServers).join(", ")));
1624
+ }
1625
+ }
1626
+ console.log("");
1627
+ console.log(` ${chalk2.green("+")} ${chalk2.dim("new")} ${chalk2.yellow("~")} ${chalk2.dim("modified")}`);
1628
+ console.log("");
1629
+ }
1630
+ function printExplanation(explanation) {
1631
+ console.log(chalk2.bold("\n Why this setup?\n"));
1632
+ const lines = explanation.split("\n");
1633
+ for (const line of lines) {
1634
+ const trimmed = line.trim();
1635
+ if (!trimmed) continue;
1636
+ const headerMatch = trimmed.match(/^\[(.+)\]$/);
1637
+ if (headerMatch) {
1638
+ console.log(` ${chalk2.bold.hex("#6366f1")(headerMatch[1])}`);
1639
+ continue;
1275
1640
  }
1641
+ const itemMatch = trimmed.match(/^-\s+\*\*(.+?)\*\*[:\s]*(.*)$/);
1642
+ if (itemMatch) {
1643
+ const name = itemMatch[1];
1644
+ const desc = itemMatch[2].replace(/^\s*[-—:]\s*/, "");
1645
+ console.log(` ${chalk2.dim("\u25B8")} ${chalk2.white(name)} ${chalk2.dim(desc)}`);
1646
+ continue;
1647
+ }
1648
+ const plainMatch = trimmed.match(/^-\s+(.*)$/);
1649
+ if (plainMatch) {
1650
+ console.log(` ${chalk2.dim("\u25B8")} ${chalk2.dim(plainMatch[1])}`);
1651
+ continue;
1652
+ }
1653
+ console.log(` ${chalk2.dim(trimmed)}`);
1276
1654
  }
1277
1655
  console.log("");
1278
1656
  }
@@ -1305,14 +1683,150 @@ function undoCommand() {
1305
1683
  console.log("");
1306
1684
  } catch (err) {
1307
1685
  spinner.fail(chalk3.red(err instanceof Error ? err.message : "Undo failed"));
1308
- process.exit(1);
1686
+ throw new Error("__exit__");
1309
1687
  }
1310
1688
  }
1311
1689
 
1312
1690
  // src/commands/status.ts
1313
1691
  import chalk4 from "chalk";
1314
- import fs11 from "fs";
1315
- function statusCommand(options) {
1692
+ import fs15 from "fs";
1693
+
1694
+ // src/scanner/index.ts
1695
+ import fs14 from "fs";
1696
+ import path12 from "path";
1697
+ import crypto4 from "crypto";
1698
+ function scanLocalState(dir) {
1699
+ const items = [];
1700
+ const claudeMdPath = path12.join(dir, "CLAUDE.md");
1701
+ if (fs14.existsSync(claudeMdPath)) {
1702
+ items.push({
1703
+ type: "rule",
1704
+ platform: "claude",
1705
+ name: "CLAUDE.md",
1706
+ contentHash: hashFile(claudeMdPath),
1707
+ path: claudeMdPath
1708
+ });
1709
+ }
1710
+ const settingsPath = path12.join(dir, ".claude", "settings.json");
1711
+ if (fs14.existsSync(settingsPath)) {
1712
+ items.push({
1713
+ type: "config",
1714
+ platform: "claude",
1715
+ name: "settings.json",
1716
+ contentHash: hashFile(settingsPath),
1717
+ path: settingsPath
1718
+ });
1719
+ }
1720
+ const skillsDir = path12.join(dir, ".claude", "skills");
1721
+ if (fs14.existsSync(skillsDir)) {
1722
+ for (const file of fs14.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
1723
+ const filePath = path12.join(skillsDir, file);
1724
+ items.push({
1725
+ type: "skill",
1726
+ platform: "claude",
1727
+ name: file,
1728
+ contentHash: hashFile(filePath),
1729
+ path: filePath
1730
+ });
1731
+ }
1732
+ }
1733
+ const mcpJsonPath = path12.join(dir, ".mcp.json");
1734
+ if (fs14.existsSync(mcpJsonPath)) {
1735
+ try {
1736
+ const mcpJson = JSON.parse(fs14.readFileSync(mcpJsonPath, "utf-8"));
1737
+ if (mcpJson.mcpServers) {
1738
+ for (const name of Object.keys(mcpJson.mcpServers)) {
1739
+ items.push({
1740
+ type: "mcp",
1741
+ platform: "claude",
1742
+ name,
1743
+ contentHash: hashContent(JSON.stringify(mcpJson.mcpServers[name])),
1744
+ path: mcpJsonPath
1745
+ });
1746
+ }
1747
+ }
1748
+ } catch {
1749
+ }
1750
+ }
1751
+ const cursorrulesPath = path12.join(dir, ".cursorrules");
1752
+ if (fs14.existsSync(cursorrulesPath)) {
1753
+ items.push({
1754
+ type: "rule",
1755
+ platform: "cursor",
1756
+ name: ".cursorrules",
1757
+ contentHash: hashFile(cursorrulesPath),
1758
+ path: cursorrulesPath
1759
+ });
1760
+ }
1761
+ const cursorRulesDir = path12.join(dir, ".cursor", "rules");
1762
+ if (fs14.existsSync(cursorRulesDir)) {
1763
+ for (const file of fs14.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
1764
+ const filePath = path12.join(cursorRulesDir, file);
1765
+ items.push({
1766
+ type: "rule",
1767
+ platform: "cursor",
1768
+ name: file,
1769
+ contentHash: hashFile(filePath),
1770
+ path: filePath
1771
+ });
1772
+ }
1773
+ }
1774
+ const cursorMcpPath = path12.join(dir, ".cursor", "mcp.json");
1775
+ if (fs14.existsSync(cursorMcpPath)) {
1776
+ try {
1777
+ const mcpJson = JSON.parse(fs14.readFileSync(cursorMcpPath, "utf-8"));
1778
+ if (mcpJson.mcpServers) {
1779
+ for (const name of Object.keys(mcpJson.mcpServers)) {
1780
+ items.push({
1781
+ type: "mcp",
1782
+ platform: "cursor",
1783
+ name,
1784
+ contentHash: hashContent(JSON.stringify(mcpJson.mcpServers[name])),
1785
+ path: cursorMcpPath
1786
+ });
1787
+ }
1788
+ }
1789
+ } catch {
1790
+ }
1791
+ }
1792
+ return items;
1793
+ }
1794
+ function compareState(serverItems, localItems) {
1795
+ const installed = [];
1796
+ const missing = [];
1797
+ const outdated = [];
1798
+ const extra = [];
1799
+ const localMap = /* @__PURE__ */ new Map();
1800
+ for (const item of localItems) {
1801
+ localMap.set(`${item.type}:${item.platform}:${item.name}`, item);
1802
+ }
1803
+ for (const server of serverItems) {
1804
+ const key = `${server.type}:${server.platform}:${server.name}`;
1805
+ const local = localMap.get(key);
1806
+ localMap.delete(key);
1807
+ if (!local) {
1808
+ missing.push(server);
1809
+ } else if (local.contentHash !== server.content_hash) {
1810
+ outdated.push({ server, local });
1811
+ } else {
1812
+ installed.push({ server, local });
1813
+ }
1814
+ }
1815
+ for (const local of localMap.values()) {
1816
+ extra.push(local);
1817
+ }
1818
+ return { installed, missing, outdated, extra };
1819
+ }
1820
+ function hashFile(filePath) {
1821
+ const content = fs14.readFileSync(filePath);
1822
+ return crypto4.createHash("sha256").update(content).digest("hex");
1823
+ }
1824
+ function hashContent(content) {
1825
+ return crypto4.createHash("sha256").update(JSON.stringify({ text: content })).digest("hex");
1826
+ }
1827
+
1828
+ // src/commands/status.ts
1829
+ async function statusCommand(options) {
1316
1830
  const auth2 = getStoredAuth();
1317
1831
  const manifest = readManifest();
1318
1832
  if (options.json) {
@@ -1336,10 +1850,34 @@ function statusCommand(options) {
1336
1850
  }
1337
1851
  console.log(` Files managed: ${chalk4.cyan(manifest.entries.length.toString())}`);
1338
1852
  for (const entry of manifest.entries) {
1339
- const exists = fs11.existsSync(entry.path);
1853
+ const exists = fs15.existsSync(entry.path);
1340
1854
  const icon = exists ? chalk4.green("\u2713") : chalk4.red("\u2717");
1341
1855
  console.log(` ${icon} ${entry.path} (${entry.action})`);
1342
1856
  }
1857
+ if (auth2) {
1858
+ try {
1859
+ const fingerprint = collectFingerprint(process.cwd());
1860
+ const hash = computeFingerprintHash(fingerprint);
1861
+ const match = await apiRequest(
1862
+ "/api/projects/match",
1863
+ { method: "POST", body: { fingerprintHash: hash } }
1864
+ );
1865
+ if (match?.project) {
1866
+ const serverItems = await apiRequest(
1867
+ `/api/sync/project/${match.project.id}/items`
1868
+ );
1869
+ if (serverItems?.length) {
1870
+ const localItems = scanLocalState(process.cwd());
1871
+ const diff = compareState(serverItems, localItems);
1872
+ console.log(chalk4.bold("\n Sync"));
1873
+ console.log(` ${chalk4.green("\u2713")} Installed: ${diff.installed.length}`);
1874
+ if (diff.missing.length) console.log(` ${chalk4.red("\u2717")} Missing: ${diff.missing.length}`);
1875
+ if (diff.outdated.length) console.log(` ${chalk4.yellow("~")} Outdated: ${diff.outdated.length}`);
1876
+ }
1877
+ }
1878
+ } catch {
1879
+ }
1880
+ }
1343
1881
  console.log("");
1344
1882
  }
1345
1883
 
@@ -1351,12 +1889,12 @@ async function updateCommand(options) {
1351
1889
  const auth2 = getStoredAuth();
1352
1890
  if (!auth2) {
1353
1891
  console.log(chalk5.red("Not logged in. Run `caliber login` first."));
1354
- process.exit(1);
1892
+ throw new Error("__exit__");
1355
1893
  }
1356
1894
  const manifest = readManifest();
1357
1895
  if (!manifest) {
1358
1896
  console.log(chalk5.yellow("No existing setup found. Run `caliber init` first."));
1359
- process.exit(1);
1897
+ throw new Error("__exit__");
1360
1898
  }
1361
1899
  const spinner = ora4("Re-analyzing project...").start();
1362
1900
  const fingerprint = collectFingerprint(process.cwd());
@@ -1374,41 +1912,43 @@ async function updateCommand(options) {
1374
1912
  });
1375
1913
  let generatedSetup = null;
1376
1914
  trackEvent("generation_started", { target_agent: "both" });
1377
- const generationStart = Date.now();
1378
1915
  const genSpinner = ora4("Regenerating setup...").start();
1379
1916
  const genMessages = new SpinnerMessages(genSpinner, GENERATION_MESSAGES);
1380
1917
  genMessages.start();
1381
- await apiStream(
1382
- "/api/setups/generate",
1383
- {
1384
- fingerprint,
1385
- targetAgent: "both"
1386
- },
1387
- () => {
1388
- },
1389
- (payload) => {
1390
- generatedSetup = payload.setup;
1391
- },
1392
- (error) => {
1393
- genMessages.stop();
1394
- trackEvent("error_occurred", { error_type: "generation_failed", error_message: error });
1395
- genSpinner.fail(`Generation error: ${error}`);
1396
- },
1397
- (status) => {
1398
- genMessages.handleServerStatus(status);
1399
- }
1400
- );
1918
+ try {
1919
+ await apiStream(
1920
+ "/api/setups/generate",
1921
+ {
1922
+ fingerprint,
1923
+ targetAgent: "both"
1924
+ },
1925
+ () => {
1926
+ },
1927
+ (payload) => {
1928
+ generatedSetup = payload.setup;
1929
+ },
1930
+ (error) => {
1931
+ genMessages.stop();
1932
+ trackEvent("error_occurred", { error_type: "generation_failed", error_message: error, command: "update" });
1933
+ genSpinner.fail(`Generation error: ${error}`);
1934
+ },
1935
+ (status) => {
1936
+ genMessages.handleServerStatus(status);
1937
+ }
1938
+ );
1939
+ } catch (err) {
1940
+ genMessages.stop();
1941
+ const msg = err instanceof Error ? err.message : "Unknown error";
1942
+ trackEvent("error_occurred", { error_type: "generation_request_failed", error_message: msg, command: "update" });
1943
+ genSpinner.fail(`Regeneration failed: ${msg}`);
1944
+ throw new Error("__exit__");
1945
+ }
1401
1946
  genMessages.stop();
1402
1947
  if (!generatedSetup) {
1403
1948
  genSpinner.fail("Failed to regenerate setup.");
1404
- process.exit(1);
1949
+ throw new Error("__exit__");
1405
1950
  }
1406
1951
  genSpinner.succeed("Setup regenerated");
1407
- trackEvent("generation_completed", {
1408
- target_agent: "both",
1409
- duration_ms: Date.now() - generationStart,
1410
- files_suggested: countSuggestedFiles(generatedSetup)
1411
- });
1412
1952
  if (options.dryRun) {
1413
1953
  console.log(chalk5.yellow("\n[Dry run] Would write:"));
1414
1954
  console.log(JSON.stringify(generatedSetup, null, 2));
@@ -1420,7 +1960,7 @@ async function updateCommand(options) {
1420
1960
  });
1421
1961
  rl.close();
1422
1962
  if (answer.trim().toLowerCase() !== "y") {
1423
- trackEvent("generation_cancelled");
1963
+ trackEvent("update_declined");
1424
1964
  console.log(chalk5.dim("Update cancelled."));
1425
1965
  return;
1426
1966
  }
@@ -1447,32 +1987,454 @@ function logoutCommand() {
1447
1987
  console.log(chalk6.green("Logged out successfully."));
1448
1988
  }
1449
1989
 
1990
+ // src/commands/recommend.ts
1991
+ import chalk7 from "chalk";
1992
+ import ora5 from "ora";
1993
+ async function recommendCommand(options) {
1994
+ const auth2 = getStoredAuth();
1995
+ if (!auth2) {
1996
+ console.log(chalk7.red("Not authenticated. Run `caliber login` first."));
1997
+ throw new Error("__exit__");
1998
+ }
1999
+ const fingerprint = collectFingerprint(process.cwd());
2000
+ const hash = computeFingerprintHash(fingerprint);
2001
+ const match = await apiRequest(
2002
+ "/api/projects/match",
2003
+ { method: "POST", body: { fingerprintHash: hash } }
2004
+ );
2005
+ if (!match?.project) {
2006
+ console.log(chalk7.yellow("No project found. Run `caliber init` first."));
2007
+ throw new Error("__exit__");
2008
+ }
2009
+ const projectId = match.project.id;
2010
+ if (options.generate) {
2011
+ const spinner = ora5("Detecting technologies and searching for skills...").start();
2012
+ try {
2013
+ const recs2 = await apiRequest(
2014
+ `/api/recommendations/project/${projectId}/generate`,
2015
+ { method: "POST" }
2016
+ );
2017
+ spinner.succeed(`Found ${recs2?.length || 0} recommendations`);
2018
+ if (recs2?.length) {
2019
+ console.log("");
2020
+ printRecommendations(recs2);
2021
+ }
2022
+ } catch (err) {
2023
+ spinner.fail("Failed to generate recommendations");
2024
+ throw err;
2025
+ }
2026
+ return;
2027
+ }
2028
+ const statusFilter = options.status || "pending";
2029
+ const recs = await apiRequest(
2030
+ `/api/recommendations/project/${projectId}?status=${statusFilter}`
2031
+ );
2032
+ if (!recs?.length) {
2033
+ console.log(chalk7.dim(`
2034
+ No ${statusFilter} recommendations. Run \`caliber recommend --generate\` to discover skills.
2035
+ `));
2036
+ return;
2037
+ }
2038
+ printRecommendations(recs);
2039
+ }
2040
+ function printRecommendations(recs) {
2041
+ console.log(chalk7.bold("\n Skill Recommendations\n"));
2042
+ console.log(
2043
+ ` ${chalk7.dim("Name".padEnd(30))} ${chalk7.dim("Score".padEnd(8))} ${chalk7.dim("Technology".padEnd(15))} ${chalk7.dim("Status")}`
2044
+ );
2045
+ console.log(chalk7.dim(" " + "\u2500".repeat(70)));
2046
+ for (const rec of recs) {
2047
+ const scoreColor = rec.score >= 90 ? chalk7.green : rec.score >= 70 ? chalk7.blue : chalk7.yellow;
2048
+ console.log(
2049
+ ` ${rec.skill_name.padEnd(30)} ${scoreColor(`${rec.score}%`.padEnd(8))} ${rec.detected_technology.padEnd(15)} ${rec.status}`
2050
+ );
2051
+ if (rec.reason) {
2052
+ console.log(` ${chalk7.dim(rec.reason)}`);
2053
+ }
2054
+ }
2055
+ console.log("");
2056
+ }
2057
+
2058
+ // src/commands/health.ts
2059
+ import chalk8 from "chalk";
2060
+ import ora6 from "ora";
2061
+ async function healthCommand(options) {
2062
+ const auth2 = getStoredAuth();
2063
+ if (!auth2) {
2064
+ console.log(chalk8.red("Not authenticated. Run `caliber login` first."));
2065
+ throw new Error("__exit__");
2066
+ }
2067
+ const fingerprint = collectFingerprint(process.cwd());
2068
+ const hash = computeFingerprintHash(fingerprint);
2069
+ const match = await apiRequest(
2070
+ "/api/projects/match",
2071
+ { method: "POST", body: { fingerprintHash: hash } }
2072
+ );
2073
+ if (!match?.project) {
2074
+ console.log(chalk8.yellow("No project found. Run `caliber init` first."));
2075
+ throw new Error("__exit__");
2076
+ }
2077
+ const projectId = match.project.id;
2078
+ const spinner = ora6("Analyzing context health...").start();
2079
+ let report;
2080
+ try {
2081
+ report = await apiRequest(
2082
+ `/api/context/project/${projectId}/analyze`,
2083
+ { method: "POST" }
2084
+ );
2085
+ spinner.succeed("Analysis complete");
2086
+ } catch (err) {
2087
+ spinner.fail("Analysis failed");
2088
+ throw err;
2089
+ }
2090
+ if (options.json) {
2091
+ console.log(JSON.stringify(report, null, 2));
2092
+ return;
2093
+ }
2094
+ printReport(report);
2095
+ if (options.fix) {
2096
+ console.log(chalk8.bold("\nGenerating fix plan..."));
2097
+ const plan = await apiRequest(
2098
+ `/api/context/reports/${report.id}/fix-plan`,
2099
+ { method: "POST", body: { projectId } }
2100
+ );
2101
+ if (!plan?.actions.length) {
2102
+ console.log(chalk8.dim("No fixes needed."));
2103
+ return;
2104
+ }
2105
+ console.log(chalk8.bold("\nProposed actions:"));
2106
+ for (const action of plan.actions) {
2107
+ const icon = action.type === "remove" ? chalk8.red("- ") : action.type === "add" ? chalk8.green("+ ") : chalk8.yellow("~ ");
2108
+ console.log(` ${icon}${action.type}: ${action.items.join(", ")}`);
2109
+ }
2110
+ console.log(chalk8.dim(`
2111
+ Estimated improvement: +${plan.estimatedScoreImprovement} points`));
2112
+ const fixSpinner = ora6("Executing fix plan...").start();
2113
+ try {
2114
+ const result = await apiRequest(
2115
+ `/api/context/reports/${report.id}/fix-execute`,
2116
+ { method: "POST", body: { projectId } }
2117
+ );
2118
+ fixSpinner.succeed("Fix applied");
2119
+ console.log("");
2120
+ console.log(` Score: ${result.scoreBefore} \u2192 ${chalk8.green(String(result.scoreAfter))} (${chalk8.green(`+${result.improvement}`)})`);
2121
+ if (result.itemsRemoved) console.log(` Removed: ${result.itemsRemoved} items`);
2122
+ if (result.itemsConsolidated) console.log(` Consolidated: ${result.itemsConsolidated} items`);
2123
+ if (result.itemsAdded) console.log(` Added: ${result.itemsAdded} items`);
2124
+ console.log("");
2125
+ } catch (err) {
2126
+ fixSpinner.fail("Fix execution failed");
2127
+ throw err;
2128
+ }
2129
+ }
2130
+ }
2131
+ function printReport(report) {
2132
+ const gradeColors = {
2133
+ A: chalk8.green,
2134
+ B: chalk8.blue,
2135
+ C: chalk8.yellow,
2136
+ D: chalk8.hex("#FFA500"),
2137
+ F: chalk8.red
2138
+ };
2139
+ const gradeColor = gradeColors[report.grade] || chalk8.white;
2140
+ console.log(chalk8.bold("\n Context Health Report\n"));
2141
+ console.log(` Grade: ${gradeColor(report.grade)} Score: ${gradeColor(String(report.score))}/100
2142
+ `);
2143
+ if (Object.keys(report.category_breakdown).length) {
2144
+ console.log(chalk8.dim(" Category Breakdown:"));
2145
+ const categories = Object.entries(report.category_breakdown);
2146
+ const maxCount = Math.max(...categories.map(([, v]) => v.count));
2147
+ for (const [name, data] of categories) {
2148
+ const barLen = maxCount > 0 ? Math.round(data.count / maxCount * 20) : 0;
2149
+ const bar = "\u2588".repeat(barLen) + "\u2591".repeat(20 - barLen);
2150
+ console.log(` ${name.padEnd(12)} ${bar} ${data.count} items (${data.tokens} tokens)`);
2151
+ }
2152
+ console.log("");
2153
+ }
2154
+ if (report.recommendations.length) {
2155
+ console.log(chalk8.dim(" Recommendations:"));
2156
+ for (const rec of report.recommendations) {
2157
+ const icon = rec.priority === "high" ? chalk8.red("!") : rec.priority === "medium" ? chalk8.yellow("~") : chalk8.dim("-");
2158
+ console.log(` ${icon} ${rec.description}`);
2159
+ }
2160
+ console.log("");
2161
+ }
2162
+ }
2163
+
2164
+ // src/commands/sync.ts
2165
+ import chalk9 from "chalk";
2166
+ import ora7 from "ora";
2167
+ async function syncCommand(options) {
2168
+ const auth2 = getStoredAuth();
2169
+ if (!auth2) {
2170
+ console.log(chalk9.red("Not authenticated. Run `caliber login` first."));
2171
+ throw new Error("__exit__");
2172
+ }
2173
+ const fingerprint = collectFingerprint(process.cwd());
2174
+ const hash = computeFingerprintHash(fingerprint);
2175
+ const match = await apiRequest(
2176
+ "/api/projects/match",
2177
+ { method: "POST", body: { fingerprintHash: hash } }
2178
+ );
2179
+ if (!match?.project) {
2180
+ console.log(chalk9.yellow("No project found. Run `caliber init` first."));
2181
+ throw new Error("__exit__");
2182
+ }
2183
+ const projectId = match.project.id;
2184
+ const spinner = ora7("Scanning local state...").start();
2185
+ const localItems = scanLocalState(process.cwd());
2186
+ spinner.text = "Fetching server items...";
2187
+ const serverItems = await apiRequest(
2188
+ `/api/sync/project/${projectId}/items`
2189
+ );
2190
+ spinner.succeed(`Found ${localItems.length} local items, ${serverItems?.length || 0} server items`);
2191
+ if (!serverItems?.length) {
2192
+ console.log(chalk9.dim("\nNo items configured on server. Run `caliber init` to set up your project.\n"));
2193
+ return;
2194
+ }
2195
+ const platformFilter = options.platform;
2196
+ const filteredServer = platformFilter ? serverItems.filter((i) => i.platform === platformFilter || i.platform === "both") : serverItems;
2197
+ const filteredLocal = platformFilter ? localItems.filter((i) => i.platform === platformFilter) : localItems;
2198
+ const diff = compareState(filteredServer, filteredLocal);
2199
+ printDiff(diff);
2200
+ if (diff.missing.length === 0 && diff.outdated.length === 0) {
2201
+ console.log(chalk9.green("\nAll items synced.\n"));
2202
+ await reportToServer(projectId, filteredServer, diff);
2203
+ return;
2204
+ }
2205
+ if (options.dryRun) {
2206
+ console.log(chalk9.dim("\nDry run \u2014 no changes made.\n"));
2207
+ return;
2208
+ }
2209
+ const installSpinner = ora7("Installing missing and outdated items...").start();
2210
+ try {
2211
+ const setup = buildSetupFromItems([...diff.missing, ...diff.outdated.map((o) => o.server)]);
2212
+ if (setup) {
2213
+ writeSetup(setup);
2214
+ }
2215
+ installSpinner.succeed("Items synced successfully");
2216
+ } catch (err) {
2217
+ installSpinner.fail("Sync failed");
2218
+ throw err;
2219
+ }
2220
+ await reportToServer(projectId, filteredServer, diff);
2221
+ }
2222
+ async function reportToServer(projectId, serverItems, diff) {
2223
+ const items = [
2224
+ ...diff.installed.map((i) => ({
2225
+ itemId: i.server.id,
2226
+ state: "installed",
2227
+ localHash: i.local.contentHash
2228
+ })),
2229
+ ...diff.missing.map((i) => ({
2230
+ itemId: i.id,
2231
+ state: "missing"
2232
+ })),
2233
+ ...diff.outdated.map((i) => ({
2234
+ itemId: i.server.id,
2235
+ state: "outdated",
2236
+ localHash: i.local.contentHash
2237
+ })),
2238
+ ...diff.extra.map((i) => ({
2239
+ itemId: i.name,
2240
+ state: "extra",
2241
+ localHash: i.contentHash
2242
+ }))
2243
+ ];
2244
+ const platforms = new Set(serverItems.map((i) => i.platform));
2245
+ for (const platform of platforms) {
2246
+ try {
2247
+ await apiRequest(`/api/sync/project/${projectId}/report`, {
2248
+ method: "POST",
2249
+ body: {
2250
+ platform,
2251
+ items: items.filter((i) => {
2252
+ const match = serverItems.find((s) => s.id === i.itemId);
2253
+ return match?.platform === platform;
2254
+ })
2255
+ }
2256
+ });
2257
+ } catch {
2258
+ }
2259
+ }
2260
+ }
2261
+ function printDiff(diff) {
2262
+ console.log(chalk9.bold("\n Sync Status\n"));
2263
+ if (diff.installed.length) {
2264
+ console.log(` ${chalk9.green("\u2713")} Installed: ${diff.installed.length}`);
2265
+ }
2266
+ if (diff.missing.length) {
2267
+ console.log(` ${chalk9.red("\u2717")} Missing: ${diff.missing.length}`);
2268
+ for (const item of diff.missing) {
2269
+ console.log(` ${chalk9.red("-")} ${item.name} (${item.type}/${item.platform})`);
2270
+ }
2271
+ }
2272
+ if (diff.outdated.length) {
2273
+ console.log(` ${chalk9.yellow("~")} Outdated: ${diff.outdated.length}`);
2274
+ for (const item of diff.outdated) {
2275
+ console.log(` ${chalk9.yellow("~")} ${item.server.name} (${item.server.type}/${item.server.platform})`);
2276
+ }
2277
+ }
2278
+ if (diff.extra.length) {
2279
+ console.log(` ${chalk9.dim("+")} Extra (local only): ${diff.extra.length}`);
2280
+ for (const item of diff.extra) {
2281
+ console.log(` ${chalk9.dim("+")} ${item.name} (${item.type}/${item.platform})`);
2282
+ }
2283
+ }
2284
+ console.log("");
2285
+ }
2286
+ function buildSetupFromItems(items) {
2287
+ if (!items.length) return null;
2288
+ const claude = {};
2289
+ const cursor = {};
2290
+ let hasAny = false;
2291
+ for (const item of items) {
2292
+ const content = item.content;
2293
+ if (item.platform === "claude" || item.platform === "both") {
2294
+ if (item.type === "rule" && item.name === "CLAUDE.md") {
2295
+ claude.claudeMd = content.text;
2296
+ hasAny = true;
2297
+ } else if (item.type === "config" && item.name === "settings.json") {
2298
+ claude.settings = content;
2299
+ hasAny = true;
2300
+ } else if (item.type === "skill") {
2301
+ if (!claude.skills) claude.skills = [];
2302
+ claude.skills.push({
2303
+ name: item.name,
2304
+ content: content.text || ""
2305
+ });
2306
+ hasAny = true;
2307
+ } else if (item.type === "mcp") {
2308
+ if (!claude.mcpServers) claude.mcpServers = {};
2309
+ claude.mcpServers[item.name] = content;
2310
+ hasAny = true;
2311
+ }
2312
+ }
2313
+ if (item.platform === "cursor" || item.platform === "both") {
2314
+ if (item.type === "rule" && item.name === ".cursorrules") {
2315
+ cursor.cursorrules = content.text;
2316
+ hasAny = true;
2317
+ } else if (item.type === "rule") {
2318
+ if (!cursor.rules) cursor.rules = [];
2319
+ cursor.rules.push({
2320
+ filename: item.name,
2321
+ content: content.text || ""
2322
+ });
2323
+ hasAny = true;
2324
+ } else if (item.type === "mcp") {
2325
+ if (!cursor.mcpServers) cursor.mcpServers = {};
2326
+ cursor.mcpServers[item.name] = content;
2327
+ hasAny = true;
2328
+ }
2329
+ }
2330
+ }
2331
+ if (!hasAny) return null;
2332
+ const hasClaude = Object.keys(claude).length > 0;
2333
+ const hasCursor = Object.keys(cursor).length > 0;
2334
+ return {
2335
+ targetAgent: hasClaude && hasCursor ? "both" : hasClaude ? "claude" : "cursor",
2336
+ ...hasClaude ? { claude } : {},
2337
+ ...hasCursor ? { cursor } : {}
2338
+ };
2339
+ }
2340
+
2341
+ // src/commands/diff.ts
2342
+ import chalk10 from "chalk";
2343
+ import ora8 from "ora";
2344
+ async function diffCommand(options) {
2345
+ const auth2 = getStoredAuth();
2346
+ if (!auth2) {
2347
+ console.log(chalk10.red("Not authenticated. Run `caliber login` first."));
2348
+ throw new Error("__exit__");
2349
+ }
2350
+ const fingerprint = collectFingerprint(process.cwd());
2351
+ const hash = computeFingerprintHash(fingerprint);
2352
+ const match = await apiRequest(
2353
+ "/api/projects/match",
2354
+ { method: "POST", body: { fingerprintHash: hash } }
2355
+ );
2356
+ if (!match?.project) {
2357
+ console.log(chalk10.yellow("No project found. Run `caliber init` first."));
2358
+ throw new Error("__exit__");
2359
+ }
2360
+ const projectId = match.project.id;
2361
+ const spinner = ora8("Comparing local and server state...").start();
2362
+ const localItems = scanLocalState(process.cwd());
2363
+ const serverItems = await apiRequest(
2364
+ `/api/sync/project/${projectId}/items`
2365
+ );
2366
+ spinner.stop();
2367
+ if (!serverItems?.length) {
2368
+ console.log(chalk10.dim("\nNo items configured on server.\n"));
2369
+ return;
2370
+ }
2371
+ const platformFilter = options.platform;
2372
+ const filteredServer = platformFilter ? serverItems.filter((i) => i.platform === platformFilter || i.platform === "both") : serverItems;
2373
+ const filteredLocal = platformFilter ? localItems.filter((i) => i.platform === platformFilter) : localItems;
2374
+ const diff = compareState(filteredServer, filteredLocal);
2375
+ console.log(chalk10.bold("\n Config Diff\n"));
2376
+ if (diff.installed.length) {
2377
+ console.log(` ${chalk10.green("\u2713")} In sync: ${diff.installed.length}`);
2378
+ }
2379
+ if (diff.missing.length) {
2380
+ console.log(` ${chalk10.red("\u2717")} Missing locally: ${diff.missing.length}`);
2381
+ for (const item of diff.missing) {
2382
+ console.log(` ${chalk10.red("-")} ${item.name} (${item.type}/${item.platform})`);
2383
+ }
2384
+ }
2385
+ if (diff.outdated.length) {
2386
+ console.log(` ${chalk10.yellow("~")} Outdated: ${diff.outdated.length}`);
2387
+ for (const item of diff.outdated) {
2388
+ console.log(` ${chalk10.yellow("~")} ${item.server.name} (${item.server.type}/${item.server.platform})`);
2389
+ }
2390
+ }
2391
+ if (diff.extra.length) {
2392
+ console.log(` ${chalk10.dim("+")} Local only: ${diff.extra.length}`);
2393
+ for (const item of diff.extra) {
2394
+ console.log(` ${chalk10.dim("+")} ${item.name} (${item.type}/${item.platform})`);
2395
+ }
2396
+ }
2397
+ if (diff.missing.length === 0 && diff.outdated.length === 0) {
2398
+ console.log(chalk10.green("\n Everything is in sync.\n"));
2399
+ } else {
2400
+ console.log(chalk10.dim("\n Run `caliber sync` to apply changes.\n"));
2401
+ }
2402
+ }
2403
+
1450
2404
  // src/cli.ts
1451
- var pkg2 = JSON.parse(
1452
- fs12.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
2405
+ var __dirname2 = path13.dirname(fileURLToPath3(import.meta.url));
2406
+ var pkg3 = JSON.parse(
2407
+ fs16.readFileSync(path13.resolve(__dirname2, "..", "package.json"), "utf-8")
1453
2408
  );
1454
2409
  var program = new Command();
1455
- program.name("caliber").description("Configure your coding agent environment").version(pkg2.version);
2410
+ program.name("caliber").description("Configure your coding agent environment").version(pkg3.version);
1456
2411
  program.command("init").description("Initialize coding agent setup for this project").option("--agent <type>", "Target agent: claude, cursor, or both").option("--dry-run", "Preview changes without writing files").option("--force", "Overwrite existing setup without prompting").action(initCommand);
1457
2412
  program.command("undo").description("Revert all config changes made by Caliber").action(undoCommand);
1458
2413
  program.command("status").description("Show current Caliber setup status").option("--json", "Output as JSON").action(statusCommand);
1459
2414
  program.command("update").description("Re-analyze project and update setup").option("--dry-run", "Preview changes without writing files").action(updateCommand);
1460
2415
  program.command("login").description("Authenticate with Caliber").action(loginCommand);
1461
2416
  program.command("logout").description("Clear stored credentials").action(logoutCommand);
2417
+ program.command("recommend").description("Discover and manage skill recommendations").option("--generate", "Generate new recommendations").option("--status <status>", "Filter by status: pending, accepted, dismissed").action(recommendCommand);
2418
+ program.command("health").description("Analyze context health and quality").option("--fix", "Generate and execute a fix plan").option("--json", "Output as JSON").action(healthCommand);
2419
+ program.command("sync").description("Sync local config with server state").option("--platform <platform>", "Target platform: claude, cursor, or both").option("--dry-run", "Preview changes without writing files").action(syncCommand);
2420
+ program.command("diff").description("Compare local config with server state").option("--platform <platform>", "Target platform: claude, cursor, or both").action(diffCommand);
1462
2421
 
1463
2422
  // src/utils/version-check.ts
1464
- import fs13 from "fs";
2423
+ import fs17 from "fs";
2424
+ import path14 from "path";
2425
+ import { fileURLToPath as fileURLToPath4 } from "url";
1465
2426
  import readline3 from "readline";
1466
2427
  import { execSync as execSync2 } from "child_process";
1467
- import chalk7 from "chalk";
1468
- import ora5 from "ora";
1469
- var pkg3 = JSON.parse(
1470
- fs13.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
2428
+ import chalk11 from "chalk";
2429
+ import ora9 from "ora";
2430
+ var __dirname_vc = path14.dirname(fileURLToPath4(import.meta.url));
2431
+ var pkg4 = JSON.parse(
2432
+ fs17.readFileSync(path14.resolve(__dirname_vc, "..", "package.json"), "utf-8")
1471
2433
  );
1472
2434
  function promptYesNo(question) {
1473
2435
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
1474
2436
  return new Promise((resolve2) => {
1475
- rl.question(chalk7.cyan(`${question} `), (answer) => {
2437
+ rl.question(chalk11.cyan(`${question} `), (answer) => {
1476
2438
  rl.close();
1477
2439
  const normalized = answer.trim().toLowerCase();
1478
2440
  resolve2(normalized === "" || normalized === "y" || normalized === "yes");
@@ -1491,22 +2453,22 @@ async function checkForUpdates() {
1491
2453
  const data = await res.json();
1492
2454
  const latest = data.version;
1493
2455
  if (!latest) return;
1494
- const current = pkg3.version;
2456
+ const current = pkg4.version;
1495
2457
  if (current === latest) return;
1496
2458
  const isInteractive = process.stdin.isTTY === true;
1497
2459
  if (!isInteractive) {
1498
2460
  console.log(
1499
- chalk7.yellow(
2461
+ chalk11.yellow(
1500
2462
  `
1501
2463
  Update available: ${current} -> ${latest}
1502
- Run ${chalk7.bold("npm install -g @caliber-ai/cli")} to upgrade.
2464
+ Run ${chalk11.bold("npm install -g @caliber-ai/cli")} to upgrade.
1503
2465
  `
1504
2466
  )
1505
2467
  );
1506
2468
  return;
1507
2469
  }
1508
2470
  console.log(
1509
- chalk7.yellow(`
2471
+ chalk11.yellow(`
1510
2472
  Update available: ${current} -> ${latest}`)
1511
2473
  );
1512
2474
  const shouldUpdate = await promptYesNo("Would you like to update now? (Y/n)");
@@ -1514,12 +2476,12 @@ Update available: ${current} -> ${latest}`)
1514
2476
  console.log();
1515
2477
  return;
1516
2478
  }
1517
- const spinner = ora5("Updating @caliber-ai/cli...").start();
2479
+ const spinner = ora9("Updating @caliber-ai/cli...").start();
1518
2480
  try {
1519
2481
  execSync2("npm install -g @caliber-ai/cli", { stdio: "pipe" });
1520
- spinner.succeed(chalk7.green(`Updated to ${latest}`));
2482
+ spinner.succeed(chalk11.green(`Updated to ${latest}`));
1521
2483
  const args = process.argv.slice(2);
1522
- console.log(chalk7.dim(`
2484
+ console.log(chalk11.dim(`
1523
2485
  Restarting: caliber ${args.join(" ")}
1524
2486
  `));
1525
2487
  execSync2(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
@@ -1529,8 +2491,8 @@ Restarting: caliber ${args.join(" ")}
1529
2491
  } catch {
1530
2492
  spinner.fail("Update failed");
1531
2493
  console.log(
1532
- chalk7.yellow(
1533
- `Run ${chalk7.bold("npm install -g @caliber-ai/cli")} manually to upgrade.
2494
+ chalk11.yellow(
2495
+ `Run ${chalk11.bold("npm install -g @caliber-ai/cli")} manually to upgrade.
1534
2496
  `
1535
2497
  )
1536
2498
  );
@@ -1548,12 +2510,25 @@ if (firstRun) {
1548
2510
  var auth = getStoredAuth();
1549
2511
  if (auth) {
1550
2512
  identifyUser(auth.userId, auth.email);
2513
+ Sentry.setUser({ id: auth.userId, email: auth.email });
1551
2514
  }
1552
2515
  await checkForUpdates();
1553
- process.on("SIGINT", () => process.exit(130));
1554
- process.on("SIGTERM", () => process.exit(143));
1555
- program.parseAsync().finally(async () => {
1556
- await shutdownTelemetry();
1557
- process.exit(0);
1558
- });
2516
+ async function gracefulExit(code) {
2517
+ await Promise.all([
2518
+ shutdownTelemetry(),
2519
+ Sentry.flush(2e3)
2520
+ ]);
2521
+ process.exit(code);
2522
+ }
2523
+ process.on("SIGINT", () => gracefulExit(130));
2524
+ process.on("SIGTERM", () => gracefulExit(143));
2525
+ program.parseAsync().catch((err) => {
2526
+ const msg = err instanceof Error ? err.message : "Unexpected error";
2527
+ if (msg !== "__exit__") {
2528
+ console.error(msg);
2529
+ Sentry.captureException(err, { tags: { command: process.argv[2] || "unknown" } });
2530
+ }
2531
+ trackEvent("error_occurred", { error_type: "cli_crash", error_message: msg, command: process.argv[2] || "unknown" });
2532
+ process.exitCode = 1;
2533
+ }).finally(() => gracefulExit(Number(process.exitCode ?? 0)));
1559
2534
  //# sourceMappingURL=bin.js.map