@rely-ai/caliber 0.9.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +18 -14
  2. package/dist/bin.js +258 -166
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,10 +1,14 @@
1
- # Caliber
1
+ <p align="center">
2
+ <img src="assets/social-preview.svg" alt="Caliber" width="640">
3
+ </p>
2
4
 
3
- [![npm version](https://img.shields.io/npm/v/@rely-ai/caliber)](https://www.npmjs.com/package/@rely-ai/caliber)
4
- [![license](https://img.shields.io/npm/l/@rely-ai/caliber)](./LICENSE)
5
- [![node](https://img.shields.io/node/v/@rely-ai/caliber)](https://nodejs.org)
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/@rely-ai/caliber"><img src="https://img.shields.io/npm/v/@rely-ai/caliber" alt="npm version"></a>
7
+ <a href="./LICENSE"><img src="https://img.shields.io/npm/l/@rely-ai/caliber" alt="license"></a>
8
+ <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/@rely-ai/caliber" alt="node"></a>
9
+ </p>
6
10
 
7
- **Analyze your codebase. Generate optimized AI agent configs. One command.**
11
+ <p align="center"><strong>Analyze your codebase. Generate optimized AI agent configs. One command.</strong></p>
8
12
 
9
13
  Caliber scans your project — languages, frameworks, dependencies, file structure — and generates tailored config files for Claude Code and Cursor. If configs already exist, it audits them and suggests improvements.
10
14
 
@@ -13,7 +17,7 @@ Caliber scans your project — languages, frameworks, dependencies, file structu
13
17
  ## Quick Start
14
18
 
15
19
  ```bash
16
- npx @rely-ai/caliber init
20
+ npx @rely-ai/caliber onboard
17
21
  ```
18
22
 
19
23
  That's it. On first run, Caliber walks you through provider setup interactively.
@@ -22,19 +26,19 @@ Or install globally:
22
26
 
23
27
  ```bash
24
28
  npm install -g @rely-ai/caliber
25
- caliber init
29
+ caliber onboard
26
30
  ```
27
31
 
28
32
  > **Already have an API key?** Skip the interactive setup:
29
33
  > ```bash
30
34
  > export ANTHROPIC_API_KEY=sk-ant-...
31
- > npx @rely-ai/caliber init
35
+ > npx @rely-ai/caliber onboard
32
36
  > ```
33
37
 
34
38
  ## How It Works
35
39
 
36
40
  ```
37
- caliber init
41
+ caliber onboard
38
42
 
39
43
  ├─ 1. Scan Analyze languages, frameworks, dependencies, file structure,
40
44
  │ and existing agent configs in your project
@@ -64,16 +68,16 @@ If these files already exist, Caliber audits them against your actual codebase a
64
68
 
65
69
  | Command | Description |
66
70
  |---------|-------------|
67
- | `caliber init` | Scan project, generate/audit agent configs, review and apply |
71
+ | `caliber onboard` | Onboard your project for AI-assisted development |
68
72
  | `caliber score` | Score your config quality (deterministic, no LLM needed) |
69
73
  | `caliber recommend` | Discover and install skills from [skills.sh](https://skills.sh) |
70
74
  | `caliber config` | Configure LLM provider, API key, and model |
71
75
 
72
76
  ```bash
73
- caliber init --agent claude # Target Claude Code only
74
- caliber init --agent cursor # Target Cursor only
75
- caliber init --agent both # Target both
76
- caliber init --dry-run # Preview without writing files
77
+ caliber onboard --agent claude # Target Claude Code only
78
+ caliber onboard --agent cursor # Target Cursor only
79
+ caliber onboard --agent both # Target both
80
+ caliber onboard --dry-run # Preview without writing files
77
81
  caliber score --json # Machine-readable output
78
82
  ```
79
83
 
package/dist/bin.js CHANGED
@@ -51,16 +51,16 @@ var init_constants = __esm({
51
51
 
52
52
  // src/cli.ts
53
53
  import { Command } from "commander";
54
- import fs24 from "fs";
55
- import path21 from "path";
54
+ import fs25 from "fs";
55
+ import path22 from "path";
56
56
  import { fileURLToPath } from "url";
57
57
 
58
- // src/commands/init.ts
58
+ // src/commands/onboard.ts
59
59
  import chalk4 from "chalk";
60
60
  import ora from "ora";
61
61
  import readline3 from "readline";
62
62
  import select2 from "@inquirer/select";
63
- import fs17 from "fs";
63
+ import fs18 from "fs";
64
64
 
65
65
  // src/fingerprint/index.ts
66
66
  import fs7 from "fs";
@@ -2127,16 +2127,25 @@ import path12 from "path";
2127
2127
  var STAGED_DIR = path12.join(CALIBER_DIR, "staged");
2128
2128
  var PROPOSED_DIR = path12.join(STAGED_DIR, "proposed");
2129
2129
  var CURRENT_DIR = path12.join(STAGED_DIR, "current");
2130
+ function normalizeContent(content) {
2131
+ return content.split("\n").map((line) => line.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
2132
+ }
2130
2133
  function stageFiles(files, projectDir) {
2131
2134
  cleanupStaging();
2132
2135
  let newFiles = 0;
2133
2136
  let modifiedFiles = 0;
2134
2137
  const stagedFiles = [];
2135
2138
  for (const file of files) {
2139
+ const originalPath = path12.join(projectDir, file.path);
2140
+ if (fs13.existsSync(originalPath)) {
2141
+ const existing = fs13.readFileSync(originalPath, "utf-8");
2142
+ if (normalizeContent(existing) === normalizeContent(file.content)) {
2143
+ continue;
2144
+ }
2145
+ }
2136
2146
  const proposedPath = path12.join(PROPOSED_DIR, file.path);
2137
2147
  fs13.mkdirSync(path12.dirname(proposedPath), { recursive: true });
2138
2148
  fs13.writeFileSync(proposedPath, file.content);
2139
- const originalPath = path12.join(projectDir, file.path);
2140
2149
  if (fs13.existsSync(originalPath)) {
2141
2150
  const currentPath = path12.join(CURRENT_DIR, file.path);
2142
2151
  fs13.mkdirSync(path12.dirname(currentPath), { recursive: true });
@@ -2194,7 +2203,7 @@ function openDiffsInEditor(editor, files) {
2194
2203
  }
2195
2204
  }
2196
2205
 
2197
- // src/commands/init.ts
2206
+ // src/commands/onboard.ts
2198
2207
  import { createTwoFilesPatch } from "diff";
2199
2208
 
2200
2209
  // src/lib/hooks.ts
@@ -2715,15 +2724,15 @@ function computeGrade(score) {
2715
2724
  // src/scoring/checks/coverage.ts
2716
2725
  import { readFileSync, readdirSync } from "fs";
2717
2726
  import { join } from "path";
2718
- function readFileOrNull(path23) {
2727
+ function readFileOrNull(path24) {
2719
2728
  try {
2720
- return readFileSync(path23, "utf-8");
2729
+ return readFileSync(path24, "utf-8");
2721
2730
  } catch {
2722
2731
  return null;
2723
2732
  }
2724
2733
  }
2725
- function readJsonOrNull(path23) {
2726
- const content = readFileOrNull(path23);
2734
+ function readJsonOrNull(path24) {
2735
+ const content = readFileOrNull(path24);
2727
2736
  if (!content) return null;
2728
2737
  try {
2729
2738
  return JSON.parse(content);
@@ -3079,9 +3088,9 @@ function checkExistence(dir) {
3079
3088
  // src/scoring/checks/quality.ts
3080
3089
  import { readFileSync as readFileSync3 } from "fs";
3081
3090
  import { join as join3 } from "path";
3082
- function readFileOrNull2(path23) {
3091
+ function readFileOrNull2(path24) {
3083
3092
  try {
3084
- return readFileSync3(path23, "utf-8");
3093
+ return readFileSync3(path24, "utf-8");
3085
3094
  } catch {
3086
3095
  return null;
3087
3096
  }
@@ -3232,15 +3241,15 @@ function checkQuality(dir) {
3232
3241
  // src/scoring/checks/accuracy.ts
3233
3242
  import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync } from "fs";
3234
3243
  import { join as join4 } from "path";
3235
- function readFileOrNull3(path23) {
3244
+ function readFileOrNull3(path24) {
3236
3245
  try {
3237
- return readFileSync4(path23, "utf-8");
3246
+ return readFileSync4(path24, "utf-8");
3238
3247
  } catch {
3239
3248
  return null;
3240
3249
  }
3241
3250
  }
3242
- function readJsonOrNull2(path23) {
3243
- const content = readFileOrNull3(path23);
3251
+ function readJsonOrNull2(path24) {
3252
+ const content = readFileOrNull3(path24);
3244
3253
  if (!content) return null;
3245
3254
  try {
3246
3255
  return JSON.parse(content);
@@ -3423,9 +3432,9 @@ function checkAccuracy(dir) {
3423
3432
  // src/scoring/checks/freshness.ts
3424
3433
  import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
3425
3434
  import { join as join5 } from "path";
3426
- function readFileOrNull4(path23) {
3435
+ function readFileOrNull4(path24) {
3427
3436
  try {
3428
- return readFileSync5(path23, "utf-8");
3437
+ return readFileSync5(path24, "utf-8");
3429
3438
  } catch {
3430
3439
  return null;
3431
3440
  }
@@ -3535,9 +3544,9 @@ function checkFreshness(dir) {
3535
3544
  import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "fs";
3536
3545
  import { execSync as execSync7 } from "child_process";
3537
3546
  import { join as join6 } from "path";
3538
- function readFileOrNull5(path23) {
3547
+ function readFileOrNull5(path24) {
3539
3548
  try {
3540
- return readFileSync6(path23, "utf-8");
3549
+ return readFileSync6(path24, "utf-8");
3541
3550
  } catch {
3542
3551
  return null;
3543
3552
  }
@@ -3630,6 +3639,29 @@ function checkBonus(dir) {
3630
3639
  return checks;
3631
3640
  }
3632
3641
 
3642
+ // src/scoring/dismissed.ts
3643
+ init_constants();
3644
+ import fs17 from "fs";
3645
+ import path16 from "path";
3646
+ var DISMISSED_FILE = path16.join(CALIBER_DIR, "dismissed-checks.json");
3647
+ function readDismissedChecks() {
3648
+ try {
3649
+ if (!fs17.existsSync(DISMISSED_FILE)) return [];
3650
+ return JSON.parse(fs17.readFileSync(DISMISSED_FILE, "utf-8"));
3651
+ } catch {
3652
+ return [];
3653
+ }
3654
+ }
3655
+ function writeDismissedChecks(checks) {
3656
+ if (!fs17.existsSync(CALIBER_DIR)) {
3657
+ fs17.mkdirSync(CALIBER_DIR, { recursive: true });
3658
+ }
3659
+ fs17.writeFileSync(DISMISSED_FILE, JSON.stringify(checks, null, 2) + "\n");
3660
+ }
3661
+ function getDismissedIds() {
3662
+ return new Set(readDismissedChecks().map((c) => c.id));
3663
+ }
3664
+
3633
3665
  // src/scoring/index.ts
3634
3666
  function sumCategory(checks, category) {
3635
3667
  const categoryChecks = checks.filter((c) => c.category === category);
@@ -3666,7 +3698,8 @@ function computeLocalScore(dir, targetAgent) {
3666
3698
  ...checkFreshness(dir),
3667
3699
  ...checkBonus(dir)
3668
3700
  ];
3669
- const checks = filterChecksForTarget(allChecks, target);
3701
+ const dismissed = getDismissedIds();
3702
+ const checks = filterChecksForTarget(allChecks, target).filter((c) => !dismissed.has(c.id));
3670
3703
  const maxPossible = checks.reduce((s, c) => s + c.maxPoints, 0);
3671
3704
  const earned = checks.reduce((s, c) => s + c.earnedPoints, 0);
3672
3705
  const score = maxPossible > 0 ? Math.min(100, Math.max(0, Math.round(earned / maxPossible * 100))) : 0;
@@ -3772,7 +3805,7 @@ function displayScoreSummary(result) {
3772
3805
  const remaining = failing.length - shown.length;
3773
3806
  const moreText = remaining > 0 ? ` (+${remaining} more)` : "";
3774
3807
  console.log(chalk3.dim(`
3775
- Run ${chalk3.reset("caliber score")} for details.${moreText}`));
3808
+ Run ${chalk3.hex("#83D1EB")("caliber score")} for details.${moreText}`));
3776
3809
  }
3777
3810
  console.log("");
3778
3811
  }
@@ -3820,7 +3853,7 @@ function displayScoreDelta(before, after) {
3820
3853
  }
3821
3854
  }
3822
3855
 
3823
- // src/commands/init.ts
3856
+ // src/commands/onboard.ts
3824
3857
  async function initCommand(options) {
3825
3858
  const brand = chalk4.hex("#EB9D83");
3826
3859
  const title = chalk4.hex("#83D1EB");
@@ -3832,18 +3865,16 @@ async function initCommand(options) {
3832
3865
  \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
3833
3866
  \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
3834
3867
  `));
3835
- console.log(chalk4.dim(" Configure your coding agent environment\n"));
3836
- console.log(title.bold(" What is Caliber?\n"));
3837
- console.log(chalk4.dim(" Caliber audits your AI agent configurations and suggests targeted"));
3838
- console.log(chalk4.dim(" improvements. It analyzes CLAUDE.md, .cursorrules, and skills"));
3839
- console.log(chalk4.dim(" against your actual codebase \u2014 keeping what works, fixing"));
3840
- console.log(chalk4.dim(" what's stale, and adding what's missing.\n"));
3841
- console.log(title.bold(" How it works:\n"));
3842
- console.log(chalk4.dim(" 1. Scan Analyze your code, dependencies, and existing configs"));
3843
- console.log(chalk4.dim(" 2. Generate AI creates or improves config files for your project"));
3844
- console.log(chalk4.dim(" 3. Review You accept, refine, or decline the proposed changes"));
3845
- console.log(chalk4.dim(" 4. Apply Config files are written with backups\n"));
3846
- console.log(title.bold(" Step 1/4 \u2014 How do you want to use Caliber?\n"));
3868
+ console.log(chalk4.dim(" Onboard your project for AI-assisted development\n"));
3869
+ console.log(title.bold(" Welcome to Caliber\n"));
3870
+ console.log(chalk4.dim(" Caliber analyzes your codebase and creates tailored config files"));
3871
+ console.log(chalk4.dim(" so your AI coding agents understand your project from day one.\n"));
3872
+ console.log(title.bold(" How onboarding works:\n"));
3873
+ console.log(chalk4.dim(" 1. Connect Set up your LLM provider"));
3874
+ console.log(chalk4.dim(" 2. Discover Analyze your code, dependencies, and structure"));
3875
+ console.log(chalk4.dim(" 3. Generate Create config files tailored to your project"));
3876
+ console.log(chalk4.dim(" 4. Review Preview, refine, and apply the changes\n"));
3877
+ console.log(title.bold(" Step 1/4 \u2014 Connect your LLM\n"));
3847
3878
  let config = loadConfig();
3848
3879
  if (!config) {
3849
3880
  console.log(chalk4.dim(" No LLM provider set yet. Choose how to run Caliber:\n"));
@@ -3860,14 +3891,14 @@ async function initCommand(options) {
3860
3891
  console.log(chalk4.red(" Setup was cancelled or failed.\n"));
3861
3892
  throw new Error("__exit__");
3862
3893
  }
3863
- console.log(chalk4.green(" \u2713 Provider saved. Continuing with init.\n"));
3894
+ console.log(chalk4.green(" \u2713 Provider saved. Let's continue.\n"));
3864
3895
  }
3865
3896
  const displayModel = config.model === "default" && config.provider === "claude-cli" ? process.env.ANTHROPIC_MODEL || "default (inherited from Claude Code)" : config.model;
3866
3897
  const fastModel = process.env.ANTHROPIC_SMALL_FAST_MODEL;
3867
3898
  const modelLine = fastModel ? ` Provider: ${config.provider} | Model: ${displayModel} | Scan: ${fastModel}` : ` Provider: ${config.provider} | Model: ${displayModel}`;
3868
3899
  console.log(chalk4.dim(modelLine + "\n"));
3869
- console.log(title.bold(" Step 2/4 \u2014 Scan project\n"));
3870
- console.log(chalk4.dim(" Detecting languages, dependencies, file structure, and existing configs.\n"));
3900
+ console.log(title.bold(" Step 2/4 \u2014 Discover your project\n"));
3901
+ console.log(chalk4.dim(" Learning about your languages, dependencies, structure, and existing configs.\n"));
3871
3902
  const spinner = ora("Analyzing project...").start();
3872
3903
  const fingerprint = collectFingerprint(process.cwd());
3873
3904
  await enrichFingerprintWithLLM(fingerprint, process.cwd());
@@ -3876,12 +3907,23 @@ async function initCommand(options) {
3876
3907
  console.log(chalk4.dim(` Files: ${fingerprint.fileTree.length} found
3877
3908
  `));
3878
3909
  const targetAgent = options.agent || await promptAgent();
3910
+ const preScore = computeLocalScore(process.cwd(), targetAgent);
3911
+ const failingForDismissal = preScore.checks.filter((c) => !c.passed && c.maxPoints > 0);
3912
+ if (failingForDismissal.length > 0) {
3913
+ const newDismissals = await evaluateDismissals(failingForDismissal, fingerprint);
3914
+ if (newDismissals.length > 0) {
3915
+ const existing = readDismissedChecks();
3916
+ const existingIds = new Set(existing.map((d) => d.id));
3917
+ const merged = [...existing, ...newDismissals.filter((d) => !existingIds.has(d.id))];
3918
+ writeDismissedChecks(merged);
3919
+ }
3920
+ }
3879
3921
  const baselineScore = computeLocalScore(process.cwd(), targetAgent);
3880
3922
  displayScoreSummary(baselineScore);
3881
3923
  const hasExistingConfig = !!(fingerprint.existingConfigs.claudeMd || fingerprint.existingConfigs.claudeSettings || fingerprint.existingConfigs.claudeSkills?.length || fingerprint.existingConfigs.cursorrules || fingerprint.existingConfigs.cursorRules?.length);
3882
3924
  if (hasExistingConfig && baselineScore.score === 100) {
3883
3925
  console.log(chalk4.bold.green(" Your setup is already optimal \u2014 nothing to change.\n"));
3884
- console.log(chalk4.dim(" Run `caliber init --force` to regenerate anyway.\n"));
3926
+ console.log(chalk4.dim(" Run ") + chalk4.hex("#83D1EB")("caliber onboard --force") + chalk4.dim(" to regenerate anyway.\n"));
3885
3927
  if (!options.force) return;
3886
3928
  }
3887
3929
  const isEmpty = fingerprint.fileTree.length < 3;
@@ -3896,8 +3938,8 @@ async function initCommand(options) {
3896
3938
  passingChecks = baselineScore.checks.filter((c) => c.passed).map((c) => ({ name: c.name }));
3897
3939
  currentScore = baselineScore.score;
3898
3940
  if (failingChecks.length > 0) {
3899
- console.log(title.bold(" Step 3/4 \u2014 Targeted fixes\n"));
3900
- console.log(chalk4.dim(` Score is ${baselineScore.score}/100 \u2014 only fixing ${failingChecks.length} remaining issue${failingChecks.length === 1 ? "" : "s"}:
3941
+ console.log(title.bold(" Step 3/4 \u2014 Fine-tuning\n"));
3942
+ console.log(chalk4.dim(` Your setup scores ${baselineScore.score}/100 \u2014 fixing ${failingChecks.length} remaining issue${failingChecks.length === 1 ? "" : "s"}:
3901
3943
  `));
3902
3944
  for (const check of failingChecks) {
3903
3945
  console.log(chalk4.dim(` \u2022 ${check.name}`));
@@ -3905,12 +3947,12 @@ async function initCommand(options) {
3905
3947
  console.log("");
3906
3948
  }
3907
3949
  } else if (hasExistingConfig) {
3908
- console.log(title.bold(" Step 3/4 \u2014 Auditing your configs\n"));
3909
- console.log(chalk4.dim(" AI is reviewing your existing configs against your codebase"));
3910
- console.log(chalk4.dim(" and suggesting improvements.\n"));
3950
+ console.log(title.bold(" Step 3/4 \u2014 Improve your setup\n"));
3951
+ console.log(chalk4.dim(" Reviewing your existing configs against your codebase"));
3952
+ console.log(chalk4.dim(" and preparing improvements.\n"));
3911
3953
  } else {
3912
- console.log(title.bold(" Step 3/4 \u2014 Generating configs\n"));
3913
- console.log(chalk4.dim(" AI is creating agent config files tailored to your project.\n"));
3954
+ console.log(title.bold(" Step 3/4 \u2014 Build your agent setup\n"));
3955
+ console.log(chalk4.dim(" Creating config files tailored to your project.\n"));
3914
3956
  }
3915
3957
  console.log(chalk4.dim(" This can take a couple of minutes depending on your model and provider.\n"));
3916
3958
  const genStartTime = Date.now();
@@ -3970,7 +4012,7 @@ async function initCommand(options) {
3970
4012
  role: "assistant",
3971
4013
  content: summarizeSetup("Initial generation", generatedSetup)
3972
4014
  });
3973
- console.log(title.bold(" Step 4/4 \u2014 Review\n"));
4015
+ console.log(title.bold(" Step 4/4 \u2014 Review and apply\n"));
3974
4016
  const setupFiles = collectSetupFiles(generatedSetup);
3975
4017
  const staged = stageFiles(setupFiles, process.cwd());
3976
4018
  console.log(chalk4.dim(` ${chalk4.green(`${staged.newFiles} new`)} / ${chalk4.yellow(`${staged.modifiedFiles} modified`)} file${staged.newFiles + staged.modifiedFiles !== 1 ? "s" : ""}
@@ -4029,11 +4071,6 @@ async function initCommand(options) {
4029
4071
  console.error(chalk4.red(err instanceof Error ? err.message : "Unknown error"));
4030
4072
  throw new Error("__exit__");
4031
4073
  }
4032
- if (!fs17.existsSync("AGENTS.md")) {
4033
- const agentsContent = "# AGENTS.md\n\nThis project uses AI coding agents. See CLAUDE.md for Claude Code configuration and .cursor/rules/ for Cursor rules.\n";
4034
- fs17.writeFileSync("AGENTS.md", agentsContent);
4035
- console.log(` ${chalk4.green("\u2713")} AGENTS.md`);
4036
- }
4037
4074
  ensurePermissions();
4038
4075
  const sha = getCurrentHeadSha();
4039
4076
  writeState({
@@ -4041,19 +4078,22 @@ async function initCommand(options) {
4041
4078
  lastRefreshTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
4042
4079
  targetAgent
4043
4080
  });
4081
+ console.log("");
4082
+ console.log(title.bold(" Keep your configs fresh\n"));
4083
+ console.log(chalk4.dim(" Caliber can automatically update your agent configs when your code changes.\n"));
4044
4084
  const hookChoice = await promptHookType(targetAgent);
4045
4085
  if (hookChoice === "claude" || hookChoice === "both") {
4046
4086
  const hookResult = installHook();
4047
4087
  if (hookResult.installed) {
4048
4088
  console.log(` ${chalk4.green("\u2713")} Claude Code hook installed \u2014 docs update on session end`);
4049
- console.log(chalk4.dim(" Run `caliber hooks remove` to disable"));
4089
+ console.log(chalk4.dim(" Run ") + chalk4.hex("#83D1EB")("caliber hooks remove") + chalk4.dim(" to disable"));
4050
4090
  } else if (hookResult.alreadyInstalled) {
4051
4091
  console.log(chalk4.dim(" Claude Code hook already installed"));
4052
4092
  }
4053
4093
  const learnResult = installLearningHooks();
4054
4094
  if (learnResult.installed) {
4055
4095
  console.log(` ${chalk4.green("\u2713")} Learning hooks installed \u2014 session insights captured automatically`);
4056
- console.log(chalk4.dim(" Run `caliber learn remove` to disable"));
4096
+ console.log(chalk4.dim(" Run ") + chalk4.hex("#83D1EB")("caliber learn remove") + chalk4.dim(" to disable"));
4057
4097
  } else if (learnResult.alreadyInstalled) {
4058
4098
  console.log(chalk4.dim(" Learning hooks already installed"));
4059
4099
  }
@@ -4062,7 +4102,7 @@ async function initCommand(options) {
4062
4102
  const precommitResult = installPreCommitHook();
4063
4103
  if (precommitResult.installed) {
4064
4104
  console.log(` ${chalk4.green("\u2713")} Pre-commit hook installed \u2014 docs refresh before each commit`);
4065
- console.log(chalk4.dim(" Run `caliber hooks remove-precommit` to disable"));
4105
+ console.log(chalk4.dim(" Run ") + chalk4.hex("#83D1EB")("caliber hooks remove-precommit") + chalk4.dim(" to disable"));
4066
4106
  } else if (precommitResult.alreadyInstalled) {
4067
4107
  console.log(chalk4.dim(" Pre-commit hook already installed"));
4068
4108
  } else {
@@ -4070,7 +4110,7 @@ async function initCommand(options) {
4070
4110
  }
4071
4111
  }
4072
4112
  if (hookChoice === "skip") {
4073
- console.log(chalk4.dim(" Skipped auto-refresh hooks. Run `caliber hooks install` later to enable."));
4113
+ console.log(chalk4.dim(" Skipped auto-refresh hooks. Run ") + chalk4.hex("#83D1EB")("caliber hooks install") + chalk4.dim(" later to enable."));
4074
4114
  }
4075
4115
  const afterScore = computeLocalScore(process.cwd(), targetAgent);
4076
4116
  if (afterScore.score < baselineScore.score) {
@@ -4083,13 +4123,15 @@ async function initCommand(options) {
4083
4123
  }
4084
4124
  } catch {
4085
4125
  }
4086
- console.log(chalk4.dim(" Run `caliber init --force` to override.\n"));
4126
+ console.log(chalk4.dim(" Run ") + chalk4.hex("#83D1EB")("caliber onboard --force") + chalk4.dim(" to override.\n"));
4087
4127
  return;
4088
4128
  }
4089
4129
  displayScoreDelta(baselineScore, afterScore);
4090
- console.log(chalk4.bold.green(" Setup complete! Your coding agent is now configured."));
4091
- console.log(chalk4.dim(" Run `caliber undo` to revert changes.\n"));
4130
+ console.log(chalk4.bold.green(" Onboarding complete! Your project is ready for AI-assisted development."));
4131
+ console.log(chalk4.dim(" Run ") + chalk4.hex("#83D1EB")("caliber undo") + chalk4.dim(" to revert changes.\n"));
4092
4132
  console.log(chalk4.bold(" Next steps:\n"));
4133
+ console.log(` ${title("caliber score")} See your full config breakdown`);
4134
+ console.log(` ${title("caliber recommend")} Discover community skills for your stack`);
4093
4135
  console.log(` ${title("caliber undo")} Revert all changes from this run`);
4094
4136
  console.log("");
4095
4137
  }
@@ -4136,7 +4178,7 @@ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
4136
4178
  }
4137
4179
  function summarizeSetup(action, setup) {
4138
4180
  const descriptions = setup.fileDescriptions;
4139
- const files = descriptions ? Object.entries(descriptions).map(([path23, desc]) => ` ${path23}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
4181
+ const files = descriptions ? Object.entries(descriptions).map(([path24, desc]) => ` ${path24}: ${desc}`).join("\n") : Object.keys(setup).filter((k) => k !== "targetAgent" && k !== "fileDescriptions").join(", ");
4140
4182
  return `${action}. Files:
4141
4183
  ${files}`;
4142
4184
  }
@@ -4157,6 +4199,36 @@ Return {"valid": true} or {"valid": false}. Nothing else.`,
4157
4199
  return true;
4158
4200
  }
4159
4201
  }
4202
+ async function evaluateDismissals(failingChecks, fingerprint) {
4203
+ const fastModel = process.env.ANTHROPIC_SMALL_FAST_MODEL;
4204
+ const checkList = failingChecks.map((c) => ({
4205
+ id: c.id,
4206
+ name: c.name,
4207
+ suggestion: c.suggestion
4208
+ }));
4209
+ try {
4210
+ const result = await llmJsonCall({
4211
+ system: `You evaluate whether scoring checks are applicable to a project.
4212
+ Given the project's languages/frameworks and a list of failing checks, return which checks are NOT applicable.
4213
+
4214
+ Only dismiss checks that truly don't apply \u2014 e.g. "Build/test/lint commands" for a pure Terraform/HCL repo with no build system.
4215
+ Do NOT dismiss checks that could reasonably apply even if the project doesn't use them yet.
4216
+
4217
+ Return {"dismissed": [{"id": "check_id", "reason": "brief reason"}]} or {"dismissed": []} if all apply.`,
4218
+ prompt: `Languages: ${fingerprint.languages.join(", ") || "none"}
4219
+ Frameworks: ${fingerprint.frameworks.join(", ") || "none"}
4220
+
4221
+ Failing checks:
4222
+ ${JSON.stringify(checkList, null, 2)}`,
4223
+ maxTokens: 200,
4224
+ ...fastModel ? { model: fastModel } : {}
4225
+ });
4226
+ if (!Array.isArray(result.dismissed)) return [];
4227
+ return result.dismissed.filter((d) => d.id && d.reason && failingChecks.some((c) => c.id === d.id)).map((d) => ({ id: d.id, reason: d.reason, dismissedAt: (/* @__PURE__ */ new Date()).toISOString() }));
4228
+ } catch {
4229
+ return [];
4230
+ }
4231
+ }
4160
4232
  function promptInput2(question) {
4161
4233
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
4162
4234
  return new Promise((resolve2) => {
@@ -4226,8 +4298,8 @@ async function openReview(method, stagedFiles) {
4226
4298
  return;
4227
4299
  }
4228
4300
  const fileInfos = stagedFiles.map((file) => {
4229
- const proposed = fs17.readFileSync(file.proposedPath, "utf-8");
4230
- const current = file.currentPath ? fs17.readFileSync(file.currentPath, "utf-8") : "";
4301
+ const proposed = fs18.readFileSync(file.proposedPath, "utf-8");
4302
+ const current = file.currentPath ? fs18.readFileSync(file.currentPath, "utf-8") : "";
4231
4303
  const patch = createTwoFilesPatch(
4232
4304
  file.isNew ? "/dev/null" : file.relativePath,
4233
4305
  file.relativePath,
@@ -4410,7 +4482,7 @@ function printSetupSummary(setup) {
4410
4482
  };
4411
4483
  if (claude) {
4412
4484
  if (claude.claudeMd) {
4413
- const icon = fs17.existsSync("CLAUDE.md") ? chalk4.yellow("~") : chalk4.green("+");
4485
+ const icon = fs18.existsSync("CLAUDE.md") ? chalk4.yellow("~") : chalk4.green("+");
4414
4486
  const desc = getDescription("CLAUDE.md");
4415
4487
  console.log(` ${icon} ${chalk4.bold("CLAUDE.md")}`);
4416
4488
  if (desc) console.log(chalk4.dim(` ${desc}`));
@@ -4420,7 +4492,7 @@ function printSetupSummary(setup) {
4420
4492
  if (Array.isArray(skills) && skills.length > 0) {
4421
4493
  for (const skill of skills) {
4422
4494
  const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
4423
- const icon = fs17.existsSync(skillPath) ? chalk4.yellow("~") : chalk4.green("+");
4495
+ const icon = fs18.existsSync(skillPath) ? chalk4.yellow("~") : chalk4.green("+");
4424
4496
  const desc = getDescription(skillPath);
4425
4497
  console.log(` ${icon} ${chalk4.bold(skillPath)}`);
4426
4498
  console.log(chalk4.dim(` ${desc || skill.description || skill.name}`));
@@ -4430,7 +4502,7 @@ function printSetupSummary(setup) {
4430
4502
  }
4431
4503
  if (cursor) {
4432
4504
  if (cursor.cursorrules) {
4433
- const icon = fs17.existsSync(".cursorrules") ? chalk4.yellow("~") : chalk4.green("+");
4505
+ const icon = fs18.existsSync(".cursorrules") ? chalk4.yellow("~") : chalk4.green("+");
4434
4506
  const desc = getDescription(".cursorrules");
4435
4507
  console.log(` ${icon} ${chalk4.bold(".cursorrules")}`);
4436
4508
  if (desc) console.log(chalk4.dim(` ${desc}`));
@@ -4440,7 +4512,7 @@ function printSetupSummary(setup) {
4440
4512
  if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
4441
4513
  for (const skill of cursorSkills) {
4442
4514
  const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
4443
- const icon = fs17.existsSync(skillPath) ? chalk4.yellow("~") : chalk4.green("+");
4515
+ const icon = fs18.existsSync(skillPath) ? chalk4.yellow("~") : chalk4.green("+");
4444
4516
  const desc = getDescription(skillPath);
4445
4517
  console.log(` ${icon} ${chalk4.bold(skillPath)}`);
4446
4518
  console.log(chalk4.dim(` ${desc || skill.description || skill.name}`));
@@ -4451,7 +4523,7 @@ function printSetupSummary(setup) {
4451
4523
  if (Array.isArray(rules) && rules.length > 0) {
4452
4524
  for (const rule of rules) {
4453
4525
  const rulePath = `.cursor/rules/${rule.filename}`;
4454
- const icon = fs17.existsSync(rulePath) ? chalk4.yellow("~") : chalk4.green("+");
4526
+ const icon = fs18.existsSync(rulePath) ? chalk4.yellow("~") : chalk4.green("+");
4455
4527
  const desc = getDescription(rulePath);
4456
4528
  console.log(` ${icon} ${chalk4.bold(rulePath)}`);
4457
4529
  if (desc) {
@@ -4464,6 +4536,11 @@ function printSetupSummary(setup) {
4464
4536
  }
4465
4537
  }
4466
4538
  }
4539
+ if (!fs18.existsSync("AGENTS.md")) {
4540
+ console.log(` ${chalk4.green("+")} ${chalk4.bold("AGENTS.md")}`);
4541
+ console.log(chalk4.dim(" Cross-agent coordination file"));
4542
+ console.log("");
4543
+ }
4467
4544
  if (Array.isArray(deletions) && deletions.length > 0) {
4468
4545
  for (const del of deletions) {
4469
4546
  console.log(` ${chalk4.red("-")} ${chalk4.bold(del.filePath)}`);
@@ -4487,8 +4564,8 @@ function ensurePermissions() {
4487
4564
  const settingsPath = ".claude/settings.json";
4488
4565
  let settings = {};
4489
4566
  try {
4490
- if (fs17.existsSync(settingsPath)) {
4491
- settings = JSON.parse(fs17.readFileSync(settingsPath, "utf-8"));
4567
+ if (fs18.existsSync(settingsPath)) {
4568
+ settings = JSON.parse(fs18.readFileSync(settingsPath, "utf-8"));
4492
4569
  }
4493
4570
  } catch {
4494
4571
  }
@@ -4502,8 +4579,8 @@ function ensurePermissions() {
4502
4579
  "Bash(git *)"
4503
4580
  ];
4504
4581
  settings.permissions = permissions;
4505
- if (!fs17.existsSync(".claude")) fs17.mkdirSync(".claude", { recursive: true });
4506
- fs17.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4582
+ if (!fs18.existsSync(".claude")) fs18.mkdirSync(".claude", { recursive: true });
4583
+ fs18.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4507
4584
  }
4508
4585
  function collectSetupFiles(setup) {
4509
4586
  const files = [];
@@ -4533,6 +4610,21 @@ function collectSetupFiles(setup) {
4533
4610
  }
4534
4611
  }
4535
4612
  }
4613
+ if (!fs18.existsSync("AGENTS.md")) {
4614
+ const agentRefs = [];
4615
+ if (claude) agentRefs.push("See `CLAUDE.md` for Claude Code configuration.");
4616
+ if (cursor) agentRefs.push("See `.cursor/rules/` for Cursor rules.");
4617
+ if (agentRefs.length === 0) agentRefs.push("See CLAUDE.md and .cursor/rules/ for agent configurations.");
4618
+ files.push({
4619
+ path: "AGENTS.md",
4620
+ content: `# AGENTS.md
4621
+
4622
+ This project uses AI coding agents configured by [Caliber](https://github.com/rely-ai-org/caliber).
4623
+
4624
+ ${agentRefs.join(" ")}
4625
+ `
4626
+ });
4627
+ }
4536
4628
  return files;
4537
4629
  }
4538
4630
 
@@ -4569,7 +4661,7 @@ function undoCommand() {
4569
4661
 
4570
4662
  // src/commands/status.ts
4571
4663
  import chalk6 from "chalk";
4572
- import fs18 from "fs";
4664
+ import fs19 from "fs";
4573
4665
  async function statusCommand(options) {
4574
4666
  const config = loadConfig();
4575
4667
  const manifest = readManifest();
@@ -4586,16 +4678,16 @@ async function statusCommand(options) {
4586
4678
  if (config) {
4587
4679
  console.log(` LLM: ${chalk6.green(config.provider)} (${config.model})`);
4588
4680
  } else {
4589
- console.log(` LLM: ${chalk6.yellow("Not configured")} \u2014 run \`caliber config\``);
4681
+ console.log(` LLM: ${chalk6.yellow("Not configured")} \u2014 run ${chalk6.hex("#83D1EB")("caliber config")}`);
4590
4682
  }
4591
4683
  if (!manifest) {
4592
4684
  console.log(` Setup: ${chalk6.dim("No setup applied")}`);
4593
- console.log(chalk6.dim("\n Run `caliber init` to get started.\n"));
4685
+ console.log(chalk6.dim("\n Run ") + chalk6.hex("#83D1EB")("caliber onboard") + chalk6.dim(" to get started.\n"));
4594
4686
  return;
4595
4687
  }
4596
4688
  console.log(` Files managed: ${chalk6.cyan(manifest.entries.length.toString())}`);
4597
4689
  for (const entry of manifest.entries) {
4598
- const exists = fs18.existsSync(entry.path);
4690
+ const exists = fs19.existsSync(entry.path);
4599
4691
  const icon = exists ? chalk6.green("\u2713") : chalk6.red("\u2717");
4600
4692
  console.log(` ${icon} ${entry.path} (${entry.action})`);
4601
4693
  }
@@ -4609,12 +4701,12 @@ import confirm from "@inquirer/confirm";
4609
4701
  async function regenerateCommand(options) {
4610
4702
  const config = loadConfig();
4611
4703
  if (!config) {
4612
- console.log(chalk7.red("No LLM provider configured. Run `caliber config` (e.g. choose Cursor) or set ANTHROPIC_API_KEY."));
4704
+ console.log(chalk7.red("No LLM provider configured. Run ") + chalk7.hex("#83D1EB")("caliber config") + chalk7.red(" (e.g. choose Cursor) or set ANTHROPIC_API_KEY."));
4613
4705
  throw new Error("__exit__");
4614
4706
  }
4615
4707
  const manifest = readManifest();
4616
4708
  if (!manifest) {
4617
- console.log(chalk7.yellow("No existing setup found. Run `caliber init` first."));
4709
+ console.log(chalk7.yellow("No existing setup found. Run ") + chalk7.hex("#83D1EB")("caliber onboard") + chalk7.yellow(" first."));
4618
4710
  throw new Error("__exit__");
4619
4711
  }
4620
4712
  const spinner = ora3("Re-analyzing project...").start();
@@ -4682,13 +4774,13 @@ import { mkdirSync, readFileSync as readFileSync7, readdirSync as readdirSync5,
4682
4774
  import { join as join8, dirname as dirname2 } from "path";
4683
4775
 
4684
4776
  // src/scanner/index.ts
4685
- import fs19 from "fs";
4686
- import path16 from "path";
4777
+ import fs20 from "fs";
4778
+ import path17 from "path";
4687
4779
  import crypto2 from "crypto";
4688
4780
  function scanLocalState(dir) {
4689
4781
  const items = [];
4690
- const claudeMdPath = path16.join(dir, "CLAUDE.md");
4691
- if (fs19.existsSync(claudeMdPath)) {
4782
+ const claudeMdPath = path17.join(dir, "CLAUDE.md");
4783
+ if (fs20.existsSync(claudeMdPath)) {
4692
4784
  items.push({
4693
4785
  type: "rule",
4694
4786
  platform: "claude",
@@ -4697,10 +4789,10 @@ function scanLocalState(dir) {
4697
4789
  path: claudeMdPath
4698
4790
  });
4699
4791
  }
4700
- const skillsDir = path16.join(dir, ".claude", "skills");
4701
- if (fs19.existsSync(skillsDir)) {
4702
- for (const file of fs19.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
4703
- const filePath = path16.join(skillsDir, file);
4792
+ const skillsDir = path17.join(dir, ".claude", "skills");
4793
+ if (fs20.existsSync(skillsDir)) {
4794
+ for (const file of fs20.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
4795
+ const filePath = path17.join(skillsDir, file);
4704
4796
  items.push({
4705
4797
  type: "skill",
4706
4798
  platform: "claude",
@@ -4710,10 +4802,10 @@ function scanLocalState(dir) {
4710
4802
  });
4711
4803
  }
4712
4804
  }
4713
- const mcpJsonPath = path16.join(dir, ".mcp.json");
4714
- if (fs19.existsSync(mcpJsonPath)) {
4805
+ const mcpJsonPath = path17.join(dir, ".mcp.json");
4806
+ if (fs20.existsSync(mcpJsonPath)) {
4715
4807
  try {
4716
- const mcpJson = JSON.parse(fs19.readFileSync(mcpJsonPath, "utf-8"));
4808
+ const mcpJson = JSON.parse(fs20.readFileSync(mcpJsonPath, "utf-8"));
4717
4809
  if (mcpJson.mcpServers) {
4718
4810
  for (const name of Object.keys(mcpJson.mcpServers)) {
4719
4811
  items.push({
@@ -4728,8 +4820,8 @@ function scanLocalState(dir) {
4728
4820
  } catch {
4729
4821
  }
4730
4822
  }
4731
- const cursorrulesPath = path16.join(dir, ".cursorrules");
4732
- if (fs19.existsSync(cursorrulesPath)) {
4823
+ const cursorrulesPath = path17.join(dir, ".cursorrules");
4824
+ if (fs20.existsSync(cursorrulesPath)) {
4733
4825
  items.push({
4734
4826
  type: "rule",
4735
4827
  platform: "cursor",
@@ -4738,10 +4830,10 @@ function scanLocalState(dir) {
4738
4830
  path: cursorrulesPath
4739
4831
  });
4740
4832
  }
4741
- const cursorRulesDir = path16.join(dir, ".cursor", "rules");
4742
- if (fs19.existsSync(cursorRulesDir)) {
4743
- for (const file of fs19.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
4744
- const filePath = path16.join(cursorRulesDir, file);
4833
+ const cursorRulesDir = path17.join(dir, ".cursor", "rules");
4834
+ if (fs20.existsSync(cursorRulesDir)) {
4835
+ for (const file of fs20.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
4836
+ const filePath = path17.join(cursorRulesDir, file);
4745
4837
  items.push({
4746
4838
  type: "rule",
4747
4839
  platform: "cursor",
@@ -4751,12 +4843,12 @@ function scanLocalState(dir) {
4751
4843
  });
4752
4844
  }
4753
4845
  }
4754
- const cursorSkillsDir = path16.join(dir, ".cursor", "skills");
4755
- if (fs19.existsSync(cursorSkillsDir)) {
4846
+ const cursorSkillsDir = path17.join(dir, ".cursor", "skills");
4847
+ if (fs20.existsSync(cursorSkillsDir)) {
4756
4848
  try {
4757
- for (const name of fs19.readdirSync(cursorSkillsDir)) {
4758
- const skillFile = path16.join(cursorSkillsDir, name, "SKILL.md");
4759
- if (fs19.existsSync(skillFile)) {
4849
+ for (const name of fs20.readdirSync(cursorSkillsDir)) {
4850
+ const skillFile = path17.join(cursorSkillsDir, name, "SKILL.md");
4851
+ if (fs20.existsSync(skillFile)) {
4760
4852
  items.push({
4761
4853
  type: "skill",
4762
4854
  platform: "cursor",
@@ -4769,10 +4861,10 @@ function scanLocalState(dir) {
4769
4861
  } catch {
4770
4862
  }
4771
4863
  }
4772
- const cursorMcpPath = path16.join(dir, ".cursor", "mcp.json");
4773
- if (fs19.existsSync(cursorMcpPath)) {
4864
+ const cursorMcpPath = path17.join(dir, ".cursor", "mcp.json");
4865
+ if (fs20.existsSync(cursorMcpPath)) {
4774
4866
  try {
4775
- const mcpJson = JSON.parse(fs19.readFileSync(cursorMcpPath, "utf-8"));
4867
+ const mcpJson = JSON.parse(fs20.readFileSync(cursorMcpPath, "utf-8"));
4776
4868
  if (mcpJson.mcpServers) {
4777
4869
  for (const name of Object.keys(mcpJson.mcpServers)) {
4778
4870
  items.push({
@@ -4790,7 +4882,7 @@ function scanLocalState(dir) {
4790
4882
  return items;
4791
4883
  }
4792
4884
  function hashFile(filePath) {
4793
- const text = fs19.readFileSync(filePath, "utf-8");
4885
+ const text = fs20.readFileSync(filePath, "utf-8");
4794
4886
  return crypto2.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
4795
4887
  }
4796
4888
  function hashJson(obj) {
@@ -5356,18 +5448,18 @@ async function scoreCommand(options) {
5356
5448
  const separator = chalk9.gray(" " + "\u2500".repeat(53));
5357
5449
  console.log(separator);
5358
5450
  if (result.score < 40) {
5359
- console.log(chalk9.gray(" Run ") + chalk9.hex("#f97316")("caliber init") + chalk9.gray(" to generate a complete, optimized setup."));
5451
+ console.log(chalk9.gray(" Run ") + chalk9.hex("#83D1EB")("caliber onboard") + chalk9.gray(" to generate a complete, optimized setup."));
5360
5452
  } else if (result.score < 70) {
5361
- console.log(chalk9.gray(" Run ") + chalk9.hex("#f97316")("caliber init") + chalk9.gray(" to improve your setup."));
5453
+ console.log(chalk9.gray(" Run ") + chalk9.hex("#83D1EB")("caliber onboard") + chalk9.gray(" to improve your setup."));
5362
5454
  } else {
5363
- console.log(chalk9.green(" Looking good!") + chalk9.gray(" Run ") + chalk9.hex("#f97316")("caliber update") + chalk9.gray(" to keep it fresh."));
5455
+ console.log(chalk9.green(" Looking good!") + chalk9.gray(" Run ") + chalk9.hex("#83D1EB")("caliber update") + chalk9.gray(" to keep it fresh."));
5364
5456
  }
5365
5457
  console.log("");
5366
5458
  }
5367
5459
 
5368
5460
  // src/commands/refresh.ts
5369
- import fs21 from "fs";
5370
- import path18 from "path";
5461
+ import fs22 from "fs";
5462
+ import path19 from "path";
5371
5463
  import chalk10 from "chalk";
5372
5464
  import ora5 from "ora";
5373
5465
 
@@ -5444,37 +5536,37 @@ function collectDiff(lastSha) {
5444
5536
  }
5445
5537
 
5446
5538
  // src/writers/refresh.ts
5447
- import fs20 from "fs";
5448
- import path17 from "path";
5539
+ import fs21 from "fs";
5540
+ import path18 from "path";
5449
5541
  function writeRefreshDocs(docs) {
5450
5542
  const written = [];
5451
5543
  if (docs.claudeMd) {
5452
- fs20.writeFileSync("CLAUDE.md", docs.claudeMd);
5544
+ fs21.writeFileSync("CLAUDE.md", docs.claudeMd);
5453
5545
  written.push("CLAUDE.md");
5454
5546
  }
5455
5547
  if (docs.readmeMd) {
5456
- fs20.writeFileSync("README.md", docs.readmeMd);
5548
+ fs21.writeFileSync("README.md", docs.readmeMd);
5457
5549
  written.push("README.md");
5458
5550
  }
5459
5551
  if (docs.cursorrules) {
5460
- fs20.writeFileSync(".cursorrules", docs.cursorrules);
5552
+ fs21.writeFileSync(".cursorrules", docs.cursorrules);
5461
5553
  written.push(".cursorrules");
5462
5554
  }
5463
5555
  if (docs.cursorRules) {
5464
- const rulesDir = path17.join(".cursor", "rules");
5465
- if (!fs20.existsSync(rulesDir)) fs20.mkdirSync(rulesDir, { recursive: true });
5556
+ const rulesDir = path18.join(".cursor", "rules");
5557
+ if (!fs21.existsSync(rulesDir)) fs21.mkdirSync(rulesDir, { recursive: true });
5466
5558
  for (const rule of docs.cursorRules) {
5467
- const filePath = path17.join(rulesDir, rule.filename);
5468
- fs20.writeFileSync(filePath, rule.content);
5559
+ const filePath = path18.join(rulesDir, rule.filename);
5560
+ fs21.writeFileSync(filePath, rule.content);
5469
5561
  written.push(filePath);
5470
5562
  }
5471
5563
  }
5472
5564
  if (docs.claudeSkills) {
5473
- const skillsDir = path17.join(".claude", "skills");
5474
- if (!fs20.existsSync(skillsDir)) fs20.mkdirSync(skillsDir, { recursive: true });
5565
+ const skillsDir = path18.join(".claude", "skills");
5566
+ if (!fs21.existsSync(skillsDir)) fs21.mkdirSync(skillsDir, { recursive: true });
5475
5567
  for (const skill of docs.claudeSkills) {
5476
- const filePath = path17.join(skillsDir, skill.filename);
5477
- fs20.writeFileSync(filePath, skill.content);
5568
+ const filePath = path18.join(skillsDir, skill.filename);
5569
+ fs21.writeFileSync(filePath, skill.content);
5478
5570
  written.push(filePath);
5479
5571
  }
5480
5572
  }
@@ -5549,11 +5641,11 @@ function log(quiet, ...args) {
5549
5641
  function discoverGitRepos(parentDir) {
5550
5642
  const repos = [];
5551
5643
  try {
5552
- const entries = fs21.readdirSync(parentDir, { withFileTypes: true });
5644
+ const entries = fs22.readdirSync(parentDir, { withFileTypes: true });
5553
5645
  for (const entry of entries) {
5554
5646
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
5555
- const childPath = path18.join(parentDir, entry.name);
5556
- if (fs21.existsSync(path18.join(childPath, ".git"))) {
5647
+ const childPath = path19.join(parentDir, entry.name);
5648
+ if (fs22.existsSync(path19.join(childPath, ".git"))) {
5557
5649
  repos.push(childPath);
5558
5650
  }
5559
5651
  }
@@ -5631,7 +5723,7 @@ async function refreshCommand(options) {
5631
5723
  const config = loadConfig();
5632
5724
  if (!config) {
5633
5725
  if (quiet) return;
5634
- console.log(chalk10.red("No LLM provider configured. Run `caliber config` (e.g. choose Cursor) or set an API key."));
5726
+ console.log(chalk10.red("No LLM provider configured. Run ") + chalk10.hex("#83D1EB")("caliber config") + chalk10.red(" (e.g. choose Cursor) or set an API key."));
5635
5727
  throw new Error("__exit__");
5636
5728
  }
5637
5729
  if (isGitRepo()) {
@@ -5648,7 +5740,7 @@ async function refreshCommand(options) {
5648
5740
  `));
5649
5741
  const originalDir = process.cwd();
5650
5742
  for (const repo of repos) {
5651
- const repoName = path18.basename(repo);
5743
+ const repoName = path19.basename(repo);
5652
5744
  try {
5653
5745
  process.chdir(repo);
5654
5746
  await refreshSingleRepo(repo, { ...options, label: repoName });
@@ -5895,8 +5987,8 @@ function readStdin() {
5895
5987
 
5896
5988
  // src/learner/storage.ts
5897
5989
  init_constants();
5898
- import fs22 from "fs";
5899
- import path19 from "path";
5990
+ import fs23 from "fs";
5991
+ import path20 from "path";
5900
5992
  var MAX_RESPONSE_LENGTH = 2e3;
5901
5993
  var DEFAULT_STATE = {
5902
5994
  sessionId: null,
@@ -5904,15 +5996,15 @@ var DEFAULT_STATE = {
5904
5996
  lastAnalysisTimestamp: null
5905
5997
  };
5906
5998
  function ensureLearningDir() {
5907
- if (!fs22.existsSync(LEARNING_DIR)) {
5908
- fs22.mkdirSync(LEARNING_DIR, { recursive: true });
5999
+ if (!fs23.existsSync(LEARNING_DIR)) {
6000
+ fs23.mkdirSync(LEARNING_DIR, { recursive: true });
5909
6001
  }
5910
6002
  }
5911
6003
  function sessionFilePath() {
5912
- return path19.join(LEARNING_DIR, LEARNING_SESSION_FILE);
6004
+ return path20.join(LEARNING_DIR, LEARNING_SESSION_FILE);
5913
6005
  }
5914
6006
  function stateFilePath() {
5915
- return path19.join(LEARNING_DIR, LEARNING_STATE_FILE);
6007
+ return path20.join(LEARNING_DIR, LEARNING_STATE_FILE);
5916
6008
  }
5917
6009
  function truncateResponse(response) {
5918
6010
  const str = JSON.stringify(response);
@@ -5923,50 +6015,50 @@ function appendEvent(event) {
5923
6015
  ensureLearningDir();
5924
6016
  const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
5925
6017
  const filePath = sessionFilePath();
5926
- fs22.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
6018
+ fs23.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
5927
6019
  const count = getEventCount();
5928
6020
  if (count > LEARNING_MAX_EVENTS) {
5929
- const lines = fs22.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6021
+ const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
5930
6022
  const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
5931
- fs22.writeFileSync(filePath, kept.join("\n") + "\n");
6023
+ fs23.writeFileSync(filePath, kept.join("\n") + "\n");
5932
6024
  }
5933
6025
  }
5934
6026
  function readAllEvents() {
5935
6027
  const filePath = sessionFilePath();
5936
- if (!fs22.existsSync(filePath)) return [];
5937
- const lines = fs22.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
6028
+ if (!fs23.existsSync(filePath)) return [];
6029
+ const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
5938
6030
  return lines.map((line) => JSON.parse(line));
5939
6031
  }
5940
6032
  function getEventCount() {
5941
6033
  const filePath = sessionFilePath();
5942
- if (!fs22.existsSync(filePath)) return 0;
5943
- const content = fs22.readFileSync(filePath, "utf-8");
6034
+ if (!fs23.existsSync(filePath)) return 0;
6035
+ const content = fs23.readFileSync(filePath, "utf-8");
5944
6036
  return content.split("\n").filter(Boolean).length;
5945
6037
  }
5946
6038
  function clearSession() {
5947
6039
  const filePath = sessionFilePath();
5948
- if (fs22.existsSync(filePath)) fs22.unlinkSync(filePath);
6040
+ if (fs23.existsSync(filePath)) fs23.unlinkSync(filePath);
5949
6041
  }
5950
6042
  function readState2() {
5951
6043
  const filePath = stateFilePath();
5952
- if (!fs22.existsSync(filePath)) return { ...DEFAULT_STATE };
6044
+ if (!fs23.existsSync(filePath)) return { ...DEFAULT_STATE };
5953
6045
  try {
5954
- return JSON.parse(fs22.readFileSync(filePath, "utf-8"));
6046
+ return JSON.parse(fs23.readFileSync(filePath, "utf-8"));
5955
6047
  } catch {
5956
6048
  return { ...DEFAULT_STATE };
5957
6049
  }
5958
6050
  }
5959
6051
  function writeState2(state) {
5960
6052
  ensureLearningDir();
5961
- fs22.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
6053
+ fs23.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
5962
6054
  }
5963
6055
  function resetState() {
5964
6056
  writeState2({ ...DEFAULT_STATE });
5965
6057
  }
5966
6058
 
5967
6059
  // src/learner/writer.ts
5968
- import fs23 from "fs";
5969
- import path20 from "path";
6060
+ import fs24 from "fs";
6061
+ import path21 from "path";
5970
6062
  var LEARNED_START = "<!-- caliber:learned -->";
5971
6063
  var LEARNED_END = "<!-- /caliber:learned -->";
5972
6064
  function writeLearnedContent(update) {
@@ -5986,8 +6078,8 @@ function writeLearnedContent(update) {
5986
6078
  function writeLearnedSection(content) {
5987
6079
  const claudeMdPath = "CLAUDE.md";
5988
6080
  let existing = "";
5989
- if (fs23.existsSync(claudeMdPath)) {
5990
- existing = fs23.readFileSync(claudeMdPath, "utf-8");
6081
+ if (fs24.existsSync(claudeMdPath)) {
6082
+ existing = fs24.readFileSync(claudeMdPath, "utf-8");
5991
6083
  }
5992
6084
  const section = `${LEARNED_START}
5993
6085
  ${content}
@@ -6001,15 +6093,15 @@ ${LEARNED_END}`;
6001
6093
  const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
6002
6094
  updated = existing + separator + "\n" + section + "\n";
6003
6095
  }
6004
- fs23.writeFileSync(claudeMdPath, updated);
6096
+ fs24.writeFileSync(claudeMdPath, updated);
6005
6097
  }
6006
6098
  function writeLearnedSkill(skill) {
6007
- const skillDir = path20.join(".claude", "skills", skill.name);
6008
- if (!fs23.existsSync(skillDir)) fs23.mkdirSync(skillDir, { recursive: true });
6009
- const skillPath = path20.join(skillDir, "SKILL.md");
6010
- if (!skill.isNew && fs23.existsSync(skillPath)) {
6011
- const existing = fs23.readFileSync(skillPath, "utf-8");
6012
- fs23.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
6099
+ const skillDir = path21.join(".claude", "skills", skill.name);
6100
+ if (!fs24.existsSync(skillDir)) fs24.mkdirSync(skillDir, { recursive: true });
6101
+ const skillPath = path21.join(skillDir, "SKILL.md");
6102
+ if (!skill.isNew && fs24.existsSync(skillPath)) {
6103
+ const existing = fs24.readFileSync(skillPath, "utf-8");
6104
+ fs24.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
6013
6105
  } else {
6014
6106
  const frontmatter = [
6015
6107
  "---",
@@ -6018,14 +6110,14 @@ function writeLearnedSkill(skill) {
6018
6110
  "---",
6019
6111
  ""
6020
6112
  ].join("\n");
6021
- fs23.writeFileSync(skillPath, frontmatter + skill.content);
6113
+ fs24.writeFileSync(skillPath, frontmatter + skill.content);
6022
6114
  }
6023
6115
  return skillPath;
6024
6116
  }
6025
6117
  function readLearnedSection() {
6026
6118
  const claudeMdPath = "CLAUDE.md";
6027
- if (!fs23.existsSync(claudeMdPath)) return null;
6028
- const content = fs23.readFileSync(claudeMdPath, "utf-8");
6119
+ if (!fs24.existsSync(claudeMdPath)) return null;
6120
+ const content = fs24.readFileSync(claudeMdPath, "utf-8");
6029
6121
  const startIdx = content.indexOf(LEARNED_START);
6030
6122
  const endIdx = content.indexOf(LEARNED_END);
6031
6123
  if (startIdx === -1 || endIdx === -1) return null;
@@ -6209,14 +6301,14 @@ Learned items in CLAUDE.md: ${chalk13.cyan(String(lineCount))}`);
6209
6301
  }
6210
6302
 
6211
6303
  // src/cli.ts
6212
- var __dirname = path21.dirname(fileURLToPath(import.meta.url));
6304
+ var __dirname = path22.dirname(fileURLToPath(import.meta.url));
6213
6305
  var pkg = JSON.parse(
6214
- fs24.readFileSync(path21.resolve(__dirname, "..", "package.json"), "utf-8")
6306
+ fs25.readFileSync(path22.resolve(__dirname, "..", "package.json"), "utf-8")
6215
6307
  );
6216
6308
  var program = new Command();
6217
6309
  var displayVersion = process.env.CALIBER_LOCAL ? `${pkg.version}-local` : pkg.version;
6218
6310
  program.name(process.env.CALIBER_LOCAL ? "caloc" : "caliber").description("Configure your coding agent environment").version(displayVersion);
6219
- 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);
6311
+ program.command("onboard").alias("init").description("Onboard your project for AI-assisted development").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);
6220
6312
  program.command("undo").description("Revert all config changes made by Caliber").action(undoCommand);
6221
6313
  program.command("status").description("Show current Caliber setup status").option("--json", "Output as JSON").action(statusCommand);
6222
6314
  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);
@@ -6233,22 +6325,22 @@ learn.command("remove").description("Remove learning hooks from .claude/settings
6233
6325
  learn.command("status").description("Show learning system status").action(learnStatusCommand);
6234
6326
 
6235
6327
  // src/utils/version-check.ts
6236
- import fs25 from "fs";
6237
- import path22 from "path";
6328
+ import fs26 from "fs";
6329
+ import path23 from "path";
6238
6330
  import { fileURLToPath as fileURLToPath2 } from "url";
6239
6331
  import { execSync as execSync9 } from "child_process";
6240
6332
  import chalk14 from "chalk";
6241
6333
  import ora6 from "ora";
6242
6334
  import confirm2 from "@inquirer/confirm";
6243
- var __dirname_vc = path22.dirname(fileURLToPath2(import.meta.url));
6335
+ var __dirname_vc = path23.dirname(fileURLToPath2(import.meta.url));
6244
6336
  var pkg2 = JSON.parse(
6245
- fs25.readFileSync(path22.resolve(__dirname_vc, "..", "package.json"), "utf-8")
6337
+ fs26.readFileSync(path23.resolve(__dirname_vc, "..", "package.json"), "utf-8")
6246
6338
  );
6247
6339
  function getInstalledVersion() {
6248
6340
  try {
6249
6341
  const globalRoot = execSync9("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
6250
- const pkgPath = path22.join(globalRoot, "@rely-ai", "caliber", "package.json");
6251
- return JSON.parse(fs25.readFileSync(pkgPath, "utf-8")).version;
6342
+ const pkgPath = path23.join(globalRoot, "@rely-ai", "caliber", "package.json");
6343
+ return JSON.parse(fs26.readFileSync(pkgPath, "utf-8")).version;
6252
6344
  } catch {
6253
6345
  return null;
6254
6346
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "0.9.1",
3
+ "version": "1.1.0",
4
4
  "description": "Analyze your codebase and generate optimized AI agent configs (CLAUDE.md, .cursorrules, skills) — no API key needed",
5
5
  "type": "module",
6
6
  "bin": {