Commit Graph

26 Commits

Author SHA1 Message Date
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 dbebd16319 feat: add npm installer and publish job for @schpet/linear-cli 2026-03-06 21:39:27 +00:00
Peter Schilling f319866efa chore: bump cargo-dist to 0.31.0 and regenerate CI 2026-03-04 20:34:14 -08:00
Dan Wager 1f6b6ac2e3 fix: move build-setup.yml out of workflows dir 2026-03-04 20:25:14 -08:00
Peter Schilling 1a6dcb0fb4 Add CI check for skill docs and update documentation
- Add CI workflow step to verify generated skill docs are up-to-date
- Add development section to README with instructions for:
  - Updating skill documentation with deno task generate-skill-docs
  - Running code formatting with deno fmt
- Update generate-docs.ts to automatically format generated files
- Regenerate skill docs to ensure they match current CLI version

Fixes #135
2026-02-12 08:03:29 -08:00
Peter Schilling 1e4c6e354d chore: fix formatting in publish.yaml 2026-01-29 05:53:25 +00:00
Peter Schilling 39b6405c37 fix: trigger JSR publish on version tags instead of every push 2026-01-29 05:53:03 +00:00
Peter Schilling c752abaf24 chore: remove duplicate ci.yml (ci.yaml already exists) 2026-01-29 05:51:19 +00:00
Peter Schilling 992f05afe1 fix: add type annotations and CI workflow for type checking
- Add explicit type annotations to team-list.ts and project-list.ts to fix implicit any errors
- Add CI workflow (.github/workflows/ci.yml) that runs type check, format, lint, and tests on PRs
- Update justfile to use 'deno check src/main.ts' instead of '--all' to avoid npm dependency type errors
2026-01-29 05:48:54 +00:00
peter schilling 51ff1b2db6 Add 'issue commits' command (jj-vcs only) (#79)
use case: 

- you are addressing feedback on an issue ABC-123
- run `linear issue start 123` to re-start that issue
- fire up claude code
- run `! linear issue view` to drop the issue, and comments into context
- run `! linear issue commits` to dump the diff of previous work into
context
- ask claude to address the feedback (it will have a much better shot of
knowing what you are talking about)
2025-12-14 08:33:34 -08:00
Peter Schilling fe469cc6d5 upgrade cargo dist to latest 2025-12-02 22:38:06 -08:00
Peter Schilling 85591f8c2c Cache Rust build in release workflow to speed builds and reuse artifacts
Claude-session-id: b7feff7e-b2b9-4051-bbcc-98b5323cc412
2025-12-02 22:38:06 -08:00
Peter Schilling 24cfbc1ca9 run ci on PRs 2025-11-23 22:20:22 -08:00
Peter Schilling 93251900ec Organize code (#46) 2025-08-14 06:38:40 -07:00
Peter Schilling 2ea8138cf4 feat: Use graphql codegen to ensure linear api data has types (#34)
CLI-5
2025-08-11 22:48:29 -07:00
Peter Schilling b8828da219 Handle long team ids, Improve error output (#29)
- improves error output to be pretty json
- handles longer team ids
- adds a script to update linear's graphql schema
- adds tests
2025-06-17 22:02:01 -07:00
Peter Schilling 7459003755 chore: Release linear-cli version 0.5.7 2025-05-22 07:40:39 -07:00
Peter Schilling e2b4ff5ca4 chore: Release linear-cli version 0.5.6 2025-05-22 07:27:26 -07:00
Peter Schilling 9137cf3cdf Switch dist to fork: astral-sh/cargo-dist (#28) 2025-05-21 21:39:07 -07:00
Peter Schilling 5928955e72 use ubuntu latest instead of deprecated 20.04 2025-05-20 11:32:52 -07:00
Peter Schilling f65d4efc9e chore: Update release.yml 2025-01-31 14:42:19 -08:00
Peter Schilling ec3f6c9ba4 chore: Setup cargo dist (#6) 2025-01-31 14:38:49 -08:00
Peter Schilling df1eb18246 Build for x86 mac, and windows 2024-11-28 07:37:49 -08:00
Peter Schilling ef263755ef Add checkout step to release workflow to fix git repository access 2024-11-28 07:33:01 -08:00
Peter Schilling bf3e434206 Compile binaries (#4) 2024-11-28 07:30:10 -08:00
Peter Schilling 03ba7c9ed7 Basic features 2024-11-23 07:08:53 -08:00