deepcode-ai 1.1.25 → 1.1.27

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.
package/dist/index.js CHANGED
@@ -2281,8 +2281,8 @@ var PermissionDeniedError = class extends DeepCodeError {
2281
2281
  }
2282
2282
  };
2283
2283
  var PathNotAllowedError = class extends DeepCodeError {
2284
- constructor(path15, reason) {
2285
- super(`Path is not allowed: ${path15}. ${reason}`, "PATH_NOT_ALLOWED");
2284
+ constructor(path152, reason) {
2285
+ super(`Path is not allowed: ${path152}. ${reason}`, "PATH_NOT_ALLOWED");
2286
2286
  this.name = "PathNotAllowedError";
2287
2287
  }
2288
2288
  };
@@ -5043,13 +5043,13 @@ var GitHubClient = class {
5043
5043
  }
5044
5044
  return parseGitHubRemote(result.stdout.trim());
5045
5045
  }
5046
- async request(path15, init = {}) {
5046
+ async request(path152, init = {}) {
5047
5047
  if (!this.options.token) {
5048
5048
  throw new Error(
5049
5049
  "GitHub token is required. Set GITHUB_TOKEN or .deepcode/config.json github.token."
5050
5050
  );
5051
5051
  }
5052
- const response = await fetch(`${this.apiBase}${path15}`, {
5052
+ const response = await fetch(`${this.apiBase}${path152}`, {
5053
5053
  ...init,
5054
5054
  headers: {
5055
5055
  accept: "application/vnd.github+json",
@@ -5065,13 +5065,13 @@ var GitHubClient = class {
5065
5065
  if (response.status === 204) return void 0;
5066
5066
  return await response.json();
5067
5067
  }
5068
- async requestText(path15, init = {}) {
5068
+ async requestText(path152, init = {}) {
5069
5069
  if (!this.options.token) {
5070
5070
  throw new Error(
5071
5071
  "GitHub token is required. Set GITHUB_TOKEN or .deepcode/config.json github.token."
5072
5072
  );
5073
5073
  }
5074
- const response = await fetch(`${this.apiBase}${path15}`, {
5074
+ const response = await fetch(`${this.apiBase}${path152}`, {
5075
5075
  ...init,
5076
5076
  headers: {
5077
5077
  accept: "application/vnd.github+json",
@@ -5539,12 +5539,12 @@ function fromFileUri(uri) {
5539
5539
  var SECRET_KEY_PATTERN = /(api[_-]?key|token|authorization|secret|password|passwd|credential|private[_-]?key)/i;
5540
5540
  var MIN_SECRET_VALUE_LENGTH = 4;
5541
5541
  function redactSecrets(value, options = {}) {
5542
- const path15 = options.path ?? [];
5542
+ const path152 = options.path ?? [];
5543
5543
  const secretPlaceholder = options.secretPlaceholder ?? "[redacted]";
5544
5544
  const emptySecretPlaceholder = options.emptySecretPlaceholder ?? "[empty]";
5545
5545
  const secretValues = options.secretValues ?? collectSecretValues();
5546
5546
  if (typeof value === "string") {
5547
- if (isSecretPath(path15)) {
5547
+ if (isSecretPath(path152)) {
5548
5548
  return value.length > 0 ? secretPlaceholder : emptySecretPlaceholder;
5549
5549
  }
5550
5550
  return redactText(value, secretValues, secretPlaceholder);
@@ -5553,7 +5553,7 @@ function redactSecrets(value, options = {}) {
5553
5553
  return value.map(
5554
5554
  (item, index) => redactSecrets(item, {
5555
5555
  ...options,
5556
- path: [...path15, String(index)],
5556
+ path: [...path152, String(index)],
5557
5557
  secretValues
5558
5558
  })
5559
5559
  );
@@ -5564,7 +5564,7 @@ function redactSecrets(value, options = {}) {
5564
5564
  key,
5565
5565
  redactSecrets(item, {
5566
5566
  ...options,
5567
- path: [...path15, key],
5567
+ path: [...path152, key],
5568
5568
  secretValues
5569
5569
  })
5570
5570
  ])
@@ -5602,8 +5602,8 @@ function collectSecretValues(config) {
5602
5602
  }
5603
5603
  return [...values].sort((left, right) => right.length - left.length);
5604
5604
  }
5605
- function isSecretPath(path15) {
5606
- const key = path15[path15.length - 1] ?? "";
5605
+ function isSecretPath(path152) {
5606
+ const key = path152[path152.length - 1] ?? "";
5607
5607
  if (/(api[_-]?key|token|secret|credential).*file/i.test(key)) return false;
5608
5608
  return SECRET_KEY_PATTERN.test(key);
5609
5609
  }
@@ -7824,8 +7824,8 @@ import { useState as useState22, useEffect as useEffect22, useCallback as useCal
7824
7824
  import { Box as Box2, Text as Text22, useInput as useInput2, useApp as useApp2 } from "ink";
7825
7825
  import path42 from "path";
7826
7826
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
7827
- import fs7 from "fs";
7828
- import path142 from "path";
7827
+ import fs8 from "fs";
7828
+ import path15 from "path";
7829
7829
  import { isValidElement, useCallback as useCallback26, useEffect as useEffect27, useMemo as useMemo18, useRef as useRef17, useState as useState29 } from "react";
7830
7830
  import { Box as Box42, Text as Text50, useInput as useInput6, useStdin as useStdin3 } from "ink";
7831
7831
  import os4 from "os";
@@ -9810,6 +9810,9 @@ import { jsx as jsx39, jsxs as jsxs35 } from "react/jsx-runtime";
9810
9810
  import { Box as Box31, Text as Text39 } from "ink";
9811
9811
  import { jsx as jsx40, jsxs as jsxs36 } from "react/jsx-runtime";
9812
9812
  import { jsx as jsx41, jsxs as jsxs37 } from "react/jsx-runtime";
9813
+ import fs6 from "fs";
9814
+ import os5 from "os";
9815
+ import path132 from "path";
9813
9816
  import { Box as Box33, Text as Text41 } from "ink";
9814
9817
  import { jsx as jsx42, jsxs as jsxs38 } from "react/jsx-runtime";
9815
9818
  import { useCallback as useCallback19, useMemo as useMemo11, useRef as useRef14 } from "react";
@@ -9827,8 +9830,8 @@ import { jsx as jsx46, jsxs as jsxs42 } from "react/jsx-runtime";
9827
9830
  import { useCallback as useCallback23, useEffect as useEffect25, useMemo as useMemo15, useRef as useRef16, useState as useState27 } from "react";
9828
9831
  import { Box as Box38, Text as Text46, useInput as useInput4 } from "ink";
9829
9832
  import { jsx as jsx47, jsxs as jsxs43 } from "react/jsx-runtime";
9830
- import fs6 from "fs";
9831
- import path132 from "path";
9833
+ import fs7 from "fs";
9834
+ import path142 from "path";
9832
9835
  import { useCallback as useCallback24, useMemo as useMemo16 } from "react";
9833
9836
  import { Box as Box39, Text as Text47 } from "ink";
9834
9837
  import { jsx as jsx48, jsxs as jsxs44 } from "react/jsx-runtime";
@@ -10382,6 +10385,141 @@ function parseProviderId(value) {
10382
10385
  if (parsed.success) return parsed.data;
10383
10386
  throw new Error(`Invalid provider: ${value}. Expected one of: ${PROVIDER_IDS.join(", ")}`);
10384
10387
  }
10388
+ var DIFF_MAX_CHARS = 2e4;
10389
+ function truncateDiff(raw, maxChars = DIFF_MAX_CHARS) {
10390
+ const fileChunks = raw.split(/(?=^diff --git )/m).filter(Boolean);
10391
+ const totalFiles = fileChunks.length;
10392
+ let result = "";
10393
+ let included = 0;
10394
+ for (const chunk of fileChunks) {
10395
+ if (result.length + chunk.length > maxChars) break;
10396
+ result += chunk;
10397
+ included++;
10398
+ }
10399
+ if (!result && fileChunks.length > 0) {
10400
+ result = fileChunks[0].slice(0, maxChars);
10401
+ included = 1;
10402
+ }
10403
+ return { diff: result.trimEnd(), omittedFiles: totalFiles - included, totalFiles };
10404
+ }
10405
+ async function runGit(cwd, args) {
10406
+ const result = await execFileAsync("git", args, { cwd, timeoutMs: 3e4 });
10407
+ if (result.exitCode !== 0) {
10408
+ throw new Error(result.stderr || `git ${args.join(" ")} failed`);
10409
+ }
10410
+ return result.stdout;
10411
+ }
10412
+ async function isGitRepo(cwd) {
10413
+ const result = await execFileAsync(
10414
+ "git",
10415
+ ["rev-parse", "--is-inside-work-tree"],
10416
+ { cwd, timeoutMs: 5e3 }
10417
+ );
10418
+ return result.exitCode === 0;
10419
+ }
10420
+ function buildDiffArgs(options) {
10421
+ if (options.staged) {
10422
+ const args2 = ["diff", "--cached"];
10423
+ if (options.file) args2.push("--", options.file);
10424
+ return { args: args2, label: "staged changes" };
10425
+ }
10426
+ if (options.ref) {
10427
+ const args2 = ["diff", options.ref];
10428
+ if (options.file) args2.push("--", options.file);
10429
+ return { args: args2, label: `diff vs ${options.ref}` };
10430
+ }
10431
+ const args = ["diff", "HEAD"];
10432
+ if (options.file) args.push("--", options.file);
10433
+ return { args, label: options.file ? `local changes in ${options.file}` : "local changes vs HEAD" };
10434
+ }
10435
+ function buildPrompt(diff, label, focus, truncation) {
10436
+ const focusLine = focus.length > 0 ? `
10437
+ Focus areas: ${focus.join(", ")}.` : "";
10438
+ const truncationNote = truncation.omittedFiles > 0 ? `
10439
+ (Showing ${truncation.totalFiles - truncation.omittedFiles} of ${truncation.totalFiles} changed files; ${truncation.omittedFiles} file(s) omitted due to size.)
10440
+ ` : "";
10441
+ return [
10442
+ `Review the following local git diff (${label}).`,
10443
+ "Do not modify any files. Output the review only.",
10444
+ focusLine,
10445
+ "",
10446
+ `\`\`\`diff`,
10447
+ diff,
10448
+ `\`\`\``,
10449
+ truncationNote,
10450
+ "Produce a structured code review:",
10451
+ "1. **Summary** \u2014 what changed (inferred from the diff)",
10452
+ "2. **Issues** \u2014 bugs, security concerns, logic errors, missing error handling; quote the relevant lines",
10453
+ "3. **Suggestions** \u2014 improvements and nitpicks",
10454
+ "4. **Verdict** \u2014 Looks good / Has issues, with a one-line rationale"
10455
+ ].filter((l) => l !== void 0).join("\n");
10456
+ }
10457
+ async function reviewCommand(options) {
10458
+ if (!await isGitRepo(options.cwd)) {
10459
+ await writeStderrLine("error: not inside a git repository");
10460
+ process.exit(1);
10461
+ }
10462
+ const { args, label } = buildDiffArgs(options);
10463
+ let rawDiff;
10464
+ try {
10465
+ rawDiff = await runGit(options.cwd, args);
10466
+ } catch (err) {
10467
+ const msg = err instanceof Error ? err.message : String(err);
10468
+ await writeStderrLine(`error: ${msg}`);
10469
+ process.exit(1);
10470
+ }
10471
+ const trimmed = rawDiff.trim();
10472
+ if (!trimmed) {
10473
+ await writeStdoutLine(`No changes to review (${label}).`);
10474
+ return;
10475
+ }
10476
+ const truncation = truncateDiff(trimmed);
10477
+ const runtime = await createRuntime({
10478
+ cwd: options.cwd,
10479
+ configPath: options.config,
10480
+ interactive: Boolean(options.yes)
10481
+ });
10482
+ if (options.yes) {
10483
+ runtime.events.on("approval:request", (request) => {
10484
+ runtime.events.emit("approval:decision", {
10485
+ requestId: request.id,
10486
+ decision: { allowed: true }
10487
+ });
10488
+ });
10489
+ }
10490
+ const target = resolveSessionTarget(runtime.config, {
10491
+ provider: options.provider,
10492
+ model: options.model
10493
+ });
10494
+ const session = runtime.sessions.create({
10495
+ provider: target.provider,
10496
+ model: target.model
10497
+ });
10498
+ const prompt = buildPrompt(truncation.diff, label, options.focus ?? [], truncation);
10499
+ const secretValues = collectSecretValues(runtime.config);
10500
+ await writeStdoutLine(`Reviewing ${label}\u2026
10501
+ `);
10502
+ let streamed = false;
10503
+ try {
10504
+ const output = await runtime.agent.run({
10505
+ session,
10506
+ input: prompt,
10507
+ mode: "plan",
10508
+ provider: target.provider,
10509
+ onChunk: (text) => {
10510
+ streamed = true;
10511
+ process.stdout.write(redactText(text, secretValues));
10512
+ }
10513
+ });
10514
+ if (!streamed && output) {
10515
+ process.stdout.write(redactText(output, secretValues));
10516
+ }
10517
+ if (!streamed || !output) process.stdout.write("\n");
10518
+ } finally {
10519
+ await runtime.sessions.persist(session.id).catch(() => {
10520
+ });
10521
+ }
10522
+ }
10385
10523
  async function githubLoginCommand(options) {
10386
10524
  const loader = new ConfigLoader();
10387
10525
  const loadOptions = { cwd: options.cwd, configPath: options.config };
@@ -10561,8 +10699,8 @@ async function solveIssueCommand(issueNumber, options) {
10561
10699
  const issue = await client.getIssue({ ...repo, number: issueNumber });
10562
10700
  const base = options.base ?? "main";
10563
10701
  const branch = `deepcode/issue-${issueNumber}-${slugify(issue.title)}`.slice(0, 80);
10564
- await runGit(options.cwd, ["fetch", "origin", base]);
10565
- await runGit(options.cwd, ["checkout", "-B", branch, `origin/${base}`]);
10702
+ await runGit2(options.cwd, ["fetch", "origin", base]);
10703
+ await runGit2(options.cwd, ["checkout", "-B", branch, `origin/${base}`]);
10566
10704
  const target = resolveUsableProviderTarget(runtime.config, [runtime.config.defaultProvider]);
10567
10705
  const session = runtime.sessions.create({
10568
10706
  provider: target.provider,
@@ -10587,16 +10725,16 @@ async function solveIssueCommand(issueNumber, options) {
10587
10725
  onChunk: (text) => void writeStdout(redactText(text, secretValues))
10588
10726
  });
10589
10727
  await writeStdout("\n");
10590
- const status = await runGit(options.cwd, ["status", "--porcelain"]);
10591
- const aheadLog = await runGit(options.cwd, ["log", `origin/${base}..HEAD`, "--oneline"]);
10728
+ const status = await runGit2(options.cwd, ["status", "--porcelain"]);
10729
+ const aheadLog = await runGit2(options.cwd, ["log", `origin/${base}..HEAD`, "--oneline"]);
10592
10730
  const hasUncommitted = Boolean(status.stdout.trim());
10593
10731
  const hasCommits = Boolean(aheadLog.stdout.trim());
10594
10732
  if (!hasUncommitted && !hasCommits) {
10595
10733
  throw new Error("Agent completed without file changes; no PR was created.");
10596
10734
  }
10597
10735
  if (hasUncommitted) {
10598
- await runGit(options.cwd, ["add", "."]);
10599
- await runGit(options.cwd, [
10736
+ await runGit2(options.cwd, ["add", "."]);
10737
+ await runGit2(options.cwd, [
10600
10738
  "commit",
10601
10739
  "-m",
10602
10740
  `fix: resolve issue #${issue.number}`,
@@ -10606,7 +10744,7 @@ async function solveIssueCommand(issueNumber, options) {
10606
10744
  Closes #${issue.number}`
10607
10745
  ]);
10608
10746
  }
10609
- await runGit(options.cwd, ["push", "-u", "origin", branch]);
10747
+ await runGit2(options.cwd, ["push", "-u", "origin", branch]);
10610
10748
  const pr = await client.createPullRequest({
10611
10749
  ...repo,
10612
10750
  title: `Fix: ${issue.title}`,
@@ -10645,6 +10783,10 @@ async function reviewPrCommand(prNumber, options) {
10645
10783
  ]);
10646
10784
  const focusLine = options.focus && options.focus.length > 0 ? `
10647
10785
  Focus areas: ${options.focus.join(", ")}.` : "";
10786
+ const truncation = truncateDiff(diff.trim());
10787
+ const truncationNote = truncation.omittedFiles > 0 ? `
10788
+ (Showing ${truncation.totalFiles - truncation.omittedFiles} of ${truncation.totalFiles} changed files; ${truncation.omittedFiles} file(s) omitted due to size.)
10789
+ ` : "";
10648
10790
  const prompt = [
10649
10791
  `Review PR #${pr.number}: ${pr.title}`,
10650
10792
  `Branch: ${pr.head ?? "?"} \u2192 ${pr.base ?? "?"}`,
@@ -10655,8 +10797,9 @@ ${pr.body}` : "No description provided.",
10655
10797
  "",
10656
10798
  `Diff:
10657
10799
  \`\`\`diff
10658
- ${diff}
10800
+ ${truncation.diff}
10659
10801
  \`\`\``,
10802
+ truncationNote,
10660
10803
  "",
10661
10804
  `Produce a structured code review with:${focusLine}`,
10662
10805
  "1. **Summary** \u2014 what the PR does",
@@ -10664,7 +10807,10 @@ ${diff}
10664
10807
  "3. **Suggestions** \u2014 improvements and nitpicks",
10665
10808
  "4. **Verdict** \u2014 Approve / Request Changes / Neutral with a one-line rationale"
10666
10809
  ].join("\n");
10667
- const target = resolveSessionTarget(runtime.config, {});
10810
+ const target = resolveSessionTarget(runtime.config, {
10811
+ provider: options.provider,
10812
+ model: options.model
10813
+ });
10668
10814
  const session = runtime.sessions.create({
10669
10815
  provider: target.provider,
10670
10816
  model: target.model
@@ -10684,7 +10830,7 @@ ${diff}
10684
10830
  }
10685
10831
  await writeStdout("\n");
10686
10832
  }
10687
- async function runGit(cwd, args) {
10833
+ async function runGit2(cwd, args) {
10688
10834
  const result = await execFileAsync("git", args, { cwd, timeoutMs: 18e4 });
10689
10835
  if (result.exitCode !== 0) {
10690
10836
  throw new Error(result.stderr || result.stdout || `git ${args.join(" ")} failed`);
@@ -10990,130 +11136,6 @@ async function projectsCommand(options) {
10990
11136
  );
10991
11137
  await waitUntilExit();
10992
11138
  }
10993
- var DIFF_MAX_CHARS = 2e4;
10994
- async function runGit2(cwd, args) {
10995
- const result = await execFileAsync("git", args, { cwd, timeoutMs: 3e4 });
10996
- if (result.exitCode !== 0) {
10997
- throw new Error(result.stderr || `git ${args.join(" ")} failed`);
10998
- }
10999
- return result.stdout;
11000
- }
11001
- async function isGitRepo(cwd) {
11002
- const result = await execFileAsync(
11003
- "git",
11004
- ["rev-parse", "--is-inside-work-tree"],
11005
- { cwd, timeoutMs: 5e3 }
11006
- );
11007
- return result.exitCode === 0;
11008
- }
11009
- function buildDiffArgs(options) {
11010
- if (options.staged) {
11011
- const args2 = ["diff", "--cached"];
11012
- if (options.file) args2.push("--", options.file);
11013
- return { args: args2, label: "staged changes" };
11014
- }
11015
- if (options.ref) {
11016
- const args2 = ["diff", options.ref];
11017
- if (options.file) args2.push("--", options.file);
11018
- return { args: args2, label: `diff vs ${options.ref}` };
11019
- }
11020
- const args = ["diff", "HEAD"];
11021
- if (options.file) args.push("--", options.file);
11022
- return { args, label: options.file ? `local changes in ${options.file}` : "local changes vs HEAD" };
11023
- }
11024
- function buildPrompt(diff, label, focus, truncated) {
11025
- const focusLine = focus.length > 0 ? `
11026
- Focus areas: ${focus.join(", ")}.` : "";
11027
- const truncationNote = truncated ? `
11028
- (Diff truncated at ${DIFF_MAX_CHARS} characters; some changes are not shown.)
11029
- ` : "";
11030
- return [
11031
- `Review the following local git diff (${label}).`,
11032
- "Do not modify any files. Output the review only.",
11033
- focusLine,
11034
- "",
11035
- `\`\`\`diff`,
11036
- diff,
11037
- `\`\`\``,
11038
- truncationNote,
11039
- "Produce a structured code review:",
11040
- "1. **Summary** \u2014 what changed (inferred from the diff)",
11041
- "2. **Issues** \u2014 bugs, security concerns, logic errors, missing error handling; quote the relevant lines",
11042
- "3. **Suggestions** \u2014 improvements and nitpicks",
11043
- "4. **Verdict** \u2014 Looks good / Has issues, with a one-line rationale"
11044
- ].filter((l) => l !== void 0).join("\n");
11045
- }
11046
- async function reviewCommand(options) {
11047
- if (!await isGitRepo(options.cwd)) {
11048
- await writeStderrLine("error: not inside a git repository");
11049
- process.exit(1);
11050
- }
11051
- const { args, label } = buildDiffArgs(options);
11052
- let rawDiff;
11053
- try {
11054
- rawDiff = await runGit2(options.cwd, args);
11055
- } catch (err) {
11056
- const msg = err instanceof Error ? err.message : String(err);
11057
- await writeStderrLine(`error: ${msg}`);
11058
- process.exit(1);
11059
- }
11060
- const trimmed = rawDiff.trim();
11061
- if (!trimmed) {
11062
- await writeStdoutLine(`No changes to review (${label}).`);
11063
- return;
11064
- }
11065
- let diff = trimmed;
11066
- let truncated = false;
11067
- if (diff.length > DIFF_MAX_CHARS) {
11068
- diff = diff.slice(0, DIFF_MAX_CHARS);
11069
- truncated = true;
11070
- }
11071
- const runtime = await createRuntime({
11072
- cwd: options.cwd,
11073
- configPath: options.config,
11074
- interactive: Boolean(options.yes)
11075
- });
11076
- if (options.yes) {
11077
- runtime.events.on("approval:request", (request) => {
11078
- runtime.events.emit("approval:decision", {
11079
- requestId: request.id,
11080
- decision: { allowed: true }
11081
- });
11082
- });
11083
- }
11084
- const target = resolveSessionTarget(runtime.config, {
11085
- provider: options.provider,
11086
- model: options.model
11087
- });
11088
- const session = runtime.sessions.create({
11089
- provider: target.provider,
11090
- model: target.model
11091
- });
11092
- const prompt = buildPrompt(diff, label, options.focus ?? [], truncated);
11093
- const secretValues = collectSecretValues(runtime.config);
11094
- await writeStdoutLine(`Reviewing ${label}\u2026
11095
- `);
11096
- let streamed = false;
11097
- try {
11098
- const output = await runtime.agent.run({
11099
- session,
11100
- input: prompt,
11101
- mode: "plan",
11102
- provider: target.provider,
11103
- onChunk: (text) => {
11104
- streamed = true;
11105
- process.stdout.write(redactText(text, secretValues));
11106
- }
11107
- });
11108
- if (!streamed && output) {
11109
- process.stdout.write(redactText(output, secretValues));
11110
- }
11111
- if (!streamed || !output) process.stdout.write("\n");
11112
- } finally {
11113
- await runtime.sessions.persist(session.id).catch(() => {
11114
- });
11115
- }
11116
- }
11117
11139
  function sessionLabel(session) {
11118
11140
  const name = typeof session.metadata["name"] === "string" ? session.metadata["name"] : void 0;
11119
11141
  const firstUser = session.messages.find((m) => m.role === "user");
@@ -28020,6 +28042,109 @@ var compactCommand = {
28020
28042
  await context.ui.compact();
28021
28043
  }
28022
28044
  };
28045
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
28046
+ var PACKAGE_NAME = "deepcode-ai";
28047
+ function cachePath() {
28048
+ const cacheHome = process.env["XDG_CACHE_HOME"] ?? path132.join(os5.homedir(), ".cache");
28049
+ return path132.join(cacheHome, "deepcode-ai", "update.json");
28050
+ }
28051
+ function readCache() {
28052
+ try {
28053
+ const raw = fs6.readFileSync(cachePath(), "utf8");
28054
+ const parsed = JSON.parse(raw);
28055
+ if (typeof parsed.checkedAt !== "number" || typeof parsed.latest !== "string" || Date.now() - parsed.checkedAt >= CACHE_TTL_MS) {
28056
+ return null;
28057
+ }
28058
+ return {
28059
+ checkedAt: parsed.checkedAt,
28060
+ latest: parsed.latest,
28061
+ stable: typeof parsed.stable === "string" ? parsed.stable : null
28062
+ };
28063
+ } catch {
28064
+ return null;
28065
+ }
28066
+ }
28067
+ function writeCache(cache) {
28068
+ try {
28069
+ const filePath = cachePath();
28070
+ fs6.mkdirSync(path132.dirname(filePath), { recursive: true });
28071
+ fs6.writeFileSync(filePath, `${JSON.stringify(cache)}
28072
+ `, "utf8");
28073
+ } catch {
28074
+ }
28075
+ }
28076
+ async function checkForUpdate(_currentVersion, options = {}) {
28077
+ if (process.env["CI"] || process.env["NODE_ENV"] === "test" || process.env["DEEPCODE_DISABLE_UPDATE_CHECK"] === "1") {
28078
+ return null;
28079
+ }
28080
+ if (!options.force) {
28081
+ const cached = readCache();
28082
+ if (cached) {
28083
+ return { latest: cached.latest, stable: cached.stable };
28084
+ }
28085
+ }
28086
+ try {
28087
+ const response = await fetch(
28088
+ `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`,
28089
+ { signal: AbortSignal.timeout(3e3) }
28090
+ );
28091
+ if (!response.ok) return null;
28092
+ const tags = await response.json();
28093
+ const latest = tags["latest"];
28094
+ if (typeof latest !== "string" || latest.length === 0) return null;
28095
+ const stable = typeof tags["stable"] === "string" && tags["stable"].length > 0 ? tags["stable"] : null;
28096
+ const update = { latest, stable };
28097
+ writeCache({ ...update, checkedAt: Date.now() });
28098
+ return update;
28099
+ } catch {
28100
+ return null;
28101
+ }
28102
+ }
28103
+ function isNewer(current, candidate) {
28104
+ const currentParts = parseVersion2(current);
28105
+ const candidateParts = parseVersion2(candidate);
28106
+ if (!currentParts || !candidateParts) return false;
28107
+ for (let index = 0; index < 3; index += 1) {
28108
+ const currentPart = currentParts[index] ?? 0;
28109
+ const candidatePart = candidateParts[index] ?? 0;
28110
+ if (candidatePart !== currentPart) {
28111
+ return candidatePart > currentPart;
28112
+ }
28113
+ }
28114
+ return false;
28115
+ }
28116
+ function parseVersion2(version) {
28117
+ const match = version.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
28118
+ if (!match) return null;
28119
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
28120
+ }
28121
+ var VERSION = "1.1.27".length > 0 ? "1.1.27" : "0.0.0-dev";
28122
+ var updateCommand = {
28123
+ name: "update",
28124
+ description: "Check published DeepCode versions",
28125
+ kind: "built-in",
28126
+ supportedModes: ["interactive"],
28127
+ action: async () => {
28128
+ const update = await checkForUpdate(VERSION, { force: true });
28129
+ const lines = [`Current version: ${VERSION}`];
28130
+ if (!update) {
28131
+ lines.push("Could not reach the npm registry right now.");
28132
+ } else {
28133
+ const latestStatus = isNewer(VERSION, update.latest) ? "available" : "current or older";
28134
+ lines.push(`Latest version: ${update.latest} (${latestStatus})`);
28135
+ if (update.stable) {
28136
+ const stableStatus = isNewer(VERSION, update.stable) ? "available" : "current or older";
28137
+ lines.push(`Stable version: ${update.stable} (${stableStatus})`);
28138
+ } else {
28139
+ lines.push("Stable version: not published yet");
28140
+ }
28141
+ }
28142
+ lines.push("");
28143
+ lines.push("Install latest: npm install -g deepcode-ai@latest");
28144
+ lines.push("Install stable: npm install -g deepcode-ai@stable");
28145
+ return { type: "message", messageType: "info", content: lines.join("\n") };
28146
+ }
28147
+ };
28023
28148
  function sessionNotReady() {
28024
28149
  return {
28025
28150
  type: "message",
@@ -29074,11 +29199,11 @@ var RATINGS = [
29074
29199
  ];
29075
29200
  var CANCEL_VALUE2 = "__cancel__";
29076
29201
  function appendFeedbackEntry(cwd, rating, label) {
29077
- const file = path132.join(cwd, ".deepcode", "feedback.log");
29202
+ const file = path142.join(cwd, ".deepcode", "feedback.log");
29078
29203
  const entry = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), rating, label });
29079
29204
  try {
29080
- fs6.mkdirSync(path132.dirname(file), { recursive: true });
29081
- fs6.appendFileSync(file, `${entry}
29205
+ fs7.mkdirSync(path142.dirname(file), { recursive: true });
29206
+ fs7.appendFileSync(file, `${entry}
29082
29207
  `, "utf8");
29083
29208
  } catch {
29084
29209
  }
@@ -29655,12 +29780,12 @@ var AppContainer = ({ cwd, config, provider, model, resumeSessionId }) => {
29655
29780
  const configAdapter = configAdapterRef.current ?? new DeepCodeConfigAdapter(cwd);
29656
29781
  const isValidPath = useCallback26(
29657
29782
  (candidate) => {
29658
- const resolved = path142.resolve(cwd, candidate);
29659
- const relative2 = path142.relative(cwd, resolved);
29660
- if (relative2.startsWith("..") || path142.isAbsolute(relative2)) {
29783
+ const resolved = path15.resolve(cwd, candidate);
29784
+ const relative2 = path15.relative(cwd, resolved);
29785
+ if (relative2.startsWith("..") || path15.isAbsolute(relative2)) {
29661
29786
  return false;
29662
29787
  }
29663
- return fs7.existsSync(resolved);
29788
+ return fs8.existsSync(resolved);
29664
29789
  },
29665
29790
  [cwd]
29666
29791
  );
@@ -29690,6 +29815,7 @@ var AppContainer = ({ cwd, config, provider, model, resumeSessionId }) => {
29690
29815
  modelCommand,
29691
29816
  modeCommand,
29692
29817
  renameCommand,
29818
+ updateCommand,
29693
29819
  settingsDialogCommand,
29694
29820
  themeDialogCommand,
29695
29821
  permissionsDialogCommand,
@@ -30072,6 +30198,25 @@ var AppContainer = ({ cwd, config, provider, model, resumeSessionId }) => {
30072
30198
  );
30073
30199
  }
30074
30200
  setIsInitializing(false);
30201
+ checkForUpdate(VERSION).then((update) => {
30202
+ if (!mounted || !update) return;
30203
+ const available = [];
30204
+ if (isNewer(VERSION, update.latest)) {
30205
+ available.push(`latest v${update.latest}`);
30206
+ }
30207
+ if (update.stable && isNewer(VERSION, update.stable)) {
30208
+ available.push(`stable v${update.stable}`);
30209
+ }
30210
+ if (available.length === 0) return;
30211
+ addHistoryItem(
30212
+ {
30213
+ type: "info",
30214
+ text: `Update available: ${available.join(", ")}. Run /update for install commands.`
30215
+ },
30216
+ Date.now()
30217
+ );
30218
+ }).catch(() => {
30219
+ });
30075
30220
  } catch (error) {
30076
30221
  if (!mounted) return;
30077
30222
  const message = error instanceof Error ? error.message : String(error);
@@ -30944,7 +31089,7 @@ var AppContainer = ({ cwd, config, provider, model, resumeSessionId }) => {
30944
31089
  mainControlsRef,
30945
31090
  constrainHeight: false,
30946
31091
  currentModel,
30947
- sessionName: path142.basename(cwd),
31092
+ sessionName: path15.basename(cwd),
30948
31093
  isConfigInitialized: !isInitializing && !initError,
30949
31094
  sessionStats: {
30950
31095
  lastPromptTokenCount,
@@ -31183,11 +31328,11 @@ function isInteractiveDialog(dialog) {
31183
31328
  return dialog === "theme" || dialog === "permissions" || dialog === "auth" || dialog === "provider" || dialog === "model" || dialog === "feedback" || dialog === "sessions";
31184
31329
  }
31185
31330
  function tuiThemeFilePath(cwd) {
31186
- return path142.join(cwd, ".deepcode", "tui-theme.json");
31331
+ return path15.join(cwd, ".deepcode", "tui-theme.json");
31187
31332
  }
31188
31333
  function readSavedTheme(cwd) {
31189
31334
  try {
31190
- const parsed = JSON.parse(fs7.readFileSync(tuiThemeFilePath(cwd), "utf8"));
31335
+ const parsed = JSON.parse(fs8.readFileSync(tuiThemeFilePath(cwd), "utf8"));
31191
31336
  return typeof parsed.theme === "string" ? parsed.theme : null;
31192
31337
  } catch {
31193
31338
  return null;
@@ -31195,8 +31340,8 @@ function readSavedTheme(cwd) {
31195
31340
  }
31196
31341
  function writeSavedTheme(cwd, themeName) {
31197
31342
  const file = tuiThemeFilePath(cwd);
31198
- fs7.mkdirSync(path142.dirname(file), { recursive: true });
31199
- fs7.writeFileSync(file, `${JSON.stringify({ theme: themeName }, null, 2)}
31343
+ fs8.mkdirSync(path15.dirname(file), { recursive: true });
31344
+ fs8.writeFileSync(file, `${JSON.stringify({ theme: themeName }, null, 2)}
31200
31345
  `);
31201
31346
  }
31202
31347
  function errorMessage(error) {
@@ -31335,7 +31480,7 @@ function createProgram() {
31335
31480
  writeOut: writeStdoutSync,
31336
31481
  writeErr: writeStderrSync
31337
31482
  });
31338
- program.name("deepcode").description("AI coding agent for the terminal").version("1.0.0").option("-C, --cwd <path>", "working directory", process.cwd()).option("--config <path>", "config file path");
31483
+ program.name("deepcode").description("AI coding agent for the terminal").version(VERSION).option("-C, --cwd <path>", "working directory", process.cwd()).option("--config <path>", "config file path");
31339
31484
  program.command("init").description("create .deepcode/config.json").action(async () => {
31340
31485
  await initCommand(program.opts().cwd);
31341
31486
  });
@@ -31484,7 +31629,7 @@ function createProgram() {
31484
31629
  "focus area: security, performance, correctness, style; repeat for multiple",
31485
31630
  collectOption,
31486
31631
  []
31487
- ).action(async (number, options) => {
31632
+ ).option("--provider <provider>", "provider override").option("--model <model>", "model override").action(async (number, options) => {
31488
31633
  const prNumber = Number.parseInt(number, 10);
31489
31634
  if (!Number.isInteger(prNumber) || prNumber <= 0) {
31490
31635
  throw new Error(`Invalid PR number: ${number}`);
@@ -31492,7 +31637,9 @@ function createProgram() {
31492
31637
  await reviewPrCommand(prNumber, {
31493
31638
  cwd: program.opts().cwd,
31494
31639
  config: program.opts().config,
31495
- focus: options.focus
31640
+ focus: options.focus,
31641
+ provider: options.provider,
31642
+ model: options.model
31496
31643
  });
31497
31644
  });
31498
31645
  github.command("solve").description("solve a GitHub issue end-to-end with branch, commit, push, and PR").argument("<number>", "issue number").option("--base <base>", "base branch", "main").option("-y, --yes", "approve commit/push/PR workflow").action(async (number, options) => {