@meistrari/tela-cli 0.7.44 → 0.7.46

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 +333 -63
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1642,6 +1642,9 @@ var require_agent_api = __commonJS({
1642
1642
  exports2.fetchRepositoryLookup = fetchRepositoryLookup;
1643
1643
  exports2.fetchCommitFeedback = fetchCommitFeedback;
1644
1644
  exports2.listTelaAgents = listTelaAgents;
1645
+ exports2.listAgentRunHistory = listAgentRunHistory;
1646
+ exports2.fetchAgentRunDetail = fetchAgentRunDetail;
1647
+ exports2.searchAgentRuns = searchAgentRuns;
1645
1648
  exports2.listAvailableRepositories = listAvailableRepositories;
1646
1649
  var tela_local_config_1 = require_dist();
1647
1650
  var auth_1 = require_auth2();
@@ -1853,6 +1856,82 @@ var require_agent_api = __commonJS({
1853
1856
  }
1854
1857
  return payload.data;
1855
1858
  }
1859
+ function isAgentRunHistoryItem(value) {
1860
+ if (!value || typeof value !== "object") {
1861
+ return false;
1862
+ }
1863
+ const candidate = value;
1864
+ return typeof candidate.id === "string" && typeof candidate.sessionId === "string" && typeof candidate.status === "string" && typeof candidate.provider === "string" && (typeof candidate.calculatedCostUsd === "number" || candidate.calculatedCostUsd === null) && typeof candidate.executionSurface === "string" && typeof candidate.createdAt === "string";
1865
+ }
1866
+ async function listAgentRunHistory(config, options, fetchFn = fetch) {
1867
+ const headers = await (0, auth_1.buildAgentApiHeaders)(config, { fetchFn });
1868
+ const url = new URL(joinUrl2(config.agentApiUrl, "/v3/runs"));
1869
+ if (options?.agentRepoId) {
1870
+ url.searchParams.set("agentRepoId", options.agentRepoId);
1871
+ }
1872
+ if (options?.status) {
1873
+ url.searchParams.set("status", options.status);
1874
+ }
1875
+ if (options?.cursor) {
1876
+ url.searchParams.set("cursor", options.cursor);
1877
+ }
1878
+ if (options?.limit != null) {
1879
+ url.searchParams.set("limit", String(options.limit));
1880
+ }
1881
+ const response = await fetchFn(url.toString(), {
1882
+ method: "GET",
1883
+ headers,
1884
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
1885
+ });
1886
+ const responseText = await response.text();
1887
+ const payload = parseJsonBody(responseText);
1888
+ if (!response.ok) {
1889
+ throw new AgentApiError(payload?.error ?? `Agent API run history listing failed with status ${response.status}`, response.status, responseText);
1890
+ }
1891
+ if (!payload || !Array.isArray(payload.runs) || !payload.runs.every(isAgentRunHistoryItem)) {
1892
+ throw new AgentApiError("Agent API run history listing returned an invalid payload", response.status, responseText);
1893
+ }
1894
+ return {
1895
+ runs: payload.runs,
1896
+ nextCursor: payload.nextCursor ?? null
1897
+ };
1898
+ }
1899
+ async function fetchAgentRunDetail(runId, config, fetchFn = fetch) {
1900
+ const headers = await (0, auth_1.buildAgentApiHeaders)(config, { fetchFn });
1901
+ const response = await fetchFn(joinUrl2(config.agentApiUrl, `/v3/runs/${encodeURIComponent(runId)}`), {
1902
+ method: "GET",
1903
+ headers,
1904
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
1905
+ });
1906
+ const responseText = await response.text();
1907
+ const payload = parseJsonBody(responseText);
1908
+ if (!response.ok) {
1909
+ throw new AgentApiError(payload?.error ?? `Agent API run detail failed with status ${response.status}`, response.status, responseText);
1910
+ }
1911
+ if (!payload || !isAgentRunHistoryItem(payload)) {
1912
+ throw new AgentApiError("Agent API run detail returned an invalid payload", response.status, responseText);
1913
+ }
1914
+ return payload;
1915
+ }
1916
+ async function searchAgentRuns(query, config, options, fetchFn = fetch) {
1917
+ const headers = await (0, auth_1.buildAgentApiHeaders)(config, { fetchFn });
1918
+ const url = new URL(joinUrl2(config.agentApiUrl, "/v3/runs/search"));
1919
+ url.searchParams.set("q", query);
1920
+ if (options?.limit != null) {
1921
+ url.searchParams.set("limit", String(options.limit));
1922
+ }
1923
+ const response = await fetchFn(url.toString(), {
1924
+ method: "GET",
1925
+ headers,
1926
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
1927
+ });
1928
+ const responseText = await response.text();
1929
+ const payload = parseJsonBody(responseText);
1930
+ if (!response.ok) {
1931
+ throw new AgentApiError(payload?.error ?? `Agent API run search failed with status ${response.status}`, response.status, responseText);
1932
+ }
1933
+ return payload ?? { results: [] };
1934
+ }
1856
1935
  async function listAvailableRepositories(config, fetchFn = fetch) {
1857
1936
  const headers = await (0, auth_1.buildAgentApiHeaders)(config, { fetchFn });
1858
1937
  const response = await fetchFn(joinUrl2(config.agentApiUrl, "/v3/agents/repositories"), {
@@ -2267,12 +2346,14 @@ var require_git = __commonJS({
2267
2346
  Object.defineProperty(exports2, "__esModule", { value: true });
2268
2347
  exports2.buildGitSshCommand = buildGitSshCommand;
2269
2348
  exports2.isSshRemoteUrl = isSshRemoteUrl;
2349
+ exports2.getRemoteUrl = getRemoteUrl;
2270
2350
  exports2.findRepositoryRoot = findRepositoryRoot;
2271
2351
  exports2.getRepositoryRoot = getRepositoryRoot;
2272
2352
  exports2.assertGitRepository = assertGitRepository;
2273
2353
  exports2.cloneRepository = cloneRepository;
2274
2354
  exports2.getOriginUrl = getOriginUrl;
2275
2355
  exports2.fetchOrigin = fetchOrigin;
2356
+ exports2.fetchRemote = fetchRemote;
2276
2357
  exports2.fetchAllRemotes = fetchAllRemotes;
2277
2358
  exports2.getWorktreeStatus = getWorktreeStatus;
2278
2359
  exports2.isWorktreeDirty = isWorktreeDirty;
@@ -2281,6 +2362,10 @@ var require_git = __commonJS({
2281
2362
  exports2.unstageMatching = unstageMatching;
2282
2363
  exports2.getCurrentBranch = getCurrentBranch;
2283
2364
  exports2.getUpstreamBranch = getUpstreamBranch;
2365
+ exports2.getPushBranch = getPushBranch;
2366
+ exports2.refExists = refExists;
2367
+ exports2.getAheadBehind = getAheadBehind;
2368
+ exports2.getCommitCount = getCommitCount;
2284
2369
  exports2.pullCurrentBranch = pullCurrentBranch;
2285
2370
  exports2.pullDefaultBranch = pullDefaultBranch;
2286
2371
  exports2.getShortStatus = getShortStatus;
@@ -2348,6 +2433,9 @@ var require_git = __commonJS({
2348
2433
  }
2349
2434
  return result.stdout.trim() || null;
2350
2435
  }
2436
+ async function getRemoteUrl(cwd, remote) {
2437
+ return await getRemoteUrlInternal(cwd, remote);
2438
+ }
2351
2439
  async function remoteRequiresConfiguredSsh(cwd, remote) {
2352
2440
  const remoteUrl = await getRemoteUrlInternal(cwd, remote);
2353
2441
  return Boolean(remoteUrl && isSshRemoteUrl(remoteUrl));
@@ -2420,6 +2508,14 @@ var require_git = __commonJS({
2420
2508
  sshHostKeyPolicy: "accept-new"
2421
2509
  });
2422
2510
  }
2511
+ async function fetchRemote(cwd, remote, sshPrivateKeyPath) {
2512
+ await runGitCommand(["fetch", remote, "--prune"], {
2513
+ cwd,
2514
+ requireConfiguredSsh: await remoteRequiresConfiguredSsh(cwd, remote),
2515
+ sshPrivateKeyPath,
2516
+ sshHostKeyPolicy: "accept-new"
2517
+ });
2518
+ }
2423
2519
  async function fetchAllRemotes(cwd, sshPrivateKeyPath) {
2424
2520
  await runGitCommand(["fetch", "--all", "--prune"], {
2425
2521
  cwd,
@@ -2465,6 +2561,38 @@ var require_git = __commonJS({
2465
2561
  }
2466
2562
  return result.stdout.trim() || null;
2467
2563
  }
2564
+ async function getPushBranch(cwd) {
2565
+ const result = await runGitCommand(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{push}"], { cwd, allowFailure: true });
2566
+ if (result.exitCode !== 0) {
2567
+ return null;
2568
+ }
2569
+ return result.stdout.trim() || null;
2570
+ }
2571
+ async function refExists(cwd, ref) {
2572
+ const result = await runGitCommand(["rev-parse", "--verify", "--quiet", ref], {
2573
+ cwd,
2574
+ allowFailure: true
2575
+ });
2576
+ return result.exitCode === 0;
2577
+ }
2578
+ async function getAheadBehind(cwd, ref) {
2579
+ if (!await refExists(cwd, ref)) {
2580
+ return { aheadCount: 0, behindCount: 0 };
2581
+ }
2582
+ const result = await runGitCommand(["rev-list", "--left-right", "--count", `HEAD...${ref}`], { cwd });
2583
+ const [aheadRaw, behindRaw] = result.stdout.trim().split(/\s+/);
2584
+ return {
2585
+ aheadCount: Number.parseInt(aheadRaw ?? "0", 10) || 0,
2586
+ behindCount: Number.parseInt(behindRaw ?? "0", 10) || 0
2587
+ };
2588
+ }
2589
+ async function getCommitCount(cwd, ref = "HEAD") {
2590
+ const result = await runGitCommand(["rev-list", "--count", ref], { cwd, allowFailure: true });
2591
+ if (result.exitCode !== 0) {
2592
+ return 0;
2593
+ }
2594
+ return Number.parseInt(result.stdout.trim(), 10) || 0;
2595
+ }
2468
2596
  async function pullCurrentBranch(cwd, sshPrivateKeyPath) {
2469
2597
  await runGitCommand(["pull", "--ff-only"], {
2470
2598
  cwd,
@@ -2555,26 +2683,27 @@ var require_git = __commonJS({
2555
2683
  }
2556
2684
  function buildFallbackCommitMessage(files) {
2557
2685
  if (files.length === 0) {
2558
- return "Update repository files";
2686
+ return "chore: update repository files";
2559
2687
  }
2560
2688
  if (files.length === 1) {
2561
- return `Update ${(0, node_path_1.basename)(files[0]) || files[0]}`;
2689
+ return `chore: update ${(0, node_path_1.basename)(files[0]) || files[0]}`;
2562
2690
  }
2563
- return `Update ${(0, node_path_1.basename)(files[0]) || files[0]} and ${files.length - 1} other files`;
2691
+ return `chore: update ${(0, node_path_1.basename)(files[0]) || files[0]} and ${files.length - 1} other files`;
2564
2692
  }
2565
2693
  async function commitWithMessage(cwd, message) {
2566
- await runGitCommand(["commit", "-F", "-"], {
2694
+ await runGitCommand(["commit", "--no-verify", "-F", "-"], {
2567
2695
  cwd,
2568
2696
  input: message
2569
2697
  });
2570
2698
  }
2571
- async function pushBranch(cwd, branch, setUpstream, sshPrivateKeyPath) {
2572
- const args = setUpstream ? ["push", "--set-upstream", "origin", branch] : ["push"];
2573
- const requireConfiguredSsh = setUpstream ? await remoteRequiresConfiguredSsh(cwd, "origin") : await upstreamRequiresConfiguredSsh(cwd);
2699
+ async function pushBranch(cwd, branch, setUpstream, sshPrivateKeyPath, remote = "origin") {
2700
+ const args = setUpstream ? ["push", "--set-upstream", remote, branch] : ["push"];
2701
+ const requireConfiguredSsh = sshPrivateKeyPath ? await anyRemoteRequiresConfiguredSsh(cwd) : setUpstream ? await remoteRequiresConfiguredSsh(cwd, remote) : await upstreamRequiresConfiguredSsh(cwd);
2574
2702
  await runGitCommand(args, {
2575
2703
  cwd,
2576
2704
  requireConfiguredSsh,
2577
- sshPrivateKeyPath
2705
+ sshPrivateKeyPath,
2706
+ sshHostKeyPolicy: "accept-new"
2578
2707
  });
2579
2708
  }
2580
2709
  async function getHeadShortSha(cwd) {
@@ -3208,33 +3337,73 @@ var require_commit_message = __commonJS({
3208
3337
  "../../packages/agent/dist/commit-message.js"(exports2) {
3209
3338
  "use strict";
3210
3339
  Object.defineProperty(exports2, "__esModule", { value: true });
3340
+ exports2.isConventionalCommitMessage = isConventionalCommitMessage;
3341
+ exports2.normalizeConventionalCommitMessage = normalizeConventionalCommitMessage;
3211
3342
  exports2.generateCommitMessageWithClaude = generateCommitMessageWithClaude;
3212
3343
  exports2.fallbackCommitMessageFromFiles = fallbackCommitMessageFromFiles;
3213
3344
  var node_path_1 = require("node:path");
3214
3345
  var process_1 = require_process();
3346
+ var conventionalCommitPattern = /^(?:build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(?:\([^)]+\))?!?:\s+\S/;
3215
3347
  function firstNonEmptyLine(input) {
3216
3348
  return input.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "";
3217
3349
  }
3350
+ function splitCommitMessage(message) {
3351
+ const lines = message.trim().split(/\r?\n/);
3352
+ const subjectIndex = lines.findIndex((line) => Boolean(line.trim()));
3353
+ if (subjectIndex === -1) {
3354
+ return { subject: "", body: "" };
3355
+ }
3356
+ return {
3357
+ subject: lines[subjectIndex].trim(),
3358
+ body: lines.slice(subjectIndex + 1).join("\n").trim()
3359
+ };
3360
+ }
3361
+ function cleanCommitSubject(message) {
3362
+ const rawSubject = splitCommitMessage(message).subject;
3363
+ const subject = rawSubject.replace(/^["'`]+|["'`]+$/g, "").replace(/^commit message:\s*/i, "").replace(/^[-*]\s+/, "").replace(/\s+/g, " ").replace(/[.!?]$/, "").trim();
3364
+ if (!subject) {
3365
+ return "update repository files";
3366
+ }
3367
+ return `${subject.charAt(0).toLowerCase()}${subject.slice(1)}`;
3368
+ }
3369
+ function isConventionalCommitMessage(message) {
3370
+ return conventionalCommitPattern.test(firstNonEmptyLine(message));
3371
+ }
3372
+ function normalizeConventionalCommitMessage(message) {
3373
+ const trimmed = message.trim();
3374
+ if (!trimmed) {
3375
+ return "chore: update repository files";
3376
+ }
3377
+ if (isConventionalCommitMessage(trimmed)) {
3378
+ return trimmed;
3379
+ }
3380
+ const { body } = splitCommitMessage(trimmed);
3381
+ const subject = `chore: ${cleanCommitSubject(trimmed)}`;
3382
+ return body ? `${subject}
3383
+
3384
+ ${body}` : subject;
3385
+ }
3218
3386
  async function generateCommitMessageWithClaude(context) {
3219
3387
  const claudePath = await (0, process_1.resolveExecutable)("claude");
3220
3388
  if (!claudePath) {
3221
3389
  throw new Error("Claude CLI is not available");
3222
3390
  }
3223
- const prompt = `You are writing a git commit message for already staged changes.
3391
+ const prompt = `You are writing a git commit message for pushable changes.
3224
3392
 
3225
3393
  Recent git status:
3226
3394
  ${context.status || "<empty>"}
3227
3395
 
3228
- Recent git diff --cached:
3396
+ Recent git diff:
3229
3397
  ${context.diff || "<empty>"}
3230
3398
 
3231
3399
  Recent git log:
3232
3400
  ${context.log || "<empty>"}
3233
3401
 
3234
- Write exactly one short natural-language git commit message.
3235
- - Do not use conventional commit prefixes like feat:, fix:, or chore:
3402
+ Write exactly one short Conventional Commit message.
3403
+ - Format the first line as <type>: <subject>
3404
+ - Use a lowercase type such as feat, fix, docs, refactor, test, chore, or perf
3236
3405
  - Focus on the purpose of the change
3237
- - Keep it concise
3406
+ - Keep the first line under 72 characters when possible
3238
3407
  - Return only the commit message text`;
3239
3408
  const result = await (0, process_1.runCommand)([
3240
3409
  claudePath,
@@ -3249,16 +3418,16 @@ Write exactly one short natural-language git commit message.
3249
3418
  if (!message) {
3250
3419
  throw new Error("Claude returned an empty commit message");
3251
3420
  }
3252
- return message;
3421
+ return normalizeConventionalCommitMessage(message);
3253
3422
  }
3254
3423
  function fallbackCommitMessageFromFiles(files) {
3255
3424
  if (files.length === 0) {
3256
- return "Update repository files";
3425
+ return "chore: update repository files";
3257
3426
  }
3258
3427
  if (files.length === 1) {
3259
- return `Update ${(0, node_path_1.basename)(files[0]) || files[0]}`;
3428
+ return `chore: update ${(0, node_path_1.basename)(files[0]) || files[0]}`;
3260
3429
  }
3261
- return `Update ${(0, node_path_1.basename)(files[0]) || files[0]} and ${files.length - 1} other files`;
3430
+ return `chore: update ${(0, node_path_1.basename)(files[0]) || files[0]} and ${files.length - 1} other files`;
3262
3431
  }
3263
3432
  }
3264
3433
  });
@@ -3269,10 +3438,11 @@ var require_push = __commonJS({
3269
3438
  "use strict";
3270
3439
  Object.defineProperty(exports2, "__esModule", { value: true });
3271
3440
  exports2.previewPush = previewPush;
3441
+ exports2.preparePushPreview = preparePushPreview;
3272
3442
  exports2.pushRepository = pushRepository;
3273
3443
  var commit_message_1 = require_commit_message();
3274
3444
  var git_1 = require_git();
3275
- var process_1 = require_process();
3445
+ var errors_1 = require_errors();
3276
3446
  var PUSH_ARTIFACT_ROOTS = ["input", "output"];
3277
3447
  var PUSH_INCLUDED_PATHSPECS = [
3278
3448
  ".",
@@ -3281,14 +3451,6 @@ var require_push = __commonJS({
3281
3451
  ":(exclude)output",
3282
3452
  ":(exclude)output/**"
3283
3453
  ];
3284
- function shouldRetryCommit(error) {
3285
- if (!(error instanceof process_1.CommandError)) {
3286
- return false;
3287
- }
3288
- const details = `${error.result.stdout}
3289
- ${error.result.stderr}`.toLowerCase();
3290
- return details.includes("hook") || details.includes("pre-commit") || details.includes("modified") || details.includes("stashed changes conflicted");
3291
- }
3292
3454
  async function resolveCommitMessage(repoRoot, options, mode = "staged") {
3293
3455
  if (options.mode === "manual" && options.manualMessage?.trim()) {
3294
3456
  return {
@@ -3307,7 +3469,7 @@ ${error.result.stderr}`.toLowerCase();
3307
3469
  const generatedMessage = await generateCommitMessage({ status, diff, log });
3308
3470
  if (generatedMessage.trim()) {
3309
3471
  return {
3310
- message: generatedMessage.trim(),
3472
+ message: (0, commit_message_1.normalizeConventionalCommitMessage)(generatedMessage),
3311
3473
  source: "claude"
3312
3474
  };
3313
3475
  }
@@ -3332,8 +3494,72 @@ ${error.result.stderr}`.toLowerCase();
3332
3494
  await (0, git_1.stageAllMatching)(repoRoot, PUSH_INCLUDED_PATHSPECS);
3333
3495
  await (0, git_1.unstageMatching)(repoRoot, PUSH_ARTIFACT_ROOTS);
3334
3496
  }
3335
- async function previewPush(options) {
3336
- const repoRoot = await (0, git_1.getRepositoryRoot)(options.cwd ?? process.cwd());
3497
+ function getUpstreamRemoteName(upstream) {
3498
+ if (!upstream) {
3499
+ return null;
3500
+ }
3501
+ const separator = upstream.indexOf("/");
3502
+ return separator > 0 ? upstream.slice(0, separator) : null;
3503
+ }
3504
+ async function inspectPushRemoteState(repoRoot, branch, upstream, sshPrivateKeyPath) {
3505
+ const pushRef = await (0, git_1.getPushBranch)(repoRoot);
3506
+ const compareRef = pushRef ?? upstream ?? `origin/${branch}`;
3507
+ const remoteName = getUpstreamRemoteName(compareRef) ?? "origin";
3508
+ const setUpstream = !upstream;
3509
+ const remoteUrl = await (0, git_1.getRemoteUrl)(repoRoot, remoteName);
3510
+ if (!remoteUrl) {
3511
+ return {
3512
+ branch,
3513
+ upstream,
3514
+ remoteName,
3515
+ compareRef,
3516
+ setUpstream,
3517
+ aheadCount: 0,
3518
+ behindCount: 0
3519
+ };
3520
+ }
3521
+ await (0, git_1.fetchRemote)(repoRoot, remoteName, sshPrivateKeyPath);
3522
+ if (!await (0, git_1.refExists)(repoRoot, compareRef)) {
3523
+ return {
3524
+ branch,
3525
+ upstream,
3526
+ remoteName,
3527
+ compareRef,
3528
+ setUpstream,
3529
+ aheadCount: await (0, git_1.getCommitCount)(repoRoot),
3530
+ behindCount: 0
3531
+ };
3532
+ }
3533
+ const counts = await (0, git_1.getAheadBehind)(repoRoot, compareRef);
3534
+ return {
3535
+ branch,
3536
+ upstream,
3537
+ remoteName,
3538
+ compareRef,
3539
+ setUpstream,
3540
+ ...counts
3541
+ };
3542
+ }
3543
+ function assertPushRemoteCurrent(state) {
3544
+ if (state.behindCount === 0) {
3545
+ return;
3546
+ }
3547
+ throw (0, errors_1.createUserFacingError)({
3548
+ code: "push_remote_ahead",
3549
+ summary: state.aheadCount > 0 ? "Remote branch has diverged." : "Remote branch has new commits.",
3550
+ details: `${state.compareRef ?? "The push target"} is ${state.behindCount} commit${state.behindCount === 1 ? "" : "s"} ahead of the local branch.`,
3551
+ fixes: [
3552
+ "Run `git pull --rebase --autostash`, resolve conflicts if needed, then retry Push."
3553
+ ]
3554
+ });
3555
+ }
3556
+ function getReusableRemoteState(state, branch, upstream) {
3557
+ if (!state || state.branch !== branch || state.upstream !== upstream) {
3558
+ return null;
3559
+ }
3560
+ return state;
3561
+ }
3562
+ async function buildPushPreview(repoRoot, options, mode) {
3337
3563
  const [worktreeStatus, statusCounts] = await Promise.all([
3338
3564
  (0, git_1.getShortStatus)(repoRoot, PUSH_INCLUDED_PATHSPECS),
3339
3565
  (0, git_1.getStatusCounts)(repoRoot, PUSH_INCLUDED_PATHSPECS)
@@ -3349,15 +3575,24 @@ ${error.result.stderr}`.toLowerCase();
3349
3575
  detached = true;
3350
3576
  }
3351
3577
  const upstream = detached ? null : await (0, git_1.getUpstreamBranch)(repoRoot);
3352
- const hasChanges = Boolean(worktreeStatus.trim());
3578
+ const remoteState = branch && !detached ? await inspectPushRemoteState(repoRoot, branch, upstream, options.sshPrivateKeyPath) : null;
3579
+ if (remoteState) {
3580
+ assertPushRemoteCurrent(remoteState);
3581
+ }
3582
+ const hasChanges = mode === "staged" ? await (0, git_1.hasStagedChanges)(repoRoot, PUSH_INCLUDED_PATHSPECS) : Boolean(worktreeStatus.trim());
3583
+ const willCommit = hasChanges;
3584
+ const willPushExistingCommits = !willCommit && Boolean(remoteState && remoteState.aheadCount > 0);
3353
3585
  let suggestedMessage;
3354
3586
  let messageSource;
3355
3587
  let fallbackReason;
3356
3588
  if (hasChanges) {
3357
- const resolved = await resolveCommitMessage(repoRoot, options, "worktree");
3589
+ const resolved = await resolveCommitMessage(repoRoot, options, mode);
3358
3590
  suggestedMessage = resolved.message;
3359
3591
  messageSource = resolved.source;
3360
3592
  fallbackReason = resolved.fallbackReason;
3593
+ } else if (willPushExistingCommits) {
3594
+ suggestedMessage = await (0, git_1.getHeadCommitMessage)(repoRoot);
3595
+ messageSource = "manual";
3361
3596
  }
3362
3597
  return {
3363
3598
  repoRoot,
@@ -3369,39 +3604,56 @@ ${error.result.stderr}`.toLowerCase();
3369
3604
  statusCounts,
3370
3605
  suggestedMessage,
3371
3606
  messageSource,
3372
- fallbackReason
3607
+ fallbackReason,
3608
+ aheadCount: remoteState?.aheadCount ?? 0,
3609
+ behindCount: remoteState?.behindCount ?? 0,
3610
+ willCommit,
3611
+ willPushExistingCommits,
3612
+ remoteState: remoteState ?? void 0
3373
3613
  };
3374
3614
  }
3615
+ async function previewPush(options) {
3616
+ const repoRoot = await (0, git_1.getRepositoryRoot)(options.cwd ?? process.cwd());
3617
+ return await buildPushPreview(repoRoot, options, "worktree");
3618
+ }
3619
+ async function preparePushPreview(options) {
3620
+ const repoRoot = await (0, git_1.getRepositoryRoot)(options.cwd ?? process.cwd());
3621
+ await stagePushableChanges(repoRoot);
3622
+ return await buildPushPreview(repoRoot, options, "staged");
3623
+ }
3375
3624
  async function pushRepository(options) {
3376
3625
  const repoRoot = await (0, git_1.getRepositoryRoot)(options.cwd ?? process.cwd());
3626
+ const branch = await (0, git_1.getCurrentBranch)(repoRoot);
3627
+ const upstream = await (0, git_1.getUpstreamBranch)(repoRoot);
3628
+ const remoteState = getReusableRemoteState(options.remoteState, branch, upstream) ?? await inspectPushRemoteState(repoRoot, branch, upstream, options.sshPrivateKeyPath);
3629
+ assertPushRemoteCurrent(remoteState);
3377
3630
  await stagePushableChanges(repoRoot);
3378
3631
  if (!await (0, git_1.hasStagedChanges)(repoRoot, PUSH_INCLUDED_PATHSPECS)) {
3632
+ if (remoteState.aheadCount > 0) {
3633
+ const message2 = options.resolvedMessage?.message ?? options.manualMessage?.trim() ?? await (0, git_1.getHeadCommitMessage)(repoRoot);
3634
+ await (0, git_1.pushBranch)(repoRoot, branch, remoteState.setUpstream, options.sshPrivateKeyPath, remoteState.remoteName ?? "origin");
3635
+ return {
3636
+ repoRoot,
3637
+ branch,
3638
+ message: message2,
3639
+ retriedCommit: false,
3640
+ commitSha: await (0, git_1.getHeadShortSha)(repoRoot),
3641
+ upstream: await (0, git_1.getUpstreamBranch)(repoRoot),
3642
+ messageSource: options.resolvedMessage?.source ?? "manual",
3643
+ fallbackReason: options.resolvedMessage?.fallbackReason
3644
+ };
3645
+ }
3379
3646
  throw new Error("No changes to push.");
3380
3647
  }
3381
3648
  const resolvedMessage = options.resolvedMessage ?? await resolveCommitMessage(repoRoot, options);
3382
3649
  const message = resolvedMessage.message;
3383
- let retriedCommit = false;
3384
- try {
3385
- await (0, git_1.commitWithMessage)(repoRoot, message);
3386
- } catch (error) {
3387
- if (!shouldRetryCommit(error)) {
3388
- throw error;
3389
- }
3390
- retriedCommit = true;
3391
- await stagePushableChanges(repoRoot);
3392
- if (!await (0, git_1.hasStagedChanges)(repoRoot, PUSH_INCLUDED_PATHSPECS)) {
3393
- throw new Error("No changes to push.");
3394
- }
3395
- await (0, git_1.commitWithMessage)(repoRoot, message);
3396
- }
3397
- const branch = await (0, git_1.getCurrentBranch)(repoRoot);
3398
- const upstream = await (0, git_1.getUpstreamBranch)(repoRoot);
3399
- await (0, git_1.pushBranch)(repoRoot, branch, !upstream, options.sshPrivateKeyPath);
3650
+ await (0, git_1.commitWithMessage)(repoRoot, message);
3651
+ await (0, git_1.pushBranch)(repoRoot, branch, remoteState.setUpstream, options.sshPrivateKeyPath, remoteState.remoteName ?? "origin");
3400
3652
  return {
3401
3653
  repoRoot,
3402
3654
  branch,
3403
3655
  message,
3404
- retriedCommit,
3656
+ retriedCommit: false,
3405
3657
  commitSha: await (0, git_1.getHeadShortSha)(repoRoot),
3406
3658
  upstream: await (0, git_1.getUpstreamBranch)(repoRoot),
3407
3659
  messageSource: resolvedMessage.source,
@@ -4747,10 +4999,10 @@ ${detail.errors.join("\n")}` : summary);
4747
4999
  function buildOpenRouterProxyBaseUrl(agentApiUrl) {
4748
5000
  return new URL("/v3/openrouter", agentApiUrl.endsWith("/") ? agentApiUrl : `${agentApiUrl}/`).toString().replace(/\/$/, "");
4749
5001
  }
4750
- function buildOpenRouterClaudeEnvironmentOverrides(model, dataToken, agentApiUrl) {
4751
- const proxyBaseUrl = buildOpenRouterProxyBaseUrl(agentApiUrl);
5002
+ function buildOpenRouterClaudeEnvironmentOverrides(model, dataToken, agentApiUrl, sessionId) {
4752
5003
  return {
4753
- ANTHROPIC_BASE_URL: proxyBaseUrl,
5004
+ ANTHROPIC_BASE_URL: buildOpenRouterProxyBaseUrl(agentApiUrl),
5005
+ ...sessionId ? { ANTHROPIC_CUSTOM_HEADERS: `x-agent-session-id: ${sessionId}` } : {},
4754
5006
  ANTHROPIC_AUTH_TOKEN: dataToken,
4755
5007
  ANTHROPIC_API_KEY: "",
4756
5008
  ANTHROPIC_DEFAULT_OPUS_MODEL: model,
@@ -4809,7 +5061,8 @@ ${detail.errors.join("\n")}` : summary);
4809
5061
  };
4810
5062
  const models = await fetchModelsFn(config, options.fetch, { headers });
4811
5063
  await ensureOpenRouterModelAvailable(model, models);
4812
- const environmentOverrides = buildOpenRouterClaudeEnvironmentOverrides(model, dataToken, config.agentApiUrl);
5064
+ const agentSessionId = `local-${(0, node_crypto_1.randomUUID)()}`;
5065
+ const environmentOverrides = buildOpenRouterClaudeEnvironmentOverrides(model, dataToken, config.agentApiUrl, agentSessionId);
4813
5066
  const redactedEnvironmentOverrides = redactClaudeEnvironmentOverrides(environmentOverrides);
4814
5067
  return {
4815
5068
  command,
@@ -6726,23 +6979,25 @@ var require_cli = __commonJS({
6726
6979
  });
6727
6980
  }
6728
6981
  const mode = resolvePushMode(options);
6982
+ const config = await (0, config_1.resolveAgentConfig)();
6729
6983
  const preview = await (0, push_1.previewPush)({
6730
6984
  cwd: repoRoot,
6731
6985
  mode,
6732
- manualMessage: options.message?.trim() || void 0
6986
+ manualMessage: options.message?.trim() || void 0,
6987
+ sshPrivateKeyPath: config.gitSshKey?.privateKeyPath
6733
6988
  });
6734
- if (!preview.hasChanges) {
6735
- throw (0, errors_1.createUserFacingError)({
6736
- code: "push_no_changes",
6737
- summary: "No changes to push."
6738
- });
6739
- }
6740
6989
  if (preview.detached) {
6741
6990
  throw (0, errors_1.createUserFacingError)({
6742
6991
  code: "push_detached_head",
6743
6992
  summary: "Detached HEAD; check out a branch before pushing."
6744
6993
  });
6745
6994
  }
6995
+ if (!preview.hasChanges && !preview.willPushExistingCommits) {
6996
+ throw (0, errors_1.createUserFacingError)({
6997
+ code: "push_no_changes",
6998
+ summary: "No changes to push."
6999
+ });
7000
+ }
6746
7001
  if (!options.json) {
6747
7002
  write(io.stdout, renderPushPreview(preview));
6748
7003
  }
@@ -6756,15 +7011,24 @@ var require_cli = __commonJS({
6756
7011
  if (options.dryRun) {
6757
7012
  const result2 = {
6758
7013
  ok: true,
7014
+ repoRoot: preview.repoRoot,
6759
7015
  branch: preview.branch,
7016
+ upstream: preview.upstream,
7017
+ statusCounts: preview.statusCounts,
7018
+ worktreeStatus: preview.worktreeStatus,
6760
7019
  commitMessage: resolvedMessage,
7020
+ messageSource: preview.messageSource,
7021
+ fallbackReason: preview.fallbackReason,
7022
+ aheadCount: preview.aheadCount,
7023
+ behindCount: preview.behindCount,
7024
+ willCommit: preview.willCommit,
7025
+ willPushExistingCommits: preview.willPushExistingCommits,
6761
7026
  nextActions: ["Run `tela agent push` without `--dry-run` when the plan looks correct."]
6762
7027
  };
6763
7028
  if (options.json)
6764
7029
  emitJson(io, result2);
6765
7030
  return 0;
6766
7031
  }
6767
- const config = await (0, config_1.resolveAgentConfig)();
6768
7032
  const push = await (0, push_1.pushRepository)({
6769
7033
  cwd: repoRoot,
6770
7034
  mode: "manual",
@@ -6774,12 +7038,17 @@ var require_cli = __commonJS({
6774
7038
  source: preview.messageSource ?? (options.message ? "manual" : "fallback"),
6775
7039
  fallbackReason: preview.fallbackReason
6776
7040
  },
6777
- sshPrivateKeyPath: config.gitSshKey?.privateKeyPath
7041
+ sshPrivateKeyPath: config.gitSshKey?.privateKeyPath,
7042
+ remoteState: preview.remoteState
6778
7043
  });
6779
7044
  const result = {
6780
7045
  ok: true,
7046
+ repoRoot: push.repoRoot,
6781
7047
  branch: push.branch,
7048
+ upstream: push.upstream,
6782
7049
  commitMessage: push.message,
7050
+ messageSource: push.messageSource,
7051
+ fallbackReason: push.fallbackReason,
6783
7052
  commitSha: push.commitSha,
6784
7053
  retriedCommit: push.retriedCommit,
6785
7054
  nextActions: ["Run `tela agent feedback --list` to inspect feedback for the pushed commit."]
@@ -7229,6 +7498,7 @@ var require_dist2 = __commonJS({
7229
7498
  __exportStar(require_push(), exports2);
7230
7499
  __exportStar(require_run(), exports2);
7231
7500
  __exportStar(require_sync(), exports2);
7501
+ __exportStar(require_commit_message(), exports2);
7232
7502
  var config_1 = require_config2();
7233
7503
  Object.defineProperty(exports2, "getDefaultAgentCloneRoot", { enumerable: true, get: function() {
7234
7504
  return config_1.getDefaultAgentCloneRoot;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/tela-cli",
3
- "version": "0.7.44",
3
+ "version": "0.7.46",
4
4
  "description": "Tela command line tools for local state and agent workflows.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {