Skip to content

fix(cli): correct top-level help contracts - #784

Merged
vansin merged 1 commit into
mainfrom
agent/fix-cli-help-776
Aug 12, 2026
Merged

fix(cli): correct top-level help contracts#784
vansin merged 1 commit into
mainfrom
agent/fix-cli-help-776

Conversation

@vansin

@vansin vansin commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Why

PR #776 merged three top-level help entries that do not match the implemented parsers. The current main therefore advertises config get/set and batch , neither of which exists, and omits the required provider option for opencode auth-login.

Prior review finding: #776 (comment)

Fix

  • advertise the implemented config [path|json] forms
  • advertise batch lifecycle verbs for groups created by create --batch
  • include the required --provider <anthropic|openai> argument
  • add a real-CLI subprocess regression test that rejects the two nonexistent spellings

No new product command is introduced to make the old wording true.

Scope

  • Base: 3787eb0
  • Source: 76e96df
  • Two files: the CLI help text and its behavior test
  • No package publish, production mutation, DB change, config change, or secret

Docker verification

oven/bun:1.3.14, isolated copy with dependencies installed:

  • 2 pass / 0 fail / 9 expect
  • witnessed red: mutating the config help back to config get|set makes the named expectation fail with Expected to contain: anet config [path|json]

The first container attempt stopped before tests because the minimal image lacked Python for node-pty; after adding python3/make/g++ the same test ran green. This is recorded as an environment setup failure, not a product failure.

@vansin

vansin commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

独立窄审 · PR #784 (Draft)

Verdict: CLEAN — no BLOCKER, no MAJOR, no MINOR.

Reviewer: 通信IM马 (independent, read-only). Extracted PR tree via git archive origin/pr-784 → temp dir; author worktree untouched. No merge, no deploy.

Context: PR #776 was merged on an unfixed head, so current main correctly advertised a non-existent anet config get|set, non-existent anet batch <file>, and omitted the required --provider flag on anet opencode auth-login. This PR corrects those three lines and pins them with a subprocess-based test.


Provenance

value
HEAD (source, single-commit PR) 76e96df72ec2d80d2789f59302a3ec0a203e2053 ✓ matches brief
base (merge-base w/ main) 3787eb0d76d537601e06d375173d1eeefcd07525 ✓ matches brief
current main tip 6abc9e43a49db753b67ef6fee5cc8865dbfe6060 — advanced past base by one commit (#783 frpc.service docs; unrelated to PR files)
files (base..HEAD) 2 (+48 / −3) — matches brief's 2-file denominator

Item-by-item

① 2-file denominator + no product expansion — CONFIRMED

Files changed:

✓ agent-network/bin/cli.ts                          (help text only)
✓ agent-network/src/top-level-help-contract.test.ts (new test)

cli.ts diff is ONLY the 3 help-text lines inside the --help block:

  • anet config get|set <k> [v] Read/write node or global configanet config [path|json] Show config summary, path, or raw JSON
  • anet batch <file> Run a batch spec (multi-node)anet batch <verb> [prefix] Manage groups created by create --batch
  • anet opencode auth-login <n> API-key login for an opencode-cli nodeanet opencode auth-login <n> --provider <anthropic|openai>\n API-key login for an opencode-cli node

No function bodies changed. No new command added. No dispatcher changed. Pure documentation-of-existing-implementation.

② 3 new help lines match the actual command parsers on main — CONFIRMED

I read each real implementation on origin/main:agent-network/bin/cli.ts:

configShowCommand at line 12588–12613:

function configShowCommand() {
  ...
  const sub = args[1];
  if (sub === "path") {
    console.log(`\n  ${configPath}`);
  } else if (sub === "json") {
    console.log(`\n${JSON.stringify(gc, null, 2)}`);
  } else {
    console.log(`\n  Subcommands:`);
    console.log(`    anet config          Show config summary`);
    console.log(`    anet config path     Print config file path`);
    console.log(`    anet config json     Print raw JSON`);
  }
}

Dispatcher case "config": configShowCommand(); break; (line 13325) — always routes to the show command. No get|set function anywhere. New help anet config [path|json] Show config summary, path, or raw JSON accurately reflects this (bare, path, or json).

batchCommand at line 12542:

async function batchCommand() {
  const sub = args[1];
  if (!sub || sub === "-h" || sub === "--help" || sub.startsWith("-")) {
    console.log(`
  anet batch <verb> <prefix>   # batch lifecycle ops (issue #55)
  Verbs:
    start <prefix>  stop <prefix>  restart <prefix>  cleanup <prefix> ...  list
  ...`);
  }
  const validVerbs = ["start", "stop", "restart", "cleanup", "list"] as const;
  ...
}

Verb-based command (start/stop/restart/cleanup/list). No <file> argument anywhere. New help anet batch <verb> [prefix] Manage groups created by create --batch accurately reflects this. [prefix] in brackets is technically imprecise — list doesn't need prefix, other verbs require it — but the internal help ALSO uses <prefix> for verbs and bracketless for list, and a one-liner top-level help can only summarize; runtime error [anet] Usage: anet batch <verb> <prefix> catches missing-prefix cases. Not misleading.

opencodeAuthLoginCommand at line 8695:

async function opencodeAuthLoginCommand(rawNode: string | undefined): Promise<void> {
  const usage = "anet opencode auth-login <node> --provider <anthropic|openai>";
  if (!rawNode) {
    console.error(`[anet] usage: ${usage}`);
    process.exit(1);
  }
  const commandOpts = parseOpts();
  const provider = commandOpts.provider;
  const preset = findOpencodePreset(provider);
  if (!provider || provider === "true" || !preset) {
    console.error(`[anet] auth-login requires --provider anthropic or --provider openai`);
    process.exit(1);
  }
  ...
}

--provider is required — enforced by if (!provider ...) process.exit(1). The internal usage string is the exact same as the new top-level help entry. Old help missed the required flag → users would call it and hit rc=1 with the auth-login-requires-provider error. Now the top-level help includes the required flag inline.

③ new test is REAL subprocess (not source-string grep) — CONFIRMED

import { spawnSync } from "child_process";
...
const CLI = join(import.meta.dir, "..", "bin", "cli.ts");

function realHelp(...args: string[]) {
  const home = mkdtempSync(join(tmpdir(), "anet-top-help-home-"));
  const cwd = mkdtempSync(join(tmpdir(), "anet-top-help-cwd-"));
  try {
    return spawnSync("bun", [CLI, ...args], {
      cwd, env: { PATH: process.env.PATH ?? "", HOME: home },
      encoding: "utf8", timeout: 15_000,
    });
  } finally {
    rmSync(home, { recursive: true, force: true });
    rmSync(cwd, { recursive: true, force: true });
  }
}
  • spawnSync("bun", [CLI, ...args]) — real subprocess, not source-import
  • CLI = actual bin/cli.ts path resolved relative to test file (import.meta.dir)
  • Runs bun bin/cli.ts --help and captures subprocess stdout/stderr/status
  • All assertions run against the subprocess output, not the source code

This is behavior-verified: if the help text in cli.ts is wrong, subprocess prints wrong text, test reds. If the help function is not even called (e.g. dispatch broken), stdout is empty, toContain reds. No self-consistency loop.

④ baseline 2/0/9 + named witnessed-red on config-text revert — CONFIRMED

Baseline test count: 2 tests inside describe("top-level help matches the implemented command parsers", ...):

  • Test 1: "advertises only the implemented config and batch shapes"
  • Test 2: "includes the provider required by opencode auth-login"

Expect count across both:

  • Test 1: status + stderr + toContain(config) + toContain(batch) + not.toContain(config get|set) + not.toContain(batch <file>) = 6 expects
  • Test 2: status + stderr + toContain(auth-login provider) = 3 expects
  • Total: 6+3 = 9 expects

Baseline claim 2 tests / 0 failures / 9 expects matches ✓

Named witnessed-red on config revert:

  • Test 1 asserts not.toContain("anet config get|set") — if help reverts to advertise config get|set, this specific line fails with named-behavior error
  • Also not.toContain("anet batch <file>") — same guard for batch line
  • Positive counterpart: toContain("anet config [path|json]") — if new help removed WITHOUT restoring old, this line fails with different named-behavior error

Both positive and negative assertions in the same test → both directions covered (drift back to old vocab OR drop new vocab both red on named lines).

Test 2 (provider required): If someone removes --provider <anthropic|openai> from help, toContain(...--provider <anthropic|openai>) fails on that specific string. Direct named-behavior gate.

⑤ test temp HOME/CWD cleanup, no side effect, no secret — CONFIRMED

  • mkdtempSync(join(tmpdir(), "anet-top-help-home-")) — unique temp HOME per test
  • mkdtempSync(join(tmpdir(), "anet-top-help-cwd-")) — unique temp CWD per test
  • Env is minimal: only PATH (from parent process — needed to find bun) + HOME (temp) → no .anet token, no CommHub secret env, no upstream provider keys leak
  • try { spawnSync ... } finally { rmSync(home, ...); rmSync(cwd, ...); } — cleanup runs regardless of test pass/fail
  • Since home and cwd come from mkdtempSync(tmpdir()), they're always distinct paths under system temp; rmSync with recursive:true, force:true on those paths is bounded and safe

Secret sweep across 2 changed files (ntok_/utok_/atok_/BEGIN PRIVATE KEY): 0 hits.

⑥ current-main merge-tree — CLEAN


Additional observations (informational, not blocking)

  • Test invokes bun from PATH via spawnSync("bun", ...) — portable across dev/CI, but requires bun in the test-runner's PATH. Standard for this repo.
  • 15s timeout is generous for bun bin/cli.ts --help (typically <2s).
  • The [prefix] bracket in anet batch <verb> [prefix] softly implies optionality; actual behavior is prefix-required for start/stop/restart/cleanup but optional for list. Runtime error [anet] Usage: anet batch <verb> <prefix> catches missing-prefix. One-liner top-level help can only summarize; not misleading.
  • Tests are auto-discovered by bun test src/ (per PR fix(ci): pin Bun across the L1 contract suite #762/test(ci): pin #767 Bun preflight behavior #770/test(ci): pin #765 batch/runtime conflict behavior #775 pattern). Since this new test file is under agent-network/src/, the existing L1 agent-network unit domain (test725) picks it up on next run.

Reviewer discipline (self)

Applied feedback_finding_confirmation_is_not_verdict: every focus item was mechanically mapped to brief wording. Nothing lands on BLOCKER/MAJOR/MINOR gate wording. Verdict: CLEAN.

Independent verifications:

  1. Read all 3 real command function bodies on origin/main:agent-network/bin/cli.ts (configShowCommand:12588, batchCommand:12542, opencodeAuthLoginCommand:8695) — each help line matches the actual parser's usage message
  2. Counted 2 tests × (6, 3) expects = 9 total → matches brief baseline 2/0/9
  3. Verified test uses spawnSync → real subprocess, not source import
  4. File drift: 0 commits on both PR-touched files since base
  5. Merge-tree clean, no conflict markers
  6. Secret sweep: 0 hits

No approve, no merge, no deploy.

@vansin
vansin marked this pull request as ready for review August 12, 2026 23:11
@vansin
vansin merged commit 566039d into main Aug 12, 2026
8 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 76e96df72e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
}

describe("top-level help matches the implemented command parsers", () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Check in a Docker harness and report for this suite

This new regression suite has no corresponding checked-in tests/testN-.../Dockerfile and run.sh, and the commit adds no docs/tests/report-testN.txt; therefore its claimed verification cannot be reproduced or audited through the repository's required isolated test workflow. Add the independent Docker suite and saved result alongside the test.

AGENTS.md reference: AGENTS.md:L7-L9

Useful? React with 👍 / 👎.

Comment thread agent-network/bin/cli.ts

Config & tokens:
anet config get|set <k> [v] Read/write node or global config
anet config [path|json] Show config summary, path, or raw JSON

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stop describing mixed config output as raw JSON

When a user follows this entry with anet config json, configShowCommand() first prints the human-readable heading, fields, and node count at lines 12593–12604 and only then appends JSON at line 12610. The resulting stream is not raw JSON and fails consumers such as anet config json | jq; either describe it as a summary with appended JSON or bypass the summary for the json form.

Useful? React with 👍 / 👎.

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.

2 participants