@rely-ai/caliber 0.8.1 → 0.9.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 (2) hide show
  1. package/dist/bin.js +206 -30
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -3965,6 +3965,11 @@ async function initCommand(options) {
3965
3965
  const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
3966
3966
  genSpinner.succeed(`Setup generated ${chalk4.dim(`in ${timeStr}`)}`);
3967
3967
  printSetupSummary(generatedSetup);
3968
+ const sessionHistory = [];
3969
+ sessionHistory.push({
3970
+ role: "assistant",
3971
+ content: summarizeSetup("Initial generation", generatedSetup)
3972
+ });
3968
3973
  console.log(title.bold(" Step 4/4 \u2014 Review\n"));
3969
3974
  const setupFiles = collectSetupFiles(generatedSetup);
3970
3975
  const staged = stageFiles(setupFiles, process.cwd());
@@ -3973,11 +3978,11 @@ async function initCommand(options) {
3973
3978
  const wantsReview = await promptWantsReview();
3974
3979
  if (wantsReview) {
3975
3980
  const reviewMethod = await promptReviewMethod();
3976
- openReview(reviewMethod, staged.stagedFiles);
3981
+ await openReview(reviewMethod, staged.stagedFiles);
3977
3982
  }
3978
3983
  let action = await promptReviewAction();
3979
3984
  while (action === "refine") {
3980
- generatedSetup = await refineLoop(generatedSetup, targetAgent);
3985
+ generatedSetup = await refineLoop(generatedSetup, targetAgent, sessionHistory);
3981
3986
  if (!generatedSetup) {
3982
3987
  cleanupStaging();
3983
3988
  console.log(chalk4.dim("Refinement cancelled. No files were modified."));
@@ -3988,11 +3993,7 @@ async function initCommand(options) {
3988
3993
  console.log(chalk4.dim(` ${chalk4.green(`${restaged.newFiles} new`)} / ${chalk4.yellow(`${restaged.modifiedFiles} modified`)} file${restaged.newFiles + restaged.modifiedFiles !== 1 ? "s" : ""}
3989
3994
  `));
3990
3995
  printSetupSummary(generatedSetup);
3991
- const wantsReviewAgain = await promptWantsReview();
3992
- if (wantsReviewAgain) {
3993
- const reviewMethod = await promptReviewMethod();
3994
- openReview(reviewMethod, restaged.stagedFiles);
3995
- }
3996
+ await openReview("terminal", restaged.stagedFiles);
3996
3997
  action = await promptReviewAction();
3997
3998
  }
3998
3999
  cleanupStaging();
@@ -4092,8 +4093,7 @@ async function initCommand(options) {
4092
4093
  console.log(` ${title("caliber undo")} Revert all changes from this run`);
4093
4094
  console.log("");
4094
4095
  }
4095
- async function refineLoop(currentSetup, _targetAgent) {
4096
- const history = [];
4096
+ async function refineLoop(currentSetup, _targetAgent, sessionHistory) {
4097
4097
  while (true) {
4098
4098
  const message = await promptInput2("\nWhat would you like to change?");
4099
4099
  if (!message || message.toLowerCase() === "done" || message.toLowerCase() === "accept") {
@@ -4102,19 +4102,29 @@ async function refineLoop(currentSetup, _targetAgent) {
4102
4102
  if (message.toLowerCase() === "cancel") {
4103
4103
  return null;
4104
4104
  }
4105
+ const isValid = await classifyRefineIntent(message);
4106
+ if (!isValid) {
4107
+ console.log(chalk4.dim(" This doesn't look like a config change request."));
4108
+ console.log(chalk4.dim(" Describe what to add, remove, or modify in your configs."));
4109
+ console.log(chalk4.dim(' Type "done" to accept the current setup.\n'));
4110
+ continue;
4111
+ }
4105
4112
  const refineSpinner = ora("Refining setup...").start();
4106
4113
  const refineMessages = new SpinnerMessages(refineSpinner, REFINE_MESSAGES);
4107
4114
  refineMessages.start();
4108
4115
  const refined = await refineSetup(
4109
4116
  currentSetup,
4110
4117
  message,
4111
- history
4118
+ sessionHistory
4112
4119
  );
4113
4120
  refineMessages.stop();
4114
4121
  if (refined) {
4115
4122
  currentSetup = refined;
4116
- history.push({ role: "user", content: message });
4117
- history.push({ role: "assistant", content: JSON.stringify(refined) });
4123
+ sessionHistory.push({ role: "user", content: message });
4124
+ sessionHistory.push({
4125
+ role: "assistant",
4126
+ content: summarizeSetup("Applied changes", refined)
4127
+ });
4118
4128
  refineSpinner.succeed("Setup updated");
4119
4129
  printSetupSummary(refined);
4120
4130
  console.log(chalk4.dim('Type "done" to accept, or describe more changes.'));
@@ -4124,6 +4134,29 @@ async function refineLoop(currentSetup, _targetAgent) {
4124
4134
  }
4125
4135
  }
4126
4136
  }
4137
+ function summarizeSetup(action, setup) {
4138
+ 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(", ");
4140
+ return `${action}. Files:
4141
+ ${files}`;
4142
+ }
4143
+ async function classifyRefineIntent(message) {
4144
+ const fastModel = process.env.ANTHROPIC_SMALL_FAST_MODEL;
4145
+ try {
4146
+ const result = await llmJsonCall({
4147
+ system: `You classify whether a user message is a valid request to modify AI agent config files (CLAUDE.md, .cursorrules, skills).
4148
+ Valid: requests to add, remove, change, or restructure config content. Examples: "add testing commands", "remove the terraform section", "make CLAUDE.md shorter".
4149
+ Invalid: questions, requests to show/display something, general chat, or anything that isn't a concrete config change.
4150
+ Return {"valid": true} or {"valid": false}. Nothing else.`,
4151
+ prompt: message,
4152
+ maxTokens: 20,
4153
+ ...fastModel ? { model: fastModel } : {}
4154
+ });
4155
+ return result.valid === true;
4156
+ } catch {
4157
+ return true;
4158
+ }
4159
+ }
4127
4160
  function promptInput2(question) {
4128
4161
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
4129
4162
  return new Promise((resolve2) => {
@@ -4183,34 +4216,177 @@ async function promptReviewMethod() {
4183
4216
  });
4184
4217
  return select2({ message: "How would you like to review the changes?", choices });
4185
4218
  }
4186
- function openReview(method, stagedFiles) {
4219
+ async function openReview(method, stagedFiles) {
4187
4220
  if (method === "cursor" || method === "vscode") {
4188
4221
  openDiffsInEditor(method, stagedFiles.map((f) => ({
4189
4222
  originalPath: f.originalPath,
4190
4223
  proposedPath: f.proposedPath
4191
4224
  })));
4192
4225
  console.log(chalk4.dim(" Diffs opened in your editor.\n"));
4193
- } else {
4194
- for (const file of stagedFiles) {
4195
- if (file.currentPath) {
4196
- const currentLines = fs17.readFileSync(file.currentPath, "utf-8").split("\n");
4197
- const proposedLines = fs17.readFileSync(file.proposedPath, "utf-8").split("\n");
4198
- const patch = createTwoFilesPatch(file.relativePath, file.relativePath, currentLines.join("\n"), proposedLines.join("\n"));
4199
- let added = 0, removed = 0;
4200
- for (const line of patch.split("\n")) {
4201
- if (line.startsWith("+") && !line.startsWith("+++")) added++;
4202
- if (line.startsWith("-") && !line.startsWith("---")) removed++;
4203
- }
4204
- console.log(` ${chalk4.yellow("~")} ${file.relativePath} ${chalk4.green(`+${added}`)} ${chalk4.red(`-${removed}`)}`);
4226
+ return;
4227
+ }
4228
+ 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") : "";
4231
+ const patch = createTwoFilesPatch(
4232
+ file.isNew ? "/dev/null" : file.relativePath,
4233
+ file.relativePath,
4234
+ current,
4235
+ proposed
4236
+ );
4237
+ let added = 0, removed = 0;
4238
+ for (const line of patch.split("\n")) {
4239
+ if (line.startsWith("+") && !line.startsWith("+++")) added++;
4240
+ if (line.startsWith("-") && !line.startsWith("---")) removed++;
4241
+ }
4242
+ return {
4243
+ relativePath: file.relativePath,
4244
+ isNew: file.isNew,
4245
+ added,
4246
+ removed,
4247
+ lines: proposed.split("\n").length,
4248
+ patch
4249
+ };
4250
+ });
4251
+ await interactiveDiffExplorer(fileInfos);
4252
+ }
4253
+ async function interactiveDiffExplorer(files) {
4254
+ if (!process.stdin.isTTY) {
4255
+ for (const f of files) {
4256
+ const icon = f.isNew ? chalk4.green("+") : chalk4.yellow("~");
4257
+ const stats = f.isNew ? chalk4.dim(`${f.lines} lines`) : `${chalk4.green(`+${f.added}`)} ${chalk4.red(`-${f.removed}`)}`;
4258
+ console.log(` ${icon} ${f.relativePath} ${stats}`);
4259
+ }
4260
+ console.log("");
4261
+ return;
4262
+ }
4263
+ const { stdin, stdout } = process;
4264
+ let cursor = 0;
4265
+ let viewing = null;
4266
+ let scrollOffset = 0;
4267
+ let lineCount = 0;
4268
+ function getTermHeight() {
4269
+ return (stdout.rows || 24) - 4;
4270
+ }
4271
+ function renderFileList() {
4272
+ const lines = [];
4273
+ lines.push(chalk4.bold(" Review changes"));
4274
+ lines.push("");
4275
+ for (let i = 0; i < files.length; i++) {
4276
+ const f = files[i];
4277
+ const ptr = i === cursor ? chalk4.cyan(">") : " ";
4278
+ const icon = f.isNew ? chalk4.green("+") : chalk4.yellow("~");
4279
+ const stats = f.isNew ? chalk4.dim(`${f.lines} lines`) : `${chalk4.green(`+${f.added}`)} ${chalk4.red(`-${f.removed}`)}`;
4280
+ lines.push(` ${ptr} ${icon} ${f.relativePath} ${stats}`);
4281
+ }
4282
+ lines.push("");
4283
+ lines.push(chalk4.dim(" \u2191\u2193 navigate \u23CE view diff q done"));
4284
+ return lines.join("\n");
4285
+ }
4286
+ function renderDiff(index) {
4287
+ const f = files[index];
4288
+ const lines = [];
4289
+ const header = f.isNew ? ` ${chalk4.green("+")} ${f.relativePath} ${chalk4.dim("(new file)")}` : ` ${chalk4.yellow("~")} ${f.relativePath} ${chalk4.green(`+${f.added}`)} ${chalk4.red(`-${f.removed}`)}`;
4290
+ lines.push(header);
4291
+ lines.push(chalk4.dim(" " + "\u2500".repeat(60)));
4292
+ const patchLines = f.patch.split("\n");
4293
+ const bodyLines = patchLines.slice(4);
4294
+ const maxVisible = getTermHeight() - 4;
4295
+ const visibleLines = bodyLines.slice(scrollOffset, scrollOffset + maxVisible);
4296
+ for (const line of visibleLines) {
4297
+ if (line.startsWith("+")) {
4298
+ lines.push(chalk4.green(" " + line));
4299
+ } else if (line.startsWith("-")) {
4300
+ lines.push(chalk4.red(" " + line));
4301
+ } else if (line.startsWith("@@")) {
4302
+ lines.push(chalk4.cyan(" " + line));
4205
4303
  } else {
4206
- const lines = fs17.readFileSync(file.proposedPath, "utf-8").split("\n").length;
4207
- console.log(` ${chalk4.green("+")} ${file.relativePath} ${chalk4.dim(`${lines} lines`)}`);
4304
+ lines.push(chalk4.dim(" " + line));
4208
4305
  }
4209
4306
  }
4210
- console.log("");
4211
- console.log(chalk4.dim(` Files staged at .caliber/staged/ for manual inspection.
4212
- `));
4307
+ const totalBody = bodyLines.length;
4308
+ if (totalBody > maxVisible) {
4309
+ const pct = Math.round((scrollOffset + maxVisible) / totalBody * 100);
4310
+ lines.push(chalk4.dim(` \u2500\u2500 ${Math.min(pct, 100)}% \u2500\u2500`));
4311
+ }
4312
+ lines.push("");
4313
+ lines.push(chalk4.dim(" \u2191\u2193 scroll \u23B5/esc back to file list"));
4314
+ return lines.join("\n");
4315
+ }
4316
+ function draw(initial) {
4317
+ if (!initial && lineCount > 0) {
4318
+ stdout.write(`\x1B[${lineCount}A`);
4319
+ }
4320
+ stdout.write("\x1B[0J");
4321
+ const output = viewing !== null ? renderDiff(viewing) : renderFileList();
4322
+ stdout.write(output + "\n");
4323
+ lineCount = output.split("\n").length;
4213
4324
  }
4325
+ return new Promise((resolve2) => {
4326
+ console.log("");
4327
+ draw(true);
4328
+ stdin.setRawMode(true);
4329
+ stdin.resume();
4330
+ stdin.setEncoding("utf8");
4331
+ function cleanup() {
4332
+ stdin.removeListener("data", onData);
4333
+ stdin.setRawMode(false);
4334
+ stdin.pause();
4335
+ }
4336
+ function onData(key) {
4337
+ if (viewing !== null) {
4338
+ const f = files[viewing];
4339
+ const totalBody = f.patch.split("\n").length - 4;
4340
+ const maxVisible = getTermHeight() - 4;
4341
+ switch (key) {
4342
+ case "\x1B[A":
4343
+ scrollOffset = Math.max(0, scrollOffset - 1);
4344
+ draw(false);
4345
+ break;
4346
+ case "\x1B[B":
4347
+ scrollOffset = Math.min(Math.max(0, totalBody - maxVisible), scrollOffset + 1);
4348
+ draw(false);
4349
+ break;
4350
+ case " ":
4351
+ case "\x1B":
4352
+ viewing = null;
4353
+ scrollOffset = 0;
4354
+ draw(false);
4355
+ break;
4356
+ case "q":
4357
+ case "":
4358
+ cleanup();
4359
+ console.log("");
4360
+ resolve2();
4361
+ break;
4362
+ }
4363
+ } else {
4364
+ switch (key) {
4365
+ case "\x1B[A":
4366
+ cursor = (cursor - 1 + files.length) % files.length;
4367
+ draw(false);
4368
+ break;
4369
+ case "\x1B[B":
4370
+ cursor = (cursor + 1) % files.length;
4371
+ draw(false);
4372
+ break;
4373
+ case "\r":
4374
+ case "\n":
4375
+ viewing = cursor;
4376
+ scrollOffset = 0;
4377
+ draw(false);
4378
+ break;
4379
+ case "q":
4380
+ case "":
4381
+ cleanup();
4382
+ console.log("");
4383
+ resolve2();
4384
+ break;
4385
+ }
4386
+ }
4387
+ }
4388
+ stdin.on("data", onData);
4389
+ });
4214
4390
  }
4215
4391
  async function promptReviewAction() {
4216
4392
  return select2({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "0.8.1",
3
+ "version": "0.9.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": {