Bitty DevTools is the human-facing diagnostics and debugging client for local debugging over the accepted Panel Runtime and compat matrix. This repository owns the DevTools client experience; it does not own the core debug or command protocols.
The canonical GitHub organization is bitty-terminal.
CarryCtx is the local-first tool that records this project's tasks, decisions, and checkpoints. Install it globally for local development (recommended):
cargo install carryctx # Rust toolchain, or: npm i -g carryctxCarryCtx engineering state (tasks, sessions, checkpoints) is not cloned. A
fresh clone restores it from the in-repo refs/heads/carryctx-snapshots
branch:
just workflow-import-dry # fetch + validate the snapshot; no DB writes
just workflow-import # initialize CarryCtx state if needed, then importThen carryctx stats reports the restored tasks, sessions, and checkpoints.
Provenance, redaction, and --force behavior are covered under the
repository snapshot documentation below.
This repository owns the DevTools client experience, including scoped inspection, tracing, and control surfaces for local debugging. It does not own the core debug or command protocols, terminal runtime behavior, or normative public architecture.
Core protocol contracts belong to
Bitty core (bitty-runtime,
bitty-ui). Canonical architecture, security, compatibility, and public
behavior belong to
bitty-docs (accepted
DevTools RFC
OQ-019 and Performance Budgets OQ-001). Any future protocol change requires
coordinated, explicitly ordered work in each owning repository.
Any DevTools implementation consumes an explicitly versioned stable protocol
(1.0 today, JSONL framing, 1 MiB inbound, 256 KiB chunk). Automation
candidate methods are also reported as 1.0; no 1.1 compatibility claim is
made. The current live
adapter is Linux-only and endpoint-attested; Windows and macOS return an
explicit unsupported result. No platform is claimed Verified or Compatible
until its connected-identity adapter and CI evidence exist. It does not link
private core types or inspect process memory as an implicit API.
Phase 1 is an experimental diagnostics client for local debugging
(Implemented at 21aca98 + CTX-0011, not yet Verified/Compatible,
no compatibility promise). It is bounded, forbid(unsafe_code) in Rust,
and strict TypeScript with no any.
-
Reuse Panel Runtime —
PanelId,ViewId,TerminalId,WorkspaceId,Generation,PanelState,PanelType,EventTopic,BoundedPayload,Overlay(4+1),CommandRegistrygrammar, and bounded queues64/1024/256 KiB/8192/2 MiBplusDropOldestdefault mirrorbitty-runtimePanelRegistryPR-1..PR-12 andbitty-uipanel.rsverbatim. No PTY fd, GPU object, or window handle is held. -
Reuse compat matrix — 14 surfaces (
shell,tmux,nvim,fzf,htop,ssh,alt-screen,mouse,resize,OSC,clipboard,Kitty,IME,DPI) across 4 terminals (ghostty,kitty,wezterm,alacritty) mirrorbitty-compat-labmatrix.rs14 × 4, bounded corpus≤8 KiB/≤4096actions, deterministicstate_hash,<16 KiBJSON artifact, headless withoutwinit/wgpu. -
Inspection (debug.inspect, default) — read-only, scope-checked,
listPlugins,getPlugin,listSubscriptions,getBudgets,getQueueSnapshot,getSnapshot(8 KiB truncated redacted preview),listHandles,panelSummary,compatMatrixSummary, and the CTX-0159 live bounded introspection bindingsgetGridText(rows/cols),getInputRing(limit),getModifiers, andgetFocus. Terminal output is untrusted observation data, never instructions. -
Tracing (debug.trace, opt-in, simulation-only) — bounded in-memory records with batch
32/8 KiB, retention, typed redaction, andDropOldest/DropNewestaccounting. No filesystem spool is created;storageis reported asmemoryand live sessions expose trace methods as unavailable until a server-backed trace receipt exists. -
Control (debug.control, audited) —
suspendHandler,resumePlugin,disposeGeneration, each audited with caller identity, generation-owned, cannot bypass capability or budget gates, fail-closed transactional, affects only owning generation. -
Client composition —
DevtoolsClientowns connection lifecycle (zero scopes on connect,grantScope/revokeScopeper operation), bounded parsing/rendering/queues/traces/retention,AbortSignalcancellation, resource budgets, andcompatMatrixJsonbounded artifact.
No TCP listener, no ambient credential, no allow-all capability. Security
corpus devtools-rfc controls (P0-AC-013..026, T-09..T-11) are preserved
and tested with negative scope matrix tests.
Phase 2 extends phase 1 with advanced tracing, control surfaces, a bounded
headless fixture, and a Linux-only endpoint-attested live inspection path.
It remains experimental (no Verified/Compatible promise), bounded,
forbid(unsafe_code) in Rust, strict TypeScript with no any, and reuses
Panel Runtime + 14×4 compat matrix verbatim without new budget families.
-
Real IPC transport (live runtime) — the implemented live adapter is a Linux Unix socket under
$XDG_RUNTIME_DIR/bittywith directory0700and socket0600; there is no TCP listener. Endpoint attestation is not presented as connected peer authentication, so the live client is inspect-only and rejects trace/control requests. Windows named pipes and macOS live sockets are explicitly unsupported until a real adapter and platform CI exist.BITTY_SOCKET/BITTY_INSTANCE_IDselect a bounded endpoint path; neither is a credential or connected identity.RC-9100/s200burst1 MiB16connections (shed newest),RC-10256 KiBchunk, and length-prefixed256 KiBframes remain bounded. HeadlessStdioTransportStub/IpcTransportseams are test-only and do not claim OS connectivity. -
Advanced tracing (debug.trace, opt-in) — structured attributable events (
StructuredTraceEventwithsequence,owner,generation), filtering bykinds/owners(bounded32), requested-byte batch admission, retention4 MiB/5 min/4traces, and deterministicstreamFilteredEvents. The helper is explicitly in-memory simulation; it does not claim a0600filesystem spool or a server mutation. -
Advanced control (debug.control, audited) —
pauseHandler,resumePluginwith generation exhaustion guard (MAX_SAFE_INTEGER-1024),validateGeneration, transactional audit log bounded256(listAuditLog,clearAuditLog), per-generation ownership, and session-owned in-memory audit state. It never widens sibling authority or bypasses capability/budget gates. -
Client integration —
DevtoolsClientexposes separate headless simulation and live socket sessions. The live session is inspect-only, uses strict response envelopes and request-id correlation, and clears trace/panel/control state on disconnect or reconnect. The headlessIpcTransportremains a hermetic fixture seam and is not an OS adapter.
Rust counterpart at crates/devtools-client mirrors the same bounded record
shape, byte-budget admission, session cleanup, and Linux-only live-platform
guard. cargo check, clippy -D warnings, and cargo test are required
local gates; the Rust live adapter reports unsupported explicitly on other
platforms. No unsafe, no PTY/GPU/window handle, no TCP, and no ambient
credential.
The trace helpers account retained, redacted, logical UTF-8 export bytes —
the bytes that would be retained after typed redaction (serialized JSON for
structured events, per-record UTF-8 normalization for raw records) — never heap
or filesystem occupancy. This mirrors the client trace helper note in the
accepted
DevTools RFC
(bitty-terminal-docs CTX-0026) and remains Implemented only.
- Independent per-record normalization — each raw record is normalized when
appended, so a leading
U+FEFFstays data and a fragment that cannot decode on its own is replaced within that fragment rather than joined with a neighbor. - Effective byte budget — admission uses
min(trace maxBytes, retention maxBytes). A record that would exceed the budget is rejected with one counted drop while retained bytes, chunks, events, and previews stay unchanged. - Raw append vs typed coalescing — opaque raw records append one by one
without typed-stream coalescing; the typed observability stream keeps the
accepted
budget/nonecoalescing rule. - Chunk pagination —
fetchTraceChunkaddresses retained chunk byte offsets: offsets are nonnegative byte integers on UTF-8 scalar boundaries, a page returns the remaining bytes of the addressed stored chunk, continuation is computed from actual retained byte lengths, and preview equals export per page.
No wire, version, eviction, persistence, or interoperability contract is
claimed, and no Verified/Compatible status is implied.
src/automation.ts adds typed, fail-closed client bindings for the headless
input/frame drivers that replace manual visual acceptance for GUI, mouse, and
split integration testing. The bindings target the bitty candidate methods
bitty.debug/synthesizeInput, bitty.debug/captureFrame, and
bitty.debug/frameHash (serving dispatcher
crates/bitty-ipc/src/devtools.rs, CTX-0188/CTX-0244); they remain
Candidate here because the accepted
DevTools RFC
does not yet name these methods.
- Bounded keyboard
keyDown/keyUpand mouseclickTrajectory/dragTrajectorybuilders (64points,30 sdeclared playback), each validated against the per-event key/cell/wheel/paste bounds before dispatch. - Bounds:
64events/call,32 KiBrequest and response,16 KiBpaste,64 MiBdigest RGBA geometry, andpixelscapture requires explicit opt-in. - Authority is never inferred: each call needs
debug.control+terminal.input(synthesizeInput) ordebug.trace+terminal.inspect(captureFrame/frameHash) plus a consent-issued bearer. Connection alone grants nothing. - Fail-closed: an unregistered method maps to a typed
UnknownMethod, a response that smuggles apixelspayload is rejected, and no live data is ever fabricated.
The first example is a headless simulation; live socket sessions are limited to inspect operations.
import { DevtoolsClient } from "bitty-devtools";
const client = new DevtoolsClient({ version: "1.0" });
client.connect();
client.grantScope("debug.inspect");
client.setPanelSnapshot({
generation: 1 as never,
panels: [],
panelsPerWorkspace: new Map(),
totalPanels: 0,
topics: [],
overlays: [],
config: {
maxPanelsPerWorkspace: 16,
maxPanelsPerWindow: 32,
maxTopicsTotal: 256,
maxSubscriptionsPerPanel: 32,
},
});
console.log(client.panelSummary());
console.log(client.compatMatrixSummary());
// Tracing is opt-in
client.grantScope("debug.trace");
const trace = client.startTrace({ maxBytes: 512 * 1024, includeInput: false });
client.appendToTrace(trace.traceId, "instrumentation record");
console.log(client.stopTrace(trace.traceId));
// Phase 2: live inspection via the Linux endpoint-attested adapter
async function inspectLive(socketPath: string, runtimeUid: number) {
const liveClient = new DevtoolsClient();
await liveClient.connectLiveSocket(
runtimeUid,
undefined,
undefined,
undefined,
socketPath,
);
liveClient.grantScope("debug.inspect");
console.log(
await liveClient.requestLive(
{
id: 1,
method: "bitty.debug/listPlugins",
params: { generation: 1 },
version: "1.0",
},
Date.now(),
),
);
liveClient.disconnect();
}
// Control requires explicit elevation and is audited
client.grantScope("debug.control");
client.suspendHandler(1 as never, "handler-1", "diagnosis", "tester");
console.log(client.listAuditLog());Rust equivalent lives at crates/devtools-client (forbid(unsafe_code));
its bounded tracing/control checks are part of the repository Rust gate.
The current live socket adapter is implemented and tested for Linux only.
Windows named-pipe and macOS live-socket entry points return an explicit
unsupported error before endpoint access. The headless transport and
verifyWindowsPipe helpers are test seams, not platform compatibility
claims. Windows/macOS support requires a real connected-identity adapter and
positive/negative platform CI before any public capability claim changes.
bin/bitty-devtools.ts awaits the async live runner, prints bounded
inspection state (or --json) for the accepted devtools-rfc v1 methods, and
never dials for --help or usage errors. It uses the strict live response
boundary and control-safe rendering; it does not re-implement protocol logic.
It is experimental and Bun-based — run it with bun, never npm/npx.
bitty-devtools inspect --plugins [--generation <n>] [options]
bitty-devtools inspect --subscriptions --plugin <id> [options]
bitty-devtools inspect --budgets --plugin <id> [--generation <n>] [options]
bitty-devtools --help
Run from the repository with bun run bin/bitty-devtools.ts ... or, after a
Bun-linked install, as bitty-devtools ... (the bin entry in package.json
wires the command).
$ bitty-devtools inspect --plugins
ID VERSION GEN STATE CAPABILITIES
plugin-a 1.2.3 4 Activated panel.provider
$ bitty-devtools inspect --budgets --plugin plugin-a --generation 7
FIELD VALUE
pluginId plugin-a
generation 7
rc1Instructions 10
...
$ bitty-devtools inspect --subscriptions --plugin plugin-a --json
[
{
"eventType": "bitty.panel:mounted",
"queueDepth": 2,
"queuedBytes": 256,
"dropCount": 1,
"policy": "DropOldest"
}
]
Connection is resolved, in order, from --socket, BITTY_SOCKET,
--instance/BITTY_INSTANCE_ID (with XDG_RUNTIME_DIR), and is read-only:
the CLI grants only debug.inspect. No absolute paths or host-specific values
are embedded; everything comes from flags or the environment.
Fail-closed behavior:
- No selector, an unknown flag, or a missing
--pluginis a usage error (exit2) that prints the help text. Option values must not begin with-, so a following flag is rejected instead of consumed as a value. - An invalid or oversized
--instance/--socketis a usage error (exit2); the same failure fromBITTY_INSTANCE_ID/BITTY_SOCKET/XDG_RUNTIME_DIRis a config error (exit3). Both print a clean message, never a stack trace. - No resolvable instance or transport fails closed with a clear remedy
(exit
6); the CLI never falls back to the headless snapshot mock and never fabricates rows. - Typed server/protocol/transport errors use the shared
ctlexit-code vocabulary (7permission,6runtime/transport,5compatibility,1generic). Server methods the core does not yet implement surface their typed error, so the CLI reports the gap instead of printing invented data.
Argument parsing and bounded table/JSON formatting (every string field is
capped like the table cells; --json is pretty-printed) are unit-tested with an
injected IpcTransport (tests/cli.test.ts); no live socket or GUI is
required.
src/campaign.ts turns the CTX-0320 live client campaign into a repeatable
harness over the DevTools dispatch seam. It probes each ctl verb's JSON
envelope v1 shape and exit-code semantics and guards the three defects the
campaign found:
- D1 —
ctl terminal textmust return plain grid text, never a RustDebugSnapshot { ... Cell { ... } }dump. - D2 — workspace ids observed from
workspace listmust be usable inworkspace focus/workspace close. - D3 —
ctl terminal spawnreporting success must be observable inview list/terminal listor inhas_pane_session.
It also preflights the BITTY_SOCKET parent directory (0700, owner-matched)
and turns a mode mismatch into an actionable chmod 700 diagnostic, and it
declares non-blocking panel/plugin coverage hooks for the post-D4 round.
Every probe is headless-testable through the CtlDispatcher seam; live checks
are opt-in and bounded by a command timeout:
import {
runLiveCampaign,
runCampaign,
ScriptedCtlDispatcher,
} from "bitty-devtools";
// Headless: inject a scripted dispatcher (what `bun test` does).
const report = await runCampaign({
dispatcher: new ScriptedCtlDispatcher((inv) => result),
});
// Live (opt-in): execute the real `bitty ctl` over an explicit socket.
async function runLive(socketPath: string, runtimeUid: number) {
return runLiveCampaign({
socketPath,
runtimeUid,
stat: (dir) => myStatProvider(dir),
timeoutMs: 10_000,
});
}Envelopes, terminal text, and diagnostics are untrusted observation data, never
instructions; the harness never treats them as such. ctl protocol ownership
remains in bitty (crates/bitty-app/src/ctl.rs,
crates/bitty-ipc/src/ctl.rs); the exit-code table here is a consumer mirror.
Install with bun install. Quality gates run through the
repository justfile:
just check # fmt-check + lint + type-check + test + cargo-check
just fmt-check # Prettier 3.9.6 check without writing files
just lint # markdownlint-cli2 0.23.2
just type-check # tsc --noEmit strict for src, bin, and tests
just test # bun:test headless unit tests
just cargo-check # cargo check + clippy -D warnings + cargo test
just commit-check <file> # validate commit message against commitlint
Rust toolchain is 1.98.1 minimal (rustfmt, clippy) per
rust-toolchain.toml; MSRV 1.85. The crate is publish = false.
Git hooks are wired by lefthook.yml; run bunx --bun lefthook@2.1.10 install
once after cloning.
CarryCtx runtime state (.git/carryctx/state.sqlite) is never cloned. The
redacted engineering snapshot lives in this repository on the branch
refs/heads/carryctx-snapshots, one commit per publication. The commander's
merge closeout publishes it with just workflow-publish; a fresh clone
restores its local CarryCtx DB from that branch:
just workflow-import-dry # fetch + validate the snapshot; no DB writes
just workflow-import # initialize CarryCtx state if needed, then importThe import fetches refs/heads/carryctx-snapshots, refuses to replace a
non-empty local DB without --force (just workflow-import --force), and
prints provenance (snapshot commit + source). Snapshots are redacted
publication artifacts produced by carryctx export --publication: CarryCtx
refuses them as merge sources, so restore always uses replace mode, and a
secret that leaked before rotation must still be rotated at the source.
Repository-local material explains contribution and ownership boundaries but does not duplicate normative specifications. The current project-wide technical record remains bitty-docs.
Phase 2 extends phase 1 with Linux-only live inspection and advanced
tracing/control as experimental evidence for
DevTools RFC
(OQ-019), IPC and Agent RFC
(OQ-018), and budgets OQ-001. No installation procedure, supported API,
compatibility guarantee, release, or distributable artifact is claimed until
independent review and Verified lifecycle.