Skip to content

feat: make command explanation and confirmation opt-in via --explain flag - #146

Closed
bernoussama wants to merge 3 commits into
mainfrom
cursor/command-explanation-optionality-8021
Closed

feat: make command explanation and confirmation opt-in via --explain flag#146
bernoussama wants to merge 3 commits into
mainfrom
cursor/command-explanation-optionality-8021

Conversation

@bernoussama

@bernoussama bernoussama commented Mar 18, 2026

Copy link
Copy Markdown
Owner

Summary

Makes the generated command explanation and confirmation prompt optional, independent, and off by default, each togglable via its own CLI flag. Also fixes broken command execution.

Changes

Before

  • Command explanation was shown by default (disabled with --silent)
  • The interactive confirmation prompt (Yes/Refine/Cancel) was always shown
  • runCommand used fire-and-forget exec() — process could exit before command finished

After

  • Default behavior: generate command, display it, and run immediately — no explanation, no confirmation
  • -e / --explain: show the AI-generated explanation of the command
  • -c / --confirm: present the interactive confirmation prompt (Yes/Refine/Cancel) before executing
  • Both flags are independent and can be combined
  • runCommand now uses spawn with stdio: 'inherit' and returns a Promise<number> (exit code), properly awaited by the caller

Files changed

File Change
src/index.ts Replaced -s, --silent with -e, --explain and -c, --confirm; await runCommand
src/lib/ai.ts Changed generateCommandStruct default explanation param from true to false
src/utils.ts Rewrote runCommand to use spawn + stdio: 'inherit', return Promise<number>
README.md Updated docs to reflect the new flags
src/utils.test.ts Tests for runCommand (exit codes, pipes, args, unknown commands)
src/cli.test.ts Tests for CLI flag parsing (-e, -c, --explain, --confirm, defaults)
package.json Added test script, @types/bun dev dependency
tsconfig.json Added bun-types for test type support

Usage

# Default: generate and run immediately
lsh "list files sorted by size"

# Show explanation only
lsh -e "find all JavaScript files modified in the last 7 days"

# Confirm before running only
lsh -c "show disk usage sorted by size"

# Both explanation and confirmation
lsh -e -c "delete old log files"

Summary by CodeRabbit

  • New Features
    • Added --explain and --confirm flags; flags can be combined.
  • Behavior Changes
    • Commands now auto-execute by default; previous silent mode removed.
    • Explanations are off by default and shown only when --explain is used.
    • Confirmation prompt appears when --confirm is used; otherwise commands run immediately.
  • Documentation
    • Updated usage examples and Options section to reflect new flags.
  • Tests
    • Added CLI and command-execution unit tests.

…flag

Replace the --silent flag (which disabled explanation) with --explain/-e flag
that enables both explanation and confirmation. By default, commands are now
generated and executed immediately without explanation or confirmation prompt.

- Add -e/--explain CLI option (off by default)
- When --explain is passed: show explanation + interactive confirm/refine/cancel
- When not passed: generate command, display it, and run immediately
- Update generateCommandStruct default to explanation=false
- Update README to document the new --explain flag
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the user interaction flow for the lazyshell tool, making the command explanation and confirmation prompt opt-in rather than opt-out. The primary goal is to streamline the default experience for users who prefer immediate command execution, while still providing the guided, interactive mode through a new --explain flag. This change improves efficiency and flexibility in how users interact with generated commands.

Highlights

  • Default Behavior Change: The command explanation and interactive confirmation prompt are now optional and disabled by default, leading to immediate command execution.
  • New CLI Flag: Introduced a new --explain (or -e) CLI flag to explicitly enable the AI-generated explanation and the interactive confirmation prompt.
  • Flag Renaming: The previous --silent flag has been replaced by the new --explain flag, reversing the logic of explicit control over explanation and confirmation.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly implements the --explain flag to make command explanation and confirmation optional, changing the default behavior to execute commands immediately. The implementation is solid, but I've suggested a small refactoring in src/index.ts to reduce code duplication and improve maintainability.

Comment thread src/index.ts Outdated
Comment on lines 88 to 119
if (explain) {
const action = await select({
message: 'Run command?',
options: [
{ label: '✅ Yes', value: 'execute' },
{ label: '🔧 Refine', value: 'refine' },
{ label: '❌ Cancel', value: 'cancel' },
],
});
if (isCancel(action)) {
cancel('Operation cancelled.');
process.exit(0);
}

switch (action) {
case 'execute':
outro(`running command: ${chalk.green(command)}`);
runCommand(command);
shouldContinue = false;
break;
case 'refine':
currentPrompt = await refineCommand(currentPrompt, command);
break;
case 'cancel':
outro(chalk.yellow('Command cancelled.'));
return;
}
} else {
outro(`running command: ${chalk.green(command)}`);
runCommand(command);
shouldContinue = false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's some code duplication for executing the command. You can refactor this logic to avoid repeating the outro, runCommand, and shouldContinue = false lines. This will make the code more maintainable.

        let execute = !explain;

        if (explain) {
          const action = await select({
            message: 'Run command?',
            options: [
              { label: '✅ Yes', value: 'execute' },
              { label: '🔧 Refine', value: 'refine' },
              { label: '❌ Cancel', value: 'cancel' },
            ],
          });

          if (isCancel(action)) {
            cancel('Operation cancelled.');
            process.exit(0);
          }

          switch (action) {
            case 'execute':
              execute = true;
              break;
            case 'refine':
              currentPrompt = await refineCommand(currentPrompt, command);
              break;
            case 'cancel':
              outro(chalk.yellow('Command cancelled.'));
              return;
          }
        }

        if (execute) {
          outro(`running command: ${chalk.green(command)}`);
          runCommand(command);
          shouldContinue = false;
        }

Separate explanation and confirmation into independent flags:
- -e/--explain: show AI-generated explanation of the command
- -c/--confirm: prompt for confirmation before running (Yes/Refine/Cancel)

Both are off by default. They can be used independently or together.
@coderabbitai

coderabbitai Bot commented Mar 18, 2026

Copy link
Copy Markdown

Walkthrough

The CLI replaces the previous silent mode with --explain and --confirm flags. generateCommandStruct now defaults to omitting explanations unless --explain is used. --confirm triggers an interactive Yes/Refine/Cancel prompt before running generated commands.

Changes

Cohort / File(s) Summary
Documentation
README.md
Rename "Silent Mode" → "Options"; add --explain and --confirm descriptions and update usage examples.
CLI Logic & Flow
src/index.ts
Remove -s/--silent; add -e/--explain and -c/--confirm; pass explain to generation; display explanation if present; if confirm present, prompt (Yes/Refine/Cancel) before executing, else execute immediately.
AI Generation
src/lib/ai.ts
Change generateCommandStruct(..., explanation: boolean = false) default from truefalse, so explanations are not returned unless requested.
Command Execution Utility
src/utils.ts
Switch from exec to spawn with inherited stdio; runCommand now returns Promise<number> and resolves on process close; shell selection explicit.
Tests & Tooling
src/cli.test.ts, src/utils.test.ts, package.json, tsconfig.json
Add CLI and runCommand unit tests; add test script and @types/bun devDependency; add types compiler option for bun.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as src/index.ts
    participant AI as generateCommandStruct
    participant Prompt as Confirmation UI
    participant Exec as runCommand

    User->>CLI: Invoke with prompt (+ --explain / --confirm)
    CLI->>AI: generateCommandStruct(prompt, config, explain)
    AI-->>CLI: Return { command, explanation? }

    alt explanation present
        CLI-->>User: Display explanation
    end

    alt confirm enabled
        CLI->>Prompt: Show "Run command?" (Yes / Refine / Cancel)
        Prompt-->>User: Choose
        alt User -> Yes
            CLI->>Exec: await runCommand(command)
        else User -> Refine
            CLI-->>User: Return for refinement
        else User -> Cancel
            CLI-->>User: Abort
        end
    else
        CLI->>Exec: await runCommand(command)
    end

    Exec-->>User: Streamed output / exit code
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • Fix/remove nexssp os legacy #18: Adjusts CLI handling and generateCommandStruct usage similarly; likely overlaps in explain/confirm behavior changes.
  • Feat/config file #10: Modifies generateCommandStruct signature and modelConfig handling, directly related to AI generation plumbing.
  • Feat/structured output #9: Touches CLI and command-generation integration; relevant to how flags drive generation and output.

Poem

🐇 I hopped through flags both new and bright,
Explain to show, confirm to hold tight,
No more silent whispers in the night,
We ask, we show, then run—hop! delight ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: making command explanation optional via --explain flag, which aligns with the primary objective of introducing independent -e/--explain and -c/--confirm flags.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/command-explanation-optionality-8021
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@bernoussama
bernoussama marked this pull request as ready for review March 18, 2026 23:02
Copilot AI review requested due to automatic review settings March 18, 2026 23:02

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 138be3e734

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/index.ts Outdated
Comment on lines +117 to +119
} else {
outro(`running command: ${chalk.green(command)}`);
runCommand(command);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Don't auto-execute model output without confirmation

This new default removes the only runtime guard between the model output and exec(): runCommand() in src/utils.ts shells out to whatever string the model returned, while generateCommandStruct() in src/lib/ai.ts only asks the model to be safe and even allows warning strings like error: ambiguous request. For prompts that are ambiguous or mildly destructive (delete old log files, kill the node servers, etc.), a model misgeneration now becomes an immediate side effect instead of something the user can cancel or refine, which is a significant safety regression from the previous default.

Useful? React with 👍 / 👎.

Comment thread src/index.ts
Comment on lines +60 to +61
.option('-e, --explain', 'show explanation of the generated command')
.option('-c, --confirm', 'ask for confirmation before running the command')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep --silent as a deprecated alias

Replacing -s/--silent outright is a breaking CLI change for existing aliases, scripts, and dotfiles that followed the previously documented flag. With commander 14 (package.json), unknown options abort parsing before .action() runs, so lsh --silent "..." now exits with an error instead of generating a command. Even if --explain is the new preferred flag, we should still accept --silent as a no-op/deprecated alias for compatibility.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR changes LazyShell’s CLI defaults so that AI-generated commands run immediately by default, and makes both the generated explanation and the interactive confirmation prompt opt-in via new flags.

Changes:

  • Replaces --silent with --explain and --confirm flags, which independently toggle explanation output and pre-execution confirmation.
  • Updates generateCommandStruct to default explanation to false.
  • Updates README usage/docs to match the new flags and default behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/lib/ai.ts Changes generateCommandStruct default so explanations are not generated unless explicitly requested.
src/index.ts Adds --explain / --confirm flags and runs commands immediately when --confirm isn’t provided.
README.md Documents the new default behavior and the new flags.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/index.ts
Comment on lines +90 to 121
if (confirm) {
const action = await select({
message: 'Run command?',
options: [
{ label: '✅ Yes', value: 'execute' },
{ label: '🔧 Refine', value: 'refine' },
{ label: '❌ Cancel', value: 'cancel' },
],
});
if (isCancel(action)) {
cancel('Operation cancelled.');
process.exit(0);
}

switch (action) {
case 'execute':
outro(`running command: ${chalk.green(command)}`);
runCommand(command);
shouldContinue = false;
break;
case 'refine':
currentPrompt = await refineCommand(currentPrompt, command);
break;
case 'cancel':
outro(chalk.yellow('Command cancelled.'));
return;
}
} else {
outro(`running command: ${chalk.green(command)}`);
runCommand(command);
shouldContinue = false;
}
Comment thread src/lib/ai.ts
Comment on lines 189 to 195
export async function generateCommandStruct(
prompt: string,
modelConfig?: ModelConfig,
explanation: boolean = true
explanation: boolean = false
): Promise<Command | CommandWithExplanation> {
const modelConf = modelConfig || getDefaultModel();
const schema = explanation ? zCmdExp : zCmd;

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/ai.ts (1)

207-209: ⚠️ Potential issue | 🟡 Minor

Error path returns inconsistent shape regardless of explanation parameter.

When explanation=false, the function should return a Command (only { command }), but the error path always returns { command, explanation: '' }. This breaks the type contract and makes type guards like 'explanation' in result unreliable.

The current code in src/index.ts works around this by checking truthiness (result.explanation), but this should be fixed for type consistency.

🔧 Proposed fix
   } catch {
     const result = await generateCommand(prompt, modelConfig);
-    return { command: result, explanation: '' };
+    return explanation ? { command: result, explanation: '' } : { command: result };
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/ai.ts` around lines 207 - 209, The catch block always returns {
command, explanation: '' } which violates the expected shape when explanation is
disabled; update the catch to return only { command } when modelConf.explanation
is false and return { command, explanation: '' } otherwise—inspect
modelConf.explanation in the catch, call generateCommand(prompt, modelConf) as
now, then conditionally return either the Command-only shape or the
CommandWithExplanation shape so type guards work correctly (refer to
generateCommand and modelConf).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/lib/ai.ts`:
- Around line 207-209: The catch block always returns { command, explanation: ''
} which violates the expected shape when explanation is disabled; update the
catch to return only { command } when modelConf.explanation is false and return
{ command, explanation: '' } otherwise—inspect modelConf.explanation in the
catch, call generateCommand(prompt, modelConf) as now, then conditionally return
either the Command-only shape or the CommandWithExplanation shape so type guards
work correctly (refer to generateCommand and modelConf).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d0144303-01c4-424f-a90f-e50ede6b5bf2

📥 Commits

Reviewing files that changed from the base of the PR and between f2c95c9 and 138be3e.

📒 Files selected for processing (3)
  • README.md
  • src/index.ts
  • src/lib/ai.ts

runCommand was using fire-and-forget exec() without awaiting the child
process, so the Node.js process could exit before the command finished.
This was masked when the interactive confirmation prompt was always shown.

Changes:
- Rewrite runCommand to use spawn with stdio:'inherit' and return a
  Promise<number> that resolves with the exit code
- Await runCommand at both call sites in index.ts
- Add tests for runCommand (exit codes, pipes, args) and CLI flag parsing
- Add @types/bun and bun-types to tsconfig for test type support
- Add 'test' script to package.json

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/index.ts (1)

90-121: Consider propagating the command exit code.

The runCommand now returns a Promise<number> with the exit code, but it's currently ignored. For better CLI behavior, consider propagating a non-zero exit code to the process:

♻️ Proposed enhancement
           case 'execute':
              outro(`running command: ${chalk.green(command)}`);
-              await runCommand(command);
+              const exitCode = await runCommand(command);
               shouldContinue = false;
+              if (exitCode !== 0) process.exitCode = exitCode;
               break;

Apply similar change to the non-confirm path at lines 118-120.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.ts` around lines 90 - 121, runCommand now returns a Promise<number>
(exit code) but the code ignores it; capture the returned exit code in both the
'execute' case inside the confirm flow and the non-confirm branch, and if the
exit code is non-zero call process.exit(exitCode) to propagate failure; update
the 'execute' branch (switch case for 'execute') and the else branch that calls
runCommand(command) to await the result into a variable (e.g., exitCode) and
call process.exit(exitCode) when exitCode !== 0, otherwise continue setting
shouldContinue = false or return as appropriate.
src/utils.test.ts (1)

4-28: Good test coverage for the happy path.

The tests effectively verify exit code handling for various command scenarios. Consider adding a test for the error rejection path (e.g., when spawn itself fails due to an invalid shell) to ensure the reject(err) branch in runCommand is covered.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils.test.ts` around lines 4 - 28, Add a unit test to cover the
rejection/error branch in runCommand by simulating a spawn failure: in the test
file, mock child_process.spawn (e.g., with jest.spyOn(require('child_process'),
'spawn').mockImplementationOnce(() => { throw new Error('spawn failed') }) or
return a mock EventEmitter that emits an 'error' event) and assert that
runCommand rejects with that error (use await
expect(runCommand(...)).rejects.toThrow('spawn failed')). This ensures the
reject(err) branch inside runCommand is exercised.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/index.ts`:
- Around line 90-121: runCommand now returns a Promise<number> (exit code) but
the code ignores it; capture the returned exit code in both the 'execute' case
inside the confirm flow and the non-confirm branch, and if the exit code is
non-zero call process.exit(exitCode) to propagate failure; update the 'execute'
branch (switch case for 'execute') and the else branch that calls
runCommand(command) to await the result into a variable (e.g., exitCode) and
call process.exit(exitCode) when exitCode !== 0, otherwise continue setting
shouldContinue = false or return as appropriate.

In `@src/utils.test.ts`:
- Around line 4-28: Add a unit test to cover the rejection/error branch in
runCommand by simulating a spawn failure: in the test file, mock
child_process.spawn (e.g., with jest.spyOn(require('child_process'),
'spawn').mockImplementationOnce(() => { throw new Error('spawn failed') }) or
return a mock EventEmitter that emits an 'error' event) and assert that
runCommand rejects with that error (use await
expect(runCommand(...)).rejects.toThrow('spawn failed')). This ensures the
reject(err) branch inside runCommand is exercised.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a6aebdfe-4f52-437b-8981-28f8dde78a39

📥 Commits

Reviewing files that changed from the base of the PR and between 138be3e and 6b8a6c5.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • package.json
  • src/cli.test.ts
  • src/index.ts
  • src/utils.test.ts
  • src/utils.ts
  • tsconfig.json

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants