@meistrari/tela-cli 0.7.28 → 0.7.30

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 +359 -36
  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);
@@ -1415,6 +1534,7 @@ var require_auth2 = __commonJS({
1415
1534
  "../../packages/agent/dist/auth.js"(exports2) {
1416
1535
  "use strict";
1417
1536
  Object.defineProperty(exports2, "__esModule", { value: true });
1537
+ exports2.resolveAgentApiDataToken = resolveAgentApiDataToken;
1418
1538
  exports2.buildAgentApiHeaders = buildAgentApiHeaders;
1419
1539
  var tela_local_config_1 = require_dist();
1420
1540
  var errors_1 = require_errors();
@@ -1456,18 +1576,14 @@ var require_auth2 = __commonJS({
1456
1576
  }
1457
1577
  return dataToken;
1458
1578
  }
1459
- async function buildAgentApiHeaders(config, options = {}) {
1579
+ async function resolveAgentApiDataToken(config, options = {}) {
1460
1580
  const fetchFn = options.fetchFn ?? fetch;
1461
1581
  const credentialRef = config.credentialRef;
1462
1582
  const credentials = config.credentials;
1463
1583
  const resolvedEnvironment = config.resolvedEnvironment;
1464
1584
  let credential = config.credential;
1465
1585
  if (!credential && config.auth?.session.accessToken) {
1466
- const dataToken2 = await exchangeDataToken(config, config.auth.session.accessToken, fetchFn);
1467
- return {
1468
- "x-data-token": dataToken2,
1469
- Accept: "application/json"
1470
- };
1586
+ return await exchangeDataToken(config, config.auth.session.accessToken, fetchFn);
1471
1587
  }
1472
1588
  if (!credential) {
1473
1589
  throw (0, errors_1.createUserFacingError)({
@@ -1477,10 +1593,7 @@ var require_auth2 = __commonJS({
1477
1593
  });
1478
1594
  }
1479
1595
  if (credential.kind === "data-token" && credential.dataToken) {
1480
- return {
1481
- "x-data-token": credential.dataToken,
1482
- Accept: "application/json"
1483
- };
1596
+ return credential.dataToken;
1484
1597
  }
1485
1598
  if (credential.kind === "user-session") {
1486
1599
  if (!credentialRef || !credentials || !resolvedEnvironment) {
@@ -1500,7 +1613,10 @@ var require_auth2 = __commonJS({
1500
1613
  fixes: ["Sign in with Dock or choose a user-session context before running agent commands."]
1501
1614
  });
1502
1615
  }
1503
- const dataToken = await exchangeDataToken(config, credential.accessToken, fetchFn);
1616
+ return await exchangeDataToken(config, credential.accessToken, fetchFn);
1617
+ }
1618
+ async function buildAgentApiHeaders(config, options = {}) {
1619
+ const dataToken = await resolveAgentApiDataToken(config, options);
1504
1620
  return {
1505
1621
  "x-data-token": dataToken,
1506
1622
  Accept: "application/json"
@@ -1515,6 +1631,7 @@ var require_agent_api = __commonJS({
1515
1631
  "use strict";
1516
1632
  Object.defineProperty(exports2, "__esModule", { value: true });
1517
1633
  exports2.AgentApiError = void 0;
1634
+ exports2.fetchOpenRouterModels = fetchOpenRouterModels;
1518
1635
  exports2.listOrganizations = listOrganizations;
1519
1636
  exports2.provisionForgejoUser = provisionForgejoUser;
1520
1637
  exports2.fetchRepositoryLookup = fetchRepositoryLookup;
@@ -1593,6 +1710,37 @@ var require_agent_api = __commonJS({
1593
1710
  }
1594
1711
  };
1595
1712
  exports2.AgentApiError = AgentApiError;
1713
+ function isOpenRouterModelPricingSummary(value) {
1714
+ if (!value || typeof value !== "object") {
1715
+ return false;
1716
+ }
1717
+ const candidate = value;
1718
+ return typeof candidate.prompt_per_million === "number" && typeof candidate.completion_per_million === "number" && (candidate.input_cache_read_per_million === void 0 || typeof candidate.input_cache_read_per_million === "number");
1719
+ }
1720
+ function isOpenRouterModelSummary(value) {
1721
+ if (!value || typeof value !== "object") {
1722
+ return false;
1723
+ }
1724
+ const candidate = value;
1725
+ return typeof candidate.id === "string" && typeof candidate.name === "string" && typeof candidate.context_length === "number" && (candidate.max_completion_tokens === null || typeof candidate.max_completion_tokens === "number") && isOpenRouterModelPricingSummary(candidate.pricing);
1726
+ }
1727
+ async function fetchOpenRouterModels(config, fetchFn = fetch, options = {}) {
1728
+ const headers = options.headers ?? await (0, auth_1.buildAgentApiHeaders)(config, { fetchFn });
1729
+ const response = await fetchFn(joinUrl2(config.agentApiUrl, "/v3/openrouter/models"), {
1730
+ method: "GET",
1731
+ headers,
1732
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
1733
+ });
1734
+ const responseText = await response.text();
1735
+ const payload = parseJsonBody(responseText);
1736
+ if (!response.ok) {
1737
+ throw new AgentApiError(payload?.error ?? `Agent API OpenRouter model lookup failed with status ${response.status}`, response.status, responseText);
1738
+ }
1739
+ if (!payload || payload.object !== "list" || !Array.isArray(payload.data) || !payload.data.every(isOpenRouterModelSummary)) {
1740
+ throw new AgentApiError("Agent API OpenRouter model lookup returned an invalid payload", response.status, responseText);
1741
+ }
1742
+ return payload.data;
1743
+ }
1596
1744
  async function listOrganizations(config, fetchFn = fetch) {
1597
1745
  const headers = await (0, auth_1.buildAgentApiHeaders)(config, { fetchFn });
1598
1746
  const response = await fetchFn(joinUrl2(config.agentApiUrl, "/v3/organizations"), {
@@ -1877,7 +2025,7 @@ var require_claude = __commonJS({
1877
2025
  }
1878
2026
  return tools;
1879
2027
  }
1880
- async function prepareProjectScopedClaudeLaunch(repoRoot) {
2028
+ async function prepareProjectScopedClaudeLaunch(repoRoot, options = {}) {
1881
2029
  const settingsPath = (0, node_path_1.join)(repoRoot, ".claude", "settings.json");
1882
2030
  let settingsContent;
1883
2031
  try {
@@ -1903,12 +2051,13 @@ var require_claude = __commonJS({
1903
2051
  settingsPath,
1904
2052
  allowedTools,
1905
2053
  mcpConfig: (0, node_fs_1.existsSync)(mcpConfigPath) ? mcpConfigPath : EMPTY_MCP_CONFIG,
1906
- 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
1907
2056
  };
1908
2057
  }
1909
2058
  function buildProjectScopedClaudeCommand(launch, options = {}) {
1910
2059
  const command = [
1911
- "claude",
2060
+ launch.claudeExecutablePath ?? "claude",
1912
2061
  "--setting-sources",
1913
2062
  "project",
1914
2063
  "--permission-mode",
@@ -1970,9 +2119,10 @@ var require_claude = __commonJS({
1970
2119
  };
1971
2120
  }
1972
2121
  async function inspectClaudeCli(options = {}) {
1973
- const commandExistsFn = options.commandExistsFn ?? process_1.commandExists;
2122
+ const resolveExecutableFn = options.resolveExecutableFn ?? process_1.resolveExecutable;
1974
2123
  const runCommandFn = options.runCommandFn ?? process_1.runCommand;
1975
- if (!await commandExistsFn("claude")) {
2124
+ const executablePath = options.commandExistsFn ? await options.commandExistsFn("claude") ? "claude" : null : await resolveExecutableFn("claude");
2125
+ if (!executablePath) {
1976
2126
  return {
1977
2127
  available: false,
1978
2128
  supportsProjectScopedRun: false,
@@ -1980,13 +2130,14 @@ var require_claude = __commonJS({
1980
2130
  detail: "`claude` is not available on PATH."
1981
2131
  };
1982
2132
  }
1983
- const result = await runCommandFn(["claude", "--version"], { allowFailure: true });
2133
+ const result = await runCommandFn([executablePath, "--version"], { allowFailure: true });
1984
2134
  const output = `${result.stdout}
1985
2135
  ${result.stderr}`.trim();
1986
2136
  const parsedVersion = parseClaudeCliVersion(output);
1987
2137
  if (!parsedVersion) {
1988
2138
  return {
1989
2139
  available: true,
2140
+ executablePath,
1990
2141
  supportsProjectScopedRun: false,
1991
2142
  problem: "unparseable",
1992
2143
  detail: output || "Claude CLI version output was empty."
@@ -1996,6 +2147,7 @@ ${result.stderr}`.trim();
1996
2147
  if (compareClaudeCliVersions(parsedVersion, exports2.MIN_CLAUDE_VERSION_FOR_PROJECT_SCOPED_RUN) < 0) {
1997
2148
  return {
1998
2149
  available: true,
2150
+ executablePath,
1999
2151
  version,
2000
2152
  parsedVersion,
2001
2153
  supportsProjectScopedRun: false,
@@ -2005,6 +2157,7 @@ ${result.stderr}`.trim();
2005
2157
  }
2006
2158
  return {
2007
2159
  available: true,
2160
+ executablePath,
2008
2161
  version,
2009
2162
  parsedVersion,
2010
2163
  supportsProjectScopedRun: true,
@@ -2840,7 +2993,9 @@ var require_feedback = __commonJS({
2840
2993
  }
2841
2994
  async function launchFeedbackClaude(options) {
2842
2995
  const runInteractiveCommandFn = options.runInteractiveCommand ?? process_1.runInteractiveCommand;
2843
- const launch = await (0, claude_1.prepareProjectScopedClaudeLaunch)(options.repoRoot);
2996
+ const launch = await (0, claude_1.prepareProjectScopedClaudeLaunch)(options.repoRoot, {
2997
+ claudeExecutablePath: options.claudeExecutablePath
2998
+ });
2844
2999
  const command = (0, claude_1.buildProjectScopedClaudeCommand)(launch, {
2845
3000
  prompt: options.prompt
2846
3001
  });
@@ -2878,7 +3033,10 @@ var require_feedback = __commonJS({
2878
3033
  const getOriginUrlFn = options.getOriginUrl ?? git_1.getOriginUrl;
2879
3034
  const getHeadCommitHashFn = options.getHeadCommitHash ?? git_1.getHeadCommitHash;
2880
3035
  const getDiffStatAgainstHeadFn = options.getDiffStatAgainstHead ?? git_1.getDiffStatAgainstHead;
2881
- 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
+ }));
2882
3040
  const runInteractiveCommandFn = options.runInteractiveCommand ?? process_1.runInteractiveCommand;
2883
3041
  const selectionMode = getFeedbackSelectionMode(options);
2884
3042
  const config = await ensureConfig();
@@ -2902,13 +3060,27 @@ var require_feedback = __commonJS({
2902
3060
  launchedClaude: false
2903
3061
  });
2904
3062
  }
2905
- if (!await commandExistsFn("claude")) {
3063
+ let claudeExecutablePath;
3064
+ const claudeInspection = await inspectClaudeCliFn();
3065
+ if (!claudeInspection.available) {
2906
3066
  throw (0, errors_1.createUserFacingError)({
2907
3067
  code: "feedback_claude_missing",
2908
3068
  summary: "Claude CLI is not available on PATH.",
2909
3069
  fixes: ["Install the Claude CLI before using `tela feedback --apply ...`."]
2910
3070
  });
2911
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;
2912
3084
  let selectedFeedbacks = selectionMode === "select" ? [] : actionableFeedbacks;
2913
3085
  if (selectionMode === "select" && options.feedbackIds?.length) {
2914
3086
  selectedFeedbacks = selectFeedbackEntriesById(feedbackEntries, options.feedbackIds);
@@ -3004,6 +3176,7 @@ var require_feedback = __commonJS({
3004
3176
  await launchFeedbackClaude({
3005
3177
  repoRoot: data.repoRoot,
3006
3178
  prompt,
3179
+ claudeExecutablePath,
3007
3180
  runInteractiveCommand: runInteractiveCommandFn
3008
3181
  });
3009
3182
  const diffStat = await getDiffStatAgainstHeadFn(repoRoot) || "<no local diff detected>";
@@ -3038,7 +3211,8 @@ var require_commit_message = __commonJS({
3038
3211
  return input.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
3039
3212
  }
3040
3213
  async function generateCommitMessageWithClaude(context) {
3041
- if (!await (0, process_1.commandExists)("claude")) {
3214
+ const claudePath = await (0, process_1.resolveExecutable)("claude");
3215
+ if (!claudePath) {
3042
3216
  throw new Error("Claude CLI is not available");
3043
3217
  }
3044
3218
  const prompt = `You are writing a git commit message for already staged changes.
@@ -3058,7 +3232,7 @@ Write exactly one short natural-language git commit message.
3058
3232
  - Keep it concise
3059
3233
  - Return only the commit message text`;
3060
3234
  const result = await (0, process_1.runCommand)([
3061
- "claude",
3235
+ claudePath,
3062
3236
  "-p",
3063
3237
  prompt,
3064
3238
  "--output-format",
@@ -3232,6 +3406,22 @@ ${error.result.stderr}`.toLowerCase();
3232
3406
  }
3233
3407
  });
3234
3408
 
3409
+ // ../../packages/agent/dist/shell.js
3410
+ var require_shell = __commonJS({
3411
+ "../../packages/agent/dist/shell.js"(exports2) {
3412
+ "use strict";
3413
+ Object.defineProperty(exports2, "__esModule", { value: true });
3414
+ exports2.shellSingleQuote = shellSingleQuote;
3415
+ exports2.shellJoinCommand = shellJoinCommand;
3416
+ function shellSingleQuote(value) {
3417
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
3418
+ }
3419
+ function shellJoinCommand(command) {
3420
+ return command.map((argument) => shellSingleQuote(argument)).join(" ");
3421
+ }
3422
+ }
3423
+ });
3424
+
3235
3425
  // ../../packages/agent/dist/commands/run.js
3236
3426
  var require_run = __commonJS({
3237
3427
  "../../packages/agent/dist/commands/run.js"(exports2) {
@@ -3239,7 +3429,9 @@ var require_run = __commonJS({
3239
3429
  Object.defineProperty(exports2, "__esModule", { value: true });
3240
3430
  exports2.readLocalAgentRunConfig = readLocalAgentRunConfig;
3241
3431
  exports2.updateLocalAgentRunMode = updateLocalAgentRunMode;
3432
+ exports2.updateLocalAgentRunModel = updateLocalAgentRunModel;
3242
3433
  exports2.buildLocalAgentClaudeCommand = buildLocalAgentClaudeCommand;
3434
+ exports2.buildLocalAgentClaudeLaunchCommand = buildLocalAgentClaudeLaunchCommand;
3243
3435
  exports2.prepareLocalAgentRun = prepareLocalAgentRun;
3244
3436
  exports2.parseRunInputAssignment = parseRunInputAssignment;
3245
3437
  exports2.parseRunInputAssignments = parseRunInputAssignments;
@@ -3258,10 +3450,14 @@ var require_run = __commonJS({
3258
3450
  var promises_1 = require("node:fs/promises");
3259
3451
  var node_os_1 = require("node:os");
3260
3452
  var node_path_1 = require("node:path");
3453
+ var agent_api_1 = require_agent_api();
3454
+ var auth_1 = require_auth2();
3261
3455
  var claude_1 = require_claude();
3262
3456
  var git_1 = require_git();
3263
3457
  var errors_1 = require_errors();
3264
3458
  var process_1 = require_process();
3459
+ var config_1 = require_config2();
3460
+ var shell_1 = require_shell();
3265
3461
  var ui_1 = require_ui();
3266
3462
  var INITIAL_PROMPT = "Follow the instructions and return the result.";
3267
3463
  var REQUIRED_AGENT_FILES = [
@@ -4250,6 +4446,27 @@ ${detail.errors.join("\n")}` : summary);
4250
4446
  isMultiturn: options.mode === "chat"
4251
4447
  };
4252
4448
  await (0, promises_1.writeFile)(agentConfig.path, `${JSON.stringify(updated, null, 2)}
4449
+ `);
4450
+ return parseAgentConfig("agent.config.json", JSON.stringify(updated));
4451
+ }
4452
+ async function updateLocalAgentRunModel(options) {
4453
+ const repoRoot = await resolveRepositoryRoot(options.cwd ?? process.cwd());
4454
+ const agentConfig = await readAgentConfigFile(repoRoot);
4455
+ const parsed = parseJsonObjectFile("agent.config.json", agentConfig.content, "agent.config.json must be a valid JSON object.");
4456
+ const model = options.model.trim();
4457
+ parseAgentConfig("agent.config.json", agentConfig.content);
4458
+ if (!model || !isOpenRouterModelId(model)) {
4459
+ throw (0, errors_1.createUserFacingError)({
4460
+ code: "run_openrouter_model_invalid",
4461
+ summary: "Choose an OpenRouter model id.",
4462
+ fixes: ["Use a provider-qualified OpenRouter model id, for example `openai/gpt-5.4`."]
4463
+ });
4464
+ }
4465
+ const updated = {
4466
+ ...parsed,
4467
+ model
4468
+ };
4469
+ await (0, promises_1.writeFile)(agentConfig.path, `${JSON.stringify(updated, null, 2)}
4253
4470
  `);
4254
4471
  return parseAgentConfig("agent.config.json", JSON.stringify(updated));
4255
4472
  }
@@ -4281,6 +4498,89 @@ ${detail.errors.join("\n")}` : summary);
4281
4498
  verbose: mode === "one-shot"
4282
4499
  });
4283
4500
  }
4501
+ function isOpenRouterModelId(model) {
4502
+ return Boolean(model && model.includes("/"));
4503
+ }
4504
+ function buildOpenRouterProxyBaseUrl(agentApiUrl) {
4505
+ return new URL("/v3/openrouter", agentApiUrl.endsWith("/") ? agentApiUrl : `${agentApiUrl}/`).toString().replace(/\/$/, "");
4506
+ }
4507
+ function buildOpenRouterClaudeEnvironmentOverrides(model, dataToken, agentApiUrl) {
4508
+ const proxyBaseUrl = buildOpenRouterProxyBaseUrl(agentApiUrl);
4509
+ return {
4510
+ ANTHROPIC_BASE_URL: proxyBaseUrl,
4511
+ ANTHROPIC_AUTH_TOKEN: dataToken,
4512
+ ANTHROPIC_API_KEY: "",
4513
+ ANTHROPIC_DEFAULT_OPUS_MODEL: model,
4514
+ ANTHROPIC_DEFAULT_SONNET_MODEL: model,
4515
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: model,
4516
+ CLAUDE_CODE_SUBAGENT_MODEL: model
4517
+ };
4518
+ }
4519
+ function redactClaudeEnvironmentOverrides(overrides) {
4520
+ return {
4521
+ ...overrides,
4522
+ ANTHROPIC_AUTH_TOKEN: overrides.ANTHROPIC_AUTH_TOKEN === void 0 ? void 0 : "<redacted>"
4523
+ };
4524
+ }
4525
+ function buildRedactedClaudeShellCommand(command, environmentOverrides) {
4526
+ const unsetArgs = (0, claude_1.getProjectScopedClaudeEnvDenylist)().map((key) => `-u ${key}`);
4527
+ const setArgs = Object.entries(environmentOverrides).filter((entry) => entry[1] !== void 0).map(([key, value]) => `${key}=${(0, shell_1.shellSingleQuote)(value)}`);
4528
+ return ["env", ...unsetArgs, ...setArgs, (0, shell_1.shellJoinCommand)(command)].join(" ");
4529
+ }
4530
+ async function ensureOpenRouterModelAvailable(model, models) {
4531
+ if (models.some((candidate) => candidate.id === model)) {
4532
+ return;
4533
+ }
4534
+ throw (0, errors_1.createUserFacingError)({
4535
+ code: "run_openrouter_model_unavailable",
4536
+ summary: `OpenRouter model is not available through Tela: ${model}`,
4537
+ fixes: [
4538
+ "Use a model returned by `GET /v3/openrouter/models`.",
4539
+ "Update `agent.config.json` to a supported OpenRouter model id, for example `openai/gpt-5.4`."
4540
+ ]
4541
+ });
4542
+ }
4543
+ async function buildLocalAgentClaudeLaunchCommand(prepared, mode, options = {}) {
4544
+ const command = buildLocalAgentClaudeCommand(prepared, mode);
4545
+ const baseEnv = (0, claude_1.buildProjectScopedClaudeEnvironment)(options.baseEnv);
4546
+ const model = prepared.config.model;
4547
+ if (!isOpenRouterModelId(model)) {
4548
+ return {
4549
+ command,
4550
+ env: baseEnv,
4551
+ environmentOverrides: {},
4552
+ redactedEnvironmentOverrides: {},
4553
+ redactedCommand: buildRedactedClaudeShellCommand(command, {}),
4554
+ provider: "anthropic",
4555
+ model
4556
+ };
4557
+ }
4558
+ const resolveConfigFn = options.resolveAgentConfig ?? config_1.resolveAgentConfig;
4559
+ const resolveDataTokenFn = options.resolveAgentApiDataToken ?? auth_1.resolveAgentApiDataToken;
4560
+ const fetchModelsFn = options.fetchOpenRouterModels ?? agent_api_1.fetchOpenRouterModels;
4561
+ const config = await resolveConfigFn();
4562
+ const dataToken = await resolveDataTokenFn(config, { fetchFn: options.fetch });
4563
+ const headers = {
4564
+ "x-data-token": dataToken,
4565
+ Accept: "application/json"
4566
+ };
4567
+ const models = await fetchModelsFn(config, options.fetch, { headers });
4568
+ await ensureOpenRouterModelAvailable(model, models);
4569
+ const environmentOverrides = buildOpenRouterClaudeEnvironmentOverrides(model, dataToken, config.agentApiUrl);
4570
+ const redactedEnvironmentOverrides = redactClaudeEnvironmentOverrides(environmentOverrides);
4571
+ return {
4572
+ command,
4573
+ env: {
4574
+ ...baseEnv,
4575
+ ...environmentOverrides
4576
+ },
4577
+ environmentOverrides,
4578
+ redactedEnvironmentOverrides,
4579
+ redactedCommand: buildRedactedClaudeShellCommand(command, redactedEnvironmentOverrides),
4580
+ provider: "openrouter",
4581
+ model
4582
+ };
4583
+ }
4284
4584
  async function resolveRepositoryRoot(cwd) {
4285
4585
  try {
4286
4586
  return await (0, git_1.getRepositoryRoot)(cwd);
@@ -4342,7 +4642,9 @@ ${detail.errors.join("\n")}` : summary);
4342
4642
  const outputFormat = parseJsonObjectFile("output-format.json", outputFormatContent, "output-format.json must be a valid JSON object.");
4343
4643
  const config = parseAgentConfig("agent.config.json", agentConfigContent);
4344
4644
  const resultKind = Object.keys(outputFormat).length > 0 ? "json" : "markdown";
4345
- const claudeLaunch = await (0, claude_1.prepareProjectScopedClaudeLaunch)(repoRoot);
4645
+ const claudeLaunch = await (0, claude_1.prepareProjectScopedClaudeLaunch)(repoRoot, {
4646
+ claudeExecutablePath: claudeInspection.executablePath
4647
+ });
4346
4648
  return {
4347
4649
  repoRoot,
4348
4650
  config,
@@ -4999,7 +5301,7 @@ Input folder: ${comparableInputRoot}`,
4999
5301
  inputs
5000
5302
  };
5001
5303
  }
5002
- async function runInteractiveAgent(prepared, runInteractiveCommandFn, options) {
5304
+ async function runInteractiveAgent(prepared, claudeLaunch, runInteractiveCommandFn, options) {
5003
5305
  if (options.surface === "json") {
5004
5306
  throw (0, errors_1.createUserFacingError)({
5005
5307
  code: "run_multiturn_json_unsupported",
@@ -5019,12 +5321,12 @@ Input folder: ${comparableInputRoot}`,
5019
5321
  if (options.surface === "plain") {
5020
5322
  writeLine2(options.writeStdout, "Launching Claude in interactive chat mode. Tela will resume after Claude exits.");
5021
5323
  }
5022
- await runInteractiveCommandFn(buildLocalAgentClaudeCommand(prepared, "chat"), {
5324
+ await runInteractiveCommandFn(claudeLaunch.command, {
5023
5325
  cwd: prepared.repoRoot,
5024
- env: (0, claude_1.buildProjectScopedClaudeEnvironment)()
5326
+ env: claudeLaunch.env
5025
5327
  });
5026
5328
  }
5027
- async function runOneShotAgent(prepared, options, tracker) {
5329
+ async function runOneShotAgent(prepared, options, tracker, claudeLaunch) {
5028
5330
  const plainRenderer = new PlainTranscriptRenderer();
5029
5331
  const runStreamingCommandFn = options.runStreamingCommand ?? process_1.runStreamingCommand;
5030
5332
  const spawnStreamingCommandFn = options.spawnStreamingCommand ?? ((command2, spawnOptions) => ({
@@ -5042,10 +5344,10 @@ Input folder: ${comparableInputRoot}`,
5042
5344
  writeLine2(options.writeStdout, "Streaming Claude execution in one-shot mode.");
5043
5345
  }
5044
5346
  let cancelled = false;
5045
- const command = buildLocalAgentClaudeCommand(prepared, "one-shot");
5347
+ const command = claudeLaunch.command;
5046
5348
  const handle = spawnStreamingCommandFn(command, {
5047
5349
  cwd: prepared.repoRoot,
5048
- env: (0, claude_1.buildProjectScopedClaudeEnvironment)(),
5350
+ env: claudeLaunch.env,
5049
5351
  allowFailure: true,
5050
5352
  onStdoutLine: async (line) => {
5051
5353
  const trimmed = line.trim();
@@ -5132,26 +5434,32 @@ Input folder: ${comparableInputRoot}`,
5132
5434
  isMultiturn: options.mode === "chat"
5133
5435
  }
5134
5436
  } : preparedBase;
5437
+ const mode = prepared.config.isMultiturn ? "chat" : "one-shot";
5438
+ const claudeLaunch = await buildLocalAgentClaudeLaunchCommand(prepared, mode, {
5439
+ resolveAgentConfig: options.resolveAgentConfig,
5440
+ fetchOpenRouterModels: options.fetchOpenRouterModels,
5441
+ resolveAgentApiDataToken: options.resolveAgentApiDataToken,
5442
+ fetch: options.fetch
5443
+ });
5135
5444
  const inputs = await prepareLocalAgentRunInputsForLaunch(prepared, {
5136
5445
  cwd: options.cwd,
5137
5446
  inputAssignments: options.inputAssignments,
5138
5447
  stagedInputs: options.stagedInputs,
5139
5448
  resolveInputConflict: options.resolveInputConflict
5140
5449
  });
5141
- const mode = prepared.config.isMultiturn ? "chat" : "one-shot";
5142
5450
  const tracker = new RunStateTracker(prepared, mode);
5143
5451
  const agentRunsRoot = options.agentRunsRoot ?? getDefaultAgentRunsRoot();
5144
5452
  let snapshot = tracker.snapshot();
5145
5453
  effectiveOptions.onSnapshot?.(snapshot);
5146
5454
  try {
5147
5455
  if (prepared.config.isMultiturn) {
5148
- await runInteractiveAgent(prepared, options.runInteractiveCommand ?? process_1.runInteractiveCommand, effectiveOptions);
5456
+ await runInteractiveAgent(prepared, claudeLaunch, options.runInteractiveCommand ?? process_1.runInteractiveCommand, effectiveOptions);
5149
5457
  snapshot = tracker.finalize("success", {
5150
5458
  summary: "Claude chat exited. Reading result artifact\u2026"
5151
5459
  });
5152
5460
  effectiveOptions.onSnapshot?.(snapshot);
5153
5461
  } else {
5154
- snapshot = await runOneShotAgent(prepared, effectiveOptions, tracker);
5462
+ snapshot = await runOneShotAgent(prepared, effectiveOptions, tracker, claudeLaunch);
5155
5463
  }
5156
5464
  } catch (error) {
5157
5465
  let terminalSnapshot = tracker.snapshot();
@@ -5741,6 +6049,7 @@ var require_cli = __commonJS({
5741
6049
  var sync_1 = require_sync();
5742
6050
  var config_1 = require_config2();
5743
6051
  var errors_1 = require_errors();
6052
+ var claude_1 = require_claude();
5744
6053
  var git_1 = require_git();
5745
6054
  var process_1 = require_process();
5746
6055
  var setup_1 = require_setup();
@@ -6419,15 +6728,28 @@ var require_cli = __commonJS({
6419
6728
  write(io.stdout, renderFeedbackResult(result2));
6420
6729
  return 0;
6421
6730
  }
6422
- if (!await (0, process_1.commandExists)("claude")) {
6731
+ const claudeInspection = await (0, claude_1.inspectClaudeCli)();
6732
+ if (!claudeInspection.available) {
6423
6733
  throw (0, errors_1.createUserFacingError)({
6424
6734
  code: "feedback_claude_missing",
6425
6735
  summary: "Claude CLI is not available on PATH."
6426
6736
  });
6427
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
+ }
6428
6749
  const selectedFeedbacks = data.actionableEntries;
6429
6750
  await (0, feedback_1.launchFeedbackClaude)({
6430
6751
  repoRoot: data.repoRoot,
6752
+ claudeExecutablePath: claudeInspection.executablePath,
6431
6753
  prompt: (0, feedback_1.buildClaudeFeedbackPrompt)({
6432
6754
  repoRoot: data.repoRoot,
6433
6755
  organizationName: data.organizationName,
@@ -6641,6 +6963,7 @@ var require_dist2 = __commonJS({
6641
6963
  __exportStar(require_git(), exports2);
6642
6964
  __exportStar(require_process(), exports2);
6643
6965
  __exportStar(require_setup(), exports2);
6966
+ __exportStar(require_shell(), exports2);
6644
6967
  __exportStar(require_types2(), exports2);
6645
6968
  }
6646
6969
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/tela-cli",
3
- "version": "0.7.28",
3
+ "version": "0.7.30",
4
4
  "description": "Tela command line tools for local state and agent workflows.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {