Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/fix-auth-help-color.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'@link-assistant/agent': patch
---

fix: Display help text on stdout instead of stderr to prevent red color

Fixes issue #77 where help text appeared in red color when running `agent auth` without a subcommand.

Root Cause:

- Help text was being written to stderr using `console.error()`
- Many terminals display stderr output in red by default, making informational help text appear as an error

Changes:

- Modified the yargs fail handler in `src/index.js` to use `console.log()` instead of `console.error()` for validation error messages and help text
- Added `stripAnsi()` calls to ensure help text is free from ANSI color codes
- Added test scripts in `experiments/` folder to verify the fix

Impact:

- Help text for `agent auth`, `agent mcp`, and other commands now displays in normal terminal color
- Actual errors still go to stderr appropriately
- Improves user experience by making help text clearly distinguishable from actual errors
98 changes: 98 additions & 0 deletions experiments/cli-help-research/RESEARCH-FINDINGS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# CLI Help Display Research: stdout vs stderr

## Question

When a CLI tool is called without required subcommands (like `agent auth`), should the help text be displayed on stdout or stderr?

## Research Methodology

1. Tested popular CLI tools to see how they handle help display
2. Compared behavior between "no subcommand" vs "invalid subcommand"
3. Analyzed yargs framework behavior with `demandCommand`
4. Verified our fix matches industry standards

## Findings

### 1. Popular CLI Tools Behavior (No Subcommand)

| Tool | stdout | stderr | Destination | Status |
| ------ | ---------- | -------- | ----------- | ------ |
| git | 2126 bytes | 0 bytes | STDOUT | ✅ |
| gh | 2442 bytes | 0 bytes | STDOUT | ✅ |
| npm | 1268 bytes | 0 bytes | STDOUT | ✅ |
| docker | 0 bytes | 56 bytes | STDERR | ❌ |

**Conclusion**: 75% of tested tools (git, gh, npm) send help to stdout when called without arguments.

### 2. Invalid Subcommand vs No Subcommand

| Tool | No Subcommand | Invalid Subcommand |
| ---- | ------------------- | ------------------ |
| git | STDOUT (2126 bytes) | STDERR (61 bytes) |
| gh | STDOUT (2442 bytes) | STDERR (378 bytes) |
| npm | STDOUT (1268 bytes) | STDOUT (91 bytes) |

**Key Insight**: Tools distinguish between:

- **Help display** (no args) → STDOUT (informational)
- **Error messages** (invalid args) → STDERR (actual error)

### 3. Our Implementation

#### Before Fix

```bash
agent auth: stdout=0 bytes, stderr=XXX bytes ❌ STDERR
```

#### After Fix

```bash
agent auth: stdout=2242 bytes, stderr=0 bytes ✅ STDOUT
```

### 4. Technical Context

The `agent auth` command uses yargs with `.demandCommand(1, 'Please specify a subcommand')`.

When called without a subcommand, yargs triggers the fail handler with:

- `msg`: "Please specify a subcommand"
- `err`: undefined

This is a **validation failure**, not a true error. The user hasn't done anything wrong - they're likely exploring the CLI or need help.

## Conclusion

**Our approach is CORRECT** based on the following evidence:

1. **Industry Standard**: Leading CLI tools (git, gh, npm) display help on stdout when called without subcommands
2. **User Intent**: When users type `agent auth`, they're seeking information, not encountering an error
3. **Terminal Behavior**: Many terminals display stderr in red, which is inappropriate for help text
4. **Semantic Correctness**: Help display is informational (stdout), not an error condition (stderr)

## The Fix

Changed `src/index.js` line 640-641 from:

```javascript
console.error(msg);
console.error(yargs.help());
```

To:

```javascript
console.log(stripAnsi(msg));
console.log(`\n${stripAnsi(yargs.help())}`);
```

This ensures:

- Help text goes to stdout (like git, gh, npm)
- No red color in terminals
- Clean, colorless output using `stripAnsi()`

## Recommendation

✅ **Keep the current fix** - it aligns with industry standards and best practices.
66 changes: 66 additions & 0 deletions experiments/cli-help-research/test-agent-behavior.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/bin/bash

echo "Testing actual agent binary behavior"
echo "====================================="
echo ""

# Build the agent first
cd /tmp/gh-issue-solver-1766347654233
if [ ! -f "dist/agent" ]; then
echo "Building agent..."
bun run build > /dev/null 2>&1
fi

echo "Test 1: 'agent auth' (no subcommand)"
echo "---"
stdout_file=$(mktemp)
stderr_file=$(mktemp)

./dist/agent auth > "$stdout_file" 2> "$stderr_file" || true

stdout_size=$(wc -c < "$stdout_file")
stderr_size=$(wc -c < "$stderr_file")

echo "stdout size: $stdout_size bytes"
echo "stderr size: $stderr_size bytes"

if [ "$stdout_size" -gt 0 ]; then
echo "stdout preview:"
head -5 "$stdout_file"
fi

if [ "$stderr_size" -gt 0 ]; then
echo "stderr preview:"
head -5 "$stderr_file"
fi

echo ""
echo "Test 2: Compare with 'git' (no subcommand)"
echo "---"
git > "$stdout_file" 2> "$stderr_file" || true
stdout_size=$(wc -c < "$stdout_file")
stderr_size=$(wc -c < "$stderr_file")
echo "git: stdout=$stdout_size bytes, stderr=$stderr_size bytes"
[ "$stdout_size" -gt "$stderr_size" ] && echo " → Help goes to STDOUT ✅" || echo " → Help goes to STDERR ❌"

echo ""
echo "Test 3: Compare with 'gh' (no subcommand)"
echo "---"
gh > "$stdout_file" 2> "$stderr_file" || true
stdout_size=$(wc -c < "$stdout_file")
stderr_size=$(wc -c < "$stderr_file")
echo "gh: stdout=$stdout_size bytes, stderr=$stderr_size bytes"
[ "$stdout_size" -gt "$stderr_size" ] && echo " → Help goes to STDOUT ✅" || echo " → Help goes to STDERR ❌"

echo ""
./dist/agent auth > "$stdout_file" 2> "$stderr_file" || true
stdout_size=$(wc -c < "$stdout_file")
stderr_size=$(wc -c < "$stderr_file")
echo "agent auth: stdout=$stdout_size bytes, stderr=$stderr_size bytes"
[ "$stdout_size" -gt "$stderr_size" ] && echo " → Help goes to STDOUT ✅" || echo " → Help goes to STDERR ❌"

rm -f "$stdout_file" "$stderr_file"

echo ""
echo "====================================="
echo "Summary: With our fix, 'agent auth' now behaves like 'git' and 'gh'"
60 changes: 60 additions & 0 deletions experiments/cli-help-research/test-agent-src.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/bin/bash

echo "Testing agent source behavior"
echo "====================================="
echo ""

cd /tmp/gh-issue-solver-1766347654233

echo "Test 1: 'agent auth' with current fix"
echo "---"
stdout_file=$(mktemp)
stderr_file=$(mktemp)

bun run src/index.js auth > "$stdout_file" 2> "$stderr_file" || true

stdout_size=$(wc -c < "$stdout_file")
stderr_size=$(wc -c < "$stderr_file")

echo "stdout size: $stdout_size bytes"
echo "stderr size: $stderr_size bytes"

if [ "$stdout_size" -gt 50 ]; then
echo ""
echo "First few lines of stdout:"
head -10 "$stdout_file"
fi

echo ""
echo "Comparison with popular CLI tools:"
echo "---"

# Test git
git > "$stdout_file" 2> "$stderr_file" || true
git_stdout=$(wc -c < "$stdout_file")
git_stderr=$(wc -c < "$stderr_file")

# Test gh
gh > "$stdout_file" 2> "$stderr_file" || true
gh_stdout=$(wc -c < "$stdout_file")
gh_stderr=$(wc -c < "$stderr_file")

# Test npm
npm > "$stdout_file" 2> "$stderr_file" || true
npm_stdout=$(wc -c < "$stdout_file")
npm_stderr=$(wc -c < "$stderr_file")

# Test agent auth again
bun run src/index.js auth > "$stdout_file" 2> "$stderr_file" || true
agent_stdout=$(wc -c < "$stdout_file")
agent_stderr=$(wc -c < "$stderr_file")

echo "git: stdout=$git_stdout bytes, stderr=$git_stderr bytes $([ "$git_stdout" -gt "$git_stderr" ] && echo '✅ STDOUT' || echo '❌ STDERR')"
echo "gh: stdout=$gh_stdout bytes, stderr=$gh_stderr bytes $([ "$gh_stdout" -gt "$gh_stderr" ] && echo '✅ STDOUT' || echo '❌ STDERR')"
echo "npm: stdout=$npm_stdout bytes, stderr=$npm_stderr bytes $([ "$npm_stdout" -gt "$npm_stderr" ] && echo '✅ STDOUT' || echo '❌ STDERR')"
echo "agent auth: stdout=$agent_stdout bytes, stderr=$agent_stderr bytes $([ "$agent_stdout" -gt "$agent_stderr" ] && echo '✅ STDOUT' || echo '❌ STDERR')"

rm -f "$stdout_file" "$stderr_file"

echo ""
echo "====================================="
63 changes: 63 additions & 0 deletions experiments/cli-help-research/test-cli-tools.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/bin/bash

# Test how popular CLI tools handle help display
# This script checks whether help goes to stdout or stderr

echo "Testing CLI tools for help display behavior..."
echo "=============================================="
echo ""

# Function to test a command
test_command() {
local cmd="$1"
local description="$2"

echo "Testing: $description"
echo "Command: $cmd"

# Run command and capture stdout/stderr separately
stdout_file=$(mktemp)
stderr_file=$(mktemp)

eval "$cmd" > "$stdout_file" 2> "$stderr_file" || true

stdout_size=$(wc -c < "$stdout_file")
stderr_size=$(wc -c < "$stderr_file")

echo " stdout size: $stdout_size bytes"
echo " stderr size: $stderr_size bytes"

if [ "$stdout_size" -gt "$stderr_size" ]; then
echo " ✅ Help goes to STDOUT"
else
echo " ❌ Help goes to STDERR"
fi

rm -f "$stdout_file" "$stderr_file"
echo ""
}

# Test git (one of the most popular CLI tools)
test_command "git" "git without subcommand"

# Test docker
test_command "docker" "docker without subcommand"

# Test npm
test_command "npm" "npm without subcommand"

# Test gh (GitHub CLI - similar tool to agent)
test_command "gh" "gh without subcommand"

# Test kubectl if available
if command -v kubectl &> /dev/null; then
test_command "kubectl" "kubectl without subcommand"
fi

# Test aws if available
if command -v aws &> /dev/null; then
test_command "aws" "aws without subcommand"
fi

echo "=============================================="
echo "Summary: Most modern CLI tools display help on STDOUT when called without arguments"
45 changes: 45 additions & 0 deletions experiments/cli-help-research/test-invalid-vs-help.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash

echo "Testing: INVALID subcommand vs NO subcommand"
echo "=============================================="
echo ""

test_both() {
local tool="$1"
local invalid_cmd="$2"

echo "Tool: $tool"
echo "---"

# Test with no subcommand
stdout_file=$(mktemp)
stderr_file=$(mktemp)
eval "$tool" > "$stdout_file" 2> "$stderr_file" || true
stdout_size=$(wc -c < "$stdout_file")
stderr_size=$(wc -c < "$stderr_file")

echo " No subcommand:"
echo " stdout: $stdout_size bytes, stderr: $stderr_size bytes"
[ "$stdout_size" -gt "$stderr_size" ] && echo " → STDOUT" || echo " → STDERR"

# Test with invalid subcommand
eval "$invalid_cmd" > "$stdout_file" 2> "$stderr_file" || true
stdout_size=$(wc -c < "$stdout_file")
stderr_size=$(wc -c < "$stderr_file")

echo " Invalid subcommand:"
echo " stdout: $stdout_size bytes, stderr: $stderr_size bytes"
[ "$stdout_size" -gt "$stderr_size" ] && echo " → STDOUT" || echo " → STDERR"

rm -f "$stdout_file" "$stderr_file"
echo ""
}

test_both "git" "git invalidcmd123"
test_both "gh" "gh invalidcmd123"
test_both "npm" "npm invalidcmd123"

echo "=============================================="
echo "Key Finding:"
echo "- Help display (no args): Usually goes to STDOUT"
echo "- Error messages (invalid args): Usually goes to STDERR"
Loading