@meistrari/tela-cli 0.7.29 → 0.7.31

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/index.js +177 -16
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -910,11 +910,16 @@ var require_process = __commonJS({
910
910
  exports2.runCommand = runCommand;
911
911
  exports2.runShell = runShell;
912
912
  exports2.commandExists = commandExists;
913
+ exports2.resolveExecutable = resolveExecutable;
913
914
  exports2.runInteractiveCommand = runInteractiveCommand;
914
915
  exports2.runStreamingCommand = runStreamingCommand;
915
916
  exports2.spawnStreamingCommand = spawnStreamingCommand;
916
917
  var node_child_process_1 = require("node:child_process");
917
918
  var node_events_1 = require("node:events");
919
+ var node_fs_1 = require("node:fs");
920
+ var promises_1 = require("node:fs/promises");
921
+ var node_os_1 = require("node:os");
922
+ var node_path_1 = require("node:path");
918
923
  var CommandError = class extends Error {
919
924
  result;
920
925
  constructor(message, result) {
@@ -981,8 +986,89 @@ var require_process = __commonJS({
981
986
  } catch {
982
987
  }
983
988
  }
989
+ function hasPathSeparator(command) {
990
+ return command.includes("/") || command.includes("\\");
991
+ }
992
+ function pathDelimiterForPlatform(platform) {
993
+ return platform === "win32" ? ";" : ":";
994
+ }
995
+ function shellSingleQuote(value) {
996
+ return `'${String(value).replace(/'/g, "'\\''")}'`;
997
+ }
998
+ function isSafeExecutableName(command) {
999
+ return /^[A-Za-z0-9._+-]+$/.test(command);
1000
+ }
1001
+ function getWindowsExecutableSuffixes(command, env) {
1002
+ if (/\.[^\\/]+$/.test(command)) {
1003
+ return [""];
1004
+ }
1005
+ const pathExt = env.PATHEXT || ".COM;.EXE;.BAT;.CMD";
1006
+ return pathExt.split(";").map((item) => item.trim()).filter(Boolean);
1007
+ }
1008
+ function getPathExecutableCandidates(command, env, platform) {
1009
+ if (hasPathSeparator(command)) {
1010
+ return [command];
1011
+ }
1012
+ const suffixes = platform === "win32" ? getWindowsExecutableSuffixes(command, env) : [""];
1013
+ return (env.PATH || "").split(pathDelimiterForPlatform(platform)).map((item) => item.trim()).filter(Boolean).flatMap((directory) => suffixes.map((suffix) => (0, node_path_1.join)(directory, `${command}${suffix}`)));
1014
+ }
1015
+ function getKnownExecutableCandidates(command, homeDir, platform) {
1016
+ if (platform === "win32" || hasPathSeparator(command)) {
1017
+ return [];
1018
+ }
1019
+ return [
1020
+ "/opt/homebrew/bin",
1021
+ "/usr/local/bin",
1022
+ (0, node_path_1.join)(homeDir, ".claude", "local"),
1023
+ (0, node_path_1.join)(homeDir, ".claude", "local", "bin"),
1024
+ (0, node_path_1.join)(homeDir, ".bun", "bin"),
1025
+ (0, node_path_1.join)(homeDir, ".npm-global", "bin"),
1026
+ (0, node_path_1.join)(homeDir, ".local", "bin")
1027
+ ].map((directory) => (0, node_path_1.join)(directory, command));
1028
+ }
1029
+ async function findFirstExecutable(candidates, accessFn) {
1030
+ const seen = /* @__PURE__ */ new Set();
1031
+ for (const candidate of candidates) {
1032
+ if (!candidate || seen.has(candidate)) {
1033
+ continue;
1034
+ }
1035
+ seen.add(candidate);
1036
+ try {
1037
+ await accessFn(candidate, node_fs_1.constants.X_OK);
1038
+ return candidate;
1039
+ } catch {
1040
+ }
1041
+ }
1042
+ return null;
1043
+ }
1044
+ async function resolveExecutableFromShell(command, options) {
1045
+ if (hasPathSeparator(command) || !isSafeExecutableName(command)) {
1046
+ return null;
1047
+ }
1048
+ const lookupCommand = options.platform === "win32" ? ["where", command] : [
1049
+ options.shell || (options.platform === "darwin" ? "/bin/zsh" : options.env.SHELL || process.env.SHELL || "/bin/sh"),
1050
+ "-lc",
1051
+ `command -v ${shellSingleQuote(command)}`
1052
+ ];
1053
+ try {
1054
+ const result = await options.runCommandFn(lookupCommand, {
1055
+ env: options.env,
1056
+ allowFailure: true,
1057
+ timeoutMs: options.timeoutMs
1058
+ });
1059
+ if (result.exitCode !== 0) {
1060
+ return null;
1061
+ }
1062
+ const candidates = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1063
+ return await findFirstExecutable(candidates, options.accessFn);
1064
+ } catch {
1065
+ return null;
1066
+ }
1067
+ }
984
1068
  async function runCommand(command, options = {}) {
985
1069
  assertCommand(command);
1070
+ let timedOut = false;
1071
+ let timeout;
986
1072
  const child = (0, node_child_process_1.spawn)(command[0], command.slice(1), {
987
1073
  cwd: options.cwd,
988
1074
  env: buildEnv(options.env),
@@ -992,6 +1078,13 @@ var require_process = __commonJS({
992
1078
  "pipe"
993
1079
  ]
994
1080
  });
1081
+ if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
1082
+ timeout = setTimeout(() => {
1083
+ timedOut = true;
1084
+ signalProcessGroup(child, "SIGTERM");
1085
+ }, options.timeoutMs);
1086
+ timeout.unref?.();
1087
+ }
995
1088
  if (options.input !== void 0) {
996
1089
  child.stdin?.end(options.input);
997
1090
  }
@@ -1000,12 +1093,15 @@ var require_process = __commonJS({
1000
1093
  readStream(child.stderr),
1001
1094
  (0, node_events_1.once)(child, "close")
1002
1095
  ]);
1096
+ if (timeout) {
1097
+ clearTimeout(timeout);
1098
+ }
1003
1099
  const [code] = closeResult;
1004
1100
  const result = {
1005
1101
  command,
1006
1102
  exitCode: code ?? 1,
1007
1103
  stdout,
1008
- stderr
1104
+ stderr: timedOut ? `${stderr}${stderr ? "\n" : ""}Command timed out after ${options.timeoutMs}ms.` : stderr
1009
1105
  };
1010
1106
  if (result.exitCode !== 0 && !options.allowFailure) {
1011
1107
  throw new CommandError(`Command failed (${result.exitCode}): ${command.join(" ")}`, result);
@@ -1016,8 +1112,31 @@ var require_process = __commonJS({
1016
1112
  return await runCommand([process.env.SHELL ?? "/bin/sh", "-c", script], options);
1017
1113
  }
1018
1114
  async function commandExists(command) {
1019
- const result = await runCommand(["which", command], { allowFailure: true });
1020
- return result.exitCode === 0;
1115
+ return await resolveExecutable(command) !== null;
1116
+ }
1117
+ async function resolveExecutable(command, options = {}) {
1118
+ const platform = options.platform ?? process.platform;
1119
+ const env = options.env ?? process.env;
1120
+ const accessFn = options.accessFn ?? promises_1.access;
1121
+ const runCommandFn = options.runCommandFn ?? runCommand;
1122
+ const timeoutMs = options.timeoutMs ?? 5e3;
1123
+ const homeDir = options.homeDir ?? (0, node_os_1.homedir)();
1124
+ const pathCandidate = await findFirstExecutable(getPathExecutableCandidates(command, env, platform), accessFn);
1125
+ if (pathCandidate) {
1126
+ return pathCandidate;
1127
+ }
1128
+ const shellCandidate = await resolveExecutableFromShell(command, {
1129
+ env,
1130
+ runCommandFn,
1131
+ accessFn,
1132
+ platform,
1133
+ shell: options.shell,
1134
+ timeoutMs
1135
+ });
1136
+ if (shellCandidate) {
1137
+ return shellCandidate;
1138
+ }
1139
+ return await findFirstExecutable(getKnownExecutableCandidates(command, homeDir, platform), accessFn);
1021
1140
  }
1022
1141
  async function runInteractiveCommand(command, options = {}) {
1023
1142
  assertCommand(command);
@@ -1906,7 +2025,7 @@ var require_claude = __commonJS({
1906
2025
  }
1907
2026
  return tools;
1908
2027
  }
1909
- async function prepareProjectScopedClaudeLaunch(repoRoot) {
2028
+ async function prepareProjectScopedClaudeLaunch(repoRoot, options = {}) {
1910
2029
  const settingsPath = (0, node_path_1.join)(repoRoot, ".claude", "settings.json");
1911
2030
  let settingsContent;
1912
2031
  try {
@@ -1932,12 +2051,13 @@ var require_claude = __commonJS({
1932
2051
  settingsPath,
1933
2052
  allowedTools,
1934
2053
  mcpConfig: (0, node_fs_1.existsSync)(mcpConfigPath) ? mcpConfigPath : EMPTY_MCP_CONFIG,
1935
- mcpConfigPath: (0, node_fs_1.existsSync)(mcpConfigPath) ? mcpConfigPath : void 0
2054
+ mcpConfigPath: (0, node_fs_1.existsSync)(mcpConfigPath) ? mcpConfigPath : void 0,
2055
+ claudeExecutablePath: options.claudeExecutablePath
1936
2056
  };
1937
2057
  }
1938
2058
  function buildProjectScopedClaudeCommand(launch, options = {}) {
1939
2059
  const command = [
1940
- "claude",
2060
+ launch.claudeExecutablePath ?? "claude",
1941
2061
  "--setting-sources",
1942
2062
  "project",
1943
2063
  "--permission-mode",
@@ -1999,9 +2119,10 @@ var require_claude = __commonJS({
1999
2119
  };
2000
2120
  }
2001
2121
  async function inspectClaudeCli(options = {}) {
2002
- const commandExistsFn = options.commandExistsFn ?? process_1.commandExists;
2122
+ const resolveExecutableFn = options.resolveExecutableFn ?? process_1.resolveExecutable;
2003
2123
  const runCommandFn = options.runCommandFn ?? process_1.runCommand;
2004
- if (!await commandExistsFn("claude")) {
2124
+ const executablePath = options.commandExistsFn ? await options.commandExistsFn("claude") ? "claude" : null : await resolveExecutableFn("claude");
2125
+ if (!executablePath) {
2005
2126
  return {
2006
2127
  available: false,
2007
2128
  supportsProjectScopedRun: false,
@@ -2009,13 +2130,14 @@ var require_claude = __commonJS({
2009
2130
  detail: "`claude` is not available on PATH."
2010
2131
  };
2011
2132
  }
2012
- const result = await runCommandFn(["claude", "--version"], { allowFailure: true });
2133
+ const result = await runCommandFn([executablePath, "--version"], { allowFailure: true });
2013
2134
  const output = `${result.stdout}
2014
2135
  ${result.stderr}`.trim();
2015
2136
  const parsedVersion = parseClaudeCliVersion(output);
2016
2137
  if (!parsedVersion) {
2017
2138
  return {
2018
2139
  available: true,
2140
+ executablePath,
2019
2141
  supportsProjectScopedRun: false,
2020
2142
  problem: "unparseable",
2021
2143
  detail: output || "Claude CLI version output was empty."
@@ -2025,6 +2147,7 @@ ${result.stderr}`.trim();
2025
2147
  if (compareClaudeCliVersions(parsedVersion, exports2.MIN_CLAUDE_VERSION_FOR_PROJECT_SCOPED_RUN) < 0) {
2026
2148
  return {
2027
2149
  available: true,
2150
+ executablePath,
2028
2151
  version,
2029
2152
  parsedVersion,
2030
2153
  supportsProjectScopedRun: false,
@@ -2034,6 +2157,7 @@ ${result.stderr}`.trim();
2034
2157
  }
2035
2158
  return {
2036
2159
  available: true,
2160
+ executablePath,
2037
2161
  version,
2038
2162
  parsedVersion,
2039
2163
  supportsProjectScopedRun: true,
@@ -2869,7 +2993,9 @@ var require_feedback = __commonJS({
2869
2993
  }
2870
2994
  async function launchFeedbackClaude(options) {
2871
2995
  const runInteractiveCommandFn = options.runInteractiveCommand ?? process_1.runInteractiveCommand;
2872
- const launch = await (0, claude_1.prepareProjectScopedClaudeLaunch)(options.repoRoot);
2996
+ const launch = await (0, claude_1.prepareProjectScopedClaudeLaunch)(options.repoRoot, {
2997
+ claudeExecutablePath: options.claudeExecutablePath
2998
+ });
2873
2999
  const command = (0, claude_1.buildProjectScopedClaudeCommand)(launch, {
2874
3000
  prompt: options.prompt
2875
3001
  });
@@ -2907,7 +3033,10 @@ var require_feedback = __commonJS({
2907
3033
  const getOriginUrlFn = options.getOriginUrl ?? git_1.getOriginUrl;
2908
3034
  const getHeadCommitHashFn = options.getHeadCommitHash ?? git_1.getHeadCommitHash;
2909
3035
  const getDiffStatAgainstHeadFn = options.getDiffStatAgainstHead ?? git_1.getDiffStatAgainstHead;
2910
- const commandExistsFn = options.commandExists ?? process_1.commandExists;
3036
+ const inspectClaudeCliFn = options.inspectClaudeCli ?? (async () => await (0, claude_1.inspectClaudeCli)({
3037
+ commandExistsFn: options.commandExists,
3038
+ runCommandFn: options.runCommand
3039
+ }));
2911
3040
  const runInteractiveCommandFn = options.runInteractiveCommand ?? process_1.runInteractiveCommand;
2912
3041
  const selectionMode = getFeedbackSelectionMode(options);
2913
3042
  const config = await ensureConfig();
@@ -2931,13 +3060,27 @@ var require_feedback = __commonJS({
2931
3060
  launchedClaude: false
2932
3061
  });
2933
3062
  }
2934
- if (!await commandExistsFn("claude")) {
3063
+ let claudeExecutablePath;
3064
+ const claudeInspection = await inspectClaudeCliFn();
3065
+ if (!claudeInspection.available) {
2935
3066
  throw (0, errors_1.createUserFacingError)({
2936
3067
  code: "feedback_claude_missing",
2937
3068
  summary: "Claude CLI is not available on PATH.",
2938
3069
  fixes: ["Install the Claude CLI before using `tela feedback --apply ...`."]
2939
3070
  });
2940
3071
  }
3072
+ if (!claudeInspection.supportsProjectScopedRun) {
3073
+ throw (0, errors_1.createUserFacingError)({
3074
+ code: "feedback_claude_version_unsupported",
3075
+ summary: "Claude CLI version does not support project-scoped feedback runs.",
3076
+ details: claudeInspection.detail,
3077
+ fixes: [
3078
+ "Update Claude CLI before using `tela feedback --apply ...`.",
3079
+ "Run `claude --version` to confirm the installed version."
3080
+ ]
3081
+ });
3082
+ }
3083
+ claudeExecutablePath = claudeInspection.executablePath;
2941
3084
  let selectedFeedbacks = selectionMode === "select" ? [] : actionableFeedbacks;
2942
3085
  if (selectionMode === "select" && options.feedbackIds?.length) {
2943
3086
  selectedFeedbacks = selectFeedbackEntriesById(feedbackEntries, options.feedbackIds);
@@ -3033,6 +3176,7 @@ var require_feedback = __commonJS({
3033
3176
  await launchFeedbackClaude({
3034
3177
  repoRoot: data.repoRoot,
3035
3178
  prompt,
3179
+ claudeExecutablePath,
3036
3180
  runInteractiveCommand: runInteractiveCommandFn
3037
3181
  });
3038
3182
  const diffStat = await getDiffStatAgainstHeadFn(repoRoot) || "<no local diff detected>";
@@ -3067,7 +3211,8 @@ var require_commit_message = __commonJS({
3067
3211
  return input.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
3068
3212
  }
3069
3213
  async function generateCommitMessageWithClaude(context) {
3070
- if (!await (0, process_1.commandExists)("claude")) {
3214
+ const claudePath = await (0, process_1.resolveExecutable)("claude");
3215
+ if (!claudePath) {
3071
3216
  throw new Error("Claude CLI is not available");
3072
3217
  }
3073
3218
  const prompt = `You are writing a git commit message for already staged changes.
@@ -3087,7 +3232,7 @@ Write exactly one short natural-language git commit message.
3087
3232
  - Keep it concise
3088
3233
  - Return only the commit message text`;
3089
3234
  const result = await (0, process_1.runCommand)([
3090
- "claude",
3235
+ claudePath,
3091
3236
  "-p",
3092
3237
  prompt,
3093
3238
  "--output-format",
@@ -4497,7 +4642,9 @@ ${detail.errors.join("\n")}` : summary);
4497
4642
  const outputFormat = parseJsonObjectFile("output-format.json", outputFormatContent, "output-format.json must be a valid JSON object.");
4498
4643
  const config = parseAgentConfig("agent.config.json", agentConfigContent);
4499
4644
  const resultKind = Object.keys(outputFormat).length > 0 ? "json" : "markdown";
4500
- const claudeLaunch = await (0, claude_1.prepareProjectScopedClaudeLaunch)(repoRoot);
4645
+ const claudeLaunch = await (0, claude_1.prepareProjectScopedClaudeLaunch)(repoRoot, {
4646
+ claudeExecutablePath: claudeInspection.executablePath
4647
+ });
4501
4648
  return {
4502
4649
  repoRoot,
4503
4650
  config,
@@ -5902,6 +6049,7 @@ var require_cli = __commonJS({
5902
6049
  var sync_1 = require_sync();
5903
6050
  var config_1 = require_config2();
5904
6051
  var errors_1 = require_errors();
6052
+ var claude_1 = require_claude();
5905
6053
  var git_1 = require_git();
5906
6054
  var process_1 = require_process();
5907
6055
  var setup_1 = require_setup();
@@ -6580,15 +6728,28 @@ var require_cli = __commonJS({
6580
6728
  write(io.stdout, renderFeedbackResult(result2));
6581
6729
  return 0;
6582
6730
  }
6583
- if (!await (0, process_1.commandExists)("claude")) {
6731
+ const claudeInspection = await (0, claude_1.inspectClaudeCli)();
6732
+ if (!claudeInspection.available) {
6584
6733
  throw (0, errors_1.createUserFacingError)({
6585
6734
  code: "feedback_claude_missing",
6586
6735
  summary: "Claude CLI is not available on PATH."
6587
6736
  });
6588
6737
  }
6738
+ if (!claudeInspection.supportsProjectScopedRun) {
6739
+ throw (0, errors_1.createUserFacingError)({
6740
+ code: "feedback_claude_version_unsupported",
6741
+ summary: "Claude CLI version does not support project-scoped feedback runs.",
6742
+ details: claudeInspection.detail,
6743
+ fixes: [
6744
+ "Update Claude CLI before using `tela agent feedback`.",
6745
+ "Run `claude --version` to confirm the installed version."
6746
+ ]
6747
+ });
6748
+ }
6589
6749
  const selectedFeedbacks = data.actionableEntries;
6590
6750
  await (0, feedback_1.launchFeedbackClaude)({
6591
6751
  repoRoot: data.repoRoot,
6752
+ claudeExecutablePath: claudeInspection.executablePath,
6592
6753
  prompt: (0, feedback_1.buildClaudeFeedbackPrompt)({
6593
6754
  repoRoot: data.repoRoot,
6594
6755
  organizationName: data.organizationName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/tela-cli",
3
- "version": "0.7.29",
3
+ "version": "0.7.31",
4
4
  "description": "Tela command line tools for local state and agent workflows.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {