Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export function runCli(argv: string[]): void {
program
.name("primer")
.description("Prime repositories for AI-assisted development")
.version("0.1.0");
.version("0.1.0")
.option("--accessible", "Enable screen reader friendly output (removes box-drawing characters, replaces Unicode symbols with text)");
Comment on lines +20 to +21
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The CLI version is hardcoded as 0.1.0, but package.json is 1.0.0. This can cause confusing/mismatched primer --version output. Consider sourcing the version from package.json (or keeping these in sync).

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

--accessible is added only on the root command. With Commander’s default parsing rules, primer tui --accessible will be treated as an unknown option on the tui subcommand (users must write primer --accessible tui). If you want it to behave like a true global flag regardless of position, consider also adding the option to subcommands (or enabling positional/pass-through options).

Copilot uses AI. Check for mistakes.

program
.command("init")
Expand Down
10 changes: 6 additions & 4 deletions src/commands/batch.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React from "react";
import { render } from "ink";
import { Command } from "commander";
import { BatchTui } from "../ui/BatchTui";
import { getGitHubToken } from "../services/github";

type BatchOptions = {
output?: string;
};

export async function batchCommand(options: BatchOptions): Promise<void> {
export async function batchCommand(options: BatchOptions, cmd: Command): Promise<void> {
const token = await getGitHubToken();

if (!token) {
console.error("Error: GitHub authentication required.");
console.error("");
Expand All @@ -22,10 +23,11 @@ export async function batchCommand(options: BatchOptions): Promise<void> {
return;
}

const accessible = cmd.parent?.opts().accessible || process.env.INK_SCREEN_READER === "true";
const { waitUntilExit } = render(
<BatchTui token={token} outputPath={options.output} />
<BatchTui token={token} outputPath={options.output} accessible={accessible} />,
{ isScreenReaderEnabled: accessible }
Comment on lines +26 to +29
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

batchCommand dereferences cmd.parent without guarding cmd itself. If the Command instance is not passed into the action handler (depending on Commander configuration), this will crash primer batch at startup. Consider making cmd optional (cmd?.parent?.opts()) and/or enabling passCommandToAction(true) when registering commands.

Copilot uses AI. Check for mistakes.
);


await waitUntilExit();
}
9 changes: 7 additions & 2 deletions src/commands/tui.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import path from "path";
import React from "react";
import { render } from "ink";
import { Command } from "commander";
import { PrimerTui } from "../ui/tui";

type TuiOptions = {
repo?: string;
animation?: boolean;
};

export async function tuiCommand(options: TuiOptions): Promise<void> {
export async function tuiCommand(options: TuiOptions, cmd: Command): Promise<void> {
const repoPath = path.resolve(options.repo ?? process.cwd());
const skipAnimation = options.animation === false;
const { waitUntilExit } = render(<PrimerTui repoPath={repoPath} skipAnimation={skipAnimation} />);
const accessible = cmd.parent?.opts().accessible || process.env.INK_SCREEN_READER === "true";
const { waitUntilExit } = render(
<PrimerTui repoPath={repoPath} skipAnimation={skipAnimation || accessible} accessible={accessible} />,
{ isScreenReaderEnabled: accessible }
Comment on lines +15 to +18
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

tuiCommand assumes Commander passes a cmd argument, but cmd is dereferenced unguarded (cmd.parent...). If Commander doesn’t pass the Command instance (depending on configuration), this will throw at runtime and break primer tui. Make cmd optional (use cmd?.parent?.opts()) and/or enable passCommandToAction(true) when wiring commands so the Command instance is reliably available.

Copilot uses AI. Check for mistakes.
);
await waitUntilExit();
}
13 changes: 11 additions & 2 deletions src/ui/AnimatedBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,19 @@ export function AnimatedBanner({

/**
* Static banner for use after animation or when animation is disabled.
* When accessible=true, renders plain text instead of block art.
*/
export function StaticBanner({ darkMode = true }: { darkMode?: boolean }): React.JSX.Element {
export function StaticBanner({ darkMode = true, accessible = false }: { darkMode?: boolean; accessible?: boolean }): React.JSX.Element {
const color = darkMode ? "magentaBright" : "magenta";


if (accessible) {
return (
<Box flexDirection="column">
<Text color={color} bold>PRIMER</Text>
</Box>
);
}

return (
<Box flexDirection="column">
{FULL_BANNER.map((line, i) => (
Expand Down
27 changes: 14 additions & 13 deletions src/ui/BatchTui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { StaticBanner } from "./AnimatedBanner";
type Props = {
token: string;
outputPath?: string;
accessible?: boolean;
};

type Status =
Expand All @@ -38,7 +39,7 @@ type ProcessResult = {
error?: string;
};

export function BatchTui({ token, outputPath }: Props): React.JSX.Element {
export function BatchTui({ token, outputPath, accessible = false }: Props): React.JSX.Element {
const app = useApp();
const [status, setStatus] = useState<Status>("loading-orgs");
const [message, setMessage] = useState<string>("Fetching organizations...");
Expand Down Expand Up @@ -315,8 +316,8 @@ export function BatchTui({ token, outputPath }: Props): React.JSX.Element {
};

return (
<Box flexDirection="column" padding={1} borderStyle="round">
<StaticBanner />
<Box flexDirection="column" padding={1} borderStyle={accessible ? undefined : "round"}>
<StaticBanner accessible={accessible} />
<Text color="cyan">Batch Processing - Prime repositories at scale</Text>
<Box marginTop={1}>
<Text>{message}</Text>
Expand All @@ -338,8 +339,8 @@ export function BatchTui({ token, outputPath }: Props): React.JSX.Element {
const isCursor = realIndex === cursorIndex;
return (
<Text key={org.login}>
<Text color={isCursor ? "cyan" : undefined}>{isCursor ? "❯ " : " "}</Text>
<Text color={isSelected ? "green" : "gray"}>{isSelected ? "◉" : "○"} </Text>
<Text color={isCursor ? "cyan" : undefined}>{isCursor ? (accessible ? "> " : "❯ ") : " "}</Text>
<Text color={isSelected ? "green" : "gray"}>{isSelected ? (accessible ? "[x]" : "◉") : (accessible ? "[ ]" : "○")} </Text>
<Text>{org.name ?? org.login}</Text>
<Text color="gray"> ({org.login})</Text>
</Text>
Expand All @@ -364,9 +365,9 @@ export function BatchTui({ token, outputPath }: Props): React.JSX.Element {
const isCursor = realIndex === cursorIndex;
return (
<Text key={repo.fullName}>
<Text color={isCursor ? "cyan" : undefined}>{isCursor ? "❯ " : " "}</Text>
<Text color={isSelected ? "green" : "gray"}>{isSelected ? "◉" : "○"} </Text>
<Text color={repo.hasInstructions ? "green" : "red"}>{repo.hasInstructions ? "✓" : "✗"} </Text>
<Text color={isCursor ? "cyan" : undefined}>{isCursor ? (accessible ? "> " : "❯ ") : " "}</Text>
<Text color={isSelected ? "green" : "gray"}>{isSelected ? (accessible ? "[x]" : "◉") : (accessible ? "[ ]" : "○")} </Text>
<Text color={repo.hasInstructions ? "green" : "red"}>{repo.hasInstructions ? (accessible ? "HAS" : "✓") : (accessible ? "NEEDS" : "✗")} </Text>
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

In accessible mode, the repo status indicator uses very terse labels (HAS / NEEDS). For screen reader output this can be ambiguous (has/needs what?). Consider using more explicit text like HAS_INSTRUCTIONS / NEEDS_INSTRUCTIONS (or similar) so the meaning is clear when read aloud.

Suggested change
<Text color={repo.hasInstructions ? "green" : "red"}>{repo.hasInstructions ? (accessible ? "HAS" : "✓") : (accessible ? "NEEDS" : "✗")} </Text>
<Text color={repo.hasInstructions ? "green" : "red"}>{repo.hasInstructions ? (accessible ? "HAS_INSTRUCTIONS" : "✓") : (accessible ? "NEEDS_INSTRUCTIONS" : "✗")} </Text>

Copilot uses AI. Check for mistakes.
<Text color={repo.hasInstructions ? "gray" : undefined}>{repo.fullName}</Text>
{repo.isPrivate && <Text color="yellow"> (private)</Text>}
</Text>
Expand Down Expand Up @@ -394,8 +395,8 @@ export function BatchTui({ token, outputPath }: Props): React.JSX.Element {
<Text color="cyan">Completed:</Text>
{results.slice(-5).map((r) => (
<Text key={r.repo} color={r.success ? "green" : "red"}>
{r.success ? "✓" : "✗"} {r.repo}
{r.success && r.prUrl && <Text color="gray"> {r.prUrl}</Text>}
{r.success ? (accessible ? "OK" : "✓") : (accessible ? "FAIL" : "✗")} {r.repo}
{r.success && r.prUrl && <Text color="gray"> {accessible ? "- " : "→ "}{r.prUrl}</Text>}
{!r.success && r.error && <Text color="gray"> ({r.error})</Text>}
</Text>
))}
Expand All @@ -407,13 +408,13 @@ export function BatchTui({ token, outputPath }: Props): React.JSX.Element {
{status === "complete" && (
<Box flexDirection="column" marginTop={1}>
<Text color="green" bold>
Batch complete: {results.filter(r => r.success).length} succeeded, {results.filter(r => !r.success).length} failed
{accessible ? "DONE" : "✓"} Batch complete: {results.filter(r => r.success).length} succeeded, {results.filter(r => !r.success).length} failed
</Text>
<Box flexDirection="column" marginTop={1}>
{results.map((r) => (
<Text key={r.repo} color={r.success ? "green" : "red"}>
{r.success ? "✓" : "✗"} {r.repo}
{r.success && r.prUrl && <Text color="gray"> {r.prUrl}</Text>}
{r.success ? (accessible ? "OK" : "✓") : (accessible ? "FAIL" : "✗")} {r.repo}
{r.success && r.prUrl && <Text color="gray"> {accessible ? "- " : "→ "}{r.prUrl}</Text>}
{!r.success && r.error && <Text color="gray"> ({r.error})</Text>}
</Text>
))}
Expand Down
15 changes: 8 additions & 7 deletions src/ui/tui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import { getGitHubToken } from "../services/github";
type Props = {
repoPath: string;
skipAnimation?: boolean;
accessible?: boolean;
};

type Status = "intro" | "idle" | "analyzing" | "generating" | "evaluating" | "preview" | "done" | "error" | "batch";

export function PrimerTui({ repoPath, skipAnimation = false }: Props): React.JSX.Element {
export function PrimerTui({ repoPath, skipAnimation = false, accessible = false }: Props): React.JSX.Element {
const app = useApp();
const [status, setStatus] = useState<Status>(skipAnimation ? "idle" : "intro");
const [analysis, setAnalysis] = useState<RepoAnalysis | null>(null);
Expand Down Expand Up @@ -161,15 +162,15 @@ export function PrimerTui({ repoPath, skipAnimation = false }: Props): React.JSX

// Render BatchTui when in batch mode
if (status === "batch" && batchToken) {
return <BatchTui token={batchToken} />;
return <BatchTui token={batchToken} accessible={accessible} />;
}

return (
<Box flexDirection="column" padding={1} borderStyle="round">
<Box flexDirection="column" padding={1} borderStyle={accessible ? undefined : "round"}>
{status === "intro" ? (
<AnimatedBanner onComplete={handleAnimationComplete} />
) : (
<StaticBanner />
<StaticBanner accessible={accessible} />
)}
<Text color="cyan">Prime your repo for AI.</Text>
<Text color="gray">Repo: {repoLabel}</Text>
Expand All @@ -187,17 +188,17 @@ export function PrimerTui({ repoPath, skipAnimation = false }: Props): React.JSX
<Text>{message}</Text>
</Box>
{status === "preview" && generatedContent && (
<Box flexDirection="column" marginTop={1} borderStyle="single" borderColor="gray" paddingX={1}>
<Box flexDirection="column" marginTop={1} borderStyle={accessible ? undefined : "single"} borderColor="gray" paddingX={1}>
<Text color="cyan" bold>Preview (.github/copilot-instructions.md):</Text>
<Text color="gray">{truncatedPreview}</Text>
</Box>
)}
{evalResults && evalResults.length > 0 && (
<Box flexDirection="column" marginTop={1} borderStyle="single" borderColor="gray" paddingX={1}>
<Box flexDirection="column" marginTop={1} borderStyle={accessible ? undefined : "single"} borderColor="gray" paddingX={1}>
<Text color="cyan" bold>Eval Results:</Text>
{evalResults.map((r) => (
<Text key={r.id} color={r.verdict === "pass" ? "green" : r.verdict === "fail" ? "red" : "yellow"}>
{r.verdict === "pass" ? "✓" : r.verdict === "fail" ? "✗" : "?"} {r.id}: {r.verdict} (score: {r.score})
{r.verdict === "pass" ? (accessible ? "PASS" : "✓") : r.verdict === "fail" ? (accessible ? "FAIL" : "✗") : "?"} {r.id}: {r.verdict} (score: {r.score})
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

In accessible mode, the verdict line prints both the text label and the verdict value (e.g., PASS <id>: pass ... / FAIL <id>: fail ...), which is redundant when read aloud. Consider outputting a single normalized label (e.g., Verdict: PASS) or omitting r.verdict when the accessible label is used.

Suggested change
{r.verdict === "pass" ? (accessible ? "PASS" : "✓") : r.verdict === "fail" ? (accessible ? "FAIL" : "✗") : "?"} {r.id}: {r.verdict} (score: {r.score})
{accessible
? `Verdict: ${r.verdict === "pass" ? "PASS" : r.verdict === "fail" ? "FAIL" : "UNKNOWN"} ${r.id} (score: ${r.score})`
: `${r.verdict === "pass" ? "✓" : r.verdict === "fail" ? "✗" : "?"} ${r.id}: ${r.verdict} (score: ${r.score})`}

Copilot uses AI. Check for mistakes.
</Text>
))}
</Box>
Expand Down
Loading