cli: let flags be written anywhere on the line - #10
Closed
ale-rinaldi wants to merge 4 commits into
Closed
Conversation
Choosing between DWService accounts per command, the way the AWS CLI chooses a profile, under two constraints that shape the whole design: someone who does not want the feature must never learn it exists, and an existing configuration keeps working. Hence the email as the key, the first account registered becoming the default silently, and a flat configuration migrated in memory on load rather than rewritten underneath a read-only command. @ is not available as a selector: dwshell alice@myserver already means the remote OS user. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG
Six tasks. The design keeps the multi-account logic inside internal/config, so the client changes from cfg.Session to cfg.Current().Session and little else, and the two constraints that motivated the feature are each pinned by a test rather than left as intentions: loading a flat configuration must not rewrite the file, and a single account must be usable whether or not it is marked default. The live check exploits a property of the two real accounts: the second owns the machines the first sees as shares, so a wrong selection shows up as own-versus-shared on the same machine instead of something subtle. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG
Log in twice with different emails and both are kept; choose between them with --account or DWSHELL_ACCOUNT. The email is the account's identity, so logging in again with a known one refreshes it, exactly as before. The feature is built to be invisible until it is wanted. With a single account there is no default to think about: Select uses the lone account whether or not it is marked default, no output changes, and the word appears only in `account list`. That is a test, not an intention. An existing configuration is migrated in memory on load and written in the new shape only when something saves for a reason of its own — a read-only command must not rewrite the user's file behind their back. Verified against a real configuration: after `list` the file still had its flat keys, and only a command that saves turned them into accounts. Removing the default promotes the survivor when exactly one remains and otherwise leaves it unset, refusing later commands with an explanation rather than silently pointing them at the wrong account. login refuses --account: which account it touches is decided by the email being logged in, and it deliberately does not select an account, since "several accounts and no default" is a state logging in resolves. Verified live with two real accounts, which see the same machines from opposite sides: Regia reads shared from one and own from the other, so a wrong selection would be obvious rather than subtle. Checked through the flag, the environment variable, the default, and back. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG
dwshell dispatched on os.Args[1], so a flag could only follow its subcommand. `dwshell --config x list` did not complain — it took "list" for a machine name and tried to connect to it, which is worse than an error, and `dwshell --json list` failed with a puzzling "flag provided but not defined". Arguments are now partitioned into flags and positionals before anything is dispatched, so the command is the first positional wherever it sits. `dwshell list --account a@b` and `dwshell --account a@b list` are the same command, and the same holds inside `account <verb>` and for the shell shortcut. Telling a flag's value from a positional needs to know which flags consume a following token, and that registry is the hazard: a value flag missing from it makes dwshell take the value for a command name and quietly do the wrong thing. So a test walks the package's own source with go/ast and holds the registry to the flags actually registered, in both directions. It earned its place immediately, catching two stale entries I had added from memory, and it found a real gap: --user was absent, harmless only because login is dispatched by name. Everything after a bare -- is positional, so an agent or path beginning with a dash can still be passed. Verified live: the reported case, both orders of every flag, a flag between two positionals, the shell shortcut with the flag on either side, the explicit `shell <name>` form for a machine named like a command, help, version, -- and an unknown flag still failing loudly. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG
ale-rinaldi
force-pushed
the
feat/multi-account
branch
from
September 5, 2026 21:40
55b4f75 to
139ca69
Compare
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.
Answers "can't flags be position-independent?" — yes, everywhere, not just before the subcommand.
Based on #9, since
--accountis the motivating case and the registry test covers it.What was wrong
dwshell dispatched on
os.Args[1], so a flag could only follow its subcommand. The failure mode was not an error:The first is the bad one: it did not complain, it quietly did something else.
What it does now
Arguments are partitioned into flags and positionals before anything is dispatched, so the command is the first positional wherever it sits — at the top level, inside
account <verb>, and in the shell shortcut. Everything after a bare--is positional, so an agent or path starting with a dash can still be passed.The hazard, and the guard
Telling a flag's value from a positional requires knowing which flags consume a following token. That registry is the risk: a value flag missing from it makes dwshell take the value for a command name and quietly do the wrong thing.
So
TestValueFlagsMatchesTheFlagsActuallyRegisteredwalks the package's own source withgo/astand holds the registry to what is actually registered, in both directions — missing entries and stale ones.It earned its place immediately: it rejected two entries I had added from memory (
description,group, which live on another branch), and it surfaced a real pre-existing gap —--userwas missing, harmless only becauseloginis dispatched by name.Verification
Live, against a copy of a real configuration:
--account … list(the reported case)list --account …--json list(previously "flag not defined")ls --config … GHE:/etcandls GHE:/etc --config …-cbefore and after the agentshell listfor a machine named like a commandhelp,version,--versionshell -- GHE -c …--respectedlist --nonesistegofmt,go vet,go test -race ./...green.🤖 Generated with Claude Code
https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG