Skip to content

feat(login): --api-key — adopt a durable insta_ token non-interactively - #79

Merged
CarmenDou merged 3 commits into
mainfrom
feat/login-api-key
Aug 5, 2026
Merged

feat(login): --api-key — adopt a durable insta_ token non-interactively#79
CarmenDou merged 3 commits into
mainfrom
feat/login-api-key

Conversation

@CarmenDou

@CarmenDou CarmenDou commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What

insta login --api-key <insta_...> — a non-interactive login that adopts a durable insta_ API token (from POST /tokens) as the standing credential.

Closes a gap a dogfood test surfaced: an agent can obtain a credential by driving the device-flow endpoints (and POST /tokens for a durable key), but the CLI had no way to consume that token — login only supported email/password, --oauth, and --device (all interactive/credential). So a headless agent could authenticate but then couldn't drive the CLI to provision. (InsForge solves the same with login --user-api-key.)

How

  • src/index.ts: --api-key <key> on login.
  • src/commands/auth.ts: exclusive-mode guard (errors if combined with another login mode); loginApiKey + the testable DI core applyApiKeyLogin — rejects a non-insta_ prefix before any request, verifies via the /me probe authed with the key itself, maps a 401 to a clear "rejected (invalid or revoked)" error, stores + prints logged in as <email>.
  • src/api.ts: setApiKey / storeApiKeyCredential store the key as the bearer and drop any refresh token (an insta_ key never rotates; a stale refresh token would be POSTed to /auth/refresh on a 401 — a cross-credential leak).

Verify

  • typecheck clean; build succeeds.
  • test/login-api-key.test.ts — 7 tests (DI / no network). Full suite 214/214.
  • insta login --help shows --api-key; the mode-conflict guard fires offline.
  • End-to-end against production: device flow → POST /tokens (durable insta_ key, expiresAt: null) → insta login --api-key <key>logged in as …insta project create provisions. The full headless auth→provision chain works.

🤖 Generated with Claude Code


Summary by cubic

Add non-interactive login mode insta login --api-key <insta_...> to adopt a durable insta_ API token. Lets headless agents and CI authenticate without a browser or prompts.

  • New Features
    • Added --api-key to login; exclusive with --device, --oauth, and --email.
    • Validates early: trims whitespace and rejects empty or non-insta_ tokens before any request.
    • Verifies via /me; clear 401 error: “rejected (invalid or revoked)”.
    • Stores the key as the bearer and clears any refresh token to avoid cross-credential refresh.
    • Updated command help and tests, including whitespace/empty handling and verify/store behavior.

Written for commit b879a75. Summary will update on new commits.

Review in cubic

## What
Add `insta login --api-key <insta_...>` so a headless agent can authenticate the
CLI with a durable insta_ API token it obtained out-of-band (via POST /tokens) --
closing the gap where an agent could get a token from the device-flow endpoints
but had no way to hand it to the CLI (login only supported email/password,
--oauth, --device).

## How
- src/index.ts: add `--api-key <key>` to the `login` command.
- src/commands/auth.ts: exclusive-mode guard (errors if combined with
  --device/--oauth/--email); `loginApiKey` + the testable DI core
  `applyApiKeyLogin` -- rejects a non-insta_ prefix before any request, verifies
  via the /me probe authed with the key itself, clear "rejected (invalid or
  revoked)" on 401, stores + prints "logged in as <email>".
- src/api.ts: `setApiKey`/`storeApiKeyCredential` store the key as the bearer and
  DROP any refresh token (an insta_ key never rotates; a stale refresh token
  would be POSTed to /auth/refresh on a 401 -- a cross-credential leak).

## Verify
- typecheck clean; build succeeds.
- New test/login-api-key.test.ts: 7 tests (store+verify; reject bad prefix before
  touching the client; 401 -> clear error; non-401 propagated; 200-without-user
  fails loudly; refresh token dropped). Full suite 214/214.
- `insta login --help` shows --api-key; the mode-conflict guard fires offline.
- End-to-end against production: device flow -> POST /tokens (durable insta_ key,
  expiresAt null) -> `insta login --api-key <key>` -> "logged in as ..." ->
  `insta project create` provisions. The full headless auth->provision chain works.
@CarmenDou
CarmenDou marked this pull request as ready for review August 5, 2026 21:04

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review: feat(login): --api-key (non-interactive insta_ token adoption)

Summary: A small, well-scoped, well-tested addition of a headless login --api-key mode that adopts a durable insta_ token; the credential-store and verify seams are cleanly extracted for DI testing, and I found no blocking issues.

Requirements context

No matching spec/plan found — insta-cli has no docs/superpowers/ or docs/specs/ directory. Assessed against the PR description, the closed dogfood gap it describes, and the surrounding auth conventions in src/commands/auth.ts / src/api.ts.

Findings

Critical

(none)

Suggestion

  • Software engineering — exclusive-mode guard has no automated test (src/commands/auth.ts:19-23). The DI cores (applyApiKeyLogin, storeApiKeyCredential) are covered by 7 focused tests, which is the right seam to test. But the new exclusive-mode guard (die('choose one login mode…')) is only verified manually per the PR body. Because it's a die()process.exit path it's awkward to unit-test and it matches the existing untested pattern for the other login modes, so this is low priority — but note that adding a future login mode and forgetting to add it to this OR-chain would silently regress with no test to catch it.

Information

  • Software engineering — --password not covered by the exclusivity check (src/commands/auth.ts:21). The guard rejects --device/--oauth/--email alongside --api-key, but insta login --api-key K --password P silently ignores --password. Harmless (password without email is a no-op today), but a note/warning would be marginally friendlier. Not worth blocking.
  • Functionality — logout after an api-key login is intentionally server-side-silent (src/commands/auth.ts:196-202). Because storeApiKeyCredential drops the refresh token, logout skips the /auth/logout call and only clears local state. That's the correct behavior — an insta_ key is revoked via the tokens API, not session logout — just flagging it as intentional, not an oversight.

Dimension coverage

  • Software engineering: Good TDD — new test/login-api-key.test.ts exercises the prefix guard (pre-request), the /me verify probe, 401→"rejected" mapping, non-401 pass-through, 200-without-user, and the refresh-token-drop store rule. Assertions match the implementation exactly. Naming/import style (.js extensions, ApiError, DI-fake pattern) follows repo convention. Thrown plain Errors surface cleanly via guardonErrordie (src/index.ts:27-34), no stack dump.
  • Functionality: The verify-before-persist ordering is correct — loginApiKey only calls api.persist() after applyApiKeyLogin resolves, so a rejected/failed key leaves no partial on-disk credential. The probe is authed with the key itself (bearer set first), and the refresh path in raw() (src/api.ts:65-66) is correctly neutered because setApiKey deletes refreshToken, so a 401 can't trigger a cross-credential /auth/refresh.
  • Security: Net positive. Dropping the refresh token prevents a stale rotating token from being POSTed to /auth/refresh under a durable key. The key is stored the same way session tokens already are (global config), never logged, and error messages / logged in as <email> output don't echo the key. Prefix validated before any network call.
  • Performance: No concern — one /me round-trip, no loops, no polling (by design, vs --device).

Verdict

approved (informational; a human still gives the explicit GitHub approval). Zero Critical findings — the Suggestion and Information items are non-blocking. Nice, tight implementation with sound reasoning in the comments.

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM - approved.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 4 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/commands/auth.ts Outdated
Comment thread src/commands/auth.ts
Addresses cubic review on #79:
- check opts.apiKey !== undefined so an explicit empty --api-key= is validated
  instead of silently falling through to another login mode.
- trim the key in applyApiKeyLogin so the headless --api-key "$(cat token)"
  path tolerates a trailing newline / stray whitespace.
- add 2 tests (whitespace trimmed; empty/whitespace-only rejected).
@CarmenDou
CarmenDou merged commit 06d48c0 into main Aug 5, 2026
2 checks passed
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