@rotorsoft/gent 1.15.2 → 1.16.0

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
@@ -2221,7 +2221,7 @@ import { homedir } from "os";
2221
2221
  // package.json
2222
2222
  var package_default = {
2223
2223
  name: "@rotorsoft/gent",
2224
- version: "1.15.2",
2224
+ version: "1.16.0",
2225
2225
  description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
2226
2226
  keywords: [
2227
2227
  "cli",
@@ -2646,6 +2646,7 @@ async function statusCommand() {
2646
2646
  import { execa as execa4 } from "execa";
2647
2647
 
2648
2648
  // src/tui/state.ts
2649
+ var envCache = null;
2649
2650
  async function aggregateState() {
2650
2651
  const isGitRepo = await checkGitRepo();
2651
2652
  if (!isGitRepo) {
@@ -2675,16 +2676,20 @@ async function aggregateState() {
2675
2676
  }
2676
2677
  const config = loadConfig();
2677
2678
  const workflowLabels = getWorkflowLabels(config);
2678
- const [
2679
- isGhAuthenticated,
2680
- isAIProviderAvailable,
2681
- branch,
2682
- isOnMain,
2683
- uncommitted,
2684
- baseBranch
2685
- ] = await Promise.all([
2686
- checkGhAuth(),
2687
- checkAIProvider(config.ai.provider),
2679
+ if (!envCache) {
2680
+ const [ghAuth, aiAvail] = await Promise.all([
2681
+ checkGhAuth(),
2682
+ checkAIProvider(config.ai.provider)
2683
+ ]);
2684
+ envCache = {
2685
+ isGhAuthenticated: ghAuth,
2686
+ isAIProviderAvailable: aiAvail,
2687
+ isPlaywrightAvailable: false
2688
+ // resolved below on first feature branch visit
2689
+ };
2690
+ }
2691
+ const { isGhAuthenticated, isAIProviderAvailable } = envCache;
2692
+ const [branch, isOnMain, uncommitted, baseBranch] = await Promise.all([
2688
2693
  getCurrentBranch(),
2689
2694
  isOnMainBranch(),
2690
2695
  hasUncommittedChanges(),
@@ -2706,16 +2711,20 @@ async function aggregateState() {
2706
2711
  let playwrightAvailable = false;
2707
2712
  if (!isOnMain) {
2708
2713
  const issueNumber = extractIssueNumber(branch);
2714
+ const needsPlaywrightCheck = !envCache.isPlaywrightAvailable;
2709
2715
  const [issueResult, prResult, changedFiles, playwrightResult] = await Promise.all([
2710
2716
  issueNumber ? getIssue(issueNumber).catch(() => null) : Promise.resolve(null),
2711
2717
  getPrStatus().catch(() => null),
2712
2718
  getChangedFiles(baseBranch),
2713
- isPlaywrightAvailable()
2719
+ needsPlaywrightCheck ? isPlaywrightAvailable() : Promise.resolve(envCache.isPlaywrightAvailable)
2714
2720
  ]);
2715
2721
  issue = issueResult;
2716
2722
  pr = prResult;
2717
2723
  uiChanges = hasUIChanges(changedFiles);
2718
2724
  playwrightAvailable = playwrightResult;
2725
+ if (needsPlaywrightCheck) {
2726
+ envCache.isPlaywrightAvailable = playwrightResult;
2727
+ }
2719
2728
  if (issue) {
2720
2729
  if (issue.labels.includes(workflowLabels.ready)) {
2721
2730
  workflowStatus = "ready";
@@ -2797,6 +2806,23 @@ function getAvailableActions(state) {
2797
2806
  import chalk3 from "chalk";
2798
2807
  var stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, "");
2799
2808
  var visibleLen = (str) => stripAnsi(str).length;
2809
+ function truncateAnsi(text, max) {
2810
+ if (visibleLen(text) <= max) return text;
2811
+ let visible = 0;
2812
+ let i = 0;
2813
+ while (i < text.length && visible < max - 1) {
2814
+ if (text[i] === "\x1B") {
2815
+ const end = text.indexOf("m", i);
2816
+ if (end !== -1) {
2817
+ i = end + 1;
2818
+ continue;
2819
+ }
2820
+ }
2821
+ visible++;
2822
+ i++;
2823
+ }
2824
+ return text.slice(0, i) + "\x1B[0m\u2026";
2825
+ }
2800
2826
  function termWidth() {
2801
2827
  return Math.min(process.stdout.columns || 80, 90);
2802
2828
  }
@@ -2836,8 +2862,9 @@ function botRow(w) {
2836
2862
  }
2837
2863
  function row(text, w) {
2838
2864
  const inner = w - 4;
2839
- const pad = Math.max(0, inner - visibleLen(text));
2840
- return chalk3.dim("\u2502") + " " + text + " ".repeat(pad) + " " + chalk3.dim("\u2502");
2865
+ const fitted = truncateAnsi(text, inner);
2866
+ const pad = Math.max(0, inner - visibleLen(fitted));
2867
+ return chalk3.dim("\u2502") + " " + fitted + " ".repeat(pad) + " " + chalk3.dim("\u2502");
2841
2868
  }
2842
2869
  function workflowBadge(status) {
2843
2870
  switch (status) {
@@ -2917,14 +2944,24 @@ function renderActionPanel(title, content) {
2917
2944
  }
2918
2945
  console.log(botRow(w));
2919
2946
  }
2920
- function renderSettingsTo(state, w, out) {
2947
+ function renderSettingsTo(state, w, out, versionCheck) {
2921
2948
  const provider = getProviderDisplayName(state.config.ai.provider);
2922
2949
  const provTag = state.isAIProviderAvailable ? chalk3.green(provider) : chalk3.red(provider);
2923
2950
  const ghTag = state.isGhAuthenticated ? chalk3.green("authenticated") : chalk3.red("not authenticated");
2924
2951
  out(row(chalk3.dim("Provider: ") + provTag, w));
2925
2952
  out(row(chalk3.dim("GitHub: ") + ghTag, w));
2953
+ if (versionCheck?.updateAvailable && versionCheck.latestVersion) {
2954
+ out(
2955
+ row(
2956
+ chalk3.yellow(
2957
+ `Update available: ${versionCheck.currentVersion} \u2192 ${versionCheck.latestVersion}`
2958
+ ) + chalk3.dim(' \u2014 run "npm install -g @rotorsoft/gent" to upgrade'),
2959
+ w
2960
+ )
2961
+ );
2962
+ }
2926
2963
  }
2927
- function buildDashboardLines(state, actions, hint, refreshing) {
2964
+ function buildDashboardLines(state, actions, hint, refreshing, versionCheck) {
2928
2965
  const lines = [];
2929
2966
  const out = (line) => lines.push(line);
2930
2967
  const w = termWidth();
@@ -2932,7 +2969,7 @@ function buildDashboardLines(state, actions, hint, refreshing) {
2932
2969
  const version2 = getVersion();
2933
2970
  const titleLabel = `gent v${version2}`;
2934
2971
  out(topRow(titleLabel, w));
2935
- renderSettingsTo(state, w, out);
2972
+ renderSettingsTo(state, w, out, versionCheck);
2936
2973
  if (!state.isGitRepo) {
2937
2974
  out(row(chalk3.red("Not a git repository"), w));
2938
2975
  out(row(chalk3.dim("Run gent init in a git repo to get started"), w));
@@ -2953,12 +2990,12 @@ function buildDashboardLines(state, actions, hint, refreshing) {
2953
2990
  if (state.issue) {
2954
2991
  out(
2955
2992
  row(
2956
- chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(truncate(state.issue.title, descMax - 6)),
2993
+ chalk3.dim("\xB7 ") + chalk3.cyan(`#${state.issue.number}`) + " " + chalk3.bold(state.issue.title),
2957
2994
  w
2958
2995
  )
2959
2996
  );
2960
2997
  const desc = extractDescription(state.issue.body, descMax);
2961
- if (desc) out(row(chalk3.dim(desc), w));
2998
+ if (desc) out(row(" " + chalk3.dim(desc), w));
2962
2999
  const tags = [];
2963
3000
  if (state.workflowStatus !== "none")
2964
3001
  tags.push(workflowBadge(state.workflowStatus));
@@ -2966,13 +3003,13 @@ function buildDashboardLines(state, actions, hint, refreshing) {
2966
3003
  const l = state.issue.labels.find((x) => x.startsWith(prefix));
2967
3004
  if (l) tags.push(chalk3.dim(l));
2968
3005
  }
2969
- if (tags.length) out(row(tags.join(" "), w));
3006
+ if (tags.length) out(row(" " + tags.join(" "), w));
2970
3007
  } else {
2971
- out(row(chalk3.dim("No linked issue"), w));
3008
+ out(row(chalk3.dim(" No linked issue"), w));
2972
3009
  }
2973
3010
  }
2974
3011
  section("Branch");
2975
- let branchLine = chalk3.magenta(state.branch);
3012
+ let branchLine = chalk3.dim("\xB7 ") + chalk3.magenta(state.branch);
2976
3013
  if (state.isOnMain && !state.hasUncommittedChanges) {
2977
3014
  branchLine += chalk3.dim(" \xB7 ready to start new work");
2978
3015
  }
@@ -2985,15 +3022,20 @@ function buildDashboardLines(state, actions, hint, refreshing) {
2985
3022
  if (!state.hasUncommittedChanges && !state.hasUnpushedCommits && state.commits.length > 0) {
2986
3023
  bits.push(chalk3.green("\u25CF synced"));
2987
3024
  }
2988
- if (bits.length) out(row(bits.join(chalk3.dim(" \xB7 ")), w));
3025
+ if (bits.length) out(row(" " + bits.join(chalk3.dim(" \xB7 ")), w));
2989
3026
  if (state.pr || !state.isOnMain) {
2990
3027
  section("Pull Request");
2991
3028
  if (state.pr) {
2992
- const titleText = state.pr.title ? " " + truncate(state.pr.title, descMax - 12) : "";
2993
- out(row(chalk3.cyan(`#${state.pr.number}`) + titleText, w));
3029
+ const titleText = state.pr.title ? " " + state.pr.title : "";
3030
+ out(
3031
+ row(
3032
+ chalk3.dim("\xB7 ") + chalk3.cyan(`#${state.pr.number}`) + titleText,
3033
+ w
3034
+ )
3035
+ );
2994
3036
  out(
2995
3037
  row(
2996
- prBadge(state.pr.state, state.pr.isDraft) + reviewBadge(state.pr.reviewDecision),
3038
+ " " + prBadge(state.pr.state, state.pr.isDraft) + reviewBadge(state.pr.reviewDecision),
2997
3039
  w
2998
3040
  )
2999
3041
  );
@@ -3001,7 +3043,7 @@ function buildDashboardLines(state, actions, hint, refreshing) {
3001
3043
  const n = state.reviewFeedback.length;
3002
3044
  out(
3003
3045
  row(
3004
- chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`),
3046
+ " " + chalk3.yellow(`${n} actionable comment${n !== 1 ? "s" : ""} pending`),
3005
3047
  w
3006
3048
  )
3007
3049
  );
@@ -3009,14 +3051,14 @@ function buildDashboardLines(state, actions, hint, refreshing) {
3009
3051
  if (state.hasUIChanges && state.isPlaywrightAvailable && state.config.video.enabled && state.pr.state === "open") {
3010
3052
  out(
3011
3053
  row(
3012
- chalk3.cyan("UI changes detected") + chalk3.dim(" \xB7 video capture available"),
3054
+ " " + chalk3.cyan("UI changes detected") + chalk3.dim(" \xB7 video capture available"),
3013
3055
  w
3014
3056
  )
3015
3057
  );
3016
3058
  }
3017
- out(row(chalk3.dim(state.pr.url), w));
3059
+ out(row(" " + chalk3.dim(state.pr.url), w));
3018
3060
  } else {
3019
- out(row(chalk3.dim("No PR created"), w));
3061
+ out(row(chalk3.dim(" No PR created"), w));
3020
3062
  }
3021
3063
  }
3022
3064
  if (state.commits.length > 0 || !state.isOnMain) {
@@ -3024,13 +3066,13 @@ function buildDashboardLines(state, actions, hint, refreshing) {
3024
3066
  if (state.commits.length > 0) {
3025
3067
  const max = 6;
3026
3068
  for (const c of state.commits.slice(0, max)) {
3027
- out(row(c.substring(0, w - 5), w));
3069
+ out(row(chalk3.dim("\xB7 ") + c, w));
3028
3070
  }
3029
3071
  if (state.commits.length > max) {
3030
- out(row(chalk3.dim(`\u2026 and ${state.commits.length - max} more`), w));
3072
+ out(row(chalk3.dim(` \u2026 and ${state.commits.length - max} more`), w));
3031
3073
  }
3032
3074
  } else {
3033
- out(row(chalk3.dim("No commits"), w));
3075
+ out(row(chalk3.dim(" No commits"), w));
3034
3076
  }
3035
3077
  }
3036
3078
  if (hint) {
@@ -3048,8 +3090,8 @@ function buildDashboardLines(state, actions, hint, refreshing) {
3048
3090
  out(botRow(w));
3049
3091
  return lines;
3050
3092
  }
3051
- function renderDashboard(state, actions, hint, refreshing) {
3052
- const lines = buildDashboardLines(state, actions, hint, refreshing);
3093
+ function renderDashboard(state, actions, hint, refreshing, versionCheck) {
3094
+ const lines = buildDashboardLines(state, actions, hint, refreshing, versionCheck);
3053
3095
  for (const line of lines) {
3054
3096
  console.log(line);
3055
3097
  }
@@ -3071,23 +3113,6 @@ function modalDivRow(w) {
3071
3113
  function modalBotRow(w) {
3072
3114
  return chalk4.bold("\u2514" + "\u2500".repeat(w - 2) + "\u2518");
3073
3115
  }
3074
- function truncateAnsi(text, max) {
3075
- if (visibleLen(text) <= max) return text;
3076
- let visible = 0;
3077
- let i = 0;
3078
- while (i < text.length && visible < max - 1) {
3079
- if (text[i] === "\x1B") {
3080
- const end = text.indexOf("m", i);
3081
- if (end !== -1) {
3082
- i = end + 1;
3083
- continue;
3084
- }
3085
- }
3086
- visible++;
3087
- i++;
3088
- }
3089
- return text.slice(0, i) + "\x1B[0m\u2026";
3090
- }
3091
3116
  function modalRow(text, w) {
3092
3117
  const inner = w - 4;
3093
3118
  const fitted = truncateAnsi(text, inner);
@@ -3122,9 +3147,10 @@ function buildSelectContent(items, selectedIndex, maxWidth) {
3122
3147
  } else {
3123
3148
  const isSelected = selectableIdx === selectedIndex;
3124
3149
  const prefix = isSelected ? chalk4.cyan.bold("> ") : " ";
3125
- const label = item.name.length > maxWidth - 2 ? item.name.slice(0, maxWidth - 3) + "\u2026" : item.name;
3150
+ const bullet = chalk4.dim("\xB7 ");
3151
+ const label = truncateAnsi(item.name, maxWidth - 4);
3126
3152
  lines.push(
3127
- prefix + (isSelected ? chalk4.bold(label) : label)
3153
+ prefix + bullet + (isSelected ? chalk4.bold(label) : label)
3128
3154
  );
3129
3155
  selectableIdx++;
3130
3156
  }
@@ -3821,10 +3847,12 @@ async function offerCreateBranch2(issueNumber, title, dashboardLines) {
3821
3847
  await createBranch(branchName, defaultBranch);
3822
3848
  return true;
3823
3849
  }
3850
+ var VERSION_CHECK_INTERVAL_MS = 30 * 60 * 1e3;
3824
3851
  async function tuiCommand() {
3825
3852
  let running = true;
3826
3853
  let lastActions = [];
3827
3854
  let lastDashboardLines = [];
3855
+ let versionCheck = null;
3828
3856
  const config = loadConfig();
3829
3857
  let lastState = {
3830
3858
  isGitRepo: true,
@@ -3852,11 +3880,15 @@ async function tuiCommand() {
3852
3880
  while (running) {
3853
3881
  if (needsRefresh) {
3854
3882
  clearScreen();
3855
- renderDashboard(lastState, lastActions, void 0, true);
3856
- const state = await aggregateState();
3883
+ renderDashboard(lastState, lastActions, void 0, true, versionCheck);
3884
+ const [state, versionResult] = await Promise.all([
3885
+ aggregateState(),
3886
+ checkForUpdates(VERSION_CHECK_INTERVAL_MS).catch(() => null)
3887
+ ]);
3857
3888
  const actions = getAvailableActions(state);
3858
3889
  lastState = state;
3859
3890
  lastActions = actions;
3891
+ if (versionResult) versionCheck = versionResult;
3860
3892
  }
3861
3893
  let hint;
3862
3894
  if (lastState.isOnMain) {
@@ -3866,7 +3898,7 @@ async function tuiCommand() {
3866
3898
  } else if (lastState.hasActionableFeedback) {
3867
3899
  hint = "Review feedback needs attention";
3868
3900
  }
3869
- lastDashboardLines = buildDashboardLines(lastState, lastActions, hint);
3901
+ lastDashboardLines = buildDashboardLines(lastState, lastActions, hint, false, versionCheck);
3870
3902
  clearScreen();
3871
3903
  for (const line of lastDashboardLines) {
3872
3904
  console.log(line);