Research-based analysis of the agent2linear CLI tool (v0.24.1) — a TypeScript CLI for managing Linear projects and issues, designed for AI agents and automation. The codebase has 78+ command files, 20+ library modules, and ~18K lines of source code. Two recommendation sets follow: product (user-facing) and codebase (internal quality).
Currently there's no quick way to verify your API key and identity. a2l whoami should print the authenticated user's name, email, organization, and masked API key. The testConnection() and getCurrentUser() functions in linear-client.ts already exist — this is just a thin wrapper.
For AI agent use cases, a --dry-run flag on create and update commands would let agents preview what would be sent to the API without actually mutating state. Print the resolved payload (team name, labels, etc.) and exit. This is especially valuable for automation pipelines where mistakes are costly.
Cycles (sprints) are a core Linear concept. The alias system already supports cycles ('cycle' entity type), but there are no cycle list, cycle view, or cycle set commands. Users have to know cycle IDs to use --cycle on issue create/update with no way to discover them via the CLI.
Issues support comments in Linear but the CLI has no way to add them. a2l issue comment <identifier> --body "text" or --body-file would enable AI agents to post status updates on issues they're working on — a very common automation workflow.
The current --search flag on issue list is limited. A dedicated a2l issue search "query" command (or enhancing the existing flag) with support for Linear's full-text search, filtering by date ranges, and sorting by relevance would be more discoverable and powerful.
Beyond M26's stream separation work, add support for reading input from stdin. For example: echo "Bug title" | a2l issue create --team backend or a2l issue list --format json | a2l issue update --from-stdin. This enables Unix pipeline workflows that AI agents and scripts depend on.
Let users choose which fields appear in table/TSV output. Currently fields are hardcoded per command. a2l project list --columns "name,status,lead,url" would reduce noise for automation and let users customize their view.
a2l issue update --bulk "ENG-1,ENG-2,ENG-3" --state done or a2l issue create --from-file issues.json for batch creation. AI agents frequently need to update multiple issues at once and currently must make N sequential calls.
A diagnostic command that checks: API connectivity, configuration validity (default team/initiative exist), cache health, alias counts per entity type, and version info. Useful for debugging setup issues — especially in CI/CD or when an AI agent's environment is misconfigured. Separate from whoami (P1) which handles identity only.
Delete commands are intentionally omitted for data safety. Document this decision prominently in README.md. Users should use issue update --trash for issues and the Linear UI for project deletion. The test cleanup scripts are an acceptable tradeoff.
Commander.js supports addHelpText('after', ...). Each command should show 2-3 concrete usage examples in --help output. Currently help only shows flags — users (especially AI agents) need to see example invocations to understand usage patterns.
For automation and CI environments, stripping ANSI color codes from output makes parsing easier. The output currently uses emojis and potentially ANSI codes that can interfere with machine parsing.
src/cli.ts is a monolith that registers all 78+ commands. Each entity should register its own commands in a register.ts file (e.g., src/commands/project/register.ts) and cli.ts should just import and mount them. This improves maintainability and makes it easier to add new entities.
Files affected: src/cli.ts → split into ~18 registration files + slim orchestrator
This single file handles all Linear API calls for every entity type. Split into:
src/lib/api/projects.ts— project CRUD + dependenciessrc/lib/api/issues.ts— issue CRUD + commentssrc/lib/api/teams.ts— team queriessrc/lib/api/labels.ts— issue + project labelssrc/lib/api/client.ts— authentication, connection test, shared utilities- etc.
Re-export from a barrel src/lib/api/index.ts for backward compatibility.
Files affected: src/lib/linear-client.ts → ~8-10 focused modules
There are two overlapping cache implementations: entity-cache.ts (session + persistent cache) and status-cache.ts (file-based cache with its own TTL logic). They duplicate cache loading, TTL checking, and file I/O patterns. Unify into a single CacheManager class with entity-specific methods, reducing ~400 lines of duplication.
Files affected: src/lib/entity-cache.ts, src/lib/status-cache.ts → unified src/lib/cache.ts
Many commands repeat the same pattern: resolve alias → validate entity → fetch data → format output → handle errors. Extract this into a command runner utility:
// Conceptual
await runCommand({
resolve: { team: options.team, initiative: options.initiative },
validate: true,
execute: async (resolved) => { /* command logic */ },
format: options.format,
});This would reduce per-command boilerplate by 30-50 lines.
Files affected: New src/lib/command-runner.ts, then incremental adoption across commands
Only date-parser.ts has unit tests (104 tests, 99.1% coverage). Critical modules with zero unit test coverage:
aliases.ts(1,089 lines) — alias resolution, fuzzy matchingvalidators.ts— input validation logicparsers.ts— comma/pipe parsing, dependency parsingconfig.ts— config priority chainerror-handler.ts— HTTP error handling
These are pure functions that can be tested without API calls. Adding vitest tests would catch regressions cheaply.
Files to create: src/lib/aliases.test.ts, src/lib/validators.test.ts, src/lib/parsers.test.ts, etc.
Every API function calls getLinearClient() which creates a new SDKClient instance each time. While lightweight, a singleton pattern would be cleaner and enable connection pooling or request queuing in the future.
Files affected: src/lib/linear-client.ts
Commands and library code use raw console.log throughout. A minimal logger (even just a thin wrapper) with levels (debug, info, warn, error) would enable:
--verbosemode for debugging--quietmode (M26 plans this but doesn't address the underlying architecture)- Structured output for machine consumption
- Consistent formatting
Files affected: New src/lib/logger.ts, then gradual migration from console.log/console.error
package.json says 0.24.1 but cli.ts:80 has .version('0.24.0'). These should be kept in sync — ideally by reading from package.json at build time or using a single source of truth.
Files affected: src/cli.ts:80
No .github/ directory exists. A basic CI pipeline should:
- Run
npm run typecheckandnpm run linton every PR - Run
vitestunit tests on every PR - Run integration tests on a schedule (since they need LINEAR_API_KEY)
- Automate npm publishing on tagged releases
Files to create: .github/workflows/ci.yml, .github/workflows/release.yml
Some commands use try/catch with process.exit(1), others use handleLinearError(), and some throw unhandled. Standardize on a single pattern — ideally the command runner from C4 would handle this uniformly.
Interactive UI components are split between src/ui/components/ (5 files) and src/components/ (WalkthroughScreen). Consolidate into one location.
Files affected: src/components/ → move to src/ui/components/
Imports in cli.ts and command files have no consistent ordering. Adding import sorting rules would improve readability and reduce merge conflicts.
Files affected: .eslintrc.json
These are research recommendations — no code changes to verify. The recommendations can be validated by:
- Checking the referenced files and line numbers exist
- Confirming the patterns described match the actual code
- Reviewing against the existing MILESTONES.md backlog for overlap (M26 overlaps with C7 and P12)