Skip to content

Comments

feat: add --plain output and fix auth stderr#5

Open
builtbyrobben wants to merge 2 commits intofeat/initial-templatefrom
fix/plain-output
Open

feat: add --plain output and fix auth stderr#5
builtbyrobben wants to merge 2 commits intofeat/initial-templatefrom
fix/plain-output

Conversation

@builtbyrobben
Copy link
Owner

@builtbyrobben builtbyrobben commented Feb 15, 2026

Summary

  • Adds WritePlain to outfmt package for TSV (tab-separated) output
  • Adds --plain branches to all list/get/status commands
  • Fixes auth status to write data to stdout instead of stderr

Test plan

  • make ci passes
  • exa-cli version --plain outputs TSV row
  • exa-cli auth status 2>/dev/null outputs data to stdout
  • exa-cli auth status --plain outputs TSV

🤖 Generated with Claude Code

Greptile Summary

Adds TSV-formatted --plain output mode to all commands and fixes auth status to write data to stdout instead of stderr, aligning with the project's output conventions for parseable formats.

  • Implements WritePlain function in outfmt package with proper field sanitization (tabs, newlines, carriage returns) and error propagation
  • Adds --plain branches to all list/get/status commands (version, answer, search, find-similar, contents, auth)
  • Fixes auth status human-readable output to write data to stdout (lines 134-147 in internal/cmd/auth.go), following the pattern: data to stdout, hints/errors to stderr
  • TSV output consistently uses uppercase headers and sanitized field values across all commands

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation is clean, consistent, and follows established patterns. The TSV sanitization and error handling issues identified in earlier review rounds have been properly addressed. All commands consistently implement the --plain output mode, and the auth status stdout fix correctly aligns with the project's output conventions.
  • No files require special attention

Important Files Changed

Filename Overview
internal/outfmt/outfmt.go Added WritePlain function with proper TSV sanitization and error handling
internal/cmd/auth.go Added --plain output to auth commands and fixed status output from stderr to stdout

Flowchart

flowchart TD
    A[Command Run] --> B{Check Output Mode}
    B -->|IsJSON| C[WriteJSON to stdout]
    B -->|IsPlain| D[WritePlain TSV to stdout]
    B -->|Default| E[Human-readable to stdout]
    
    D --> F[Sanitize Fields]
    F --> G[Replace tabs with spaces]
    F --> H[Replace newlines with spaces]
    F --> I[Replace carriage returns]
    
    G --> J[Join with tabs]
    H --> J
    I --> J
    
    J --> K[Write headers]
    K --> L[Write rows]
    L --> M[Propagate errors]
    
    E --> N{Command Type}
    N -->|auth status| O[Write to stdout instead of stderr]
    N -->|auth set-key/remove| P[Write to stderr]
    N -->|Other commands| Q[Data to stdout, hints to stderr]
Loading

Last reviewed commit: 95082a8

- Add WritePlain function to outfmt package for TSV output
- Add --plain branches to all commands alongside existing --json
- Fix auth status to write data to stdout (keep hints on stderr)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

7 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 68 to 77
func WritePlain(w io.Writer, headers []string, rows [][]string) error {
if len(headers) > 0 {
fmt.Fprintln(w, strings.Join(headers, "\t"))
}

for _, row := range rows {
fmt.Fprintln(w, strings.Join(row, "\t"))
}

return nil
Copy link

Choose a reason for hiding this comment

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

TSV output breaks on embedded tabs/newlines

WritePlain joins cell values with \t and writes rows with Fprintln, but does not sanitize the values. API responses for fields like Answer and Summary can contain tab and newline characters, which will corrupt the TSV structure — a newline inside a cell will be interpreted as a row boundary, and a tab will be interpreted as a column boundary.

Consider replacing or stripping \t and \n from cell values before joining. For example:

// WritePlain writes tab-separated values to the writer.
func WritePlain(w io.Writer, headers []string, rows [][]string) error {
	if len(headers) > 0 {
		fmt.Fprintln(w, strings.Join(headers, "\t"))
	}

	replacer := strings.NewReplacer("\t", " ", "\n", " ", "\r", "")
	for _, row := range rows {
		cleaned := make([]string, len(row))
		for i, cell := range row {
			cleaned[i] = replacer.Replace(cell)
		}
		fmt.Fprintln(w, strings.Join(cleaned, "\t"))
	}

	return nil
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/outfmt/outfmt.go
Line: 68:77

Comment:
**TSV output breaks on embedded tabs/newlines**

`WritePlain` joins cell values with `\t` and writes rows with `Fprintln`, but does not sanitize the values. API responses for fields like `Answer` and `Summary` can contain tab and newline characters, which will corrupt the TSV structure — a newline inside a cell will be interpreted as a row boundary, and a tab will be interpreted as a column boundary.

Consider replacing or stripping `\t` and `\n` from cell values before joining. For example:

```go
// WritePlain writes tab-separated values to the writer.
func WritePlain(w io.Writer, headers []string, rows [][]string) error {
	if len(headers) > 0 {
		fmt.Fprintln(w, strings.Join(headers, "\t"))
	}

	replacer := strings.NewReplacer("\t", " ", "\n", " ", "\r", "")
	for _, row := range rows {
		cleaned := make([]string, len(row))
		for i, cell := range row {
			cleaned[i] = replacer.Replace(cell)
		}
		fmt.Fprintln(w, strings.Join(cleaned, "\t"))
	}

	return nil
}
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Fixed — WritePlain now sanitizes tabs/newlines with strings.NewReplacer.

Comment on lines 68 to 77
func WritePlain(w io.Writer, headers []string, rows [][]string) error {
if len(headers) > 0 {
fmt.Fprintln(w, strings.Join(headers, "\t"))
}

for _, row := range rows {
fmt.Fprintln(w, strings.Join(row, "\t"))
}

return nil
Copy link

Choose a reason for hiding this comment

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

Write errors silently discarded

WritePlain returns error but never returns a non-nil one — fmt.Fprintln write errors are silently ignored. This is minor since stdout write failures are typically unrecoverable, but for consistency with WriteJSON (which wraps encode errors), consider checking at least one write:

Suggested change
func WritePlain(w io.Writer, headers []string, rows [][]string) error {
if len(headers) > 0 {
fmt.Fprintln(w, strings.Join(headers, "\t"))
}
for _, row := range rows {
fmt.Fprintln(w, strings.Join(row, "\t"))
}
return nil
// WritePlain writes tab-separated values to the writer.
func WritePlain(w io.Writer, headers []string, rows [][]string) error {
if len(headers) > 0 {
if _, err := fmt.Fprintln(w, strings.Join(headers, "\t")); err != nil {
return fmt.Errorf("write plain headers: %w", err)
}
}
for _, row := range rows {
if _, err := fmt.Fprintln(w, strings.Join(row, "\t")); err != nil {
return fmt.Errorf("write plain row: %w", err)
}
}
return nil
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: internal/outfmt/outfmt.go
Line: 68:77

Comment:
**Write errors silently discarded**

`WritePlain` returns `error` but never returns a non-nil one — `fmt.Fprintln` write errors are silently ignored. This is minor since stdout write failures are typically unrecoverable, but for consistency with `WriteJSON` (which wraps encode errors), consider checking at least one write:

```suggestion
// WritePlain writes tab-separated values to the writer.
func WritePlain(w io.Writer, headers []string, rows [][]string) error {
	if len(headers) > 0 {
		if _, err := fmt.Fprintln(w, strings.Join(headers, "\t")); err != nil {
			return fmt.Errorf("write plain headers: %w", err)
		}
	}

	for _, row := range rows {
		if _, err := fmt.Fprintln(w, strings.Join(row, "\t")); err != nil {
			return fmt.Errorf("write plain row: %w", err)
		}
	}

	return nil
}
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Fixed — WritePlain now propagates fmt.Fprintln errors.

Address Greptile review feedback:
- Sanitize tabs, newlines, and carriage returns in plain output fields
- Propagate fmt.Fprintln write errors for consistency with WriteJSON

Co-Authored-By: Claude Opus 4.6 <[email protected]>
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.

1 participant