Skip to content

feat: add --fields and --max-lines global CLI options (DIS-145)#38

Merged
ckorhonen merged 3 commits intomainfrom
devin/DIS-145-1772640544
Mar 4, 2026
Merged

feat: add --fields and --max-lines global CLI options (DIS-145)#38
ckorhonen merged 3 commits intomainfrom
devin/DIS-145-1772640544

Conversation

@ckorhonen
Copy link
Collaborator

@ckorhonen ckorhonen commented Mar 4, 2026

feat: add --fields and --max-lines global CLI options (DIS-145)

Summary

Adds two global CLI options to control output size, motivated by agents piping CLI output through python3 -m json.tool | head which caused a 30-minute false-401 debugging incident.

  • --fields <fields> — Comma-separated list of fields to include in output. Applies pickFields at the top level of the response object (or each item if the response is an array). For paginated wrapper responses like {nfts: [...], next: "cursor"}, users specify the wrapper keys they want (e.g. --fields nfts,next).
  • --max-lines <lines> — Truncates formatted output after N lines, appending ... (N more lines). Must be >= 1.

Uses Commander's preAction hook with module-level state in output.ts so zero command files were modified — all 9 existing commands get both options for free.

Updates since last revision

  • Removed wrapper-detection heuristic entirely (per code review): filterFields now always applies pickFields at the top level. No more fragile heuristic that misidentified objects like Collection (with array properties) as paginated wrappers.
  • Removed setOutputOptions/OutputOptions from SDK barrel export (per code review): These are CLI-internal concerns; exporting mutable module-level state to SDK consumers created hidden coupling and state leakage risk.
  • Added --max-lines validation (per code review): Values < 1 are rejected with process.exit(2). Removed the maxLines=0 edge case.
  • Fixed maxLines nullish check (flagged by Devin Review): Changed if (_outputOptions.maxLines) to if (_outputOptions.maxLines != null).
  • 163 tests passing, lint/typecheck clean.

Review & Testing Checklist for Human

  • --fields UX for paginated responses: With the heuristic removed, --fields identifier,name on a {nfts: [...], next: "..."} response returns {} (no matching top-level keys). Users must use --fields nfts,next to select wrapper keys. Verify this is the intended UX.
  • Manual E2E test: Unit tests only cover formatOutput directly, not the Commander argv parsing pipeline. Verify with real commands:
    npx . collections get <slug> --fields name,collection --api-key $OPENSEA_API_KEY
    npx . nfts list-by-collection <slug> --fields identifier,name --max-lines 10 --api-key $OPENSEA_API_KEY
    npx . nfts list-by-collection <slug> --max-lines 0 --api-key $OPENSEA_API_KEY  # should error
  • Truncated output is not valid JSON--max-lines appends a ... (N more lines) suffix. Worth considering whether this should be documented in --help.

Notes


Open with Devin

Add --fields <fields> to select specific JSON fields in output.
Add --max-lines <lines> to truncate output with line count indicator.

Uses Commander preAction hook and module-level output options to avoid
changing any command files. Field filtering handles plain objects,
arrays, and wrapped API responses (e.g. {nfts: [...], next: ...}).
Truncation appends '... (N more lines)' indicator.

Co-Authored-By: Chris K <ckorhonen@gmail.com>
@devin-ai-integration
Copy link
Contributor

Original prompt from Chris K
Please work on ticket "[opensea-cli] Add output truncation and field selection" ([DIS-145](https://linear.app/opensea/issue/DIS-145/opensea-cli-add-output-truncation-and-field-selection))

PLAYBOOK_md:
# Ticket to PR

## Overview

This playbook guides the process of taking a Linear ticket from initial scoping through implementation to final review. The workflow ensures proper context gathering, quality implementation, and thorough code review before delivery. The agent uses the Linear MCP to manage ticket status and communication throughout.

## What's Needed From User

- Linear ticket URL or ticket ID (e.g., `ENG-123` or `https://linear.app/team/issue/ENG-123/...`)
- Repository access for the codebase where changes will be made

<phase name="Disambiguation" id="1">
## Disambiguation Phase

Think about the full user intent. Tickets are sometimes sparse. Make sure you disambiguate to the full scope that the user intended.

1. Fetch the ticket details using the Linear MCP `get_issue` tool with the ticket ID
2. Before diving into code: use the devin MCP to get a high-level understanding of the relevant systems and architecture. Use `ask_question` to learn about the relevant systems – send queries for multiple repos that could be relevant to get the full picture. Use `read_wiki_contents` to then get a better understanding how different parts of the codebase connect to each other.
3. Gather additional context to understand what the ticket means and refers to:
   - Look at past tickets in the same project and from the same author to understand patterns and terminology
   - Search for related commits and PRs (by author and content) that may provide context on the affected systems
   - Check any linked documents, designs, or parent tickets
   - Investigate the actual code
4. Identify any ambiguity in what the ticket refers to or asks for, including jargon or project-specific terms and use all means necessary to answer this yourself
5. Consult your smart friend: pass in the ... (8110 chars truncated...)

@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@devin-ai-integration devin-ai-integration bot marked this pull request as ready for review March 4, 2026 16:12
devin-ai-integration[bot]

This comment was marked as resolved.

- filterFields now checks if any requested field matches a top-level key.
  If yes, picks from top level (handles Collection with array props like
  contracts, editors). If no match, treats as wrapper and filters array
  items (handles {nfts: [...], next: ...} responses).
- Changed maxLines guard from truthiness to != null so --max-lines 0 works.
- Added tests for objects with array properties and maxLines 0.

Co-Authored-By: Chris K <ckorhonen@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Collaborator Author

@ckorhonen ckorhonen left a comment

Choose a reason for hiding this comment

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

Code Review

Overall: Approve with suggestions — All 162 tests pass. E2E confirmed both --fields and --max-lines work correctly.

Issues Found

  1. Wrapper-detection heuristic is fragilefilterFields checks for any array-valued key to decide if an object is a "wrapper." This misidentifies real API objects like Collection (has contracts, editors, fees arrays) and NFT (has traits, owners arrays). Running --fields name,collection on a Collection object would filter inside those sub-arrays instead of the top-level object. Fix: remove wrapper detection entirely — always apply pickFields at the top level.

  2. setOutputOptions/OutputOptions shouldn't be in the SDK barrel export — These are CLI-internal concerns. Module-level mutable state exported to SDK consumers creates hidden coupling, state leakage, and concurrency hazards.

  3. Missing --max-lines validation--max-lines 0 truncates everything, --max-lines -1 silently drops the last line via slice(0, -1). Add validation that the value must be >= 1.

Minor Notes

  1. Truncated JSON output is no longer valid JSON — worth documenting in --help
  2. --fields only works on top-level keys — worth documenting this limitation
  3. pickFields output key order follows user's --fields order, not original order — arguably a feature but undocumented

I've pushed a commit addressing issues 1-3.

- Remove wrapper-detection heuristic from filterFields; always apply
  pickFields at top level (issue #1 from review)
- Remove setOutputOptions/OutputOptions from SDK barrel export to avoid
  state leakage for SDK consumers (issue #2 from review)
- Add --max-lines validation requiring >= 1 (issue #3 from review)
- Update tests to match simplified filterFields behavior

Co-Authored-By: Chris K <ckorhonen@gmail.com>
@devin-ai-integration
Copy link
Contributor

Addressed all 3 issues from the code review in c8df287:

  1. Removed wrapper-detection heuristicfilterFields now always applies pickFields at the top level.
  2. Removed setOutputOptions/OutputOptions from SDK barrel export — reverted to only export formatOutput and OutputFormat.
  3. Added --max-lines validation — rejects values < 1 with process.exit(2).

163 tests passing, lint/typecheck clean.

@ckorhonen ckorhonen merged commit 8f2fb5a into main Mar 4, 2026
5 checks passed
@ckorhonen ckorhonen deleted the devin/DIS-145-1772640544 branch March 4, 2026 20:36
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.

2 participants