Skip to content
This repository was archived by the owner on Oct 1, 2026. It is now read-only.

Latest commit

 

History

81 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bitty DevTools

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.

See the project workflow (CarryCtx)

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 carryctx

CarryCtx 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 import

Then carryctx stats reports the restored tasks, sessions, and checkpoints. Provenance, redaction, and --force behavior are covered under the repository snapshot documentation below.

Ownership boundary

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.

Implemented phase 1 (CTX-0011)

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), CommandRegistry grammar, and bounded queues 64 / 1024 / 256 KiB / 8192 / 2 MiB plus DropOldest default mirror bitty-runtime PanelRegistry PR-1..PR-12 and bitty-ui panel.rs verbatim. 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) mirror bitty-compat-lab matrix.rs 14 × 4, bounded corpus ≤8 KiB / ≤4096 actions, deterministic state_hash, <16 KiB JSON artifact, headless without winit/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 bindings getGridText (rows/cols), getInputRing (limit), getModifiers, and getFocus. 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, and DropOldest/DropNewest accounting. No filesystem spool is created; storage is reported as memory and 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 — DevtoolsClient owns connection lifecycle (zero scopes on connect, grantScope / revokeScope per operation), bounded parsing/rendering/queues/traces/retention, AbortSignal cancellation, resource budgets, and compatMatrixJson bounded 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.

Implemented phase 2 (CTX-0012)

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/bitty with directory 0700 and socket 0600; 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_ID select a bounded endpoint path; neither is a credential or connected identity. RC-9 100/s 200 burst 1 MiB 16 connections (shed newest), RC-10 256 KiB chunk, and length-prefixed 256 KiB frames remain bounded. Headless StdioTransportStub / IpcTransport seams are test-only and do not claim OS connectivity.

  • Advanced tracing (debug.trace, opt-in) — structured attributable events (StructuredTraceEvent with sequence, owner, generation), filtering by kinds/owners (bounded 32), requested-byte batch admission, retention 4 MiB / 5 min / 4 traces, and deterministic streamFilteredEvents. The helper is explicitly in-memory simulation; it does not claim a 0600 filesystem spool or a server mutation.

  • Advanced control (debug.control, audited) — pauseHandler, resumePlugin with generation exhaustion guard (MAX_SAFE_INTEGER-1024), validateGeneration, transactional audit log bounded 256 (listAuditLog, clearAuditLog), per-generation ownership, and session-owned in-memory audit state. It never widens sibling authority or bypasses capability/budget gates.

  • Client integration — DevtoolsClient exposes 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 headless IpcTransport remains 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.

Trace accounting semantics (Implemented, CTX-0053 / CTX-0054)

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+FEFF stays 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/none coalescing rule.
  • Chunk pagination — fetchTraceChunk addresses 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.

Test-automation drivers (candidate, CTX-0024)

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/keyUp and mouse clickTrajectory / dragTrajectory builders (64 points, 30 s declared playback), each validated against the per-event key/cell/wheel/paste bounds before dispatch.
  • Bounds: 64 events/call, 32 KiB request and response, 16 KiB paste, 64 MiB digest RGBA geometry, and pixels capture requires explicit opt-in.
  • Authority is never inferred: each call needs debug.control + terminal.input (synthesizeInput) or debug.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 a pixels payload is rejected, and no live data is ever fabricated.

Usage (local, human-facing)

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.

Platform support

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.

CLI wrapper (experimental, CTX-0025)

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 --plugin is a usage error (exit 2) 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/--socket is a usage error (exit 2); the same failure from BITTY_INSTANCE_ID/BITTY_SOCKET/XDG_RUNTIME_DIR is a config error (exit 3). 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 ctl exit-code vocabulary (7 permission, 6 runtime/transport, 5 compatibility, 1 generic). 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.

Live campaign conformance harness (CTX-0325)

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 text must return plain grid text, never a Rust Debug Snapshot { ... Cell { ... } } dump.
  • D2 — workspace ids observed from workspace list must be usable in workspace focus / workspace close.
  • D3 — ctl terminal spawn reporting success must be observable in view list / terminal list or in has_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.

Development

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.

Workflow snapshot restore

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 import

The 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.

Documentation authority

Repository-local material explains contribution and ownership boundaries but does not duplicate normative specifications. The current project-wide technical record remains bitty-docs.

Current status

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.

About

ARCHIVED 2026-10-01: superseded by https://github.com/bitty-terminal/devtools (Lua plugin).

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages