jackwener / jackwener/OpenCLI

[Feature]: give doctor a machine-readable result — --strict exit code and -f json

Open
#2,417 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
29.3k
Forks
2.9k
Avg merge
15h 36m
Merged PRs (30d)
70

Description

### Problem

`opencli doctor` has no machine-readable surface, so scripts and agents cannot act on its result:

- **Exit code is always 0.** The action only logs (`src/cli.ts:3094-3099`), and never assigns `process.exitCode`:

```js
.action(async (opts) => {
applyVerbose(opts);
const { runBrowserDoctor, renderBrowserDoctorReport } = await import('./doctor.js');
const report = await runBrowserDoctor({ cliVersion: PKG_VERSION });
console.log(renderBrowserDoctorReport(report));
});
```

- **No structured output.** `opencli doctor --json` → `error: unknown option '--json'`.

So a health gate cannot distinguish a healthy bridge from a missing extension:

```bash
$ opencli doctor >/dev/null 2>&1; echo $?
0 # bridge healthy

$ # ... with the extension disconnected, doctor prints [MISSING] and Issues:
$ opencli doctor >/dev/null 2>&1; echo $?
0 # indistinguishable
```

The only options left are parsing the human-readable text (which shifts with rendering changes) or bypassing `doctor` entirely. I ended up doing the latter — grepping the `Extension:` line out of `opencli daemon status` — specifically because `doctor`'s exit code carries no signal. That is the wrong tool for the job: `doctor` is the command that actually runs the live connectivity probe, checks compat ranges, and detects stale profiles.

This matters for the agent-native use case in particular. A wrapper that wants to fail fast when the bridge is down — rather than burning a 60–120s command timeout per call into a disconnected extension — needs a cheap, reliable, machine-checkable preflight.

### Proposed Solution

Follow the pattern `convention-audit` already establishes in this repo (`src/cli.ts:962-983`):

```js
.option('-f, --format ', 'Output format: table, json, yaml', 'table')
.option('--strict', 'Exit non-zero when violations are found', false)
// ...
if (opts.strict && !report.ok) process.exitCode = EXIT_CODES.GENERIC_ERROR;
```

Applied to `doctor`:

1. Add `--strict` so `doctor` exits non-zero when the report is not healthy. Default stays 0 to preserve current behavior for interactive users and existing scripts.
2. Add `-f, --format ` and emit `DoctorReport` through the existing `renderOutput()` helper for `json`/`yaml`.

One implementation note: unlike `ConventionAuditReport`, `DoctorReport` (`src/doctor.ts:61-75`) has no `ok` field — `ConnectivityResult.ok` is a different, narrower thing. So `--strict` needs a health predicate. `report.issues.length === 0` matches what `renderBrowserDoctorReport()` already treats as healthy when it prints "Everything looks good!" (line 297-299), which keeps text and exit code from drifting apart. Adding a derived `ok: boolean` to `DoctorReport` would make it explicit and mirror the audit report's shape.

`DoctorReport` is already a flat serializable object, so the JSON path needs no new plumbing, and `doctor.test.ts` has 17 existing rendering cases to extend.

If you'd like, I'm happy to send a PR along these lines — I wanted to check first whether you'd prefer `--strict` versus making non-zero the default, since the latter is a breaking change for anyone running `doctor` in a script today.

### Alternatives Considered

- **Parsing the text output.** Works today but couples callers to the rendering. The `[OK]`/`[WARN]`/`[MISSING]`/`[FAIL]` markers are presentation details, and the labels have changed before (e.g. flaky/unstable states).
- **Using `opencli daemon status` instead.** What I settled on, but it only reports the extension's connection state. It does not run the live connectivity probe, the compat-range check, or the stale-default-profile detection, so it answers a strictly weaker question than `doctor`.
- **Making `doctor` exit non-zero unconditionally.** Cleanest semantically, but breaks existing scripts and anyone running it interactively under `set -e`. Hence `--strict` as the opt-in.

### Environment

- OpenCLI: 1.8.7 (source verified at `90d5070`)
- Extension: 1.0.23
- OS: macOS 26.5 (Darwin 25.5.0, arm64)

Contributor guide

Open the contributing guide

Research direction

Start in src/cli.ts:3094-3099 for the doctor command and compare the convention-audit options at lines 962-983. Read DoctorReport in src/doctor.ts:61-75, the existing renderOutput() helper, and doctor.test.ts. Done means table output remains compatible, JSON/YAML output works, and --strict exits non-zero when the report has issues.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
cli
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.