From 85f8465248f79f87ad4c08e3d0798dad51494206 Mon Sep 17 00:00:00 2001 From: ROHAN <123131rkorohan@gmail.com> Date: Sun, 16 Aug 2026 21:07:49 +0530 Subject: [PATCH 1/2] feat(cli): add -f/--format to webcmd daemon status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `daemon status` fetches a fully-typed `DaemonStatus` object but only ever printed it as hand-written text lines, rejecting `-f` entirely. Agents/scripts polling daemon health had no structured way to read it (#175). Add `-f, --format` (default `table`, unchanged text output). Other formats render a `{ running, ...status }` envelope through the shared output path — `running` distinguishes the "daemon not reachable" case, which previously had no structured representation at all (`fetchDaemonStatus` returns null rather than a partial status object). Scope note: same slice-of-#175 approach as the `validate` PR — one complete, tested command rather than a partial pass across the full list in the issue. Co-Authored-By: Claude Sonnet 5 --- src/cli.ts | 9 +++++++-- src/commands/daemon.test.ts | 29 +++++++++++++++++++++++++++++ src/commands/daemon.ts | 7 ++++++- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index c7b2a09b..12ed15b4 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1857,10 +1857,15 @@ cli({ const daemonCmd = program.command('daemon').description('Manage the webcmd daemon'); // Snapshot before applyRootSubcommandSummaries() rewrites .description() to a child-name listing. const originalDaemonDescription = daemonCmd.description(); - daemonCmd + const daemonStatusCmd = daemonCmd .command('status') .description('Show daemon status') - .action(async () => { await daemonStatus(); }); + .option('-f, --format ', OUTPUT_FORMAT_HELP, 'table'); + daemonStatusCmd.action(async (opts) => { + const fmt = resolveOutputFormat(opts.format); + if (fmt === null) return; + await daemonStatus(fmt); + }); daemonCmd .command('stop') .description('Stop the daemon') diff --git a/src/commands/daemon.test.ts b/src/commands/daemon.test.ts index 76902221..59a12251 100644 --- a/src/commands/daemon.test.ts +++ b/src/commands/daemon.test.ts @@ -105,6 +105,35 @@ describe('daemonStatus', () => { expect(stdoutSpy).toHaveBeenCalledWith(expect.stringContaining('fake connected')); }); + + it('renders a structured envelope for -f json when running (#175)', async () => { + fetchDaemonStatusMock.mockResolvedValue({ + ok: true, + pid: 12345, + uptime: 60, + daemonVersion: PKG_VERSION, + runtimeConnected: true, + runtimeName: 'fake', + pending: 0, + memoryMB: 64, + port: 9777, + }); + + await daemonStatus('json'); + + const printed = stdoutSpy.mock.calls.map((c: unknown[]) => c[0]).join('\n'); + const data = JSON.parse(printed); + expect(data).toMatchObject({ running: true, pid: 12345, port: 9777 }); + }); + + it('renders a structured envelope for -f json when not running (#175)', async () => { + fetchDaemonStatusMock.mockResolvedValue(null); + + await daemonStatus('json'); + + const printed = stdoutSpy.mock.calls.map((c: unknown[]) => c[0]).join('\n'); + expect(JSON.parse(printed)).toEqual({ running: false }); + }); }); // ──────────────────────────────────────────────────────────────────── diff --git a/src/commands/daemon.ts b/src/commands/daemon.ts index ab69e22c..d3a91a32 100644 --- a/src/commands/daemon.ts +++ b/src/commands/daemon.ts @@ -11,9 +11,14 @@ import { formatDuration } from '../download/progress.js'; import { log } from '../logger.js'; import { PKG_VERSION } from '../version.js'; import { formatDaemonVersion, isDaemonStale } from '../browser/daemon-version.js'; +import { render } from '../output.js'; -export async function daemonStatus(): Promise { +export async function daemonStatus(fmt: string = 'table'): Promise { const status = await fetchDaemonStatus(); + if (fmt !== 'table') { + await render(status ? { running: true, ...status } : { running: false }, { fmt }); + return; + } if (!status) { console.log('Daemon: not running'); return; From aedfca6db1e4576cfa95bc76443f56d5d0320b92 Mon Sep 17 00:00:00 2001 From: ROHAN <123131rkorohan@gmail.com> Date: Sun, 16 Aug 2026 21:54:11 +0530 Subject: [PATCH 2/2] test(cli): add -f yaml coverage for daemon status structured output Address PR review feedback on #325: the new structured-output tests only covered -f json for both the running and not-running daemon states. Add the yaml equivalents so daemon status -f yaml is exercised too. Co-Authored-By: Claude Sonnet 5 --- src/commands/daemon.test.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/commands/daemon.test.ts b/src/commands/daemon.test.ts index 59a12251..5f6a3076 100644 --- a/src/commands/daemon.test.ts +++ b/src/commands/daemon.test.ts @@ -1,4 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import yaml from 'js-yaml'; const { fetchDaemonStatusMock, @@ -134,6 +135,35 @@ describe('daemonStatus', () => { const printed = stdoutSpy.mock.calls.map((c: unknown[]) => c[0]).join('\n'); expect(JSON.parse(printed)).toEqual({ running: false }); }); + + it('renders a structured envelope for -f yaml when running (#175)', async () => { + fetchDaemonStatusMock.mockResolvedValue({ + ok: true, + pid: 12345, + uptime: 60, + daemonVersion: PKG_VERSION, + runtimeConnected: true, + runtimeName: 'fake', + pending: 0, + memoryMB: 64, + port: 9777, + }); + + await daemonStatus('yaml'); + + const printed = stdoutSpy.mock.calls.map((c: unknown[]) => c[0]).join('\n'); + const data = yaml.load(printed) as Record; + expect(data).toMatchObject({ running: true, pid: 12345, port: 9777 }); + }); + + it('renders a structured envelope for -f yaml when not running (#175)', async () => { + fetchDaemonStatusMock.mockResolvedValue(null); + + await daemonStatus('yaml'); + + const printed = stdoutSpy.mock.calls.map((c: unknown[]) => c[0]).join('\n'); + expect(yaml.load(printed)).toEqual({ running: false }); + }); }); // ────────────────────────────────────────────────────────────────────