Skip to content

chore(auth): global --user account selector (cli-core 0.23)#77

Merged
scottlovegrove merged 4 commits into
mainfrom
scottl/cli-core-migration
May 24, 2026
Merged

chore(auth): global --user account selector (cli-core 0.23)#77
scottlovegrove merged 4 commits into
mainfrom
scottl/cli-core-migration

Conversation

@scottlovegrove
Copy link
Copy Markdown
Contributor

Summary

Adds a global ol --user <ref> selector so every command can act as a specific stored account (matched by Outline user UUID or display name), and bumps @doist/cli-core to 0.23.0 to support it.

Part of the cli-core migration — this is the multi-account selection layer. The storage was already multi-account; this wires the user-facing selection through.

Commit 1 — --user plumbing + cli-core 0.23

  • Bump @doist/cli-core 0.19 → 0.23.0.
  • Parse + validate a root --user, then stripUserFlag it before Commander parses (Commander has no global option). Validation rejects a value-less flag or a forgotten value that's actually a command.
  • Expose getRequestedUserRef(); extend the Outline token store with activeAccount() and have clear() return the cleared account (0.23 shape).

Commit 2 — honor --user across all commands

  • withUserRefAware wraps the token store to substitute the global ref for no-ref reads (active/activeBundle/activeAccount/clear), used by both the request path and cli-core's auth status/auth logout attachers. Explicit ref wins; unknown ref fails fast with ACCOUNT_NOT_FOUND.
  • getBaseUrl/getOAuthClientId resolve the selected account via recordForRef, so token + base URL + client id all track the same account (each account can live on a different Outline instance).
  • OUTLINE_API_TOKEN wins outright: the request path skips ref resolution so all three stay pinned to the default source together.

Behavior notes

  • --user must precede the subcommand (ol --user X document list); after the subcommand it reaches Commander. Matches todoist/twist.
  • No --help text for --user and SKILL_CONTENT not updated yet — deferred until the account list/use/current/remove commands land so the multi-account story documents as a coherent whole.

Test plan

  • npm run type-check, npm run format, npm test (186 passing, +remaining)
  • Real binary: unknown --userACCOUNT_NOT_FOUND envelope; --user <name> routes the request to that account's instance + token; bare/--user <command> validation errors; env token overrides --user.
  • Multi-instance login flow exercised manually with two real accounts

🤖 Generated with Claude Code

