@caliber-ai/cli 0.2.1 → 0.3.1
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.
- package/dist/bin.js +1280 -305
- package/dist/bin.js.map +1 -1
- 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
|
|
36
|
-
import
|
|
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 =
|
|
45
|
-
AUTH_FILE =
|
|
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 =
|
|
48
|
-
BACKUPS_DIR =
|
|
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
|
|
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
|
|
86
|
+
import fs13 from "fs";
|
|
61
87
|
|
|
62
88
|
// src/auth/token-store.ts
|
|
63
89
|
init_constants();
|
|
64
|
-
import
|
|
90
|
+
import fs2 from "fs";
|
|
65
91
|
function getStoredAuth() {
|
|
66
92
|
try {
|
|
67
|
-
if (!
|
|
68
|
-
const data = JSON.parse(
|
|
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 (!
|
|
76
|
-
|
|
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
|
-
|
|
111
|
+
fs2.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 384 });
|
|
86
112
|
}
|
|
87
113
|
function clearAuth() {
|
|
88
114
|
try {
|
|
89
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
181
|
-
import
|
|
182
|
-
import
|
|
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 =
|
|
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 (
|
|
196
|
-
return
|
|
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 (!
|
|
203
|
-
|
|
229
|
+
if (!fs3.existsSync(AUTH_DIR)) {
|
|
230
|
+
fs3.mkdirSync(AUTH_DIR, { recursive: true });
|
|
204
231
|
}
|
|
205
|
-
|
|
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 !
|
|
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
|
|
218
|
-
|
|
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:
|
|
224
|
-
os:
|
|
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
|
-
|
|
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
|
|
338
|
-
import
|
|
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
|
|
390
|
-
const allDeps = { ...
|
|
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
|
-
|
|
404
|
-
|
|
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 (!
|
|
421
|
+
if (!fs4.existsSync(filePath)) continue;
|
|
411
422
|
try {
|
|
412
|
-
const content =
|
|
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 =
|
|
433
|
+
const rootPkgPath = path4.join(dir, "package.json");
|
|
423
434
|
let name;
|
|
424
435
|
const allFrameworks = [];
|
|
425
436
|
const languages = [];
|
|
426
|
-
if (
|
|
437
|
+
if (fs4.existsSync(rootPkgPath)) {
|
|
427
438
|
try {
|
|
428
|
-
const
|
|
429
|
-
name =
|
|
430
|
-
const allDeps = { ...
|
|
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
|
|
445
|
-
const deps = { ...
|
|
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
|
|
463
|
-
import
|
|
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 =
|
|
497
|
+
const fullPath = path5.join(base, rel);
|
|
487
498
|
let entries;
|
|
488
499
|
try {
|
|
489
|
-
entries =
|
|
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
|
|
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 =
|
|
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
|
|
543
|
-
import
|
|
553
|
+
import fs6 from "fs";
|
|
554
|
+
import path7 from "path";
|
|
544
555
|
function readExistingConfigs(dir) {
|
|
545
556
|
const configs = {};
|
|
546
|
-
const claudeMdPath =
|
|
547
|
-
if (
|
|
548
|
-
configs.claudeMd =
|
|
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 =
|
|
551
|
-
if (
|
|
561
|
+
const claudeSettingsPath = path7.join(dir, ".claude", "settings.json");
|
|
562
|
+
if (fs6.existsSync(claudeSettingsPath)) {
|
|
552
563
|
try {
|
|
553
|
-
configs.claudeSettings = JSON.parse(
|
|
564
|
+
configs.claudeSettings = JSON.parse(fs6.readFileSync(claudeSettingsPath, "utf-8"));
|
|
554
565
|
} catch {
|
|
555
566
|
}
|
|
556
567
|
}
|
|
557
|
-
const skillsDir =
|
|
558
|
-
if (
|
|
568
|
+
const skillsDir = path7.join(dir, ".claude", "skills");
|
|
569
|
+
if (fs6.existsSync(skillsDir)) {
|
|
559
570
|
try {
|
|
560
|
-
const files =
|
|
571
|
+
const files = fs6.readdirSync(skillsDir).filter((f) => f.endsWith(".md"));
|
|
561
572
|
configs.claudeSkills = files.map((f) => ({
|
|
562
573
|
filename: f,
|
|
563
|
-
content:
|
|
574
|
+
content: fs6.readFileSync(path7.join(skillsDir, f), "utf-8")
|
|
564
575
|
}));
|
|
565
576
|
} catch {
|
|
566
577
|
}
|
|
567
578
|
}
|
|
568
|
-
const cursorrulesPath =
|
|
569
|
-
if (
|
|
570
|
-
configs.cursorrules =
|
|
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 =
|
|
573
|
-
if (
|
|
583
|
+
const cursorRulesDir = path7.join(dir, ".cursor", "rules");
|
|
584
|
+
if (fs6.existsSync(cursorRulesDir)) {
|
|
574
585
|
try {
|
|
575
|
-
const files =
|
|
586
|
+
const files = fs6.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"));
|
|
576
587
|
configs.cursorRules = files.map((f) => ({
|
|
577
588
|
filename: f,
|
|
578
|
-
content:
|
|
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
|
|
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
|
|
637
|
-
const
|
|
638
|
-
if (!
|
|
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
|
-
|
|
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(
|
|
657
|
-
|
|
658
|
-
|
|
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
|
|
1017
|
+
import fs12 from "fs";
|
|
699
1018
|
|
|
700
1019
|
// src/writers/claude/index.ts
|
|
701
|
-
import
|
|
702
|
-
import
|
|
1020
|
+
import fs8 from "fs";
|
|
1021
|
+
import path9 from "path";
|
|
703
1022
|
function writeClaudeConfig(config) {
|
|
704
1023
|
const written = [];
|
|
705
|
-
|
|
1024
|
+
fs8.writeFileSync("CLAUDE.md", config.claudeMd);
|
|
706
1025
|
written.push("CLAUDE.md");
|
|
707
1026
|
const claudeDir = ".claude";
|
|
708
|
-
if (!
|
|
709
|
-
|
|
710
|
-
|
|
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(
|
|
714
|
-
|
|
715
|
-
|
|
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(
|
|
1037
|
+
written.push(path9.join(claudeDir, "settings.local.json"));
|
|
719
1038
|
if (config.skills?.length) {
|
|
720
|
-
const skillsDir =
|
|
721
|
-
if (!
|
|
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 =
|
|
725
|
-
|
|
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
|
-
|
|
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
|
|
739
|
-
import
|
|
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
|
-
|
|
1062
|
+
fs9.writeFileSync(".cursorrules", config.cursorrules);
|
|
744
1063
|
written.push(".cursorrules");
|
|
745
1064
|
}
|
|
746
1065
|
if (config.rules?.length) {
|
|
747
|
-
const rulesDir =
|
|
748
|
-
if (!
|
|
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 =
|
|
751
|
-
|
|
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 (!
|
|
1076
|
+
if (!fs9.existsSync(cursorDir)) fs9.mkdirSync(cursorDir, { recursive: true });
|
|
758
1077
|
const mcpConfig = { mcpServers: config.mcpServers };
|
|
759
|
-
|
|
760
|
-
|
|
1078
|
+
fs9.writeFileSync(
|
|
1079
|
+
path10.join(cursorDir, "mcp.json"),
|
|
761
1080
|
JSON.stringify(mcpConfig, null, 2)
|
|
762
1081
|
);
|
|
763
|
-
written.push(
|
|
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
|
|
771
|
-
import
|
|
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 =
|
|
1093
|
+
const backupDir = path11.join(BACKUPS_DIR, timestamp);
|
|
775
1094
|
for (const file of files) {
|
|
776
|
-
if (!
|
|
777
|
-
const dest =
|
|
778
|
-
const destDir =
|
|
779
|
-
if (!
|
|
780
|
-
|
|
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
|
-
|
|
1101
|
+
fs10.copyFileSync(file, dest);
|
|
783
1102
|
}
|
|
784
1103
|
return backupDir;
|
|
785
1104
|
}
|
|
786
1105
|
function restoreBackup(backupDir, file) {
|
|
787
|
-
const backupFile =
|
|
788
|
-
if (!
|
|
789
|
-
const destDir =
|
|
790
|
-
if (!
|
|
791
|
-
|
|
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
|
-
|
|
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
|
|
1118
|
+
import fs11 from "fs";
|
|
800
1119
|
import crypto3 from "crypto";
|
|
801
1120
|
function readManifest() {
|
|
802
1121
|
try {
|
|
803
|
-
if (!
|
|
804
|
-
return JSON.parse(
|
|
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 (!
|
|
811
|
-
|
|
1129
|
+
if (!fs11.existsSync(CALIBER_DIR)) {
|
|
1130
|
+
fs11.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
812
1131
|
}
|
|
813
|
-
|
|
1132
|
+
fs11.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2));
|
|
814
1133
|
}
|
|
815
1134
|
function fileChecksum(filePath) {
|
|
816
|
-
const content =
|
|
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) =>
|
|
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 (
|
|
852
|
-
|
|
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 (
|
|
863
|
-
|
|
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 (
|
|
890
|
-
const content =
|
|
1208
|
+
if (fs12.existsSync(gitignorePath)) {
|
|
1209
|
+
const content = fs12.readFileSync(gitignorePath, "utf-8");
|
|
891
1210
|
if (!content.includes(".caliber/")) {
|
|
892
|
-
|
|
1211
|
+
fs12.appendFileSync(gitignorePath, "\n# Caliber local state\n.caliber/\n");
|
|
893
1212
|
}
|
|
894
1213
|
} else {
|
|
895
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
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
|
-
|
|
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
|
|
1397
|
+
let explained = false;
|
|
1398
|
+
let action = await promptAction(explained);
|
|
1062
1399
|
while (action === "explain") {
|
|
1063
1400
|
if (setupExplanation) {
|
|
1064
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
1442
|
+
throw new Error("__exit__");
|
|
1110
1443
|
}
|
|
1111
1444
|
try {
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
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
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
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(
|
|
1590
|
+
console.log(fileEntry("CLAUDE.md", descriptions["CLAUDE.md"]));
|
|
1234
1591
|
}
|
|
1235
1592
|
if (claude.settings) {
|
|
1236
|
-
console.log(
|
|
1593
|
+
console.log(fileEntry(".claude/settings.json", descriptions[".claude/settings.json"]));
|
|
1237
1594
|
}
|
|
1238
1595
|
if (claude.settingsLocal) {
|
|
1239
|
-
console.log(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
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
|
-
|
|
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
|
|
1315
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
|
1452
|
-
|
|
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(
|
|
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
|
|
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
|
|
1468
|
-
import
|
|
1469
|
-
var
|
|
1470
|
-
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
2461
|
+
chalk11.yellow(
|
|
1500
2462
|
`
|
|
1501
2463
|
Update available: ${current} -> ${latest}
|
|
1502
|
-
Run ${
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
2482
|
+
spinner.succeed(chalk11.green(`Updated to ${latest}`));
|
|
1521
2483
|
const args = process.argv.slice(2);
|
|
1522
|
-
console.log(
|
|
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
|
-
|
|
1533
|
-
`Run ${
|
|
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
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
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
|