Local studio
Normal pipeline and CLI execution is storage-free. The studio is an optional local projection of an append-only SQLite event store or a finished NDJSON trace. Core does not import it.
Record one workbench run by placing --store before the command file:
bunx tubeless run --store .tubeless/runs.sqlite ./scripts/import.ts -- --source rows.txt
Inspect recorded runs without a browser:
bunx tubeless history --store .tubeless/runs.sqlite
bunx tubeless history --json <run-id>
bunx tubeless history --trace run.ndjson
Open the studio only when you want a browser view:
bunx tubeless ui --store .tubeless/runs.sqlite
bunx tubeless ui --trace run.ndjson
Both forms can be read-only. The NDJSON form is always read-only: it does not
offer clear-history or accept a studio catalog / --command. Studio never
guesses executable modules from recorded definitions. Register marked
definePipelineCommand exports to make a
Run pipeline action available:
bunx tubeless ui \
--store .tubeless/runs.sqlite \
--command ./scripts/import.ts \
--command ./scripts/publish.ts
--export selects the export when you register exactly one --command. Launch
forms render each command's structured parameter contract: booleans as
checkboxes, constrained strings as selects, numbers with their bounds, and
paths or unconstrained values as text fields. Submitted values go through the
normal typed command parser without a shell. Preview plan uses the dry-run
and stepIds/targets values from the same form; it never creates a run.
Default bind address is 127.0.0.1, default port is 4317, and the default
store is .tubeless/runs.sqlite. Browser-triggered execution requires a
loopback host (127.0.0.1, ::1, or localhost). Non-loopback binding is
rejected when any command is registered. A non-loopback --host without
commands is allowed and stays read-only: the store is visible to anyone who
can reach the port, and clear-history is not wired. That bind is a risk you
enable; see SECURITY.md.
NDJSON traces can include sensitive log messages, structured errors, and
attributes. They are validated and bounded before serving, but their contents
are not redacted; keep Studio on loopback unless disclosure is intentional.
When --host is 0.0.0.0 or ::, a matching Host cannot be the bind
address, so the studio also accepts localhost or a literal IP on the same
port. DNS names are still refused.
Checked-in project manifest
Declare stable IDs and module references once for every workbench surface:
// tubeless.project.ts
import { definePipelineProject } from "tubeless/workbench/project";
export default definePipelineProject({
cwd: ".",
commands: [
{
id: "import-rows",
file: "./scripts/import.ts",
export: "ImportCommand",
name: "Import rows",
},
{ id: "publish", file: "./scripts/publish.ts", export: "PublishCommand" },
],
});
bunx tubeless list
bunx tubeless inspect import-rows
bunx tubeless run import-rows -- --source rows.txt
bunx tubeless ui --store .tubeless/runs.sqlite ./tubeless.project.ts
Command paths and cwd are relative to the manifest file. Empty or duplicate
IDs and duplicate module registrations fail when the manifest loads.
Presentation-name overrides do not change the registered, run, or pipeline
identities. The Studio uses the stable registered ID in its local protocol.
Legacy definePipelineStudio catalogs remain accepted by tubeless ui; their
historical file#export Studio identity is unchanged. They are UI-only and do
not participate in list, inspect, plan, graph, or run identity lookup.
What the UI shows
The studio combines active and historical runs in one running-first view. Child
pipeline executions stay beneath their top-level run. Run details include step
attempts, progress, logs, and structured errors. Nested pipeline steps are
labeled with the child pipeline and its declared steps; runtime fan-out is
identified without guessing its item count. Recorded progress keeps the last
per-item details rows under the parent bar. Truncated lists keep the original
detail_count and nested step_count so history can say how many rows were
omitted. A writer's unflushed tail stays in that process; read-only history and
studio inspect committed rows only and refuse after a drain creates a WAL, not
on a single unflushed export(). Observed definitions use the latest
pipeline.started timestamp, not insert order.
Every studio request, including reads, requires a Host header that matches
the bound authority (or, on a wildcard bind, localhost or a literal IP on
the same port). Browser plan, launch, cancel, and clear-history also send
x-tubeless-studio-plan, x-tubeless-studio-launch,
x-tubeless-studio-cancel, and x-tubeless-studio-clear-history. Plan and
launch require application/json. A successful launch response (HTTP 202)
means the run id is already queryable from the store. Cancel aborts one live
studio launch without stopping the server; it is not crash-resume. The Cancel
run control appears only for top-level launches this studio process still owns.
The header names are part of the local studio protocol. They are same-origin
guards, not authentication.
HTTP contract and errors
The published OpenAPI document describes the
local Studio HTTP API. Its server URL is loopback-only; tubeless.io hosts the
contract but does not execute pipelines.
Unsuccessful Studio API responses use application/json and include four
fields: stable code, human-readable message, actionable hint, and the
backward-compatible error alias. Use code for control flow. The error and
message fields currently contain the same text.
Programmatic callers can compose the same pieces from
tubeless/run-store/sqlite, tubeless/run-store/ndjson, and
tubeless/run-store/ui. See
local-observability.ts.