What problem does this solve?
I run several repos as a single operator, each its own Pad workspace. I want one view of "what's ready, what's blocked, what's stale" across all of them — the pad project ready / stale / standup output, unioned.
Today that's strictly one workspace at a time. --workspace is a single-value persistent flag (StringVar, cmd/pad/main.go:126), and the CLI docs describe the model as "All commands operate on the workspace detected from .pad.toml in your directory tree."
The only account-wide read I could find is GET /api/v1/auth/export (handleExportAccount), and it isn't a substitute for a dashboard-style query: it only dumps workspaces where ws.OwnerID == user.ID, it's wrapped in a 60-second timeout, and it 403s the entire export if the caller has restricted collection access in any owned workspace. That's a GDPR dump, not a query surface.
We currently solve this by fanning the CLI out per workspace and merging the --format json output in a wrapper. It works fine. My question is whether that fan-out is the intended pattern or an actual gap.
Proposed solution
I'd rather ask about direction than push a shape. The rough space, no strong preference between them:
- accept a list on
--workspace (--workspace a,b,c)
- a separate aggregate subcommand, leaving single-workspace commands' contracts untouched
- a read-only aggregate endpoint the CLI sits on
One design detail worth surfacing early: item numbers are workspace-unique (internal/store/items.go — GetItemByRef scopes by workspace_id), so every workspace has a TASK-1. Any aggregate output has to namespace refs somehow. Our wrapper does it client-side, but a native version would need to pick a convention, and that's a decision better made deliberately than discovered.
I also recognize this cuts against where v0.11.0 went — SSE membership checks at connect time (TASK-264), OAuth-consent scoping of pad_workspace list (BUG-2102). Those exist specifically to stop workspaces bleeding into each other. Anything aggregate has to resolve per workspace against the caller's real membership rather than short-circuiting it, which is genuine work and a reasonable reason to decline.
To be clear: "the fan-out wrapper is the intended pattern" is a completely useful answer. If that's the design intent I'll keep ours and stop wondering, and it may be worth a line in the docs for the next person. If you would want this in core, I'm happy to implement it — I'd want to know what shape you'd accept before anyone writes Go.
Component
CLI
Alternatives considered
- Per-workspace CLI fan-out + client-side JSON merge. Shipped, working, and what prompted the question — a thin wrapper over
pad --workspace <slug> project ... --format json.
GET /api/v1/auth/export as a poor man's rollup. Wrong shape: owner-only, 60s timeout, all-or-nothing under restricted access.
- Querying SQLite directly. Rejected — bypasses every access check and couples to the schema.
What problem does this solve?
I run several repos as a single operator, each its own Pad workspace. I want one view of "what's ready, what's blocked, what's stale" across all of them — the
pad project ready/stale/standupoutput, unioned.Today that's strictly one workspace at a time.
--workspaceis a single-value persistent flag (StringVar,cmd/pad/main.go:126), and the CLI docs describe the model as "All commands operate on the workspace detected from.pad.tomlin your directory tree."The only account-wide read I could find is
GET /api/v1/auth/export(handleExportAccount), and it isn't a substitute for a dashboard-style query: it only dumps workspaces wherews.OwnerID == user.ID, it's wrapped in a 60-second timeout, and it 403s the entire export if the caller has restricted collection access in any owned workspace. That's a GDPR dump, not a query surface.We currently solve this by fanning the CLI out per workspace and merging the
--format jsonoutput in a wrapper. It works fine. My question is whether that fan-out is the intended pattern or an actual gap.Proposed solution
I'd rather ask about direction than push a shape. The rough space, no strong preference between them:
--workspace(--workspace a,b,c)One design detail worth surfacing early: item numbers are workspace-unique (
internal/store/items.go—GetItemByRefscopes byworkspace_id), so every workspace has aTASK-1. Any aggregate output has to namespace refs somehow. Our wrapper does it client-side, but a native version would need to pick a convention, and that's a decision better made deliberately than discovered.I also recognize this cuts against where v0.11.0 went — SSE membership checks at connect time (TASK-264), OAuth-consent scoping of
pad_workspace list(BUG-2102). Those exist specifically to stop workspaces bleeding into each other. Anything aggregate has to resolve per workspace against the caller's real membership rather than short-circuiting it, which is genuine work and a reasonable reason to decline.To be clear: "the fan-out wrapper is the intended pattern" is a completely useful answer. If that's the design intent I'll keep ours and stop wondering, and it may be worth a line in the docs for the next person. If you would want this in core, I'm happy to implement it — I'd want to know what shape you'd accept before anyone writes Go.
Component
CLI
Alternatives considered
pad --workspace <slug> project ... --format json.GET /api/v1/auth/exportas a poor man's rollup. Wrong shape: owner-only, 60s timeout, all-or-nothing under restricted access.