Skip to content

feat: Add --json output to every sheets CLI command - #2

Merged
andrei-hasna merged 1 commit into
mainfrom
factory/075690b1-74ab-4a1e-b9b4-4dc4aab0-fa2b524d
Jul 31, 2026
Merged

feat: Add --json output to every sheets CLI command#2
andrei-hasna merged 1 commit into
mainfrom
factory/075690b1-74ab-4a1e-b9b4-4dc4aab0-fa2b524d

Conversation

@andrei-hasna

@andrei-hasna andrei-hasna commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Objective

Add --json output to every sheets CLI command

The sheets CLI in @hasna/sheets has no machine-readable output mode: info, get and the other inspection commands print prose that a script or agent can only screen-scrape.

Problem, in full. src/cli/index.ts registers around nine commands over workbook JSON, CSV and XLSX documents. Output is written as formatted prose. For example the info command does:

console.log(`Workbook ${workbook.id} (${workbook.sheets.length} sheet(s))`);
for (const sheet of workbook.sheets) {
  const count = Object.keys(sheet.cells).length;
  const active = sheet.id === workbook.activeSheetId ? " [active]" : "";
  console.log(`  - ${sheet.name}: ${sheet.rows}x${sheet.columns}, ${count} cell(s)${active}`);
}

and the shared emit(content, out) helper either writes a file or prints the raw string. A repository-wide search for --json in src/ returns zero hits — the flag exists on no command and not on the program.

To read a workbook's sheet dimensions programmatically, a caller must parse " - Sheet1: 100x26, 12 cell(s) [active]", which breaks as soon as a sheet name contains a colon, a comma, or the literal text [active]. Sheet names are user-controlled, so that is reachable rather than theoretical. get has the same problem: a computed cell value is printed bare, so an empty cell, the string "null", and a genuine null are indistinguishable on stdout.

This is a parity gap against the rest of the Hasna CLI surface, where --json is the standard way an agent consumes output, and the same family of work has already been carried out for other CLIs in this codebase.

Expected behaviour after the fix. A global --json flag is accepted, and every command that produces output honours it by emitting one well-formed JSON document on stdout carrying the structured data the command already holds — for info, the workbook id and an array of sheet objects with id, name, rows, columns, cell count and active flag; for get, the resolved sheet, the A1 reference, and the computed value with its real type preserved. Commands whose output is a document body (workbook JSON, CSV, XLSX) keep writing that body unchanged, since it is already machine-readable; --json must not wrap or corrupt it. Human output without the flag is unchanged character for character. Errors under --json are emitted as a JSON object on stderr with a non-zero exit code.

Reproduce. Run the info command against a workbook whose sheet is named Q1: revenue, draft and observe the printed line cannot be parsed back into fields. Run get on an empty cell and observe the output is indistinguishable from a cell containing an empty string. Confirm sheets --help offers no JSON option.

Acceptance criterion. A regression test, written first and confirmed failing against the current code, that runs info with --json against a workbook containing a sheet name with a colon and a comma and asserts the parsed JSON carries the exact name and dimensions; a test for get --json asserting an empty cell and a cell holding the string "null" are distinguishable; tests asserting the non-JSON output of both commands is unchanged; and a test asserting a failing command under --json emits a JSON error on stderr with a non-zero exit. Then make it pass across every command in the CLI.

Keep the change minimal. Confine changes to src/cli/index.ts and any small shared output helper. Do not change the workbook model, the formula recalculation, or the CSV/XLSX serializers in src/lib.

Verification

  • policy source: base e1a2f3c (immutable commit — agent-proof)
  • containment: env — allowlist env, non-login shell, run-scoped HOME (registry auth seeded for install)
  • install: pass
  • typecheck: pass
  • build: pass
  • test: pass
  • doctor (ci): ok — 11 checks passed (1 advisory)

Run run_1fd244465266 · backend codewith · task 075690b1-74ab-4a1e-b9b4-4dc4aab010b3
🏭 Generated by @hasnaxyz/factory


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Add --json output to every sheets CLI command

The `sheets` CLI in @hasna/sheets has no machine-readable output mode: `info`, `get` and the other inspection commands print prose that a script or agent can only screen-scrape.

Problem, in full. `src/cli/index.ts` registers around nine commands over workbook JSON, CSV and XLSX documents. Output is written as formatted prose. For example the `info` command does:

    console.log(`Workbook ${workbook.id} (${workbook.sheets.length} sheet(s))`);
    for (const sheet of workbook.sheets) {
      const count = Object.keys(sheet.cells).length;
      const active = sheet.id === workbook.activeSheetId ? " [active]" : "";
      console.log(`  - ${sheet.name}: ${sheet.rows}x${sheet.columns}, ${count} cell(s)${active}`);
    }

and the shared `emit(content, out)` helper either writes a file or prints the raw string. A repository-wide search for `--json` in `src/` returns zero hits — the flag exists on no command and not on the program.

To read a workbook's sheet dimensions programmatically, a caller must parse `"  - Sheet1: 100x26, 12 cell(s) [active]"`, which breaks as soon as a sheet name contains a colon, a comma, or the literal text ` [active]`. Sheet names are user-controlled, so that is reachable rather than theoretical. `get` has the same problem: a computed cell value is printed bare, so an empty cell, the string "null", and a genuine null are indistinguishable on stdout.

This is a parity gap against the rest of the Hasna CLI surface, where `--json` is the standard way an agent consumes output, and the same family of work has already been carried out for other CLIs in this codebase.

Expected behaviour after the fix. A global `--json` flag is accepted, and every command that produces output honours it by emitting one well-formed JSON document on stdout carrying the structured data the command already holds — for `info`, the workbook id and an array of sheet objects with id, name, rows, columns, cell count and active flag; for `get`, the resolved sheet, the A1 reference, and the computed value with its real type preserved. Commands whose output is a document body (workbook JSON, CSV, XLSX) keep writing that body unchanged, since it is already machine-readable; `--json` must not wrap or corrupt it. Human output without the flag is unchanged character for character. Errors under `--json` are emitted as a JSON object on stderr with a non-zero exit code.

Reproduce. Run the `info` command against a workbook whose sheet is named `Q1: revenue, draft` and observe the printed line cannot be parsed back into fields. Run `get` on an empty cell and observe the output is indistinguishable from a cell containing an empty string. Confirm `sheets --help` offers no JSON option.

Acceptance criterion. A regression test, written first and confirmed failing against the current code, that runs `info` with `--json` against a workbook containing a sheet name with a colon and a comma and asserts the parsed JSON carries the exact name and dimensions; a test for `get --json` asserting an empty cell and a cell holding the string "null" are distinguishable; tests asserting the non-JSON output of both commands is unchanged; and a test asserting a failing command under `--json` emits a JSON error on stderr with a non-zero exit. Then make it pass across every command in the CLI.

Keep the change minimal. Confine changes to `src/cli/index.ts` and any small shared output helper. Do not change the workbook model, the formula recalculation, or the CSV/XLSX serializers in `src/lib`.

X-Factory-Run: run_1fd244465266
X-Factory-Task: 075690b1-74ab-4a1e-b9b4-4dc4aab010b3
@andrei-hasna
andrei-hasna merged commit dc9b775 into main Jul 31, 2026
2 checks passed
@andrei-hasna
andrei-hasna deleted the factory/075690b1-74ab-4a1e-b9b4-4dc4aab0-fa2b524d branch July 31, 2026 09:42
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