@caliber-ai/cli 0.15.0 → 0.16.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 CHANGED
@@ -2318,7 +2318,21 @@ function logoutCommand() {
2318
2318
  import chalk8 from "chalk";
2319
2319
  import ora5 from "ora";
2320
2320
  import { mkdirSync, writeFileSync } from "fs";
2321
- 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
+ }
2322
2336
  async function recommendCommand(options) {
2323
2337
  const auth2 = getStoredAuth();
2324
2338
  if (!auth2) {
@@ -2336,44 +2350,57 @@ async function recommendCommand(options) {
2336
2350
  throw new Error("__exit__");
2337
2351
  }
2338
2352
  const projectId = match.project.id;
2339
- if (options.generate) {
2340
- const spinner = ora5("Detecting technologies and searching for skills...").start();
2341
- let recs2;
2342
- try {
2343
- recs2 = await apiRequest(
2344
- `/api/recommendations/project/${projectId}/generate`,
2345
- { method: "POST" }
2346
- );
2347
- spinner.succeed(`Found ${recs2?.length || 0} recommendations`);
2348
- } catch (err) {
2349
- spinner.fail("Failed to generate recommendations");
2350
- 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;
2351
2363
  }
2364
+ printRecommendations(recs2);
2365
+ return;
2366
+ }
2367
+ if (options.generate) {
2368
+ const recs2 = await generateRecommendations(projectId);
2352
2369
  if (recs2?.length) {
2353
- const selected = await interactiveSelect(recs2);
2354
- if (selected?.length) {
2355
- await installSkills(selected);
2370
+ const selected2 = await interactiveSelect(recs2);
2371
+ if (selected2?.length) {
2372
+ await installSkills(selected2, platforms);
2356
2373
  }
2357
2374
  }
2358
2375
  return;
2359
2376
  }
2360
- const statusFilter = options.status || "pending";
2361
- const recs = await apiRequest(
2362
- `/api/recommendations/project/${projectId}?status=${statusFilter}`
2377
+ let recs = await apiRequest(
2378
+ `/api/recommendations/project/${projectId}?status=pending`
2363
2379
  );
2364
2380
  if (!recs?.length) {
2365
- console.log(chalk8.dim(`
2366
- No ${statusFilter} recommendations. Run \`caliber recommend --generate\` to discover skills.
2367
- `));
2381
+ recs = await generateRecommendations(projectId);
2382
+ }
2383
+ if (!recs?.length) {
2384
+ console.log(chalk8.dim("\nNo recommendations found for this project.\n"));
2368
2385
  return;
2369
2386
  }
2370
- if (statusFilter === "pending") {
2371
- const selected = await interactiveSelect(recs);
2372
- if (selected?.length) {
2373
- await installSkills(selected);
2374
- }
2375
- } else {
2376
- printRecommendations(recs);
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;
2377
2404
  }
2378
2405
  }
2379
2406
  async function interactiveSelect(recs) {
@@ -2466,24 +2493,32 @@ async function interactiveSelect(recs) {
2466
2493
  stdin.on("data", onData);
2467
2494
  });
2468
2495
  }
2469
- async function installSkills(recs) {
2496
+ async function installSkills(recs, platforms) {
2470
2497
  const spinner = ora5(`Installing ${recs.length} skill${recs.length > 1 ? "s" : ""}...`).start();
2471
- const skillsDir = join(process.cwd(), ".claude", "skills");
2472
- mkdirSync(skillsDir, { recursive: true });
2473
2498
  const installed = [];
2474
2499
  const warnings = [];
2475
2500
  for (const rec of recs) {
2476
- try {
2477
- const result = await apiRequest(
2478
- `/api/recommendations/${rec.id}/content`
2479
- );
2480
- if (!result?.content) {
2481
- warnings.push(`No content available for ${rec.skill_name}`);
2482
- continue;
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}`);
2483
2519
  }
2484
- const filename = `${rec.skill_slug}.md`;
2485
- writeFileSync(join(skillsDir, filename), result.content, "utf-8");
2486
- installed.push(join(".claude", "skills", filename));
2520
+ }
2521
+ if (accepted) {
2487
2522
  try {
2488
2523
  await apiRequest(`/api/recommendations/${rec.id}/status`, {
2489
2524
  method: "PUT",
@@ -2491,12 +2526,10 @@ async function installSkills(recs) {
2491
2526
  });
2492
2527
  } catch {
2493
2528
  }
2494
- } catch {
2495
- warnings.push(`Failed to install ${rec.skill_name}`);
2496
2529
  }
2497
2530
  }
2498
2531
  if (installed.length > 0) {
2499
- spinner.succeed(`Installed ${installed.length} skill${installed.length > 1 ? "s" : ""}`);
2532
+ spinner.succeed(`Installed ${installed.length} file${installed.length > 1 ? "s" : ""}`);
2500
2533
  for (const p of installed) {
2501
2534
  console.log(chalk8.green(` \u2713 ${p}`));
2502
2535
  }
@@ -3297,7 +3330,7 @@ program.command("status").description("Show current Caliber setup status").optio
3297
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);
3298
3331
  program.command("login").description("Authenticate with Caliber").action(loginCommand);
3299
3332
  program.command("logout").description("Clear stored credentials").action(logoutCommand);
3300
- 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);
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);
3301
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);
3302
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);
3303
3336
  program.command("diff").description("Compare local config with server state").option("--platform <platform>", "Target platform: claude, cursor, or both").action(diffCommand);