@rotorsoft/gent 1.23.0 → 1.24.1

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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  githubRemoteCommand
4
- } from "./chunk-ZDOEOZDC.js";
4
+ } from "./chunk-OIHSXI5X.js";
5
5
  import {
6
6
  aiSpinnerText,
7
7
  buildIssueLabels,
@@ -11,7 +11,7 @@ import {
11
11
  setupLabelsCommand,
12
12
  sortByPriority,
13
13
  withSpinner
14
- } from "./chunk-ZQDWILU5.js";
14
+ } from "./chunk-YS7HWP4W.js";
15
15
  import {
16
16
  addIssueComment,
17
17
  addPrComment,
@@ -22,7 +22,6 @@ import {
22
22
  checkGeminiCli,
23
23
  checkGhAuth,
24
24
  checkGitRepo,
25
- checkInitialized,
26
25
  checkLabelsExist,
27
26
  checkoutBranch,
28
27
  colors,
@@ -64,7 +63,7 @@ import {
64
63
  sanitizeSlug,
65
64
  setRuntimeProvider,
66
65
  updateIssueLabels
67
- } from "./chunk-HCJWQPMW.js";
66
+ } from "./chunk-P5MZOU4B.js";
68
67
 
69
68
  // src/index.ts
70
69
  import { Command } from "commander";
@@ -208,7 +207,7 @@ All AI commits should include the Co-Authored-By trailer as specified in the tas
208
207
  - [E.g., "Always use async/await over callbacks"]
209
208
  `;
210
209
  async function initCommand(options) {
211
- logger.bold("Initializing gent workflow...");
210
+ logger.bold("Initializing gent workflow (optional \u2014 gent works with sensible defaults)...");
212
211
  logger.newline();
213
212
  const isGitRepo = await checkGitRepo();
214
213
  if (!isGitRepo) {
@@ -286,7 +285,7 @@ async function initCommand(options) {
286
285
  }
287
286
  ]);
288
287
  if (createRemote) {
289
- const { githubRemoteCommand: githubRemoteCommand2 } = await import("./github-remote-4TLUL2HX.js");
288
+ const { githubRemoteCommand: githubRemoteCommand2 } = await import("./github-remote-G6UKRDUB.js");
290
289
  const success = await githubRemoteCommand2();
291
290
  if (success) {
292
291
  const { setupLabels } = await inquirer.prompt([
@@ -298,7 +297,7 @@ async function initCommand(options) {
298
297
  }
299
298
  ]);
300
299
  if (setupLabels) {
301
- const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-2SNO3QQL.js");
300
+ const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-FZEN5TKM.js");
302
301
  await setupLabelsCommand2();
303
302
  }
304
303
  }
@@ -322,7 +321,7 @@ async function initCommand(options) {
322
321
  }
323
322
  ]);
324
323
  if (setupLabels) {
325
- const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-2SNO3QQL.js");
324
+ const { setupLabelsCommand: setupLabelsCommand2 } = await import("./setup-labels-FZEN5TKM.js");
326
325
  await setupLabelsCommand2();
327
326
  }
328
327
  }
@@ -335,6 +334,7 @@ import chalk from "chalk";
335
334
  // src/lib/ai-provider.ts
336
335
  import { spawn } from "child_process";
337
336
  import { execa as execa2 } from "execa";
337
+ var AI_DEFAULT_TIMEOUT_MS = 3e4;
338
338
  async function getOtherAvailableProviders(currentProvider) {
339
339
  const allProviders = ["claude", "gemini", "codex"];
340
340
  const others = allProviders.filter((p) => p !== currentProvider);
@@ -480,7 +480,8 @@ async function invokeClaudeInternal(options) {
480
480
  child.on("error", reject);
481
481
  });
482
482
  } else {
483
- const { stdout } = await execa2("claude", args);
483
+ const timeout = options.timeout ?? AI_DEFAULT_TIMEOUT_MS;
484
+ const { stdout } = await execa2("claude", args, { timeout });
484
485
  return stdout;
485
486
  }
486
487
  }
@@ -524,7 +525,8 @@ async function invokeGeminiInternal(options) {
524
525
  child.on("error", reject);
525
526
  });
526
527
  } else {
527
- const { stdout } = await execa2("gemini", args);
528
+ const timeout = options.timeout ?? AI_DEFAULT_TIMEOUT_MS;
529
+ const { stdout } = await execa2("gemini", args, { timeout });
528
530
  return stdout;
529
531
  }
530
532
  }
@@ -567,7 +569,8 @@ async function invokeCodexInternal(options) {
567
569
  child.on("error", reject);
568
570
  });
569
571
  } else {
570
- const { stdout } = await execa2("codex", args);
572
+ const timeout = options.timeout ?? AI_DEFAULT_TIMEOUT_MS;
573
+ const { stdout } = await execa2("codex", args, { timeout });
571
574
  return stdout;
572
575
  }
573
576
  }
@@ -695,21 +698,27 @@ ${issue ? `Closes #${issue.number}` : ""}
695
698
 
696
699
  Only output the PR description, nothing else.`;
697
700
  }
698
- function buildCommitMessagePrompt(diff, issueNumber, issueTitle) {
699
- const issueContext = issueNumber ? `
700
- Related Issue: #${issueNumber}${issueTitle ? ` - ${issueTitle}` : ""}
701
- ` : "";
702
- return `Generate a concise git commit message for the following changes.
703
- ${issueContext}
704
- ## Diff
705
- ${diff}
701
+ function buildCommitPrompt(issueNumber, issueTitle, config) {
702
+ const provider = config.ai.provider;
703
+ const providerName = getProviderDisplayName(provider);
704
+ const providerEmail = getProviderEmail(provider);
705
+ const issueContext = issueNumber ? `Related Issue: #${issueNumber}${issueTitle ? ` - ${issueTitle}` : ""}` : "No linked issue";
706
+ return `Create a git commit for the staged changes.
707
+
708
+ Context: ${issueContext}
706
709
 
707
- Rules:
708
- - Use conventional commit format: <type>: <short description>
709
- - Types: feat, fix, refactor, chore, docs, test, style, perf
710
- - Keep the first line under 72 characters
711
- - Do NOT include a body or footer
712
- - Output ONLY the commit message, nothing else`;
710
+ Steps:
711
+ 1. Run \`git diff --cached --stat\` to see what files changed
712
+ 2. Run \`git diff --cached\` to see the actual changes
713
+ 3. Generate a commit message following these rules:
714
+ - Use conventional commit format: <type>: <short description>
715
+ - Types: feat, fix, refactor, chore, docs, test, style, perf
716
+ - Keep the first line under 72 characters
717
+ - Add a blank line, then: Co-Authored-By: ${providerName} <${providerEmail}>
718
+ 4. Run \`git commit -m "<your message>"\` to create the commit
719
+ 5. Exit when done
720
+
721
+ Do not ask for confirmation - just create the commit.`;
713
722
  }
714
723
  function parseTicketMeta(output) {
715
724
  const metaMatch = output.match(
@@ -2207,7 +2216,7 @@ import { homedir } from "os";
2207
2216
  // package.json
2208
2217
  var package_default = {
2209
2218
  name: "@rotorsoft/gent",
2210
- version: "1.23.0",
2219
+ version: "1.24.1",
2211
2220
  description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
2212
2221
  keywords: [
2213
2222
  "cli",
@@ -2697,7 +2706,7 @@ async function aggregateState() {
2697
2706
  getUnpushedCommits()
2698
2707
  ]);
2699
2708
  const hasRemote = repoInfo !== null;
2700
- if (hasConfig && hasRemote && envCache.hasLabels === null) {
2709
+ if (hasRemote && envCache.hasLabels === null) {
2701
2710
  envCache.hasLabels = await checkLabelsExist().catch(() => false);
2702
2711
  }
2703
2712
  const hasLabels = envCache.hasLabels ?? false;
@@ -2781,14 +2790,14 @@ function getAvailableActions(state) {
2781
2790
  actions.push({ id: "quit", label: "quit", shortcut: "q" });
2782
2791
  return actions;
2783
2792
  }
2784
- const needsInit = !state.hasConfig;
2785
- const needsLabels = state.hasConfig && state.hasValidRemote && !state.hasLabels;
2786
- if (needsInit) {
2787
- actions.push({ id: "init", label: "init", shortcut: "i" });
2788
- } else if (needsLabels) {
2793
+ const needsLabels = state.hasValidRemote && !state.hasLabels;
2794
+ if (needsLabels) {
2789
2795
  actions.push({ id: "setup-labels", label: "setup-labels", shortcut: "b" });
2790
2796
  }
2791
- const isSetUp = state.hasConfig && (!state.hasValidRemote || state.hasLabels);
2797
+ if (!state.hasConfig) {
2798
+ actions.push({ id: "init", label: "init", shortcut: "i" });
2799
+ }
2800
+ const isSetUp = !state.hasValidRemote || state.hasLabels;
2792
2801
  if (isSetUp && state.hasValidRemote) {
2793
2802
  actions.push({ id: "create", label: "new", shortcut: "n" });
2794
2803
  }
@@ -3105,18 +3114,17 @@ function buildDashboardLines(state, actions, hint, refreshing, versionCheck) {
3105
3114
  out(row(chalk3.dim(" No commits"), w));
3106
3115
  }
3107
3116
  }
3108
- if (!state.hasConfig) {
3109
- section("Setup");
3110
- out(row(chalk3.yellow("Step 1: Initialize gent configuration"), w));
3111
- out(row(chalk3.dim("Press [i] to run gent init"), w));
3112
- } else if (!state.hasValidRemote) {
3117
+ if (!state.hasValidRemote) {
3113
3118
  section("Setup");
3114
- out(row(chalk3.yellow("Step 2: Create a GitHub repository"), w));
3119
+ out(row(chalk3.yellow("Step 1: Create a GitHub repository"), w));
3115
3120
  out(row(chalk3.dim("Press [g] to create a GitHub repo and push"), w));
3116
3121
  } else if (!state.hasLabels) {
3117
3122
  section("Setup");
3118
- out(row(chalk3.yellow("Step 3: Create workflow labels"), w));
3123
+ out(row(chalk3.yellow("Step 2: Create workflow labels"), w));
3119
3124
  out(row(chalk3.dim("Press [b] to set up labels"), w));
3125
+ } else if (!state.hasConfig) {
3126
+ section("Tip");
3127
+ out(row(chalk3.dim("Press [i] to customize configuration (optional)"), w));
3120
3128
  } else if (hint) {
3121
3129
  section("Hint");
3122
3130
  out(row(chalk3.yellow(hint), w));
@@ -3631,7 +3639,6 @@ function showStatus(title, message, dashboardLines) {
3631
3639
  }
3632
3640
 
3633
3641
  // src/commands/tui.ts
3634
- var CANCEL = /* @__PURE__ */ Symbol("cancel");
3635
3642
  async function waitForKey(validKeys) {
3636
3643
  return new Promise((resolve) => {
3637
3644
  const { stdin } = process;
@@ -3748,13 +3755,6 @@ async function handleCommit(state, dashboardLines) {
3748
3755
  return false;
3749
3756
  }
3750
3757
  await execa4("git", ["add", "-A"]);
3751
- const { stdout: diffStat } = await execa4("git", [
3752
- "diff",
3753
- "--cached",
3754
- "--stat"
3755
- ]);
3756
- const { stdout: diffPatch } = await execa4("git", ["diff", "--cached"]);
3757
- const diffContent = (diffStat + "\n\n" + diffPatch).slice(0, 4e3);
3758
3758
  const issueNumber = state.issue?.number ?? null;
3759
3759
  const issueTitle = state.issue?.title ?? null;
3760
3760
  const provider = state.config.ai.provider;
@@ -3771,75 +3771,45 @@ async function handleCommit(state, dashboardLines) {
3771
3771
  await execa4("git", ["reset", "HEAD"]);
3772
3772
  return false;
3773
3773
  }
3774
- let message;
3775
3774
  if (mode === "manual") {
3776
3775
  const input = await showInput({
3777
3776
  title: "Commit Message",
3778
3777
  label: "Enter commit message:",
3779
3778
  dashboardLines
3780
3779
  });
3781
- message = input || CANCEL;
3782
- } else {
3783
- showStatus("Generating", aiSpinnerText(providerName, "generate commit message"), dashboardLines);
3784
- message = await generateCommitMessage(
3785
- diffContent,
3786
- issueNumber,
3787
- issueTitle,
3788
- state,
3789
- dashboardLines
3790
- );
3791
- }
3792
- if (message === CANCEL) {
3793
- await execa4("git", ["reset", "HEAD"]);
3794
- return false;
3795
- }
3796
- const confirmed = await showConfirm({
3797
- title: "Commit",
3798
- message: `Message: ${message.length > 50 ? message.slice(0, 50) + "\u2026" : message}`,
3799
- dashboardLines
3800
- });
3801
- if (!confirmed) {
3802
- await execa4("git", ["reset", "HEAD"]);
3803
- return false;
3804
- }
3805
- const providerEmail = getProviderEmail(provider);
3806
- const fullMessage = `${message}
3780
+ if (!input) {
3781
+ await execa4("git", ["reset", "HEAD"]);
3782
+ return false;
3783
+ }
3784
+ const providerEmail = getProviderEmail(provider);
3785
+ const fullMessage = `${input}
3807
3786
 
3808
3787
  Co-Authored-By: ${providerName} <${providerEmail}>`;
3809
- showStatus("Committing", "Committing changes...", dashboardLines);
3810
- await execa4("git", ["commit", "-m", fullMessage]);
3811
- return true;
3788
+ showStatus("Committing", "Committing changes...", dashboardLines);
3789
+ await execa4("git", ["commit", "-m", fullMessage]);
3790
+ return true;
3791
+ } else {
3792
+ const prompt = buildCommitPrompt(issueNumber, issueTitle, state.config);
3793
+ clearScreen();
3794
+ renderActionPanel(`${providerName} Commit`, [
3795
+ "Creating commit for staged changes...",
3796
+ issueNumber ? `Related: #${issueNumber} ${issueTitle ?? ""}` : "No linked issue"
3797
+ ]);
3798
+ console.log();
3799
+ try {
3800
+ const { result } = await invokeAIInteractive(prompt, state.config);
3801
+ await result;
3802
+ return true;
3803
+ } catch (error) {
3804
+ logger.error(`${providerName} commit failed: ${error}`);
3805
+ return false;
3806
+ }
3807
+ }
3812
3808
  } catch (error) {
3813
3809
  logger.error(`Commit failed: ${error}`);
3814
3810
  return false;
3815
3811
  }
3816
3812
  }
3817
- async function generateCommitMessage(diffContent, issueNumber, issueTitle, state, dashboardLines) {
3818
- try {
3819
- const prompt = buildCommitMessagePrompt(
3820
- diffContent,
3821
- issueNumber,
3822
- issueTitle
3823
- );
3824
- const result = await invokeAI({ prompt, streamOutput: false }, state.config);
3825
- let message = result.output.trim().split("\n")[0].trim();
3826
- for (const q of ['"', "'", "`"]) {
3827
- if (message.startsWith(q) && message.endsWith(q)) {
3828
- message = message.slice(1, -1);
3829
- break;
3830
- }
3831
- }
3832
- message = message.replace(/^```\w*\s*/, "").replace(/\s*```$/, "");
3833
- return message;
3834
- } catch {
3835
- const input = await showInput({
3836
- title: "Commit Message",
3837
- label: "AI generation failed. Enter commit message:",
3838
- dashboardLines
3839
- });
3840
- return input || CANCEL;
3841
- }
3842
- }
3843
3813
  async function handleRun(state) {
3844
3814
  if (!state.issue) {
3845
3815
  logger.error("No linked issue found");
@@ -4148,10 +4118,6 @@ function startVersionCheck() {
4148
4118
  });
4149
4119
  }
4150
4120
  async function checkPrerequisites() {
4151
- if (!checkInitialized()) {
4152
- logger.warning('Repository not initialized. Run "gent init" to set up this repository.');
4153
- return false;
4154
- }
4155
4121
  const repoInfo = await getRepoInfo();
4156
4122
  if (!repoInfo) {
4157
4123
  logger.warning('No GitHub remote found. Run "gent github-remote" to create one.');
@@ -4172,7 +4138,7 @@ program.name("gent").description(
4172
4138
  startVersionCheck();
4173
4139
  }
4174
4140
  });
4175
- program.command("init").description("Initialize gent workflow in current repository").option("-f, --force", "Overwrite existing configuration").action(async (options) => {
4141
+ program.command("init").description("Initialize gent workflow in current repository (optional \u2014 for customization)").option("-f, --force", "Overwrite existing configuration").action(async (options) => {
4176
4142
  await initCommand(options);
4177
4143
  });
4178
4144
  program.command("setup-labels").description("Setup GitHub labels for AI workflow").action(async () => {