Skip to content

feat: add live brew capture console#5

Merged
ioncache merged 2 commits into
mainfrom
live-brew-capture
Jul 2, 2026
Merged

feat: add live brew capture console#5
ioncache merged 2 commits into
mainfrom
live-brew-capture

Conversation

@ioncache

@ioncache ioncache commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Overview

Adds the live brew capture CLI/TUI slice in @shotlab/meticulous-client so brew telemetry can be recorded locally, summarized offline, and inspected in a terminal console without involving the agent during the live run.

Details

  • replace the old socket utility flow with a yargs CLI entrypoint exposing monitor, record, summarize, and console
  • add recording artifact writing for raw events, normalized timeline data, and chart-oriented series output under packages/meticulous-client/recordings/
  • add the terminal console surface with summary, stream, command/help panels, recording controls, and placeholder machine commands
  • harden summarize path resolution so saved recordings can be summarized from different working directories
  • add dedicated live socket payload types separate from history types, with only shared setpoint fields reused
  • cover the CLI, console helpers, payload parsing, recording helpers, and new socket types with local tests
  • document the manual socket capture workflow in the package README and keep the branch plan doc updated in docs/plans/

Related Tickets and/or Pull Requests

None.

Checklist

  • Tests added or updated
  • README and TSDoc updated if the public API changed
  • Breaking changes called out (if any)
  • Roadmap item checked off if this PR completes one

Summary by CodeRabbit

  • New Features
    • Added a yargs-powered socket CLI with monitor, record, summarize, and an interactive console mode for live terminal viewing.
    • Recording sessions now save multiple artifacts (raw events, normalized timeline, and chart-ready series).
  • Bug Fixes
    • Improved metric extraction and sanitization to safely handle invalid/non-finite values.
    • Enhanced terminal output alignment and rendering consistency.
  • Documentation
    • Updated the client README with Socket CLI usage, commands, and artifact details.
  • Tests
    • Added coverage for CLI behavior, console utilities, payload metrics parsing, and recording artifact generation.
  • Chores
    • Updated ignore rules for generated recordings.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a yargs-based CLI (monitor, record, summarize, console) to @shotlab/meticulous-client, alongside typed socket payload/event exports, recording artifact utilities, a terminal console UI, and supporting docs/config/tests.

Changes

Socket capture CLI and typed events

Layer / File(s) Summary
Typed socket event and payload definitions
packages/meticulous-client/src/socket-types.ts, src/socket-types.test.ts, src/index.ts
Adds typed live event payload interfaces, re-exports them from index.ts, replaces HistoryShotSetpoints with an alias of MachinePhaseSetpoints, and introduces generic MeticulousSocketEvent/KnownMeticulousSocketEvent types.
Payload metric parsing and sanitization
utils/socket-payload.ts, utils/socket-payload.test.ts
Adds Zod-based parseSocketPayloadMetrics and sanitizeNormalizedValue utilities with tests for finite-number handling.
Recording persistence and summarization
utils/socket-recording.ts, utils/socket-recording.test.ts
Adds functions to create timestamped session directories, write raw/normalized/chart artifacts, and summarize a recording directory, validated with tests.
Terminal console state and rendering
utils/socket-console.ts, utils/socket-console.test.ts
Adds console state management, command parsing, panel building, and ANSI-aware formatting helpers with extensive test coverage.
CLI entrypoint and subcommand runners
utils/socket-monitor.ts, utils/socket-monitor.test.ts
Replaces the prior script with a yargs CLI exposing console, monitor, record, summarize subcommands, recording path resolution, terminal rendering, and dependency-injected execution with tests.
Package wiring, docs, and config
package.json, README.md, vitest.config.ts, .gitignore, docs/plans/2026-07-01-live-brew-capture-cli.md
Renames the CLI script, adds chalk/pino/terminal-kit/yargs dependencies, documents the Socket CLI in the README, expands vitest test globs, ignores the recordings directory, and adds the implementation plan document.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant runCli
  participant runRecordCommand
  participant Socket
  participant writeRecordingArtifacts
  participant FileSystem

  User->>runCli: node socket-monitor record baseUrl
  runCli->>runRecordCommand: dispatch(args)
  runRecordCommand->>Socket: connect(baseUrl)
  Socket-->>runRecordCommand: state/event entries
  User->>runRecordCommand: quit key
  runRecordCommand->>writeRecordingArtifacts: write(entries, sessionDir)
  writeRecordingArtifacts->>FileSystem: raw-events.jsonl, normalized-timeline.json, chart-series.json
  runRecordCommand-->>User: recording saved
