Commit Graph

106 Commits

Author SHA1 Message Date
Peter Schilling b3b9960436 fix: order issue view comments chronologically
Linear's comments connection returns newest-first and the GraphQL schema
doesn't expose a direction argument, so sort root comments ascending
client-side to match the order shown in Linear's UI.
2026-04-21 15:53:19 -07:00
Peter Schilling 26c418f4b8 feat: split issue list into mine and query commands
Introduce 'issue mine' for personal work queue and 'issue query' for
structured retrieval with optional full-text search via --search.
Keep 'issue list' as a separate backwards-compatible command.
Remove standalone 'issue search' registration in favor of query --search.

query supports multi-team filtering, --all-teams, --json output,
--search-comments, and verifies GraphQL variables in tests.
2026-04-03 18:22:51 +00:00
Peter Schilling aba660998e refactor: preserve GraphQL payload shape in project update list JSON 2026-04-02 15:15:00 -07:00
Peter Schilling 8d11f4b6fa refactor: preserve GraphQL connection shape in project list JSON 2026-04-02 15:15:00 -07:00
Peter Schilling ab1f64fb7a refactor: preserve GraphQL mutation shape in project create JSON 2026-04-02 15:15:00 -07:00
Peter Schilling 4691061aad refactor: preserve GraphQL connection shape in label list JSON 2026-04-02 15:15:00 -07:00
Peter Schilling d73884d9aa test: cover raw GraphQL JSON output in agent session view 2026-04-02 15:15:00 -07:00
Peter Schilling 8f1e406fee refactor: preserve GraphQL connection shape in agent session list JSON 2026-04-02 15:15:00 -07:00
Peter Schilling 2d2208ffd5 refactor: preserve GraphQL connection shape in issue comment list JSON 2026-04-02 15:15:00 -07:00
Peter Schilling 03a12b06bf refactor: preserve GraphQL payload shape in initiative update list JSON 2026-04-02 15:15:00 -07:00
Peter Schilling ef925984dc refactor: preserve GraphQL payload shape in initiative view JSON 2026-04-02 15:15:00 -07:00
Peter Schilling 11487b6097 refactor: preserve GraphQL connection shape in initiative list JSON 2026-04-02 15:15:00 -07:00
Peter Schilling f460007a5f refactor: preserve GraphQL connection shape in document list JSON 2026-04-02 15:15:00 -07:00
Peter Schilling f86356d435 fix: show issue documents in issue view
Fixes #183
2026-04-02 11:49:05 -07:00
Peter Schilling c4feb0866b fix: accept alphanumeric Linear issue team keys 2026-04-02 09:30:13 -07:00
Peter Schilling fb329435f3 fix: remove -w short flag from --workspace to resolve collision with --web
Also skip keyring integration test when D-Bus secrets service is unavailable
2026-04-02 09:07:13 -07:00
Peter Schilling b4aea366f6 feat: add issue search command 2026-04-02 04:58:17 +00:00
schpetbot e23e9ff856 feat: show resolved thread metadata in issue view (breaking change) (#199)
## Summary
- include resolved-thread metadata and derived `threadId` in `linear
issue view --json`
- hide resolved threads by default in human-readable `issue view`, with
`--show-resolved-threads` to reveal them
- show root thread ids in rendered output and use OSC-8 hyperlinks for
them when terminal hyperlinks are enabled

## Testing
- deno check src/main.ts
- deno lint
- deno task test
- manual QA against CLI-team issues for default view,
`--show-resolved-threads`, JSON output, no-comments JSON, open-only
threads, OSC-8 behavior, and reply creation from printed thread ids

Co-authored-by: Peter Schilling <code@schpet.com>
2026-04-01 18:44:30 +00:00
Peter Schilling 1e51d2adbd test: disable colors in issue list assertion 2026-04-01 14:59:12 +00:00
Peter Schilling 713a6c2acd style: format issue list test for CI 2026-04-01 14:59:12 +00:00
Peter Schilling 4113c4d4fc test: make issue list output deterministic 2026-04-01 14:59:12 +00:00
Evan Jacobson 6fdd1e0da2 upgrade dependencies (#194) 2026-03-31 20:36:06 -07:00
Paymahn Moghadasian 4ac13293c0 feat: add agent-session list and view commands (#192) 2026-03-31 05:59:48 +00:00
Jeffrey Holm 08fcf6ce96 feat: add --created-after and --updated-after filters to issue list (#191) 2026-03-31 05:47:31 +00:00
Jeffrey Holm f9519f7807 feat: show assignee and priority in issue view (#190)
## Summary
- Add **assignee** and **priority** fields to the `issue view` command's
metadata line
- Priority uses the existing `getPriorityDisplay` function (same visual
format as `issue list`)
- Assignee shows `@displayName` or `Unassigned` when null
- Both GraphQL queries (`GetIssueDetailsWithComments` and
`GetIssueDetails`) updated with `assignee { name displayName }` and
`priority`

## Motivation
These are critical fields for triaging issues. Previously, `issue view`
showed project, milestone, and cycle but not who owns the issue or how
urgent it is.

## Example output
```
# ENG-123: Fix authentication bug

**Priority:** ▄▆█ | **Assignee:** @Jane Smith | **Project:** Platform Infrastructure Q1
```

## Test plan
- [x] Updated all 10 issue-view snapshot tests with mock data covering:
unassigned/assigned, all priority levels (0-4)
- [x] `deno check src/main.ts` passes
- [x] `deno lint` passes
- [x] All issue-view tests pass (`deno test --allow-all --filter "Issue
View"`)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 05:28:52 +00:00
Mihai Chiorean e7851e3cb5 feat: add --label filter to issue list (#180)
## Summary

Adds `-l, --label <label>` flag to `issue list` for filtering issues by
label name. The flag is repeatable — when multiple labels are specified,
only issues matching **all** labels are returned.

- Uses `eqIgnoreCase` for case-insensitive label name matching
- Single label: `filter.labels = { some: { name: { eqIgnoreCase: "Bug" }
} }`
- Multiple labels: `filter.labels = { and: [{ some: { name: {
eqIgnoreCase: "Bug" } } }, ...] }`

### Usage

```bash
# Single label
linear issue list --label Bug

# Multiple labels (AND logic)
linear issue list --label Bug --label "High Priority"

# Combined with other filters
linear issue list --label Bug --state started --cycle active
```

## Test plan

- [x] Added snapshot test for `issue list --label Bug` with mock server
- [x] Updated help text snapshot with new `--label` flag
- [x] `deno fmt` clean
- [x] `deno lint` clean
- [x] All tests pass, existing tests unaffected

---------

Co-authored-by: Peter Schilling <code@schpet.com>
Co-authored-by: Mihai Chiorean <mihai-chiorean@users.noreply.github.com>
2026-03-31 05:04:45 +00:00
Al Johri 16ebdfc69d feat: add --project-label flag to issue list command
Filter issues by project label name, showing issues from all projects
that have a given label. Uses Linear's native GraphQL project label
filtering rather than a two-step query.
2026-03-31 04:27:57 +00:00
Luc Leray 1b510720c5 style: fix deno fmt formatting in test 2026-03-25 08:46:56 -07:00
Luc Leray e38fc08ef2 feat: add issue link command to attach URLs to issues 2026-03-25 08:46:56 -07:00
Hyunsu Lim a7c4f75b5e feat: display issue state in issue view command
Show the issue state (e.g., Todo, In Progress, Done) in the metadata
line of `linear issue view`, positioned before Project/Milestone/Cycle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 08:30:06 -07:00
Ben Drucker b8a7e54854 feat: store API keys in the system keyring (#136)
Move API key storage from plaintext TOML to OS-native keyrings (macOS
Keychain, Linux `libsecret`, Windows Credential Manager). The
credentials file retains only workspace metadata. Keys are loaded into
an in-memory cache at startup so all downstream reads remain synchronous
— no changes needed to any command files.

## Changes

### Keyring (`src/keyring/`)

- Platform-detecting wrapper with `getPassword`, `setPassword`,
`deletePassword` exports
- macOS: `/usr/bin/security` (exit 44 = not found)
- Linux: `secret-tool` via stdin for writes (exit 1 = not found)
- Windows: `Deno.dlopen("advapi32.dll")` FFI calling
`CredReadW`/`CredWriteW`/`CredDeleteW` directly
- `_setBackend()` test seam for injecting an in-memory `Map` backend

### Windows Credential Manager via FFI

The Windows backend calls `advapi32.dll` directly via Deno's FFI
(`Deno.dlopen`) rather than shelling out to PowerShell. This matches the
standard approach taken by every comparable credential tool:

-
[`danieljoos/wincred`](https://github.com/danieljoos/wincred/blob/623325312d3224d48d131159187b93e906216563/sys.go)
— Go library calling `advapi32.dll` via `windows.NewLazySystemDLL`, used
by:
-
[`docker-credential-helpers`](https://github.com/docker/docker-credential-helpers/blob/2b4e08bca3dbdb8e6c6e28790042742d0c0fc48f/wincred/wincred.go)
- [`gh`
CLI](https://github.com/cli/cli/blob/2c54a0d36a2f3c9c1f1b869a64120837c3a1e6f5/internal/keyring/keyring.go)
(via
[`zalando/go-keyring`](https://github.com/zalando/go-keyring/blob/5c6f7e0ba54d20daa8ea4e03f7ce0a27c075bfb6/keyring_windows.go))
-
[`aws-vault`](https://github.com/99designs/aws-vault/blob/70522e8f0b8f9c5b4e2e4e1e1e1cc4e3e5c3f04c/go.mod)
(via `99designs/keyring`)
-
[`node-keytar`](https://github.com/atom/node-keytar/blob/deae59a488789f2cd4a8dba6c7e58665795804fe/src/keytar_win.cc)
— C++ N-API addon, `#include <wincred.h>`
-
[`jaraco/keyring`](https://github.com/jaraco/keyring/blob/38c040133559682902f25fe96496756ee6849820/keyring/backends/Windows.py)
— Python, `win32cred` (pywin32-ctypes wrapping advapi32 via ctypes)

The implementation packs the 80-byte `CREDENTIALW` struct manually via
`DataView`, encodes strings as UTF-16LE for the `W`-suffix APIs, and
uses `GetLastError` from `kernel32.dll` to distinguish "not found"
(`ERROR_NOT_FOUND` = 1168) from real failures. DLLs are lazy-loaded so
the module import doesn't fail on macOS/Linux.

### Credentials (`src/credentials.ts`)

- `Credentials` interface changed from index signature to `{ default?:
string; workspaces: string[] }`
- `apiKeyCache` `Map` populated at startup, keeping
`getCredentialApiKey()` sync
- `addCredential`/`removeCredential` write to keyring first, only mutate
local state on success
- `parseInlineCredentials` / `parseKeyringCredentials` /
`populateKeyringCache` extracted from `loadCredentials`
- Parallel keyring lookups via `Promise.all`
- Malformed TOML parse errors caught with recovery guidance
- Warnings for: missing keyring entries, dangling default workspace,
inline format detected

### Backward Compatibility

- Inline-format TOML files (keys stored as `workspace = "lin_api_..."`)
are detected by `hasInlineKeys` and served from the file directly
- `addCredential` on an inline-format installation rewrites the file to
keyring format

### Auth List (`src/commands/auth/auth-list.ts`)

- Replaces removed `getAllCredentials()` with `getApiKeyForWorkspace()`
- Distinguishes auth errors (401/403) from network/other failures
instead of labeling everything "invalid credentials"

### CI

- Added `keyring-integration` job on `macos-latest` and `windows-latest`
for real credential round-trip testing

## Testing

- Subprocess isolation via `deno eval` for credential tests (required by
top-level `await loadCredentials()`)
- Mock keyring backend injected via `_setBackend` — covers happy paths,
error propagation, and cache consistency
- Integration test (`test/keyring.integration.test.ts`) exercises the
real macOS Keychain and Windows Credential Manager lifecycle
- Edge cases covered: keyring write/delete failures leave state
unchanged, null keyring returns warn but don't crash, dangling default
dropped on load, inline→keyring format transition on `addCredential`

## References

Closes #130

---------

Co-authored-by: Peter Schilling <code@schpet.com>
2026-03-10 22:03:52 -07:00
Peter Schilling ab21736a1c docs: add npm install instructions to README 2026-03-07 17:59:28 +00:00
Peter Schilling 4007ac76cb chore: Release linear-cli version 1.11.1 2026-03-06 22:13:11 +00:00
Jeffrey Holm 6f4d6e7530 feat: add milestone filter to issue list command
Adds --milestone flag to `linear issue list` to filter issues by project
milestone name. Requires --project since milestones belong to projects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-05 07:26:51 -08:00
Jay Reardon 69e59eed4d feat(project): add update, delete, and --json support (#127) (#148)
- Add 'project update' subcommand with --name, --description, --status,
--lead, --start-date, --target-date, --team options
- Add 'project delete' subcommand with --force to skip confirmation
- Add --json to 'project list' for machine-readable output with UUIDs
- Add --json to 'project create' to return project id/slugId/name/url

Enables automation use cases: create, rename, delete, list project IDs
programmatically without raw GraphQL API calls.
2026-03-04 20:44:33 -08:00
David Roizenman ab3f83b03c fix(issue-view): don't send auth headers to non-linear domains 2026-03-04 20:34:25 -08:00
Dan Wager ca3b04bd19 feat: add cycle list/view commands and issue list --cycle filter (#162)
### Summary
- Add `linear cycle list` and `linear cycle view` commands for browsing
team cycles
- Add `--cycle` filter to `linear issue list` for filtering issues by
cycle
- Regenerate skill docs to include new commands

### Problem
PR #150 added cycle support to issue create/update/view, but there was
no way to browse cycles themselves or filter issue lists by cycle. Users
had to know cycle names/numbers without being able to look them up from
the CLI. See #64.

### Fix
Adds a `cycle` command group (aliased `cy`) with two subcommands,
following the same patterns as the existing `milestone` commands:

**`cycle list`** shows all cycles for a team with number, name, dates,
and status. Active cycle is highlighted in green, upcoming cycles use
default color, and completed/past cycles are muted.

```
$ linear cycle list --team XXX
#  NAME       START      END        STATUS
3  Sprint 3   2026-03-10 2026-03-24 Upcoming
2  Sprint 2   2026-02-24 2026-03-10 Active
1  Sprint 1   2026-02-10 2026-02-24 Completed
```

**`cycle view`** shows full cycle details including a progress
indicator, description, issue breakdown by state, and first 10 issues.
Accepts cycle name, number, or "active".

```
$ linear cycle view active --team XXX
# Sprint 2

**Number:** 2
**Start:** 2026-02-24
**End:** 2026-03-10
**Status:** Active
**Team:** MyTeam (XXX)

## Issues

**Progress:** 17/50 (34%)
**Total Issues:** 50
**Completed:** 17
**In Progress:** 12
**To Do:** 21

**Issues:**
- XXX-412: Fix auth token refresh (In Progress)
- XXX-398: Add dark mode toggle (Todo)
...
```

**`issue list --cycle`** filters the issue list to a specific cycle,
reusing the existing `getCycleIdByNameOrNumber()` utility and the
GraphQL `IssueFilter.cycle` field.

```
$ linear issue list --cycle active --team XXX --sort priority
◌   ID      TITLE                  LABELS          E STATE UPDATED
--- XXX-101 Some task              Backend, Feature - Todo  1 day ago
--- XXX-102 Another task           Feature          - Todo  1 day ago
```

### Why
Cycles are a core part of the Linear workflow and the CLI should support
browsing them. `cycle list` lets you see what's available, `cycle view`
gives you sprint progress at a glance, and `--cycle` on issue list lets
you scope your view to a specific sprint.

### Test Plan
- Tested all three commands against a real Linear workspace with 11
cycles and 50+ issues
- `cycle view` tested with active, by number, and by exact name lookup
- All 220 unit/snapshot tests pass
2026-03-04 13:59:59 -08:00
Jeffrey Holm 47011e77d1 feat: add comment delete command
Adds `linear issue comment delete <commentId>` to delete comments via
the commentDelete GraphQL mutation. Includes snapshot test and updated
skill documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-04 13:45:00 -08:00
Peter Schilling 36db5b9ebb test: add issue update -p priority test and regenerate skill docs
- Add snapshot test for 'issue update -p <priority>' to verify -p maps
  to --priority (not --parent), mirroring the fix tested in issue create
- Regenerate skill docs to reflect corrected flag assignments:
  --parent (no short flag) and -p, --priority
2026-03-03 11:54:28 -08:00
Michael Pearson 5eebaeaa33 fix: fall back to slugId match in getProjectIdByName
When the --project flag value doesn't match any project by exact name
(e.g. because the name contains special characters like quotes or
parentheses), try matching by slugId before returning undefined.

This lets users pass either the full project name or the 12-char hex
slug ID (visible in `project list` output and Linear URLs) as the
--project value.

Fixes #157

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019ca8f4-4000-732f-91e2-5ad51d27008c
2026-03-01 06:33:52 -08:00
Peter Schilling 12be1d17eb fix: correct success message order for 'blocked-by' relation
When using 'relation add X blocked-by Y', the API is called with swapped
IDs (Y blocks X), but the success message was showing the API's returned
identifiers (in the swapped order) instead of the user-specified order.

Fixes: the message now uses the original issueIdentifier and
relatedIssueIdentifier variables, so 'relation add ENG-123 blocked-by ENG-456'
correctly shows '✓ Created relation: ENG-123 blocked-by ENG-456'.

Closes #152
2026-02-26 22:23:45 -08:00
Jeffrey Holm e24ef1de21 feat: add cycle support to issue commands
Add --cycle flag to issue create and update commands, accepting cycle
name, number, or 'active' keyword. Display cycle in issue view output
alongside project and milestone metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 14:09:15 -08:00
Jeffrey Holm b65ba20565 feat: add milestone support to issue commands (#149)
## Summary

- Adds `--milestone` flag to `issue create` and `issue update` commands
- Resolves milestones by name (case-insensitive) within the specified
project
- For `issue update`, automatically falls back to the issue's existing
project if `--project` is not explicitly provided
- Validates that a project context exists before attempting milestone
lookup, with helpful error messages
- Shows **project** and **milestone** in `issue view` output (displayed
below the title when present)

## Example usage

```sh
# Create an issue with a milestone
linear issue create --title "Implement feature" --team ENG --project "My Project" --milestone "Phase 1"

# Update an issue's milestone (project inferred from issue)
linear issue update ENG-123 --milestone "Phase 2"

# View an issue — now shows project and milestone
linear issue view ENG-123
# => Project: My Project | Milestone: Phase 1
```

## Test plan

- [x] Happy path tests for create and update with milestone
- [x] Snapshot test for `issue view` with project and milestone
displayed
- [x] All 205 tests pass
- [x] `deno check` and `deno lint` pass
- [x] GraphQL codegen regenerated
- [x] Verified end-to-end against Linear API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-24 08:57:39 -08:00
Peter Schilling fc8530ad26 fix: improve git command error handling with CliError
- Use CliError instead of generic Error for all git command failures
- Add proper error checking to getCurrentBranch() and getRepoDir()
- Improve error handling in startVcsWork() git operations
- Add comprehensive tests for git error handling
- Ensure consistent error reporting across git utilities

Fixes #62
2026-02-17 20:49:58 -08:00
Peter Schilling f8efda78d8 Add best practices for markdown content to Linear CLI skill
- Regenerate skill documentation with file-based flag guidance
- Update test snapshots to reflect new --description-file and --body-file flags
- Add section explaining why file-based flags prevent markdown formatting issues

Addresses: https://github.com/schpet/linear-cli/issues/133
2026-02-12 15:50:21 -08:00
Ben Drucker 3582fc5700 feat: add api subcommand for raw GraphQL access (#121)
Adds a `linear api` subcommand for making raw GraphQL requests,
mirroring [`gh api`](https://cli.github.com/manual/gh_api) conventions.

## Changes

- Accepts a GraphQL query as a positional arg, from stdin with `-`, or
via auto-detected piped input
- `--variable key=value` for typed variable coercion (booleans, numbers,
null, `@file` for file reads, `@-` for stdin)
- `--variables-json '{"key": "value"}'` for passing all variables as a
JSON object (merged with `--variable`, which takes precedence)
- `--paginate` walks `pageInfo.endCursor` automatically and outputs
concatenated `nodes` array
- `--silent` suppresses response output while exit code still reflects
errors
- Pretty-prints JSON when stdout is a TTY, raw JSON otherwise for piping
to `jq`
- Exits with code 1 on HTTP errors (status >= 400) and GraphQL-level
errors
- Uses raw `fetch` so users see the exact server response including both
`data` and `errors` fields

## Testing

- Snapshot tests using `MockLinearServer` cover query resolution,
variable handling (type coercion, `@file`, `--variables-json`,
precedence), output modes, pagination (multi-page, single-page,
non-connection), auth errors, and `--silent` behavior for both
successful and HTTP error responses
- Manual testing against live Linear API: cycles, workflow states,
notifications (with `--paginate`), non-existent issue lookup, variable
type mismatch errors, stdin piping

## Related

- Closes #123
2026-02-09 09:36:55 -08:00
Ben Drucker 4dd3878f2a feat: add --sort-order flag to milestone update command
Expose the sortOrder field (already in the GraphQL schema) as a CLI
option for updating milestone ordering within a project.
2026-02-02 20:58:14 -08:00
Peter Schilling 430ac0402d feat: add user-friendly error handling with LINEAR_DEBUG support
Fixes #116

Error messages are now clean and user-friendly by default. Stack traces
are only shown when LINEAR_DEBUG=1 is set, similar to RUST_BACKTRACE.

Changes:
- Add src/utils/errors.ts with error handling infrastructure:
  - CliError base class with user-facing messages and suggestions
  - NotFoundError for entity lookups
  - ValidationError for invalid input
  - AuthError for authentication issues
  - handleError() for consistent error display
  - extractGraphQLMessage() to parse Linear API errors
- Update all commands to use handleError() for consistent error display
- Error output goes to stderr with ✗ prefix
- GraphQL errors show userPresentableMessage when available

Example before:
  Error: Entity not found: Issue: {"response":{"data":null...

Example after:
  ✗ Issue not found: FAKE-9999
2026-02-01 22:51:12 -08:00
Peter Schilling 59dce3cf19 fix: use centralized shouldShowSpinner() for all spinner checks
Updated bulk.ts to use the centralized shouldShowSpinner() function instead
of directly checking Deno.stdout.isTerminal(). This ensures progress display
during bulk operations also respects the NO_COLOR environment variable.

Fixes #113
2026-02-01 20:38:23 -08:00
Peter Schilling 5e845e2ee3 fix: disable spinners in non-TTY environments
Add shouldShowSpinner() utility that checks both Deno.stdout.isTerminal()
and the NO_COLOR environment variable before showing spinners. This
prevents garbled spinner output when the CLI is used with tools that
capture output (e.g., AI assistants, scripts).

Removes --no-color flags from commands where they only controlled spinner
visibility, since spinners now automatically disable in non-TTY
environments.

Fixes #113
2026-02-01 20:38:23 -08:00