Skip to content

Add built-in JSON querying with JMESPath support #11

Description

@amondnet

Summary

Add built-in query and filtering capabilities to the Asana CLI using JMESPath, eliminating the need for external tools like jq for common filtering operations.

Motivation

Currently, users must use external tools to filter or transform CLI output:

# Current approach - requires jq installation
asana task list --format json | jq '.tasks[] | select(.completed == false)'

This creates several problems:

  • External Dependency: Users must install and learn jq
  • Broken Integration: Piping breaks the integrated CLI experience
  • AI Agent Issues: External tools may not be available in coding agents (Claude Code, GitHub Copilot)
  • Token Inefficiency: Full output generated before filtering

Proposed Solution

Implement built-in JMESPath query support using the industry-standard jmespath npm package (used by AWS CLI).

Why JMESPath?

After researching popular CLI tools (kubectl, aws-cli, gh, docker, terraform), JMESPath offers the best balance:

  • ✅ Industry standard (AWS CLI, Azure CLI)
  • ✅ Excellent TypeScript support
  • ✅ Small bundle size (~50KB)
  • ✅ Powerful filtering and transformations
  • ✅ Good documentation and examples
  • ✅ Moderate learning curve

Architecture Decision

See ADR-003: JMESPath Query Support for full analysis including alternatives considered (JSONPath, embedded JQ, simple filters).

Usage Examples

Basic Filtering

# Filter completed tasks
asana task list -q "tasks[?completed==\`true\`]"

# Filter by assignee
asana task list -q "tasks[?assignee.name==\`Alice\`]"

# Multiple conditions
asana task list -q "tasks[?completed==\`false\` && assignee.name==\`Alice\`]"

Field Projection

# Extract specific fields
asana task list -q "tasks[].{id: gid, name: name}"

# Get single field
asana task list -q "tasks[].name"

# Nested field access  
asana task list -q "tasks[].assignee.name"

Advanced Queries

# Get first task
asana task list -q "tasks[0]"

# Count tasks
asana task list -q "length(tasks)"

# Combine filter and projection
asana task list -q "tasks[?completed==\`false\`].{id: gid, name: name}"

With Output Formats

# Query with TOON output (default)
asana task list -q "tasks[?completed==\`false\`]"

# Query with JSON output  
asana task list -q "tasks[].name" -f json

# Query with Plain output
asana task list -q "tasks[?completed==\`false\`]" -f plain

Technical Specifications

Implementation Architecture

// src/utils/formatter.ts
import jmespath from 'jmespath'

export interface FormatterOptions {
  format: OutputFormat
  colors?: boolean
  query?: string  // JMESPath expression
}

export function formatOutput(data: any, options: FormatterOptions): string {
  const { format, colors = false, query } = options

  // Apply JMESPath query if provided
  let result = data
  if (query) {
    try {
      result = jmespath.search(data, query)
    } catch (error: any) {
      throw new QueryError(error, query)
    }
  }

  // Format output (existing logic)
  return formatByType(result, format, colors)
}

CLI Integration

// src/index.ts
program
  .option('-f, --format <type>', 'Output format', 'toon')
  .option('-q, --query <expression>', 'JMESPath query to filter/transform output')
  .addHelpText('after', `
Query Examples:
  Filter tasks:     -q "tasks[?completed==\`false\`]"
  Extract fields:   -q "tasks[].{id: gid, name: name}"
  Get first item:   -q "tasks[0]"

Learn more: https://jmespath.org/tutorial.html
`)

Processing Pipeline

Data Fetching → JMESPath Query → Format Selection → Output
     ↓               ↓                  ↓             ↓
  API Call    Filter/Transform    TOON/JSON/Plain  stdout

Error Handling

Provide helpful error messages with examples:

Query Error: Unexpected token at position 15

Query: tasks[?completed==true]  (missing backticks around boolean)

Examples:
  Filter: [?completed==`true`]
  Project: [].{id: gid, name: name}
  First: [0]

Learn more: https://jmespath.org/tutorial.html

Implementation Checklist

Phase 1: Core Integration

  • Add jmespath dependency to package.json
  • Add @types/jmespath dev dependency
  • Update FormatterOptions interface with query field
  • Implement query processing in formatOutput() function
  • Add --query/-q CLI flag to main program
  • Create QueryError class with helpful error messages

Phase 2: Command Integration

  • Update task list command to pass query option
  • Update task get command to pass query option
  • Update auth whoami command to pass query option
  • Update task create command to pass query option
  • Ensure all commands support query parameter

Phase 3: Testing

  • Unit tests for query functionality
    • Basic filtering operations
    • Field projection operations
    • Complex multi-condition queries
    • Error cases and validation
  • Integration tests with actual commands
  • Error message validation tests
  • Performance tests (query overhead)

Phase 4: Documentation

  • Update README.md with query examples
  • Add query section to docs/content/en/2.features/1.task-management.md
  • Add query section to docs/content/ko/2.features/1.task-management.md
  • Update command help text with query examples
  • Create query cookbook with common patterns
  • Add troubleshooting section for common query errors

Phase 5: Polish

  • Validate query syntax before execution (optional)
  • Add query examples to error messages
  • Performance optimization if needed
  • User feedback integration

Comparison with Alternatives

Approach Power Bundle Size Learning Curve Industry Adoption TypeScript Support
JMESPath (Selected) ⭐⭐⭐⭐ Small (~50KB) Moderate High (AWS, Azure) Excellent
JSONPath ⭐⭐⭐ Medium (~100KB) Moderate Medium (kubectl) Good
Embedded JQ ⭐⭐⭐⭐⭐ Large (~500KB) Hard High (DevOps) Fair
Simple Filters ⭐⭐ None Easy Low (docker) Excellent
External Tools ⭐⭐⭐⭐⭐ None Varies High N/A

Dependencies

{
  "dependencies": {
    "jmespath": "^0.16.0"
  },
  "devDependencies": {
    "@types/jmespath": "^0.15.2"
  }
}

Package Details:

  • Weekly Downloads: 1M+
  • Bundle Size: ~50KB minified
  • License: Apache-2.0
  • Maintenance: Active (AWS-backed)

Benefits

  1. Zero External Dependencies: No need to install jq, jless, or other tools
  2. Integrated Experience: Single command for fetch + filter + format
  3. AI Agent Compatible: Works in any environment
  4. Token Efficient: Filter before formatting reduces output tokens
  5. Industry Standard: Follows AWS CLI pattern
  6. Well Documented: JMESPath has excellent tutorials
  7. TypeScript Native: Full type support
  8. Backward Compatible: Query is optional

Breaking Changes

None - this is an additive feature. Existing usage continues to work unchanged.

Related

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions