@rely-ai/caliber 1.47.2 → 1.48.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.
Files changed (2) hide show
  1. package/dist/bin.js +92 -31
  2. package/package.json +4 -1
package/dist/bin.js CHANGED
@@ -276,6 +276,21 @@ var init_resolve_caliber = __esm({
276
276
  }
277
277
  });
278
278
 
279
+ // src/utils/windows.ts
280
+ function quoteForWindows(arg) {
281
+ if (!arg) return '""';
282
+ if (!/[ \t\n\v"]/.test(arg)) return arg;
283
+ return '"' + arg.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/, "$1$1") + '"';
284
+ }
285
+ function bashPath(p) {
286
+ return p.replace(/\\/g, "/");
287
+ }
288
+ var init_windows = __esm({
289
+ "src/utils/windows.ts"() {
290
+ "use strict";
291
+ }
292
+ });
293
+
279
294
  // src/llm/types.ts
280
295
  var types_exports = {};
281
296
  __export(types_exports, {
@@ -961,10 +976,20 @@ function openDiffsInEditor(editor, files) {
961
976
  try {
962
977
  const leftPath = file.originalPath ?? getEmptyFilePath(file.proposedPath);
963
978
  if (IS_WINDOWS5) {
964
- const quote = (s) => `"${s}"`;
965
- spawn4([cmd, "--diff", quote(leftPath), quote(file.proposedPath)].join(" "), { shell: true, stdio: "ignore", detached: true }).unref();
979
+ spawn4(
980
+ [
981
+ quoteForWindows(cmd),
982
+ "--diff",
983
+ quoteForWindows(leftPath),
984
+ quoteForWindows(file.proposedPath)
985
+ ].join(" "),
986
+ { shell: true, stdio: "ignore", detached: true }
987
+ ).unref();
966
988
  } else {
967
- spawn4(cmd, ["--diff", leftPath, file.proposedPath], { stdio: "ignore", detached: true }).unref();
989
+ spawn4(cmd, ["--diff", leftPath, file.proposedPath], {
990
+ stdio: "ignore",
991
+ detached: true
992
+ }).unref();
968
993
  }
969
994
  } catch {
970
995
  continue;
@@ -975,6 +1000,7 @@ var IS_WINDOWS5, DIFF_TEMP_DIR;
975
1000
  var init_editor = __esm({
976
1001
  "src/utils/editor.ts"() {
977
1002
  "use strict";
1003
+ init_windows();
978
1004
  IS_WINDOWS5 = process.platform === "win32";
979
1005
  DIFF_TEMP_DIR = path25.join(os6.tmpdir(), "caliber-diff");
980
1006
  }
@@ -2622,11 +2648,21 @@ function estimateTokens(text) {
2622
2648
  return Math.ceil(text.length / 4);
2623
2649
  }
2624
2650
 
2625
- // src/utils/windows.ts
2626
- function quoteForWindows(arg) {
2627
- if (!arg) return '""';
2628
- if (!/[ \t\n\v"]/.test(arg)) return arg;
2629
- return '"' + arg.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/, "$1$1") + '"';
2651
+ // src/llm/cursor-acp.ts
2652
+ init_windows();
2653
+
2654
+ // src/lib/subprocess-sentinel.ts
2655
+ var CALIBER_SUBPROCESS_ENV = "CALIBER_SUBPROCESS";
2656
+ var CALIBER_SUBPROCESS_LEGACY_ENV = "CALIBER_SPAWNED";
2657
+ function isCaliberSubprocess() {
2658
+ return process.env[CALIBER_SUBPROCESS_ENV] === "1";
2659
+ }
2660
+ function withCaliberSubprocessEnv(env) {
2661
+ return {
2662
+ ...env,
2663
+ [CALIBER_SUBPROCESS_ENV]: "1",
2664
+ [CALIBER_SUBPROCESS_LEGACY_ENV]: "1"
2665
+ };
2630
2666
  }
2631
2667
 
2632
2668
  // src/llm/cursor-acp.ts
@@ -2704,7 +2740,10 @@ var CursorAcpProvider = class {
2704
2740
  const targetModel = model || this.defaultModel;
2705
2741
  if (this.warmProcess && !this.warmProcess.killed && this.warmModel === targetModel) return;
2706
2742
  const args = this.buildArgs(targetModel, false);
2707
- const env = { ...process.env, ...this.cursorApiKey && { CURSOR_API_KEY: this.cursorApiKey } };
2743
+ const env = withCaliberSubprocessEnv({
2744
+ ...process.env,
2745
+ ...this.cursorApiKey && { CURSOR_API_KEY: this.cursorApiKey }
2746
+ });
2708
2747
  this.warmProcess = IS_WINDOWS ? spawn([quoteForWindows(resolveAgentBin()), ...args.map(quoteForWindows)].join(" "), {
2709
2748
  stdio: ["pipe", "pipe", "pipe"],
2710
2749
  env,
@@ -2755,7 +2794,10 @@ var CursorAcpProvider = class {
2755
2794
  return { child: warm, stderrChunks: stderrChunks2 };
2756
2795
  }
2757
2796
  const args = this.buildArgs(model, streaming);
2758
- const env = { ...process.env, ...this.cursorApiKey && { CURSOR_API_KEY: this.cursorApiKey } };
2797
+ const env = withCaliberSubprocessEnv({
2798
+ ...process.env,
2799
+ ...this.cursorApiKey && { CURSOR_API_KEY: this.cursorApiKey }
2800
+ });
2759
2801
  const child = IS_WINDOWS ? spawn([quoteForWindows(resolveAgentBin()), ...args.map(quoteForWindows)].join(" "), {
2760
2802
  stdio: ["pipe", "pipe", "pipe"],
2761
2803
  env,
@@ -2957,6 +2999,7 @@ function isCursorLoggedIn() {
2957
2999
  // src/llm/claude-cli.ts
2958
3000
  import fs7 from "fs";
2959
3001
  import { spawn as spawn2, execSync as execSync7, execFileSync as execFileSync2 } from "child_process";
3002
+ init_windows();
2960
3003
  var DEFAULT_TIMEOUT_MS3 = 10 * 60 * 1e3;
2961
3004
  var IS_WINDOWS2 = process.platform === "win32";
2962
3005
  function candidateClaudePaths() {
@@ -3006,13 +3049,19 @@ function cleanClaudeEnv() {
3006
3049
  }
3007
3050
  function spawnClaude(args) {
3008
3051
  const bin = resolveClaudeBin();
3009
- const env = { ...cleanClaudeEnv(), CALIBER_SPAWNED: "1" };
3010
- return IS_WINDOWS2 ? spawn2([bin, ...args].join(" "), {
3011
- cwd: process.cwd(),
3012
- stdio: ["pipe", "pipe", "pipe"],
3013
- env,
3014
- shell: true
3015
- }) : spawn2(bin, args, {
3052
+ const env = withCaliberSubprocessEnv(cleanClaudeEnv());
3053
+ return IS_WINDOWS2 ? (
3054
+ // Windows path: shell:true skips Node's exe quoting, so paths like
3055
+ // `C:\Program Files\caliber\claude.cmd` (with spaces) would be parsed by
3056
+ // cmd.exe as multiple words. Quote each piece explicitly. Mirrors the
3057
+ // pattern used in cursor-acp.ts and the learn-finalize background spawn.
3058
+ spawn2([quoteForWindows(bin), ...args.map(quoteForWindows)].join(" "), {
3059
+ cwd: process.cwd(),
3060
+ stdio: ["pipe", "pipe", "pipe"],
3061
+ env,
3062
+ shell: true
3063
+ })
3064
+ ) : spawn2(bin, args, {
3016
3065
  cwd: process.cwd(),
3017
3066
  stdio: ["pipe", "pipe", "pipe"],
3018
3067
  env
@@ -3174,7 +3223,7 @@ function isClaudeCliLoggedIn() {
3174
3223
  input: "",
3175
3224
  stdio: ["pipe", "pipe", "pipe"],
3176
3225
  timeout: 5e3,
3177
- env: cleanClaudeEnv()
3226
+ env: withCaliberSubprocessEnv(cleanClaudeEnv())
3178
3227
  });
3179
3228
  const output = result.toString().trim();
3180
3229
  try {
@@ -3224,7 +3273,7 @@ function isOpenCodeLoggedIn() {
3224
3273
  return cachedLoggedIn2;
3225
3274
  }
3226
3275
  function spawnOpenCode(args) {
3227
- const env = { ...process.env, OPENCODE_DISABLE_AUTOCOMPACT: "TRUE" };
3276
+ const env = withCaliberSubprocessEnv({ ...process.env, OPENCODE_DISABLE_AUTOCOMPACT: "TRUE" });
3228
3277
  if (IS_WINDOWS3) {
3229
3278
  return spawn3([OPENCODE_BIN, ...args].join(" "), {
3230
3279
  cwd: process.cwd(),
@@ -4587,6 +4636,7 @@ function getCursorConfigDir() {
4587
4636
 
4588
4637
  // src/lib/hooks.ts
4589
4638
  init_resolve_caliber();
4639
+ init_windows();
4590
4640
  import fs11 from "fs";
4591
4641
  import path10 from "path";
4592
4642
  import { execSync as execSync10 } from "child_process";
@@ -4705,6 +4755,10 @@ function createScriptHook(config) {
4705
4755
  return { isInstalled, install, remove };
4706
4756
  }
4707
4757
  var STOP_HOOK_SCRIPT_CONTENT = `#!/bin/sh
4758
+ # Don't block headless claude sessions spawned by caliber itself (e.g. during caliber refresh)
4759
+ if [ "$CALIBER_SUBPROCESS" = "1" ] || [ -n "$CALIBER_SPAWNED" ]; then
4760
+ exit 0
4761
+ fi
4708
4762
  if grep -q "caliber" .git/hooks/pre-commit 2>/dev/null; then
4709
4763
  exit 0
4710
4764
  fi
@@ -4727,6 +4781,11 @@ var removeStopHook = stopHook.remove;
4727
4781
  function getFreshnessScript() {
4728
4782
  const bin = resolveCaliber();
4729
4783
  return `#!/bin/sh
4784
+ # Don't run inside a caliber-spawned headless session \u2014 the systemMessage would
4785
+ # pollute the spawned agent's output and serves no purpose there.
4786
+ if [ "$CALIBER_SUBPROCESS" = "1" ] || [ -n "$CALIBER_SPAWNED" ]; then
4787
+ exit 0
4788
+ fi
4730
4789
  STATE_FILE=".caliber/.caliber-state.json"
4731
4790
  [ ! -f "$STATE_FILE" ] && exit 0
4732
4791
  LAST_SHA=$(grep -o '"lastRefreshSha":"[^"]*"' "$STATE_FILE" 2>/dev/null | cut -d'"' -f4)
@@ -4765,22 +4824,24 @@ function getPrecommitBlock() {
4765
4824
  let guard;
4766
4825
  let invoke;
4767
4826
  if (npx) {
4768
- const npxBin = cmd.split(" ")[0];
4769
- if (npxBin.startsWith("/")) {
4827
+ const npxBinRaw = cmd.split(" ")[0];
4828
+ const npxBin = bashPath(npxBinRaw);
4829
+ if (path10.isAbsolute(npxBinRaw)) {
4770
4830
  guard = `[ -x "${npxBin}" ]`;
4771
- const npxArgs = cmd.slice(npxBin.length);
4831
+ const npxArgs = cmd.slice(npxBinRaw.length);
4772
4832
  invoke = `"${npxBin}"${npxArgs}`;
4773
4833
  } else {
4774
4834
  guard = "command -v npx >/dev/null 2>&1";
4775
4835
  invoke = cmd;
4776
4836
  }
4777
4837
  } else {
4778
- if (cmd.startsWith("/")) {
4779
- guard = `[ -x "${cmd}" ]`;
4838
+ const cmdBash = bashPath(cmd);
4839
+ if (path10.isAbsolute(cmd)) {
4840
+ guard = `[ -x "${cmdBash}" ]`;
4780
4841
  } else {
4781
- guard = `[ -x "${cmd}" ] || command -v "${cmd}" >/dev/null 2>&1`;
4842
+ guard = `[ -x "${cmdBash}" ] || command -v "${cmdBash}" >/dev/null 2>&1`;
4782
4843
  }
4783
- invoke = `"${cmd}"`;
4844
+ invoke = `"${cmdBash}"`;
4784
4845
  }
4785
4846
  return `${PRECOMMIT_START}
4786
4847
  if ${guard}; then
@@ -13114,7 +13175,7 @@ async function refreshSingleRepo(repoDir, options) {
13114
13175
  }
13115
13176
  async function refreshCommand(options) {
13116
13177
  const quiet = !!options.quiet;
13117
- if (quiet && process.env.CALIBER_SPAWNED === "1") return;
13178
+ if (quiet && isCaliberSubprocess()) return;
13118
13179
  if (quiet) {
13119
13180
  const { isCaliberRunning: isCaliberRunning2 } = await Promise.resolve().then(() => (init_lock(), lock_exports));
13120
13181
  if (isCaliberRunning2()) return;
@@ -13425,6 +13486,7 @@ async function configCommand() {
13425
13486
  }
13426
13487
 
13427
13488
  // src/commands/learn.ts
13489
+ init_windows();
13428
13490
  import fs48 from "fs";
13429
13491
  import path39 from "path";
13430
13492
  import chalk23 from "chalk";
@@ -14082,7 +14144,7 @@ function readFinalizeError() {
14082
14144
  }
14083
14145
  }
14084
14146
  async function learnObserveCommand(options) {
14085
- if (process.env.CALIBER_SPAWNED === "1") return;
14147
+ if (isCaliberSubprocess()) return;
14086
14148
  try {
14087
14149
  const raw = await readStdin();
14088
14150
  if (!raw.trim()) return;
@@ -14162,7 +14224,7 @@ async function learnObserveCommand(options) {
14162
14224
  async function learnFinalizeCommand(options) {
14163
14225
  const isAuto = options?.auto === true;
14164
14226
  const isIncremental = options?.incremental === true;
14165
- if (isAuto && process.env.CALIBER_SPAWNED === "1") return;
14227
+ if (isAuto && isCaliberSubprocess()) return;
14166
14228
  if (!options?.force && !isAuto) {
14167
14229
  const { isCaliberRunning: isCaliberRunning2 } = await Promise.resolve().then(() => (init_lock(), lock_exports));
14168
14230
  if (isCaliberRunning2()) {
@@ -15194,10 +15256,9 @@ function parseAgentOption(value) {
15194
15256
  )
15195
15257
  ];
15196
15258
  if (agents.length === 0) {
15197
- console.error(
15259
+ throw new Error(
15198
15260
  `Invalid agent "${value}". Choose from: claude, cursor, codex, opencode, github-copilot (comma-separated for multiple)`
15199
15261
  );
15200
- process.exit(1);
15201
15262
  }
15202
15263
  return agents;
15203
15264
  }
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@rely-ai/caliber",
3
- "version": "1.47.2",
3
+ "version": "1.48.1",
4
4
  "description": "AI context infrastructure for coding agents — keeps CLAUDE.md, Cursor rules, and skills in sync as your codebase evolves",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "caliber": "./dist/bin.js"
8
8
  },
9
9
  "scripts": {
10
+ "prebuild": "npm run build:skills:check",
10
11
  "build": "tsup",
12
+ "build:skills": "node scripts/generate-skills.mjs",
13
+ "build:skills:check": "node scripts/generate-skills.mjs --check",
11
14
  "dev": "tsup --watch",
12
15
  "dev:install": "npm run build && npm link",
13
16
  "test": "vitest run",