Skip to content

feat(models): pick a profile interactively when switch is given none - #922

Open
daoiqi wants to merge 1 commit into
Tencent:mainfrom
daoiqi:feat/models-switch-interactive-pick
Open

daoiqi wants to merge 1 commit into
Tencent:mainfrom
daoiqi:feat/models-switch-interactive-pick

Conversation

@daoiqi

@daoiqi daoiqi commented Sep 30, 2026

Copy link
Copy Markdown
Collaborator

Closes #919.

What

teamai models switch required a profile name, so a member who did not
remember the exact id had to run models list first, read the ids, then
retype one.

The profile is now optional. With none given, switch lists every profile —
team ones first, then personal, the order models list already uses — and
switches the one picked:

$ teamai models switch

  1. team:tokenhub — Tencent TokenHub
  2. local:mine — My gateway (personal)

Select a profile [1-2, or "none" to cancel]: 1

Behaviour

  • Single pick. switch points each agent at exactly one gateway (Claude's
    settings.json holds a single ANTHROPIC_BASE_URL; the manifest records one
    profile per agent), so an answer naming several has nothing meaningful to do
    with the extras. Rather than silently take the first, the prompt says why and
    asks again:

    Select a profile [1-2, or "none" to cancel]: 1,2
    ⚠ switch takes one profile; you named 2. Enter a single number.
    Select a profile [1-2, or "none" to cancel]: 1
    

    An unusable answer is re-asked the same way. This is deliberately unlike
    init / push, where selecting several items is meaningful and the prompt
    advertises the syntax.

  • none (or 0) cancels: prints Cancelled, changes nothing, exits 0.

  • Non-interactive runs still require the profile. With no terminal there is
    nothing to pick from, so the command fails and names the argument rather than
    guessing — a hook or CI run must never land an agent on a gateway by accident.
    The one-profile case is not auto-selected, for the same reason.

  • Naming a profile behaves exactly as before; models switch <profile> is
    unchanged and backward compatible.

Scope is switch only — models add/configure/remove keep their required
arguments. No new subcommand: [profile] also matches models list [profile],
and adding an optional argument needs no second entry point for one action.

Implementation notes

  • chooseProfile resolves the profile (named, or picked) and findProfile now
    also accepts an already-resolved ProfileRef, so both paths run through one
    code path — the team identity stamping, key loading and the legacy-values
    checks stay identical whichever way the profile arrived.
  • The prompt loops on askQuestion + parseSelection rather than using
    askSelection: the latter returns null for both "cancelled" and "unusable
    answer", and the two need different handling now that one of them re-prompts.
  • models-cmd.test.ts gained vi.clearAllMocks() in beforeEach, because the
    new cases assert on log.warn call counts and would otherwise see the
    previous test's calls. Existing cases are unaffected.

Verification

npx tsc --noEmit and npm run lint clean; full suite 5496 passed. Three
push-env.test.ts cases fail, and I confirmed they fail identically on the base
commit (3a9a24a) without this change — pre-existing and unrelated.

Built CLI driven through a real pty (isInteractive() is false under a pipe, so
the picker needs one):

# Case Result
1 no profile, pick 2 lists both, switched claude switched to local:mirror
2 pick 1,2, then 1 rejected with the reason, re-asked; the second answer decides
3 pick banana, then 9, then 2 both re-asked; the third answer decides
4 pick none ℹ Cancelled, settings untouched
5 no terminal refuses, names teamai models switch <profile>, exit 1
6 explicit profile unchanged behaviour

--dry-run writes nothing in either form (manifest and settings.json
unchanged after a preview).

Docs updated in both languages (docs/usage-guide.md,
docs/usage-guide.zh-CN.md); skill-data/core/references/commands.md
regenerated.

@jeff-r2026 jeff-r2026 self-assigned this Sep 30, 2026
@github-actions

Copy link
Copy Markdown

No findings.

The PR description documents sufficient testing, including representative real-CLI/PTY verification for the runtime behavior change. The previously failing tests are clearly identified as pre-existing.

@jeff-r2026

Copy link
Copy Markdown
Collaborator

Please resolve the conflicts

@daoiqi

daoiqi commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

Merged origin/main (up to bae48e5) to clear the conflict.

Conflict: src/models-cmd.ts, one import line. #880 extracted the piped-stdin reader into utils/prompt.ts as readStdin and dropped the inline loop from readSecretStdin, while this branch still imports askQuestion/isInteractive/parseSelection for the picker. Both are used, so the resolution is the union:

import { askConfirmation, askQuestion, askSecret, isInteractive, parseSelection, readStdin } from './utils/prompt.js';

readSecretStdin keeps main's readStdin(); chooseProfile keeps this branch's loop. The rest of the file auto-merged — main's findProfile(reference: string) and this branch's ProfileRef-accepting version are the same signature this branch already introduced.

Re-verified after the merge: npx tsc --noEmit clean; models-cmd model-switch commands-reference 88 passed. No behaviour change from the merge itself, so the pty table above stands.

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown

No findings.

The earlier “No findings” result remains valid. The PR description documents sufficient testing, including representative real-CLI/PTY verification for the runtime behavior change and identification of unrelated pre-existing failures.

…encent#919)

`models switch` required a profile name, so a member who did not remember the
exact id had to run `models list` first and retype one. The profile is now
optional: with none given, `switch` lists every profile — team ones first,
then personal, the order `models list` uses — and switches the one picked.

The pick is single, because `switch` points each agent at exactly one gateway.
An answer naming several is asked again with the reason, not silently narrowed
to the first. Non-interactive runs still require the profile, so a hook or CI
run can never land an agent on a gateway by accident.

Backward compatible: `models switch <profile>` is unchanged.

Verified with the built CLI through a real pty: pick, multi-answer re-prompt,
`none` cancel, no-terminal refusal, explicit profile, `--dry-run`. Docs updated
in both languages, plus a `vi.clearAllMocks()` in the models test's beforeEach
for the new log.warn assertions.
@daoiqi
daoiqi force-pushed the feat/models-switch-interactive-pick branch from 701e4ef to 4244bdf Compare October 2, 2026 12:31
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown

No findings.

The earlier “No findings” reviews remain valid. The PR description documents sufficient testing, including representative real-CLI/PTY verification for the runtime behavior change and clearly identified pre-existing test failures.

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.

models switch: choose a profile interactively when none is named

2 participants