Skip to content

feat(auth): add logout/status/token-view Commander attachers#16

Merged
scottlovegrove merged 2 commits into
mainfrom
scottl/more-auth-commands
May 12, 2026
Merged

feat(auth): add logout/status/token-view Commander attachers#16
scottlovegrove merged 2 commits into
mainfrom
scottl/more-auth-commands

Conversation

@scottlovegrove
Copy link
Copy Markdown
Collaborator

@scottlovegrove scottlovegrove commented May 11, 2026

Summary

Completes the @doist/cli-core/auth registrar set started in 0.9.0. Three new attachers — attachLogoutCommand, attachStatusCommand, attachTokenViewCommand — mirror the attachLoginCommand shape so consumers wire all four auth subcommands identically (parent Command + options bag → returns the chained subcommand).

  • attachLogoutCommand — snapshots store.active(), calls clear(), emits ✓ Logged out (plain) / {ok:true} (--json) / silent (--ndjson), then fires optional onCleared({ account, view }) for keyring-fallback warnings. Bridges the TokenStore.clear: void contract gap until a shared keyring backend ships.
  • attachStatusCommand — skeleton with renderText / renderJson callbacks (account fields diverge hard across CLIs: todoist multi-user, twist minimal, outline workspace-centric). Optional fetchLive for live token probe; optional onNotAuthenticated to override the default CliError('NOT_AUTHENTICATED') on empty stores.
  • attachTokenViewCommand — writes the bare stored token to stdout (pipe-safe), appends \n only on TTY. Refuses to print when an envVarName is set + populated (CliError('TOKEN_FROM_ENV')) to avoid disclosing a token the CLI did not manage. Defaults to subcommand name token; pass name: 'view' to nest.

AuthErrorCode gains NOT_AUTHENTICATED and TOKEN_FROM_ENV.

Scope decisions

  • attachTokenSetCommand intentionally NOT extracted — `auth token ` is being sunset in favour of OAuth-only flows.
  • attachTokenViewCommand ships single-user only. Todoist's `--user ` selection depends on global-state resolution inside its `TokenStore` adapter; the current adapter (per todoist-cli#322) intentionally drops env + selector behaviour from `active()` for clean login semantics. Todoist keeps its hand-rolled `token view` until the multi-user store contract lands in cli-core. Twist + outline have no `token view` today.

Test plan

  • npm run check (oxlint + oxfmt) clean
  • npm run type-check clean
  • npm test — 244/244 across 19 files
  • npm run build clean
  • README "What's in it" + new "Sibling attachers" section + errors table reflect the new exports

Follow-up (separate PRs)

  • todoist-cli: migrate `auth logout` + `auth status` (keep `token view` local — see scope note above)
  • twist-cli: migrate `auth logout` + `auth status` (no token-view today)
  • outline-cli: migrate `auth logout` + `auth status`; wrap sync `clearConfig()` in an async adapter for `TokenStore.clear`

🤖 Generated with Claude Code

Completes the auth subpath registrar set so consumers wire all four
auth subcommands (login/logout/status/token) identically. Mirrors the
attachLoginCommand shape: parent Command + options bag → returns the
chained subcommand for further .option() / .description() chaining.

- attachLogoutCommand: snapshots store.active(), calls clear(), emits
  `✓ Logged out` (plain) / `{ok:true}` (--json) / silent (--ndjson),
  then fires optional onCleared({ account, view }) for keyring-fallback
  warnings — bridges the TokenStore.clear: void contract gap until a
  keyring backend ships.
- attachStatusCommand: skeleton with renderText / renderJson callbacks
  (account fields diverge hard between CLIs — todoist multi-user,
  twist minimal, outline workspace-centric); optional fetchLive for
  live token probe; optional onNotAuthenticated overrides the default
  CliError('NOT_AUTHENTICATED') on empty stores.
- attachTokenViewCommand: writes the bare stored token to stdout
  (pipe-safe), appends \n only on TTY, refuses to print when an
  envVarName is set + populated (CliError('TOKEN_FROM_ENV')), throws
  CliError('NOT_AUTHENTICATED') on empty stores. Defaults to
  subcommand name `token`; pass name: 'view' to nest. Single-user
  contract today — multi-user / --user selection lands when the
  keyring + multi-user store contract does.

AuthErrorCode gains NOT_AUTHENTICATED and TOKEN_FROM_ENV.

attachTokenSetCommand intentionally not extracted: `auth token <token>`
is being sunset in favour of OAuth-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@doistbot doistbot left a comment

Choose a reason for hiding this comment

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

This PR nicely rounds out the core auth registrar set by introducing the logout, status, and token-view Commander attachers. These additions lay a solid foundation for aligning authentication flows across the different CLIs with a consistent API. A few refinements are noted below regarding the reuse of existing platform helpers, aligning option propagation and async lifecycles with the login command, optimizing resource reads, and tightening test assertions to better verify the intended behavior.

Share FeedbackReview Logs

Comment thread src/auth/status.ts Outdated
Comment thread src/auth/status.ts
Comment thread src/auth/token-view.ts Outdated
Comment thread src/auth/logout.ts
Comment thread src/auth/status.ts
Comment thread src/auth/logout.ts Outdated
Comment thread src/auth/status.ts Outdated
Comment thread src/auth/status.test.ts Outdated
Comment thread src/auth/token-view.test.ts Outdated
Comment thread src/auth/status.test.ts Outdated
- Expose stripped `flags` in attachLogoutCommand and attachStatusCommand
  callback contexts so consumer-chained `.option(...)` calls (e.g.
  `logout --user`, `status --full`) reach the hooks — parity with
  attachLoginCommand's escape hatch.
- attachLogoutCommand: skip `store.active()` when `onCleared` isn't
  supplied (avoids keyring / file I/O whose result nobody consumes).
- attachStatusCommand: only invoke `renderJson` in machine-output mode;
  human mode skips the conversion entirely. `onNotAuthenticated` now
  awaits a `void | Promise<void>` return (parity with `onCleared`).
- Drop parent-specific wording from default NOT_AUTHENTICATED messages
  ('Not signed in.') since the attachers can mount under any parent.
- attachTokenViewCommand: read TTY signal through `isStdoutTTY()` from
  ../terminal.js instead of `process.stdout.isTTY` directly.
- Tests: tighten token-view non-TTY assertion to exact-emitted-output;
  use formatJson / formatNdjson in expectations so tests track the
  module's serializer rather than re-encoding inline; relax the
  multi-line status renderText test to assert joined output instead of
  call count + sequence; cover the new `flags` escape hatch and the
  async `onNotAuthenticated` path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@scottlovegrove scottlovegrove merged commit b7e5385 into main May 12, 2026
5 checks passed
@scottlovegrove scottlovegrove deleted the scottl/more-auth-commands branch May 12, 2026 19:37
doist-release-bot Bot added a commit that referenced this pull request May 12, 2026
## [0.10.0](v0.9.0...v0.10.0) (2026-05-12)

### Features

* **auth:** add logout/status/token-view Commander attachers ([#16](#16)) ([b7e5385](b7e5385))
@doist-release-bot
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 0.10.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

scottlovegrove added a commit to Doist/todoist-cli that referenced this pull request May 12, 2026
)

## Summary

Bumps `@doist/cli-core` to **0.10.0** and migrates `td auth logout` +
`td auth status` onto the new `attachLogoutCommand` /
`attachStatusCommand` registrars that ship alongside the existing
`attachLoginCommand`
([cli-core#16](Doist/cli-core#16)).

- `TodoistTokenStore` gains `getLastClearResult()` so the logout
`onCleared` callback can surface keyring-fallback warnings — cli-core's
`TokenStore.clear: void` contract can't carry the `TokenStorageResult`
directly.
- `auth/index.ts` now creates a single shared `TodoistTokenStore`
instance used by login, logout, and status (login's wrapper takes the
store as a parameter instead of constructing its own).
- `attachTodoistStatusCommand` routes both the active-snapshot path
(`fetchLive`) and the unauthenticated path (`onNotAuthenticated`)
through one `gatherStatusData` helper so env-token mode and `--user
<ref>` (both return `null` from `TokenStore.active()` by the adapter's
documented contract) render byte-for-byte identically to the
persisted-default-user path.
- `auth logout` and `auth status` now accept `--ndjson` for free
(framework-registered) on top of `--json`. `auth status` previously only
had `--json`.
- `td auth token view` stays hand-rolled — its `--user <ref>` selector
depends on multi-user state the `TokenStore` adapter intentionally drops
from `active()`. cli-core PR #16 explicitly carves this out as a scope
note pending a multi-user store contract.

## Test plan

- [x] `npm run check` (oxlint + oxfmt) clean
- [x] `npm run type-check` clean
- [x] `npm test` — 1574/1574 across 60 files
- [x] `npm run build` clean
- [x] Smoke: `HOME=$(mktemp -d) td auth status` prints the legacy "No
API token found." CliError envelope
- [x] Smoke: `td auth {login,logout,status} --help` show
framework-registered `--json` / `--ndjson` flags
- [ ] Manual: full OAuth login → `td auth status` → `td auth status
--json` → `td auth logout` round-trip against a real account
- [ ] Manual: `TODOIST_API_TOKEN=… td auth status` shows `✓
Authenticated (TODOIST_API_TOKEN)` env-mode branch
- [ ] Manual: multi-user setup shows the "Other stored accounts"
enumeration

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants