Skip to content

cli: let flags be written anywhere on the line - #10

Closed
ale-rinaldi wants to merge 4 commits into
feat/multi-accountfrom
fix/position-independent-flags
Closed

cli: let flags be written anywhere on the line#10
ale-rinaldi wants to merge 4 commits into
feat/multi-accountfrom
fix/position-independent-flags

Conversation

@ale-rinaldi

Copy link
Copy Markdown
Member

Answers "can't flags be position-independent?" — yes, everywhere, not just before the subcommand.

Based on #9, since --account is 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:

$ dwshell --config /x list
dwshell: authentication required: run `dwshell login`   # took "list" for a machine name
$ dwshell --json list
flag provided but not defined: -json

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 TestValueFlagsMatchesTheFlagsActuallyRegistered walks the package's own source with go/ast and 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 — --user was missing, harmless only because login is dispatched by name.

Verification

Live, against a copy of a real configuration:

form result
--account … list (the reported case) works
list --account … works
--json list (previously "flag not defined") works
ls --config … GHE:/etc and ls GHE:/etc --config … identical output
shell shortcut, -c before and after the agent both work
shell list for a machine named like a command still resolves as a machine
help, version, --version unchanged
shell -- GHE -c … -- respected
list --nonesiste still fails loudly

gofmt, go vet, go test -race ./... green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MvidAFW9a2r4hTgHPW9ywG

ale-rinaldi and others added 4 commits September 5, 2026 23:01
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
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.

1 participant