feat(auth): add logout/status/token-view Commander attachers#16
Merged
Conversation
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>
doistbot
reviewed
May 11, 2026
Member
doistbot
left a comment
There was a problem hiding this comment.
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.
- 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>
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))
Contributor
|
🎉 This PR is included in version 0.10.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
9 tasks
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Completes the
@doist/cli-core/authregistrar set started in 0.9.0. Three new attachers —attachLogoutCommand,attachStatusCommand,attachTokenViewCommand— mirror theattachLoginCommandshape so consumers wire all four auth subcommands identically (parentCommand+ options bag → returns the chained subcommand).attachLogoutCommand— snapshotsstore.active(), callsclear(), emits✓ Logged out(plain) /{ok:true}(--json) / silent (--ndjson), then fires optionalonCleared({ account, view })for keyring-fallback warnings. Bridges theTokenStore.clear: voidcontract gap until a shared keyring backend ships.attachStatusCommand— skeleton withrenderText/renderJsoncallbacks (account fields diverge hard across CLIs: todoist multi-user, twist minimal, outline workspace-centric). OptionalfetchLivefor live token probe; optionalonNotAuthenticatedto override the defaultCliError('NOT_AUTHENTICATED')on empty stores.attachTokenViewCommand— writes the bare stored token to stdout (pipe-safe), appends\nonly on TTY. Refuses to print when anenvVarNameis set + populated (CliError('TOKEN_FROM_ENV')) to avoid disclosing a token the CLI did not manage. Defaults to subcommand nametoken; passname: 'view'to nest.AuthErrorCodegainsNOT_AUTHENTICATEDandTOKEN_FROM_ENV.Scope decisions
attachTokenSetCommandintentionally NOT extracted — `auth token ` is being sunset in favour of OAuth-only flows.attachTokenViewCommandships 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) cleannpm run type-checkcleannpm test— 244/244 across 19 filesnpm run buildcleanFollow-up (separate PRs)
🤖 Generated with Claude Code