Loading
sequenceDiagram
  participant User
  participant runConsoleCommand
  participant SocketConsoleState
  participant Socket
  participant Terminal

  User->>runConsoleCommand: console baseUrl
  runConsoleCommand->>SocketConsoleState: createSocketConsoleState(baseUrl)
  runConsoleCommand->>Socket: connect(baseUrl)
  Socket-->>runConsoleCommand: events/state
  runConsoleCommand->>SocketConsoleState: appendStreamEvent/appendStreamState
  runConsoleCommand->>Terminal: buildConsolePanels + render
  User->>runConsoleCommand: typed command / hotkey
  runConsoleCommand->>SocketConsoleState: parseConsoleCommand -> apply action
  runConsoleCommand->>Terminal: re-render panels
Loading

Possibly related PRs

  • ioncache/shotlab#4: Shares the same typed socket event/type overhaul (MeticulousSocketEvent/KnownMeticulousSocketEvent) in packages/meticulous-client/src/index.ts.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and relevant, and it captures the new live brew capture console/CLI workflow at a high level.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch live-brew-capture

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ioncache ioncache marked this pull request as ready for review July 2, 2026 13:10

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
packages/meticulous-client/utils/socket-payload.ts (1)

9-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the shared p/t/w field schema.

sensorsSchema and payloadObjectSchema both independently declare p, t, w as finiteNumberSchema. Extracting a shared fragment avoids the two definitions drifting apart if one is updated later.

♻️ Proposed refactor to share the common metric fields
+const coreMetricFields = {
+  p: finiteNumberSchema,
+  t: finiteNumberSchema,
+  w: finiteNumberSchema,
+};
+
 const sensorsSchema = z
-  .object({
-    p: finiteNumberSchema,
-    t: finiteNumberSchema,
-    w: finiteNumberSchema,
-  })
+  .object(coreMetricFields)
   .passthrough();

 const payloadObjectSchema = z
   .object({
-    p: finiteNumberSchema,
+    ...coreMetricFields,
     profile_time: finiteNumberSchema,
     sensors: sensorsSchema.optional(),
-    t: finiteNumberSchema,
     t_ext_1: finiteNumberSchema,
     t_tube: finiteNumberSchema,
     time: finiteNumberSchema,
-    w: finiteNumberSchema,
     weight_pred: finiteNumberSchema,
   })
   .passthrough();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/meticulous-client/utils/socket-payload.ts` around lines 9 - 29, The
`sensorsSchema` and `payloadObjectSchema` definitions both repeat the same
`p`/`t`/`w` finite number fields, so extract that shared fragment into a
reusable schema or object and compose it into both schemas. Update the
`sensorsSchema` and `payloadObjectSchema` declarations in `socket-payload.ts` to
use the shared metric field definition so the two shapes cannot drift apart.
packages/meticulous-client/utils/socket-monitor.ts (2)

119-240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the dead .command() handler callbacks.

Each .command(...) call's fourth argument builds a return object (e.g. lines 133-138, 151-156, 179-186, 197-200), but .parseSync() discards handler return values — the actual SocketCliArgs is reconstructed manually afterward from result._[0]/result.* (lines 206-240). The handlers are therefore dead computations that duplicate the branching logic below and can silently drift out of sync with it.

♻️ Proposed simplification
     .command(
       'console <baseUrl>',
       'Run the interactive terminal console',
       (command) =>
         withSharedEventFlags(
           command.positional('baseUrl', {
             demandOption: true,
             describe: 'Machine base URL, for example http://<machine-ip>:8080',
             type: 'string',
           }),
         ),
-      (args) => ({
-        baseUrl: args.baseUrl,
-        command: 'console' as const,
-        depthLimit: args.depth,
-        sampleLimit: args.samples,
-      }),
     )

(repeat for the other three .command() calls), keeping only the manual result-based branching at the end as the single source of truth.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/meticulous-client/utils/socket-monitor.ts` around lines 119 - 240,
The .command() handler callbacks in parseCliArgs are dead work because
parseSync() ignores their returned objects and the real SocketCliArgs is built
later from result._[0] and result.*. Remove the fourth-argument handler
functions from the console, monitor, record, and summarize command
registrations, and keep the manual branching at the end of parseCliArgs as the
only source of truth to avoid duplicated logic drifting out of sync.

906-908: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use import.meta.main for the entry-point guard. Node 24 already supports it, so this can replace the process.argv[1] URL comparison entirely. If a fallback is needed, pathToFileURL(process.argv[1]) is the safer legacy pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/meticulous-client/utils/socket-monitor.ts` around lines 906 - 908,
The entry-point check in socket-monitor.ts is using a manual process.argv[1] URL
comparison instead of the newer standard guard. Update the top-level CLI
bootstrap around runCli(hideBin(process.argv)) to use import.meta.main directly,
and only keep a legacy fallback if absolutely needed by switching to
pathToFileURL(process.argv[1]) rather than constructing a new URL from the argv
string.
packages/meticulous-client/utils/socket-monitor.test.ts (1)

105-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Broaden resolveRecordingPath test coverage.

Only the repo-root-relative fallback is tested. Given the PR explicitly hardens this path resolution to support running summarize from different working directories, consider adding cases for: cwd-relative match, recordingsRoot-basename fallback, absolute-path passthrough, and the "no candidate exists" default-return branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/meticulous-client/utils/socket-monitor.test.ts` around lines 105 -
120, Broaden the resolveRecordingPath coverage in socket-monitor.test.ts by
adding tests for the other resolution branches besides the repo-root-relative
fallback. Use resolveRecordingPath with cwd-relative input to verify it resolves
against the current working directory, add a recordingsRoot-basename fallback
case, confirm absolute paths are returned unchanged, and cover the
default-return path when exists() matches no candidates. Keep the new cases
alongside the existing resolveRecordingPath describe block and reuse the
existing exists mock pattern.
packages/meticulous-client/utils/socket-console.ts (1)

282-321: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consolidate duplicated panel-layout math shared with renderConsole.

The leftWidth/rightWidth/topHeight computation here is re-implemented in renderConsole (packages/meticulous-client/utils/socket-monitor.ts, lines ~346-348) for actual terminal drawing. The two have already drifted: this file computes leftWidth = Math.max(24, Math.floor(size.width * 0.33)), while renderConsole uses Math.floor((width - 1) * 0.33). Since drawPanel re-truncates each line to its own (differently computed) width, this doesn't cause real overflow, but it can cause unnecessary/premature ... truncation or wasted space by a character, and the duplication is a latent source of future drift bugs.

Extract a single shared computeConsoleLayout(size) helper (in this file, exported) and have renderConsole consume it instead of recomputing independently.

♻️ Proposed consolidation
+export type ConsoleLayout = {
+  bottomHeight: number;
+  bottomY: number;
+  leftWidth: number;
+  rightWidth: number;
+  topHeight: number;
+};
+
+export function computeConsoleLayout(
+  state: SocketConsoleState,
+  size: ConsoleSize,
+): ConsoleLayout {
+  const bottomHeight = getConsoleBottomHeight(state);
+  const leftWidth = Math.max(24, Math.floor(size.width * 0.33));
+  const rightWidth = Math.max(24, size.width - leftWidth - 1);
+  const topHeight = Math.max(8, size.height - bottomHeight - 1);
+  return { bottomHeight, bottomY: topHeight + 1, leftWidth, rightWidth, topHeight };
+}

Then have both buildConsolePanels and renderConsole call computeConsoleLayout instead of recomputing the formula independently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/meticulous-client/utils/socket-console.ts` around lines 282 - 321,
The panel layout math is duplicated between buildConsolePanels and renderConsole
and has already drifted, so extract a shared exported computeConsoleLayout(size)
helper from socket-console.ts that returns leftWidth, rightWidth, and topHeight,
then update buildConsolePanels and renderConsole to consume that helper instead
of recomputing their own width/height formulas.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/meticulous-client/utils/socket-monitor.ts`:
- Around line 462-508: Wrap the `dependencies.connectSocketIo(...)` call in
`runMonitorCommand` and `runRecordCommand` with the same try/catch style used by
`runConsoleCommand.connect`, so unreachable machines produce a clean CLI error
instead of an unhandled rejection. On failure, log/report the error through the
existing `logger`/terminal output path and stop the command gracefully; use the
`runMonitorCommand`, `runRecordCommand`, and `connectSocketIo` symbols to locate
the connection setup.
- Around line 40-42: The root path constants in socket-monitor.ts are using
URL.pathname, which can preserve encoded or platform-specific fragments and
break filesystem access. Update the DEFAULT_RECORDINGS_ROOT and
DEFAULT_REPO_ROOT definitions to convert the URL with fileURLToPath(...)
instead, and make sure the fileURLToPath helper is available in this module. Use
the existing constants as the place to apply the fix so all downstream
filesystem operations get proper native paths.

In `@packages/meticulous-client/utils/socket-recording.ts`:
- Around line 112-144: summarizeRecordingDirectory currently assumes
raw-events.jsonl always exists and contains valid JSON, so readFile and
JSON.parse can throw an opaque stack trace. Add error handling around the file
read and line parsing in summarizeRecordingDirectory to catch missing, empty, or
malformed recordings and rethrow a clear summarize-specific error. Keep the
existing RecordingSummary shape and use the summarizeRecordingDirectory and
RecordedSocketEntry flow to locate the affected path.

---

Nitpick comments:
In `@packages/meticulous-client/utils/socket-console.ts`:
- Around line 282-321: The panel layout math is duplicated between
buildConsolePanels and renderConsole and has already drifted, so extract a
shared exported computeConsoleLayout(size) helper from socket-console.ts that
returns leftWidth, rightWidth, and topHeight, then update buildConsolePanels and
renderConsole to consume that helper instead of recomputing their own
width/height formulas.

In `@packages/meticulous-client/utils/socket-monitor.test.ts`:
- Around line 105-120: Broaden the resolveRecordingPath coverage in
socket-monitor.test.ts by adding tests for the other resolution branches besides
the repo-root-relative fallback. Use resolveRecordingPath with cwd-relative
input to verify it resolves against the current working directory, add a
recordingsRoot-basename fallback case, confirm absolute paths are returned
unchanged, and cover the default-return path when exists() matches no
candidates. Keep the new cases alongside the existing resolveRecordingPath
describe block and reuse the existing exists mock pattern.

In `@packages/meticulous-client/utils/socket-monitor.ts`:
- Around line 119-240: The .command() handler callbacks in parseCliArgs are dead
work because parseSync() ignores their returned objects and the real
SocketCliArgs is built later from result._[0] and result.*. Remove the
fourth-argument handler functions from the console, monitor, record, and
summarize command registrations, and keep the manual branching at the end of
parseCliArgs as the only source of truth to avoid duplicated logic drifting out
of sync.
- Around line 906-908: The entry-point check in socket-monitor.ts is using a
manual process.argv[1] URL comparison instead of the newer standard guard.
Update the top-level CLI bootstrap around runCli(hideBin(process.argv)) to use
import.meta.main directly, and only keep a legacy fallback if absolutely needed
by switching to pathToFileURL(process.argv[1]) rather than constructing a new
URL from the argv string.

In `@packages/meticulous-client/utils/socket-payload.ts`:
- Around line 9-29: The `sensorsSchema` and `payloadObjectSchema` definitions
both repeat the same `p`/`t`/`w` finite number fields, so extract that shared
fragment into a reusable schema or object and compose it into both schemas.
Update the `sensorsSchema` and `payloadObjectSchema` declarations in
`socket-payload.ts` to use the shared metric field definition so the two shapes
cannot drift apart.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d2c7167-5171-4bf0-9f15-1a443abee6d4

📥 Commits

Reviewing files that changed from the base of the PR and between ade357d and 7864c2d.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (16)
  • .gitignore
  • docs/plans/2026-07-01-live-brew-capture-cli.md
  • packages/meticulous-client/README.md
  • packages/meticulous-client/package.json
  • packages/meticulous-client/src/index.ts
  • packages/meticulous-client/src/socket-types.test.ts
  • packages/meticulous-client/src/socket-types.ts
  • packages/meticulous-client/utils/socket-console.test.ts
  • packages/meticulous-client/utils/socket-console.ts
  • packages/meticulous-client/utils/socket-monitor.test.ts
  • packages/meticulous-client/utils/socket-monitor.ts
  • packages/meticulous-client/utils/socket-payload.test.ts
  • packages/meticulous-client/utils/socket-payload.ts
  • packages/meticulous-client/utils/socket-recording.test.ts
  • packages/meticulous-client/utils/socket-recording.ts
  • packages/meticulous-client/vitest.config.ts

Comment thread packages/meticulous-client/utils/socket-monitor.ts Outdated
Comment thread packages/meticulous-client/utils/socket-monitor.ts
Comment thread packages/meticulous-client/utils/socket-recording.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/meticulous-client/utils/socket-recording.test.ts (1)

240-316: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Correct error-path coverage for missing/malformed recordings.

Both new tests correctly exercise the try/catch wrapping in summarizeRecordingDirectory — missing raw-events.jsonl triggers an ENOENT that's caught and wrapped, and the malformed-JSON line triggers a JSON.parse failure that's likewise wrapped with the same error message format. Assertions match the implementation.

Minor nit: the mkdtemp/createdPaths.push boilerplate is now repeated three times in this describe block (Lines 241-243, 289-291, 298-300). Consider extracting a small createTempSessionDir() helper shared across the block to reduce repetition, though this isn't blocking.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/meticulous-client/utils/socket-recording.test.ts` around lines 240 -
316, The new tests for summarizeRecordingDirectory already cover the wrapped
error paths correctly, so no logic change is needed; just reduce the repeated
mkdtemp and createdPaths.push setup in this describe block by extracting a small
shared helper such as createTempSessionDir and reusing it in the existing test
cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/meticulous-client/utils/socket-recording.test.ts`:
- Around line 240-316: The new tests for summarizeRecordingDirectory already
cover the wrapped error paths correctly, so no logic change is needed; just
reduce the repeated mkdtemp and createdPaths.push setup in this describe block
by extracting a small shared helper such as createTempSessionDir and reusing it
in the existing test cases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 95079463-bb58-4157-a03b-69bfea9b4268

📥 Commits

Reviewing files that changed from the base of the PR and between 7864c2d and cf11278.

📒 Files selected for processing (7)
  • packages/meticulous-client/utils/socket-console.test.ts
  • packages/meticulous-client/utils/socket-console.ts
  • packages/meticulous-client/utils/socket-monitor.test.ts
  • packages/meticulous-client/utils/socket-monitor.ts
  • packages/meticulous-client/utils/socket-payload.ts
  • packages/meticulous-client/utils/socket-recording.test.ts
  • packages/meticulous-client/utils/socket-recording.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/meticulous-client/utils/socket-monitor.test.ts
  • packages/meticulous-client/utils/socket-console.test.ts
  • packages/meticulous-client/utils/socket-payload.ts
  • packages/meticulous-client/utils/socket-console.ts
  • packages/meticulous-client/utils/socket-monitor.ts
  • packages/meticulous-client/utils/socket-recording.ts

@ioncache ioncache merged commit a00a0f0 into main Jul 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant