package manager: login, logout, owner and yank commands #22

Closed
opened 2026-10-05 21:07:11 +00:00 by orion · 7 comments
Collaborator

Goal

The write service of packages.torb.dev already has accounts, scoped tokens, owners and yank over /api/1
(tools/registry), but torb can only publish. Add the client commands torb login, torb logout, torb owner
and torb yank, so an owner manages a package without writing curl calls. torb deprecate is split out to #279.

Source

  • ROADMAP.md, the package manager: "yank, owner and login as commands over the API that exists" is the open item
  • RELEASE.md: section 7.3 (yank, --undo), 7.1 (owners and roles), 7.6 (tokens, second factor, Torb-Code), 7.13 ("Not built" list), 2 (registry readiness)
  • tools/registry/README.md (the API and its Torb-Code rules, #274/#275), compiler/src/package/upload.trb (the client's HTTP helper and token lookup), compiler/src/package/user-config.trb (the torb home files)

Design (decided by the loop, see the comment below for the reasons)

Question Decision
Command names flat, like publish: torb login, torb logout, torb owner list|add|remove, torb yank
Which registry --registry <url> first; else the registry the package's (or owner's) owner is bound to by a registry line when run inside a project; else the public registry (registryOf). Never through a mirror
torb login [--registry <url>] reads the token from standard input (one line; a prompt on a terminal), never from a flag or an argument. Verifies it with GET /api/1/tokens before storing it, so a wrong or expired token is refused and nothing is written
Token storage the machine's own file credentials.trb in the torb home ($TORB_CREDENTIALS overrides, like $TORB_CONFIG), a receiver script of literal lines token "<registry url>", value: "torb_..." read statically and never evaluated. A separate file from config.trb so config.trb stays shareable. Created with mode 0600 before the secret is written (read-only attribute on Windows, which is the profile's ACL otherwise). A project never holds a token
Token lookup (publish and the new commands) TORB_TOKEN, then the stored token of that registry, then trusted publishing (publish only). A stored token is returned only for https: or a loopback http: registry. The token never appears in an argument or a log line
torb logout [--registry <url>] removes that registry's line; says so when there was none
torb owner list <owner> (GET /owners/<owner>), add <owner> <account> [--role owner|publisher] (default publisher, the least privilege), remove <owner> <account>. add and remove send Torb-Code from --code <code> or $TORB_CODE (an authenticator app or recovery code) when the account has a second factor, and print the registry's own refusal otherwise
torb yank <owner/name> <version> --reason "..." and --undo the reason is required to yank (the lock prints it on every build) and refused with --undo. On a file: registry the command appends the yank or unyank line itself (as publish appends a release) and is refused when that registry signs its index; there are no accounts on a directory, so login and owner refuse a file: registry in words
Exit codes as the other package commands: 1 for a refusal, 2 for a usage error

Not in this issue: step-up by passkey for owner add\|remove (a passkey-only account) and deprecate, split into #279 and #280.

Acceptance criteria

  • torb login stores a verified token that torb publish, owner and yank then use; TORB_TOKEN still wins; a token the registry refuses stores nothing
  • credentials.trb is created 0600 on POSIX, read statically (a malformed line is an error at its line), holds one line per registry, and a second login replaces the line of its registry; torb logout removes it
  • A stored token is never sent over plain http: to a non-loopback host, never to a mirror, never printed, and never in a process argument (test the argument list and the output)
  • torb owner list|add|remove and torb yank [--undo] work against /api/1, print the registry's answer, and print its refusals in its words (403, 409, the Torb-Code rules)
  • torb yank [--undo] on a file: registry appends the record and readIndex then excludes or re-admits the version; login and owner on a file: registry are refused with a sentence
  • Usage errors (no package, no version, --undo with --reason, yank without --reason, unknown role) exit 2 with a sentence; torb --help and usage.trb list the new commands
  • Tests: pure parts (credentials file read/render, option parsing, token lookup order, file: yank) in compiler/tests; the network commands in tools/registry/tests against the registry over loopback with a file: storage and a temporary database, as client.test.trb drives torb publish (a full round: operator account, login, owner add, publish, yank, yank --undo, owner remove, logout). No production registry is touched
  • Docs: new pages docs/tooling/torb-login.md, torb-logout.md, torb-owner.md, torb-yank.md, torb-publish.md's token section points at login, docs/tooling/index.md and the-torb-command.md list them; RELEASE.md 7.13 "Not built" and 7.11 "Not built yet", ROADMAP.md's package-manager item and tools/registry/README.md "Not built yet" no longer name yank, owner and login; skills/ regenerated (torb docs skill docs skills)

Design record point

RELEASE.md sections 7.3 (torb yank acme/http 1.2.4 --reason "...", --undo) and 7.6 (tokens scoped and stored by the client), ROADMAP milestone 8 "the package manager's remaining commands". The storage file is new and is recorded in RELEASE.md 7.13 as part of this change.

Seed impact

None. TorbScript only in compiler/src/cli, compiler/src/package and tools/registry/tests: no syntax change, no new native (File.setPermissions and the curl/wget helper of upload.trb exist), no driver flag, no std name the compiler looks up by string. One PR, no seed refresh.

Size

One PR landing as two Conventional Commits:

  1. feat(package): torb login and logout with stored registry tokens (credentials file, token lookup shared with publish, registry resolution, tests, docs)
  2. feat(package): torb owner and torb yank over the registry API (the two commands, file: yank, tests, docs, design-record updates, regenerated skills)
  • split out: #279 (deprecate: registry endpoint, index record and client) and #280 (passkey step-up for owner add|remove), both depend on this issue
  • depends on nothing; #274 and #275 (landed) define the Torb-Code rules this client follows
## Goal The write service of packages.torb.dev already has accounts, scoped tokens, owners and yank over `/api/1` (`tools/registry`), but `torb` can only publish. Add the client commands `torb login`, `torb logout`, `torb owner` and `torb yank`, so an owner manages a package without writing `curl` calls. `torb deprecate` is split out to #279. ## Source - [ROADMAP.md, the package manager](https://git.torb.dev/torbscript/language/src/branch/main/docs/ROADMAP.md): "`yank`, `owner` and `login` as commands over the API that exists" is the open item - [RELEASE.md](https://git.torb.dev/torbscript/language/src/branch/main/docs/design/RELEASE.md): section 7.3 (yank, `--undo`), 7.1 (owners and roles), 7.6 (tokens, second factor, `Torb-Code`), 7.13 ("Not built" list), 2 (registry readiness) - `tools/registry/README.md` (the API and its `Torb-Code` rules, #274/#275), `compiler/src/package/upload.trb` (the client's HTTP helper and token lookup), `compiler/src/package/user-config.trb` (the torb home files) ## Design (decided by the loop, see the comment below for the reasons) | Question | Decision | |---|---| | Command names | flat, like `publish`: `torb login`, `torb logout`, `torb owner list\|add\|remove`, `torb yank` | | Which registry | `--registry <url>` first; else the registry the package's (or owner's) owner is bound to by a `registry` line when run inside a project; else the public registry (`registryOf`). Never through a mirror | | `torb login [--registry <url>]` | reads the token from standard input (one line; a prompt on a terminal), never from a flag or an argument. Verifies it with `GET /api/1/tokens` before storing it, so a wrong or expired token is refused and nothing is written | | Token storage | the machine's own file `credentials.trb` in the torb home (`$TORB_CREDENTIALS` overrides, like `$TORB_CONFIG`), a receiver script of literal lines `token "<registry url>", value: "torb_..."` read statically and never evaluated. A separate file from `config.trb` so `config.trb` stays shareable. Created with mode `0600` before the secret is written (read-only attribute on Windows, which is the profile's ACL otherwise). A project never holds a token | | Token lookup (publish and the new commands) | `TORB_TOKEN`, then the stored token of that registry, then trusted publishing (publish only). A stored token is returned only for `https:` or a loopback `http:` registry. The token never appears in an argument or a log line | | `torb logout [--registry <url>]` | removes that registry's line; says so when there was none | | `torb owner` | `list <owner>` (`GET /owners/<owner>`), `add <owner> <account> [--role owner\|publisher]` (default `publisher`, the least privilege), `remove <owner> <account>`. `add` and `remove` send `Torb-Code` from `--code <code>` or `$TORB_CODE` (an authenticator app or recovery code) when the account has a second factor, and print the registry's own refusal otherwise | | `torb yank <owner/name> <version> --reason "..."` and `--undo` | the reason is required to yank (the lock prints it on every build) and refused with `--undo`. On a `file:` registry the command appends the `yank` or `unyank` line itself (as `publish` appends a release) and is refused when that registry signs its index; there are no accounts on a directory, so `login` and `owner` refuse a `file:` registry in words | | Exit codes | as the other package commands: 1 for a refusal, 2 for a usage error | Not in this issue: step-up by passkey for `owner add\|remove` (a passkey-only account) and `deprecate`, split into #279 and #280. ## Acceptance criteria - [ ] `torb login` stores a verified token that `torb publish`, `owner` and `yank` then use; `TORB_TOKEN` still wins; a token the registry refuses stores nothing - [ ] `credentials.trb` is created `0600` on POSIX, read statically (a malformed line is an error at its line), holds one line per registry, and a second `login` replaces the line of its registry; `torb logout` removes it - [ ] A stored token is never sent over plain `http:` to a non-loopback host, never to a mirror, never printed, and never in a process argument (test the argument list and the output) - [ ] `torb owner list|add|remove` and `torb yank [--undo]` work against `/api/1`, print the registry's answer, and print its refusals in its words (`403`, `409`, the `Torb-Code` rules) - [ ] `torb yank [--undo]` on a `file:` registry appends the record and `readIndex` then excludes or re-admits the version; `login` and `owner` on a `file:` registry are refused with a sentence - [ ] Usage errors (no package, no version, `--undo` with `--reason`, yank without `--reason`, unknown role) exit 2 with a sentence; `torb --help` and `usage.trb` list the new commands - [ ] Tests: pure parts (credentials file read/render, option parsing, token lookup order, `file:` yank) in `compiler/tests`; the network commands in `tools/registry/tests` against the registry over loopback with a `file:` storage and a temporary database, as `client.test.trb` drives `torb publish` (a full round: operator account, `login`, `owner add`, `publish`, `yank`, `yank --undo`, `owner remove`, `logout`). No production registry is touched - [ ] Docs: new pages `docs/tooling/torb-login.md`, `torb-logout.md`, `torb-owner.md`, `torb-yank.md`, `torb-publish.md`'s token section points at `login`, `docs/tooling/index.md` and `the-torb-command.md` list them; RELEASE.md 7.13 "Not built" and 7.11 "Not built yet", ROADMAP.md's package-manager item and `tools/registry/README.md` "Not built yet" no longer name `yank`, `owner` and `login`; `skills/` regenerated (`torb docs skill docs skills`) ## Design record point RELEASE.md sections 7.3 (`torb yank acme/http 1.2.4 --reason "..."`, `--undo`) and 7.6 (tokens scoped and stored by the client), ROADMAP milestone 8 "the package manager's remaining commands". The storage file is new and is recorded in RELEASE.md 7.13 as part of this change. ## Seed impact None. TorbScript only in `compiler/src/cli`, `compiler/src/package` and `tools/registry/tests`: no syntax change, no new native (`File.setPermissions` and the `curl`/`wget` helper of `upload.trb` exist), no driver flag, no std name the compiler looks up by string. One PR, no seed refresh. ## Size One PR landing as two Conventional Commits: 1. `feat(package): torb login and logout with stored registry tokens` (credentials file, token lookup shared with `publish`, registry resolution, tests, docs) 2. `feat(package): torb owner and torb yank over the registry API` (the two commands, `file:` yank, tests, docs, design-record updates, regenerated skills) ## Related - split out: #279 (`deprecate`: registry endpoint, index record and client) and #280 (passkey step-up for `owner add|remove`), both depend on this issue - depends on nothing; #274 and #275 (landed) define the `Torb-Code` rules this client follows
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, label loop). Client commands against the registry API per RELEASE.md readiness; tests against a local or file: registry, no production registry touched. Likely adds new subcommands, so command names and token storage may need an owner decision, to settle in refine. Release freeze for 0.1.0 (2026-10-06) holds new PRs, next: refine (queued)

[loop:torbscript] intake: in scope (author orion, label loop). Client commands against the registry API per RELEASE.md readiness; tests against a local or `file:` registry, no production registry touched. Likely adds new subcommands, so command names and token storage may need an owner decision, to settle in refine. Release freeze for 0.1.0 (2026-10-06) holds new PRs, next: refine (queued)
Author
Collaborator

[loop:torbscript] refine: still queued behind the intake order. next: waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Z

[loop:torbscript] refine: still queued behind the intake order. next: waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Z
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261009 until 2026-10-10T00:30Z (ready buffer holds only 3 ready issues, 2 of them waiting on a seed, so refine is allowed; the earlier "buffer full" marker was stale)

[loop:torbscript] claim refine by session:loop-torbscript-20261009 until 2026-10-10T00:30Z (ready buffer holds only 3 ready issues, 2 of them waiting on a seed, so refine is allowed; the earlier "buffer full" marker was stale)
orion changed title from package manager: login, owner, yank and deprecate commands to package manager: login, logout, owner and yank commands 2026-10-09 20:26:46 +00:00
Author
Collaborator

[loop:torbscript] refine: ready (claim 2839 released). Decided by the loop, from RELEASE.md 7.3/7.6/7.13, the registry README and upload.trb; the issue body now holds goal, criteria, seed impact (none) and size (one PR, two commits).

  • Names: flat like publish: login, logout, owner list|add|remove, yank [--undo]; logout added as the inverse of login. Reason: existing commands are flat, the issue and RELEASE.md 7.3 use these words.
  • Token storage: a separate credentials.trb in the torb home ($TORB_CREDENTIALS), a statically read receiver script like config.trb, mode 0600, never in a project. Reason: config.trb is meant to be shared and mirrors never get secrets; reusing the receiver-script format needs no new native (File.setPermissions exists). Lookup: TORB_TOKEN, stored token, trusted publishing; stored tokens only for https: or loopback http:.
  • login input: token from standard input only, verified with GET /api/1/tokens before it is stored. Reason: a flag would leak into shell history and process lists; the API has no device flow to mint one from the client.
  • Registry choice: --registry, else the owner's registry binding, else the public registry; owner/login refuse file:, yank appends to a file: registry (gives the local-registry test the issue asks for).
  • Second factor: owner add|remove take --code/$TORB_CODE (app or recovery code). Passkey step-up is split to #280 because it needs an interactive approval loop.
  • Split: deprecate has no registry API, no index record and no stored flag, so it is a registry plus index plus client change of its own: #279 (readers skip unknown index words, so it is not a break of the published index; verified by a test there).
  • Owner decision needed: none. Public statements, costs, accounts and breaking changes are not touched; nothing is published.

next: implement

[loop:torbscript] refine: ready (claim 2839 released). Decided by the loop, from RELEASE.md 7.3/7.6/7.13, the registry README and `upload.trb`; the issue body now holds goal, criteria, seed impact (none) and size (one PR, two commits). - **Names:** flat like `publish`: `login`, `logout`, `owner list|add|remove`, `yank [--undo]`; `logout` added as the inverse of `login`. Reason: existing commands are flat, the issue and RELEASE.md 7.3 use these words. - **Token storage:** a separate `credentials.trb` in the torb home (`$TORB_CREDENTIALS`), a statically read receiver script like `config.trb`, mode `0600`, never in a project. Reason: `config.trb` is meant to be shared and mirrors never get secrets; reusing the receiver-script format needs no new native (`File.setPermissions` exists). Lookup: `TORB_TOKEN`, stored token, trusted publishing; stored tokens only for `https:` or loopback `http:`. - **`login` input:** token from standard input only, verified with `GET /api/1/tokens` before it is stored. Reason: a flag would leak into shell history and process lists; the API has no device flow to mint one from the client. - **Registry choice:** `--registry`, else the owner's `registry` binding, else the public registry; `owner`/`login` refuse `file:`, `yank` appends to a `file:` registry (gives the local-registry test the issue asks for). - **Second factor:** `owner add|remove` take `--code`/`$TORB_CODE` (app or recovery code). Passkey step-up is split to #280 because it needs an interactive approval loop. - **Split:** `deprecate` has no registry API, no index record and no stored flag, so it is a registry plus index plus client change of its own: #279 (readers skip unknown index words, so it is not a break of the published index; verified by a test there). - **Owner decision needed:** none. Public statements, costs, accounts and breaking changes are not touched; nothing is published. next: implement
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261009 until 2026-10-10T04:30Z

[loop:torbscript] claim implement by session:loop-torbscript-20261009 until 2026-10-10T04:30Z
Author
Collaborator

[loop:torbscript] implement: PR #283 open, CI green

  • Commits: 6622ea59 feat(package): torb login and logout with stored registry tokens, 6f9d340b feat(package): torb owner and torb yank over the registry API.
  • Network round tools/registry/tests/commands.test.trb (login, owner add, publish, yank, yank --undo, owner remove, logout; the token in no curl argument and no output) passes in the registry job.
  • Choices beyond the refine comments: login/logout always default to the public registry (there is no owner to bind); owner list sends no token; wget now reads headers from a --config startup file too, and header files are written 0600.
  • Token usage: about 0.65M tokens for this implement run, one CI fix round (pdisc stamp of tools/registry/README.md).

next: waiting (merge gate review of PR #283)

[loop:torbscript] implement: PR #283 open, CI green - Commits: 6622ea59 `feat(package): torb login and logout with stored registry tokens`, 6f9d340b `feat(package): torb owner and torb yank over the registry API`. - Network round `tools/registry/tests/commands.test.trb` (login, owner add, publish, yank, yank --undo, owner remove, logout; the token in no curl argument and no output) passes in the registry job. - Choices beyond the refine comments: `login`/`logout` always default to the public registry (there is no owner to bind); `owner list` sends no token; `wget` now reads headers from a `--config` startup file too, and header files are written 0600. - Token usage: about 0.65M tokens for this implement run, one CI fix round (pdisc stamp of tools/registry/README.md). next: waiting (merge gate review of PR #283)
argus closed this issue 2026-10-09 21:24:52 +00:00
Author
Collaborator

[loop:torbscript] close: landed via PR #283 by the merge gate as 6622ea596e and 6f9d340b68, main CI green on 6f9d340b. Issue closed. Unblocked: #279 (waits for PR #282), #280 (in implementation). next: done

[loop:torbscript] close: landed via PR #283 by the merge gate as https://git.torb.dev/torbscript/language/commit/6622ea596e and https://git.torb.dev/torbscript/language/commit/6f9d340b68, main CI green on 6f9d340b. Issue closed. Unblocked: #279 (waits for PR #282), #280 (in implementation). next: done
Sign in to join this conversation.
No labels
loop
ready
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#22
No description provided.