@caliber-ai/cli 0.14.1 → 0.16.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.
- package/dist/bin.js +117 -63
- package/dist/bin.js.map +1 -1
- package/package.json +3 -1
package/dist/bin.js
CHANGED
|
@@ -85,6 +85,7 @@ import ora2 from "ora";
|
|
|
85
85
|
import readline from "readline";
|
|
86
86
|
import select from "@inquirer/select";
|
|
87
87
|
import fs16 from "fs";
|
|
88
|
+
import { createTwoFilesPatch } from "diff";
|
|
88
89
|
|
|
89
90
|
// src/auth/token-store.ts
|
|
90
91
|
init_constants();
|
|
@@ -2066,26 +2067,38 @@ function collectSetupFiles(setup) {
|
|
|
2066
2067
|
}
|
|
2067
2068
|
return files;
|
|
2068
2069
|
}
|
|
2069
|
-
function
|
|
2070
|
+
function previewNewFile(filePath, content, maxLines = 40) {
|
|
2070
2071
|
const lines = content.split("\n");
|
|
2071
2072
|
const displayLines = lines.slice(0, maxLines);
|
|
2072
|
-
const maxWidth = Math.max(60, ...displayLines.map((l) => l.length + 4));
|
|
2073
|
-
const width = Math.min(maxWidth, 80);
|
|
2074
2073
|
console.log("");
|
|
2075
|
-
console.log(` ${chalk3.
|
|
2076
|
-
console.log(
|
|
2074
|
+
console.log(` ${chalk3.green("+ new")} ${chalk3.bold(filePath)}`);
|
|
2075
|
+
console.log(chalk3.dim(" \u2500".repeat(30)));
|
|
2077
2076
|
for (const line of displayLines) {
|
|
2078
|
-
|
|
2079
|
-
const padding = " ".repeat(Math.max(0, width - truncated.length - 3));
|
|
2080
|
-
console.log(` ${chalk3.dim("\u2502")} ${truncated}${padding}${chalk3.dim("\u2502")}`);
|
|
2077
|
+
console.log(` ${chalk3.green("+")} ${line}`);
|
|
2081
2078
|
}
|
|
2082
2079
|
if (lines.length > maxLines) {
|
|
2083
|
-
console.log(
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2080
|
+
console.log("");
|
|
2081
|
+
console.log(chalk3.dim(` ... ${lines.length - maxLines} more lines (${lines.length} total)`));
|
|
2082
|
+
}
|
|
2083
|
+
console.log("");
|
|
2084
|
+
}
|
|
2085
|
+
function previewDiff(filePath, oldContent, newContent) {
|
|
2086
|
+
const patch = createTwoFilesPatch(filePath, filePath, oldContent, newContent, "current", "proposed", { context: 3 });
|
|
2087
|
+
const patchLines = patch.split("\n").slice(2);
|
|
2088
|
+
console.log("");
|
|
2089
|
+
console.log(` ${chalk3.yellow("~ modified")} ${chalk3.bold(filePath)}`);
|
|
2090
|
+
console.log(chalk3.dim(" \u2500".repeat(30)));
|
|
2091
|
+
for (const line of patchLines) {
|
|
2092
|
+
if (line.startsWith("@@")) {
|
|
2093
|
+
console.log(` ${chalk3.cyan(line)}`);
|
|
2094
|
+
} else if (line.startsWith("+")) {
|
|
2095
|
+
console.log(` ${chalk3.green(line)}`);
|
|
2096
|
+
} else if (line.startsWith("-")) {
|
|
2097
|
+
console.log(` ${chalk3.red(line)}`);
|
|
2098
|
+
} else {
|
|
2099
|
+
console.log(` ${chalk3.dim(line)}`);
|
|
2100
|
+
}
|
|
2087
2101
|
}
|
|
2088
|
-
console.log(` ${chalk3.dim("\u2514" + "\u2500".repeat(width - 1) + "\u2518")}`);
|
|
2089
2102
|
console.log("");
|
|
2090
2103
|
}
|
|
2091
2104
|
async function promptFilePreview(setup) {
|
|
@@ -2094,11 +2107,19 @@ async function promptFilePreview(setup) {
|
|
|
2094
2107
|
console.log(chalk3.dim("\n No files to preview.\n"));
|
|
2095
2108
|
return;
|
|
2096
2109
|
}
|
|
2097
|
-
const
|
|
2098
|
-
|
|
2099
|
-
|
|
2110
|
+
const choices = files.map((f, i) => {
|
|
2111
|
+
const exists = fs16.existsSync(f.path);
|
|
2112
|
+
const icon = exists ? chalk3.yellow("~") : chalk3.green("+");
|
|
2113
|
+
return { name: `${icon} ${f.path}`, value: i };
|
|
2100
2114
|
});
|
|
2101
|
-
|
|
2115
|
+
const choice = await select({ message: "Which file to preview?", choices });
|
|
2116
|
+
const file = files[choice];
|
|
2117
|
+
if (fs16.existsSync(file.path)) {
|
|
2118
|
+
const existing = fs16.readFileSync(file.path, "utf-8");
|
|
2119
|
+
previewDiff(file.path, existing, file.content);
|
|
2120
|
+
} else {
|
|
2121
|
+
previewNewFile(file.path, file.content);
|
|
2122
|
+
}
|
|
2102
2123
|
}
|
|
2103
2124
|
|
|
2104
2125
|
// src/commands/undo.ts
|
|
@@ -2297,7 +2318,21 @@ function logoutCommand() {
|
|
|
2297
2318
|
import chalk8 from "chalk";
|
|
2298
2319
|
import ora5 from "ora";
|
|
2299
2320
|
import { mkdirSync, writeFileSync } from "fs";
|
|
2300
|
-
import { join } from "path";
|
|
2321
|
+
import { join, dirname as dirname2 } from "path";
|
|
2322
|
+
function detectLocalPlatforms() {
|
|
2323
|
+
const items = scanLocalState(process.cwd());
|
|
2324
|
+
const platforms = /* @__PURE__ */ new Set();
|
|
2325
|
+
for (const item of items) {
|
|
2326
|
+
platforms.add(item.platform);
|
|
2327
|
+
}
|
|
2328
|
+
return platforms.size > 0 ? Array.from(platforms) : ["claude"];
|
|
2329
|
+
}
|
|
2330
|
+
function getSkillPath(platform, slug) {
|
|
2331
|
+
if (platform === "cursor") {
|
|
2332
|
+
return join(".cursor", "rules", `${slug}.mdc`);
|
|
2333
|
+
}
|
|
2334
|
+
return join(".claude", "skills", `${slug}.md`);
|
|
2335
|
+
}
|
|
2301
2336
|
async function recommendCommand(options) {
|
|
2302
2337
|
const auth2 = getStoredAuth();
|
|
2303
2338
|
if (!auth2) {
|
|
@@ -2315,44 +2350,57 @@ async function recommendCommand(options) {
|
|
|
2315
2350
|
throw new Error("__exit__");
|
|
2316
2351
|
}
|
|
2317
2352
|
const projectId = match.project.id;
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
spinner.fail("Failed to generate recommendations");
|
|
2329
|
-
throw err;
|
|
2353
|
+
const platforms = detectLocalPlatforms();
|
|
2354
|
+
if (options.status) {
|
|
2355
|
+
const recs2 = await apiRequest(
|
|
2356
|
+
`/api/recommendations/project/${projectId}?status=${options.status}`
|
|
2357
|
+
);
|
|
2358
|
+
if (!recs2?.length) {
|
|
2359
|
+
console.log(chalk8.dim(`
|
|
2360
|
+
No ${options.status} recommendations.
|
|
2361
|
+
`));
|
|
2362
|
+
return;
|
|
2330
2363
|
}
|
|
2364
|
+
printRecommendations(recs2);
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
if (options.generate) {
|
|
2368
|
+
const recs2 = await generateRecommendations(projectId);
|
|
2331
2369
|
if (recs2?.length) {
|
|
2332
|
-
const
|
|
2333
|
-
if (
|
|
2334
|
-
await installSkills(
|
|
2370
|
+
const selected2 = await interactiveSelect(recs2);
|
|
2371
|
+
if (selected2?.length) {
|
|
2372
|
+
await installSkills(selected2, platforms);
|
|
2335
2373
|
}
|
|
2336
2374
|
}
|
|
2337
2375
|
return;
|
|
2338
2376
|
}
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
`/api/recommendations/project/${projectId}?status=${statusFilter}`
|
|
2377
|
+
let recs = await apiRequest(
|
|
2378
|
+
`/api/recommendations/project/${projectId}?status=pending`
|
|
2342
2379
|
);
|
|
2343
2380
|
if (!recs?.length) {
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2381
|
+
recs = await generateRecommendations(projectId);
|
|
2382
|
+
}
|
|
2383
|
+
if (!recs?.length) {
|
|
2384
|
+
console.log(chalk8.dim("\nNo recommendations found for this project.\n"));
|
|
2347
2385
|
return;
|
|
2348
2386
|
}
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2387
|
+
const selected = await interactiveSelect(recs);
|
|
2388
|
+
if (selected?.length) {
|
|
2389
|
+
await installSkills(selected, platforms);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
async function generateRecommendations(projectId) {
|
|
2393
|
+
const spinner = ora5("Detecting technologies and searching for skills...").start();
|
|
2394
|
+
try {
|
|
2395
|
+
const recs = await apiRequest(
|
|
2396
|
+
`/api/recommendations/project/${projectId}/generate`,
|
|
2397
|
+
{ method: "POST" }
|
|
2398
|
+
);
|
|
2399
|
+
spinner.succeed(`Found ${recs?.length || 0} recommendations`);
|
|
2400
|
+
return recs;
|
|
2401
|
+
} catch (err) {
|
|
2402
|
+
spinner.fail("Failed to generate recommendations");
|
|
2403
|
+
throw err;
|
|
2356
2404
|
}
|
|
2357
2405
|
}
|
|
2358
2406
|
async function interactiveSelect(recs) {
|
|
@@ -2445,24 +2493,32 @@ async function interactiveSelect(recs) {
|
|
|
2445
2493
|
stdin.on("data", onData);
|
|
2446
2494
|
});
|
|
2447
2495
|
}
|
|
2448
|
-
async function installSkills(recs) {
|
|
2496
|
+
async function installSkills(recs, platforms) {
|
|
2449
2497
|
const spinner = ora5(`Installing ${recs.length} skill${recs.length > 1 ? "s" : ""}...`).start();
|
|
2450
|
-
const skillsDir = join(process.cwd(), ".claude", "skills");
|
|
2451
|
-
mkdirSync(skillsDir, { recursive: true });
|
|
2452
2498
|
const installed = [];
|
|
2453
2499
|
const warnings = [];
|
|
2454
2500
|
for (const rec of recs) {
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2501
|
+
let accepted = false;
|
|
2502
|
+
for (const platform of platforms) {
|
|
2503
|
+
try {
|
|
2504
|
+
const result = await apiRequest(
|
|
2505
|
+
`/api/recommendations/${rec.id}/content?platform=${platform}`
|
|
2506
|
+
);
|
|
2507
|
+
if (!result?.content) {
|
|
2508
|
+
warnings.push(`[${platform}] No content available for ${rec.skill_name}`);
|
|
2509
|
+
continue;
|
|
2510
|
+
}
|
|
2511
|
+
const skillPath = getSkillPath(platform, rec.skill_slug);
|
|
2512
|
+
const fullPath = join(process.cwd(), skillPath);
|
|
2513
|
+
mkdirSync(dirname2(fullPath), { recursive: true });
|
|
2514
|
+
writeFileSync(fullPath, result.content, "utf-8");
|
|
2515
|
+
installed.push(`[${platform}] ${skillPath}`);
|
|
2516
|
+
accepted = true;
|
|
2517
|
+
} catch {
|
|
2518
|
+
warnings.push(`[${platform}] Failed to fetch ${rec.skill_name}`);
|
|
2462
2519
|
}
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
installed.push(join(".claude", "skills", filename));
|
|
2520
|
+
}
|
|
2521
|
+
if (accepted) {
|
|
2466
2522
|
try {
|
|
2467
2523
|
await apiRequest(`/api/recommendations/${rec.id}/status`, {
|
|
2468
2524
|
method: "PUT",
|
|
@@ -2470,12 +2526,10 @@ async function installSkills(recs) {
|
|
|
2470
2526
|
});
|
|
2471
2527
|
} catch {
|
|
2472
2528
|
}
|
|
2473
|
-
} catch {
|
|
2474
|
-
warnings.push(`Failed to install ${rec.skill_name}`);
|
|
2475
2529
|
}
|
|
2476
2530
|
}
|
|
2477
2531
|
if (installed.length > 0) {
|
|
2478
|
-
spinner.succeed(`Installed ${installed.length}
|
|
2532
|
+
spinner.succeed(`Installed ${installed.length} file${installed.length > 1 ? "s" : ""}`);
|
|
2479
2533
|
for (const p of installed) {
|
|
2480
2534
|
console.log(chalk8.green(` \u2713 ${p}`));
|
|
2481
2535
|
}
|
|
@@ -3276,7 +3330,7 @@ program.command("status").description("Show current Caliber setup status").optio
|
|
|
3276
3330
|
program.command("regenerate").alias("regen").alias("re").alias("update").description("Re-analyze project and regenerate setup").option("--dry-run", "Preview changes without writing files").action(regenerateCommand);
|
|
3277
3331
|
program.command("login").description("Authenticate with Caliber").action(loginCommand);
|
|
3278
3332
|
program.command("logout").description("Clear stored credentials").action(logoutCommand);
|
|
3279
|
-
program.command("recommend").description("Discover and manage skill recommendations").option("--generate", "
|
|
3333
|
+
program.command("recommend").description("Discover and manage skill recommendations").option("--generate", "Force fresh recommendation generation").option("--status <status>", "View recommendations by status: pending, accepted, dismissed").action(recommendCommand);
|
|
3280
3334
|
program.command("health").description("Analyze context health and quality").option("--fix", "Generate and execute a fix plan").option("--json", "Output as JSON").action(healthCommand);
|
|
3281
3335
|
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);
|
|
3282
3336
|
program.command("diff").description("Compare local config with server state").option("--platform <platform>", "Target platform: claude, cursor, or both").action(diffCommand);
|