feat: hub parity — projects API, PATs, releases, addons, AI/CI-ready contract - #2
Merged
Conversation
…rs/current/projects
The hub renamed teams to projects and moved runtime status off the Stack and
StackResource entities onto releases. Swap the API module to the public
github.com/Stackdome/stackdome, rename every Teams/TeamName call site to its
Projects/ProjectName equivalent, and read resource status from the stack's
release live status.
- config: team_name -> project_name, with a legacy read of team_name
- auth: ResolveDefaultProject via GET /api/v1/users/current/projects
- stackfile: ImageSpec/BuildSpec -> SourceSpec{Image,Git}, Stateful ->
WorkloadType=StatefulService
- volumes: volumes are per-stack now; volume list/delete take --stack
…rminal GetStackLiveStatus preferred the latest release, so `status` and `open` described a release that may still be rolling out instead of the one serving traffic. Prefer the converged release, falling back to latest only when nothing has converged yet. deploy --wait polls via output.StackRelease and still prefers latest. waitForStack also spun until timeout on a superseded release; add Superseded to the terminal states.
…env sessions file-free
Deploy now resolves stackfile secret/addon names to IDs via ResolveStack and sends the whole document to PUT /stacks/apply, replacing the FindStackByName read-modify-write (which raced and tripped the server's readOnly-PUT rejection). --wait follows the release the apply just created — identified from the apply response's latest_release, or the newest release as fallback — instead of polling GetStack, which could latch onto the *previous* release. Events stream over SSE to stderr and resume with ?after_sequence=N when the connection drops. Failed/Cancelled/Superseded releases now exit non-zero with the failure detail; only Released exits 0. Adds `stackdome release list|info|cancel|events [-f]` and a minimal postgres addon list/find in the client for name resolution.
PUT /stacks/apply only stores the stack document — the server creates no release for it, so deploy rolled out nothing and --wait followed whichever release happened to already exist (reporting a stale success for existing stacks, erroring for new ones). Deploy now applies and then calls CreateRelease, following the id it returns; the latest-release guessing is gone. The release is created with or without --wait, since that is the deploy. Also: Ctrl-C during --wait exits 130 rather than 1, `release events -f` exits non-zero when the stream ends on an error frame, and the stream's give-up send no longer blocks on a cancelled context.
- add `stackdome whoami`: user, org, project, auth method, token source, server - destroy takes a stack name via -s/--stack like every other command - status gains the -s shorthand; both now use resolveStackID - -o json/yaml: secret create/set, deploy (no --wait), and version print the result object; release events -f streams NDJSON instead of formatted prose - root --help documents non-interactive usage and exit codes - -o yaml marshals through JSON so wire names survive - token timestamps carry a timezone (local RFC3339)
- status --watch -o json/yaml emitted the human table plus TTY clear-screen
escapes; structured mode now prints one object per tick, no redraw
- config view -o json/yaml serialized access/refresh tokens verbatim; redact on
a copy at the view layer so Save() still persists the real values
- release events -f NDJSON keeps the SSE event name: {"event":..,"data":..}
…uctured output gaps
- logs: openLogStream reused the 30s-timeout HTTP client, cutting `logs -f`
and `build logs -f` off mid-stream. Reuse only the transport, as
openReleaseEventStream does; the context governs the stream's lifetime.
- status: output.StackRelease preferred the latest release while
GetStackLiveStatus reports on the converged one, so the header and the
resource rows described different releases. Both prefer converged now.
- login/signup: Save reverts values still equal to STACKDOME_URL /
STACKDOME_TOKEN, so logging in with those exact values wrote a config
missing server_url/token. New Config.AdoptEnvValues clears the latches.
- usage errors now exit 4 as the root help promises: ParseFormat returns a
ValidationError, SetFlagErrorFunc wraps flag errors, and wrapArgErrors
wraps every command's positional-args check.
- open: added the -o json|yaml path (prints the public URLs, no browser).
- deploy: structured output is now a {stack, release} envelope so scripted
deploys can follow the release's events.
… token
GET /users/current/projects returns an empty list on deployed servers even
when the user has a default project, so login and signup failed outright with
"No projects found for your account." ResolveDefaultProject now falls back to
GET /organizations/{org_id}/projects, which reports it correctly. Remove the
fallback once the per-user endpoint is fixed server-side.
Login also resolved the project before saving, throwing away a valid token
when the lookup failed. The new persistLogin helper — shared by login
--token, login with credentials, and signup — writes the credential first and
only warns when the project cannot be resolved; resolveScope retries the
lookup on the next command.
internal/stackfile was a stale copy of the hub's pkg/stackfile. It had
drifted on self-references: it emitted self_output "public.http.url" while
the server only declares "public_url" for a single-port resource (see
pkg/models/output_descriptor.go stackResourceOutputKey), which 400'd deploys.
Delete the forked types/parse/convert/resolve and their tests, and keep
internal/stackfile as a thin CLI layer over the hub package: path-based
Load with CLI errors, the CLI-only env_file merge (now applied in Load, so
validate resolves it too), the compose converter and JSON passthrough.
env_file and stateful are not in the hub schema. env_file survives as a
CLI extension read off the raw YAML; stateful was already a no-op since
openapi.StackResource has no such field, so it is dropped and init no
longer emits it. init's starter template also used the multi-port spelling
{{ db.port.postgres }} for a single-port resource — fixed.
List tables print IDs through shortID(), but build info/logs and release info/cancel/events passed the arg straight to the API, so the IDs the CLI displayed came back as "Resource not found". Add docker-style prefix resolution: exact match wins, unique prefix resolves, ambiguous prefixes list the matches, no match is a NotFound.
STACKDOME_ORG/STACKDOME_PROJECT overlay the config like STACKDOME_URL and
STACKDOME_TOKEN do (never persisted), letting a narrowly scoped API token skip
project discovery it is not allowed to perform. When discovery does fail under
token auth the error names the missing scope instead of suggesting `login`.
`config set-stack` stored its argument verbatim, so a stack name bricked every
later command. Both it and the --stack flag now go through one resolver that
accepts a name, a full ID, or an ID prefix.
Also: redact tokens in `config view` with a fixed marker, drop the SSE end frame
that printed a stray `{}` after logs, default an empty port protocol to HTTP,
and print release info timestamps in local time like everything else.
…helper
The api-server always closes a stream with `event: end`, so IsEnd no longer
sniffs the payload — a log line that is literally `{}` prints again.
A config written before set-stack resolved its argument still holds a stack
name; resolveStackID now heals that on first use instead of failing, and says
so when an env-token session cannot persist the fix.
An expired or unparseable access token comes back as 403 with the reason in the error body, not 401, so the 401-only refresh transport let every session die at access-token expiry. Gate on the reason instead, matching the web UI's patterns (frontend/src/api/client.ts) on both reason and items[0].reason. A genuine permission denial still surfaces with its body intact.
The API's ImageBuild payload has no created_at/updated_at (hub #10), so build list and build info rendered Started and Duration as "-". Fall back to status.conditions[].last_transition_time: BuildJobCreated (else earliest) for the start, Available (else latest) for the end. Duration is only derived for terminal builds; in-progress builds show elapsed-since-started.
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.
Summary
Brings the CLI in sync with the hub's projects API and adds the high-value missing surface, with a machine-friendly contract so agents/CI can drive it end-to-end. 23 commits, each task independently reviewed; whole-branch final review clean; live-verified against stackdome.io (2 releases deployed, 52-command functional sweep).
Core
github.com/Stackdome/stackdome(noreplace, plaingo build/go mod tidy),teams/{name}→projects/{name}, default project resolved via/users/current/projectswith org-endpoint fallback (server bug workaround, marked for removal)internal/stackfiledeleted; consumes hub'spkg/stackfile— output vocabulary can no longer drift (test pins emittedself_outputagainst the server's descriptor list)PUT stacks/applythenPOST releases,--waitfollows that release's SSE event stream withafter_sequenceresume; Failed/Cancelled/Superseded exit non-zeroNew commands
token(scoped PATs) ·release list/info/cancel/events·addon postgres(CRUD/credentials/backup) ·build logs(SSE) ·volume create·whoamiAI/CI contract
STACKDOME_TOKEN/URL/ORG/PROJECT, zero config file, nothing persisted-o json|yamleverywhere, data→stdout / humans→stderr, exit codes 0/1/2/3/4/5/130, TTY-guarded prompts (--yesfor scripts), short-ID prefix resolution for build/release argsVerification
go build && go vet && go test -race ./...clean throughoutapi-server/HUB-ISSUES-2026-08-06.md(separate repo)