cli: clarify no-arg usage and add top-level help (#448)

This commit is contained in:
foxytanuki
2026-04-01 01:36:10 +09:00
committed by GitHub
parent fad822e6ca
commit ab9cae3488
3 changed files with 103 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
import { describe, expect, test } from "bun:test";
import {
formatInteractiveNoArgClarification,
formatTopLevelHelp,
isInteractiveNoArgInvocation,
isTopLevelHelpInvocation,
} from "./cli";
describe("CLI top-level help", () => {
test("recognizes top-level --help", () => {
expect(isTopLevelHelpInvocation(["--help"])).toBe(true);
expect(isTopLevelHelpInvocation([])).toBe(false);
expect(isTopLevelHelpInvocation(["review", "--help"])).toBe(false);
});
test("renders concise top-level usage", () => {
const output = formatTopLevelHelp();
expect(output).toContain("plannotator --help");
expect(output).toContain("plannotator [--browser <name>]");
expect(output).toContain("plannotator review [PR_URL]");
expect(output).toContain("plannotator annotate <file.md | folder/>");
expect(output).toContain("running 'plannotator' without arguments is for hook integration");
});
});
describe("interactive no-arg invocation", () => {
test("detects bare interactive invocation only when stdin is a TTY", () => {
expect(isInteractiveNoArgInvocation([], true)).toBe(true);
expect(isInteractiveNoArgInvocation([], false)).toBe(false);
expect(isInteractiveNoArgInvocation([], undefined)).toBe(false);
expect(isInteractiveNoArgInvocation(["review"], true)).toBe(false);
});
test("renders clarification for interactive users", () => {
const output = formatInteractiveNoArgClarification();
expect(output).toContain("usually launched automatically by Claude Code hooks");
expect(output).toContain("It expects hook JSON on stdin.");
expect(output).toContain("plannotator review");
expect(output).toContain("plannotator sessions");
expect(output).toContain("Run 'plannotator --help' for top-level usage.");
});
});
+42
View File
@@ -0,0 +1,42 @@
export function isTopLevelHelpInvocation(args: string[]): boolean {
return args[0] === "--help";
}
export function isInteractiveNoArgInvocation(
args: string[],
stdinIsTTY: boolean | undefined,
): boolean {
return args.length === 0 && stdinIsTTY === true;
}
export function formatTopLevelHelp(): string {
return [
"Usage:",
" plannotator --help",
" plannotator [--browser <name>]",
" plannotator review [PR_URL]",
" plannotator annotate <file.md | folder/>",
" plannotator last",
" plannotator archive",
" plannotator sessions",
"",
"Note:",
" running 'plannotator' without arguments is for hook integration and expects JSON on stdin",
].join("\n");
}
export function formatInteractiveNoArgClarification(): string {
return [
"plannotator (without arguments) is usually launched automatically by Claude Code hooks.",
"It expects hook JSON on stdin.",
"",
"For interactive use, try:",
" plannotator review",
" plannotator annotate <file.md>",
" plannotator last",
" plannotator archive",
" plannotator sessions",
"",
"Run 'plannotator --help' for top-level usage.",
].join("\n");
}
+17
View File
@@ -38,6 +38,7 @@
* - Parses events.jsonl from session state
*
* Global flags:
* --help - Show top-level usage information
* --browser <name> - Override which browser to open (e.g. "Google Chrome")
*
* Environment variables:
@@ -71,6 +72,12 @@ import type { Origin } from "@plannotator/shared/agents";
import { findSessionLogsForCwd, resolveSessionLogByPpid, findSessionLogsByAncestorWalk, getLastRenderedMessage, type RenderedMessage } from "./session-log";
import { findCodexRolloutByThreadId, getLastCodexMessage } from "./codex-session";
import { findCopilotPlanContent, findCopilotSessionForCwd, getLastCopilotMessage } from "./copilot-session";
import {
formatInteractiveNoArgClarification,
formatTopLevelHelp,
isInteractiveNoArgInvocation,
isTopLevelHelpInvocation,
} from "./cli";
import path from "path";
// Embed the built HTML at compile time
@@ -92,6 +99,16 @@ if (browserIdx !== -1 && args[browserIdx + 1]) {
args.splice(browserIdx, 2);
}
if (isTopLevelHelpInvocation(args)) {
console.log(formatTopLevelHelp());
process.exit(0);
}
if (isInteractiveNoArgInvocation(args, process.stdin.isTTY)) {
console.log(formatInteractiveNoArgClarification());
process.exit(0);
}
// Ensure session cleanup on exit
process.on("exit", () => unregisterSession());