Skip to content

REPLY-51256: reply-cli v1 — auth, profiles, team headers, packaging - #1

Merged
vigubikReply merged 2 commits into
mainfrom
dev/REPLY-51256-reply-cli-v1
Jul 17, 2026
Merged

REPLY-51256: reply-cli v1 — auth, profiles, team headers, packaging#1
vigubikReply merged 2 commits into
mainfrom
dev/REPLY-51256-reply-cli-v1

Conversation

@vigubikReply

@vigubikReply vigubikReply commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

reply-cli v1 (REPLY-51256)

First release of the Reply.io CLI (reply) — authentication, identity, multi-account profiles, team/acting-user context, and packaging.

Auth & identity

  • auth login (OAuth authorization-code + PKCE, 127.0.0.1 loopback), auth login --with-token (API key via stdin), auth logout, auth status, auth whoami.
  • Transport is always Authorization: Bearer; v3 auto-detects JWT vs API key. whoami maps the v3 contract {userId, username, teamId}.
  • Precedence: --api-key > REPLY_API_KEY > stored credential (flag/env ephemeral, never persisted); no .env lookup. Expired OAuth tokens auto-refresh.

Credential store & profiles

  • CredentialStore interface; file backend at 0600 in a 0700 dir, keyed by profile name so multiple accounts stay isolated; OS keychain deferred behind the same interface.
  • --profile selects a named account (--profile > REPLY_PROFILE > current > built-in default/prod); profiles inherit every field from the embedded default and are optional. profile add / set / use / list / current.

Team & acting-user headers

  • X-TEAM-ID from --team-id > REPLY_TEAM_ID > profile team_id (pins the OAuth workspace; required for organization keys).
  • Organization-key acting user via flag-only --user-id / --user-emailX-USER-ID / X-User-Email, validated client-side.

Output, packaging & CI

  • Data → stdout, messages → stderr; --json / --pretty; exit 0/1/2; secrets redacted.
  • Published to GitHub Packages (@reply-team/reply-cli): every main build → @next, tested build promoted to @latest. Public npm release is a separate story (REPLY-51261).
  • Windows supported (browser-open handling + CI matrix). Fully offline test suite (149 tests) — no test hits the real API or IdentityServer; CI builds + tests on Ubuntu and Windows.

Docs

  • README.md (install / auth / profiles / env vars) and CONTRIBUTING.md (contributor access model, build/test, conventions, credential-store internals, release process).

Tracked separately (not in this PR)

  • reply call-api (REPLY-51259), team discovery + conflict guidance (REPLY-51258), self-documenting manifest (REPLY-51281).

🤖 Generated with Claude Code

https://claude.ai/code/session_01894nPAQK7eWpiQzsgr4LW2

REPLY-51256 — first release of the Reply.io CLI (`reply`).

Authentication & identity:
- auth login (OAuth authorization-code + PKCE, 127.0.0.1 loopback),
  auth login --with-token (API key via stdin), auth logout, auth status,
  auth whoami. Transport is always Authorization: Bearer; v3 auto-detects
  JWT vs API key. whoami maps the v3 contract {userId, username, teamId}.
- Credential precedence: --api-key flag > REPLY_API_KEY env > stored credential
  (flag/env ephemeral, never persisted); no .env resolution. Expired OAuth
  tokens refresh automatically.

Credential store & profiles:
- CredentialStore interface; file backend at 0600 in a 0700 dir, keyed by
  PROFILE NAME so multiple accounts stay isolated (OS keychain deferred behind
  the same interface).
- --profile selects a named account (--profile > REPLY_PROFILE > current >
  built-in default/prod); profiles inherit every field from the embedded
  default and are optional. profile add/set/use/list/current.

Team & acting-user headers:
- X-TEAM-ID from --team-id > REPLY_TEAM_ID > profile team_id (pins the OAuth
  workspace / required for organization keys). Org-key acting user via flag-only
  --user-id / --user-email -> X-USER-ID / X-User-Email, validated client-side.

Output, packaging & CI:
- Data to stdout, messages to stderr; --json/--pretty; exit 0/1/2; secrets redacted.
- Published to GitHub Packages (@reply-team/reply-cli): every main build -> @next,
  tested build promoted to @latest. Windows supported (browser-open + CI matrix).
- Fully offline test suite (149 tests) — no test hits the real API or IdP; CI
  builds + tests on Ubuntu and Windows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01894nPAQK7eWpiQzsgr4LW2
@vigubikReply
vigubikReply force-pushed the dev/REPLY-51256-reply-cli-v1 branch from 02b9c10 to 9ac3d96 Compare July 16, 2026 21:15
@vigubikReply vigubikReply changed the title REPLY-51256: reply-cli v1 — auth (OAuth + API key), whoami, internal packaging REPLY-51256: reply-cli v1 — auth, profiles, team headers, packaging Jul 16, 2026
@vigubikReply
vigubikReply enabled auto-merge July 16, 2026 21:23

@ArtemKosolap ArtemKosolap left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed reply-cli v1 locally (branch dev/REPLY-51256-reply-cli-v1). Marking Comment — not blocking, nice work overall.

The REPLY-51235 design is implemented faithfully across the board: single reply binary + --profile accounts (no replyrc, no --env), file credential store 0600-in-0700 keyed by profile, precedence --api-key > REPLY_API_KEY > stored (flag/env never written, no .env), OAuth code+PKCE (crypto.randomBytes(32) + S256) on an ephemeral loopback port, client_id = Reply.Cli, X-TEAM-ID / X-USER-ID / X-User-Email headers, GitHub Packages @next@latest, Windows support + fully-offline tests. Clean and well-tested.

Should fix before merge

  1. package-lock.json:16 still declares "bin": {"replyrc": ...} — a stale, now-forbidden name; package.json bin is correctly reply. Risk: npm ci (build.yml) can fail on a lockfile/manifest mismatch, and the old name shouldn't exist anywhere. Regenerate the lockfile (npm install) and commit.
  2. OAuth principal dropped on refreshto_oauth_record (token.ts:50) doesn't carry user, and resolve.ts:88 stores that user-less record; after the first silent refresh, auth status loses the User: line until a full re-login. Preserve prev.user.
  3. Reflected error not HTML-escaped in the loopback page (oauth-flow.ts:114) — low severity (loopback-only, no secret reflected), but escape it (or serve a static message).

Minor / non-blocking

  • Interactive --with-token echoes the pasted key (auth.ts:88); the documented echo <key> | reply auth login --with-token path is fine — mute echo on the TTY fallback.
  • Login fetches /whoami before persisting (auth.ts:105); a transient whoami failure discards a good login → an extra browser round-trip. Store first, best-effort whoami after.
  • Dead --timing branch (client.ts:99); @types/node ^20 vs Node 22 engines.

Tests

Strong offline coverage: PKCE shape/S256, token grant/refresh mapping, credential precedence (incl. ephemeral + clear-on-fail), file-store round-trip/corruption/perms, profile inheritance/current, client Bearer/retry/error mapping, redaction, and an end-to-end loopback login with CSRF-state rejection. fetch stubbed, loopback over local 127.0.0.1 — no external calls. Gaps (non-blocking): index.ts commander wiring / exit-code mapping; handle_login / handle_login_token paths (incl. the whoami-drops-tokens case above); a command-layer assertion that flag/env creds are never written to disk.

…, store-first login

Addresses Artem's review of PR #1 (REPLY-51256):
- token: to_oauth_record carries the previous principal, so a silent refresh no
  longer wipes the user shown by `auth status`.
- oauth-flow: HTML-escape the loopback page so a reflected ?error cannot inject markup.
- auth login: persist the credential before the /whoami lookup (identity enriched
  best-effort after), so a transient whoami failure can't discard a good login;
  mute the TTY echo on interactive --with-token.
- client: drop the dead --timing branch.
- package-lock.json: regenerate (bin was still the removed `replyrc`).

Tests (offline): carry-user-on-refresh, loopback error-escaping, store-first
login, verify-then-store token, ephemeral-creds-never-written. 156 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01894nPAQK7eWpiQzsgr4LW2
@vigubikReply

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, @ArtemKosolap! All addressed in 9c387a5.

Should fix

  1. Lockfile replyrc — regenerated package-lock.json; the bin is now reply (single-line change).
  2. Identity dropped on refreshto_oauth_record now carries the previous user, so a silent refresh keeps the User: line in auth status. Regression test added.
  3. Reflected error escaped — the loopback page now HTML-escapes every interpolated value; added an end-to-end test that a crafted ?error can't inject markup.

Minor

  • Store-first loginauth login persists the credential before the /whoami lookup (identity enriched best-effort after), so a transient whoami failure no longer discards a good login. --with-token stays verify-then-store on purpose (don't persist an unverifiable key).
  • TTY echo muted on the interactive --with-token paste.
  • Dead --timing branch removed.

Test gaps — added handle_login / handle_login_token coverage (including the store-first + whoami-failure paths) and a command-layer assertion that flag/env credentials are never written to disk. Suite is now 156 offline tests.

One I'd leave as-is: @types/node ^20 vs Node 22 — engines is >=20 and ^20 types match that floor, so it isn't an actual mismatch. Happy to bump to ^22 if you'd rather the types track a newer runtime.

@ArtemKosolap ArtemKosolap left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-reviewed on the updated branch — all three must-fix items are addressed. Approving.

  1. package-lock.json regenerated: the stale replyrc bin is gone, root bin is reply.
  2. Identity preserved on refresh: refresh_stored passes the prior record into to_oauth_record, which now carries userauth status keeps the principal after a silent refresh.
  3. Reflected loopback error is escaped: html_escape applied in success_page.

Thanks for the quick turnaround. The earlier non-blocking nits (mute echo on interactive --with-token, store-before-whoami on login) are fine as follow-ups whenever. LGTM.

@vigubikReply
vigubikReply merged commit e0c72a7 into main Jul 17, 2026
2 checks passed
@vigubikReply
vigubikReply deleted the dev/REPLY-51256-reply-cli-v1 branch July 17, 2026 01:21
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