scottlovegrove and others added 2 commits May 23, 2026 22:36
Bump @doist/cli-core to 0.23.0 and add the plumbing for a root
`--user <ref>` account selector: parse + validate it (rejecting a
value-less flag or a forgotten value that's actually a command),
warm the global-args cache, then strip it before Commander parses.
The resolved ref is exposed via getRequestedUserRef() for the
account-aware store wiring still to come; selection is not yet wired
into request token/base-URL resolution.

Also extend the Outline token store with activeAccount() and have
clear() return the cleared account, matching the cli-core 0.23
TokenStore shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Thread the global `ol --user <ref>` selector into account resolution so
every command can act as a specific stored account, matched by Outline
user UUID or display name.

- withUserRefAware wraps the token store to substitute the global ref
  for no-ref reads (active/activeBundle/activeAccount/clear), used by
  both the request path and cli-core's auth status/logout attachers.
  An explicit ref wins; an unknown ref fails fast with ACCOUNT_NOT_FOUND.
- getBaseUrl/getOAuthClientId resolve the selected account's instance
  via recordForRef, so token, base URL, and client id all track the
  same account (each account can live on a different Outline instance).
- An OUTLINE_API_TOKEN wins outright: the request path skips ref
  resolution so token/base URL/client id stay pinned to the default
  source together.

matchOutlineAccount moves to outline-account.ts (re-exported from
auth-provider) to keep the wrap free of a lib->commands import.

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 introduces a global --user selector to enable multi-account support across commands and bumps @doist/cli-core to version 0.23.0. The implementation effectively wires the user-facing selection through the existing storage layer, significantly enhancing the flexibility of the CLI. A few areas need refinement, including adjusting the argument parser to correctly ignore late flags, ensuring accurate legacy account resolution and status reporting, keeping configuration resolvers account-agnostic, updating the skill documentation to match the new option, and applying some minor testing and type-safety improvements.

Share FeedbackReview Logs

Comment thread src/lib/global-args.ts Outdated
Comment thread src/commands/auth.ts
Comment thread src/lib/user-ref-store.ts Outdated
Comment thread src/lib/user-records.ts Outdated
Comment thread src/lib/auth.ts Outdated
Comment thread src/lib/auth.test.ts Outdated
Comment thread src/lib/global-args.test.ts Outdated
Comment thread src/lib/user-ref-store.test.ts
Comment thread src/index.ts Outdated
Comment thread src/lib/user-ref-store.ts Outdated
scottlovegrove and others added 2 commits May 24, 2026 10:11
- Validate `--user` only in root position; a flag after the command falls
  through to Commander as an unknown option instead of a false "requires a
  value" error. Move the validate→warm→strip flow into `applyUserSelector`
  so the order is exercised by tests rather than living untested in index.ts.
- Check account existence in the ref-aware wrap via `activeAccount` (token-free
  and legacy-aware) instead of `list()`, so `--user <legacy-name>` resolves a
  pending v1→v2 account instead of throwing ACCOUNT_NOT_FOUND.
- Keep `getBaseUrl`/`getOAuthClientId` account-agnostic so `auth login` isn't
  skewed by `--user`; resolve the selected account's instance + client id only
  on the request path via `getRequestContext`, passed as the refresh handshake.
- Make `getActiveTokenSource(ref?)` account-aware so `auth status --user <ref>`
  reports the selected account's token source, not the default/env one.
- Build the wrap without a type assertion; drop the duplicate `toRecord`
  allocation in `recordForRef`.
- Document the global `--user` option in SKILL_CONTENT + root help, and
  regenerate SKILL.md.
- Tests: static imports + a real `applyUserSelector` entrypoint test;
  `.rejects` matchers; and an `auth status --user` integration test that proves
  the wired store routes to the selected account.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract the two-account config into a shared TWO_USER_CONFIG fixture
(was hand-rolled in three test files), collapse the ACCOUNT_NOT_FOUND
case to a single assertion, and drop two getRequestedUserRef cases
already covered by the applyUserSelector entrypoint test and cli-core's
own parser.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@scottlovegrove scottlovegrove changed the title feat(auth): global --user account selector (cli-core 0.23) chore(auth): global --user account selector (cli-core 0.23) May 24, 2026
@scottlovegrove scottlovegrove merged commit c3fc3c9 into main May 24, 2026
6 checks passed
@scottlovegrove scottlovegrove deleted the scottl/cli-core-migration branch May 24, 2026 09:25
scottlovegrove added a commit that referenced this pull request May 24, 2026
## Summary

Surfaces the multi-account storage (already shipped in #77 — `users[]`
config, keyring, `--user` selector, `withUserRefAware` store-wrap) to
users via a new `ol account` command group, wiring cli-core's four
generic account attachers.

- `ol account list` (and bare `ol account`, via default subcommand) —
lists stored accounts with an accessibility-aware default marker;
`--json` emits a `{ accounts, default }` envelope, `--ndjson` streams
one per line.
- `ol account use <id|name>` — sets the default account used when
`--user` is omitted.
- `ol account current` — shows the active account, honours `ol --user
<ref>`, and reports the `OUTLINE_API_TOKEN` env source and legacy
single-user sessions via `onNotAuthenticated` (rather than rendering
them as a blank stored account).
- `ol account remove <id|name>` — clears the keyring + config entry and
surfaces any keyring-fallback warning.
- Machine output drops the OAuth client id (`{ id, label, teamName,
baseUrl, isDefault }`).

Reuses existing plumbing: `createOutlineTokenStore`, `withUserRefAware`,
`matchOutlineAccount`, `logTokenStorageResult`. No changes to the
env/legacy resolution logic beyond a read-only wrapper for `current`.

## Test plan

- [x] `npm run type-check`
- [x] `npm run test` — 200/200 pass, incl. new
`src/commands/account.test.ts` (list/use/current/remove, env-token,
legacy, empty, not-authenticated)
- [x] `npm run format:check` clean
- [x] `npm run check:skill-sync` in sync
- [x] `node dist/index.js account --help` renders all four subcommands +
examples
- [ ] Manual smoke against a real multi-account config: `ol account`,
`ol account use`, `ol --user <ref> account current`,
`OUTLINE_API_TOKEN=… ol account current`

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

---------

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 24, 2026
## [1.9.0](v1.8.0...v1.9.0) (2026-05-24)

### Features

* **account:** account command group (cli-core attachers) ([#79](#79)) ([01bd6a6](01bd6a6)), closes [#77](#77)
@doist-release-bot
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.9.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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