diff --git a/src/cli/tui/App.tsx b/src/cli/tui/App.tsx
index 81795d16d..618f87413 100644
--- a/src/cli/tui/App.tsx
+++ b/src/cli/tui/App.tsx
@@ -12,6 +12,7 @@ import { DatasetFlow } from './screens/dataset-hub';
import { DeployScreen } from './screens/deploy/DeployScreen';
import { EvalHubScreen, EvalScreen } from './screens/eval';
import { ExportHarnessFlow } from './screens/export';
+import { FeedbackScreen } from './screens/feedback';
import { FetchAccessScreen } from './screens/fetch-access';
import { HelpScreen, HomeScreen } from './screens/home';
import { ImportFlow } from './screens/import';
@@ -80,6 +81,7 @@ type Route =
| { name: 'dataset' }
| { name: 'import' }
| { name: 'export-harness' }
+ | { name: 'feedback' }
| { name: 'cli-only'; commandId: string };
// Commands that don't require being at the project root
@@ -199,6 +201,8 @@ function AppContent({
return;
}
setRoute({ name: 'export-harness' });
+ } else if (id === 'feedback') {
+ setRoute({ name: 'feedback' });
}
};
@@ -493,6 +497,10 @@ function AppContent({
);
}
+ if (route.name === 'feedback') {
+ return ;
+ }
+
if (route.name === 'cli-only') {
const info = CLI_ONLY_EXAMPLES[route.commandId];
if (info) {
diff --git a/src/cli/tui/__tests__/app-command-coverage.test.ts b/src/cli/tui/__tests__/app-command-coverage.test.ts
index 8eaa1eeea..458cc3a9a 100644
--- a/src/cli/tui/__tests__/app-command-coverage.test.ts
+++ b/src/cli/tui/__tests__/app-command-coverage.test.ts
@@ -33,6 +33,7 @@ const ROUTED_COMMANDS = new Set([
'config-bundle',
'dataset',
'batch-evaluations',
+ 'feedback',
]);
describe('TUI home screen command coverage', () => {
@@ -52,4 +53,19 @@ describe('TUI home screen command coverage', () => {
expect(unhandled).toEqual([]);
});
+
+ it('the cliOnly flag and CLI_ONLY_EXAMPLES agree for every visible command', () => {
+ const program = createProgram();
+ const commands = getCommandsForUI(program, { inProject: true });
+
+ // A command can never be in the main interactive list while routing to the
+ // cli-only dead-end: cliOnly is true iff selecting it hits CliOnlyScreen.
+ for (const cmd of commands) {
+ const hasCliOnlyExamples = cmd.id in CLI_ONLY_EXAMPLES;
+ expect(cmd.cliOnly, `${cmd.id}: cliOnly flag must match CLI_ONLY_EXAMPLES membership`).toBe(hasCliOnlyExamples);
+ }
+
+ // feedback is interactive (real FeedbackScreen), not a cli-only dead-end.
+ expect('feedback' in CLI_ONLY_EXAMPLES).toBe(false);
+ });
});
diff --git a/src/cli/tui/copy.ts b/src/cli/tui/copy.ts
index 794dc000b..57ddc0818 100644
--- a/src/cli/tui/copy.ts
+++ b/src/cli/tui/copy.ts
@@ -135,16 +135,8 @@ export const CLI_ONLY_EXAMPLES: Record'],
- },
};
diff --git a/src/cli/tui/utils/commands.ts b/src/cli/tui/utils/commands.ts
index 7d48eae79..a9ba3ead6 100644
--- a/src/cli/tui/utils/commands.ts
+++ b/src/cli/tui/utils/commands.ts
@@ -1,3 +1,4 @@
+import { CLI_ONLY_EXAMPLES } from '../copy';
import type { Command } from '@commander-js/extra-typings';
export interface CommandMeta {
@@ -15,9 +16,13 @@ export interface CommandMeta {
const HIDDEN_FROM_TUI = ['help', 'telemetry', 'promote'] as const;
/**
- * Commands that are CLI-only (shown but marked as requiring CLI invocation).
+ * Single source of truth for "this top-level command cannot run interactively in
+ * the TUI". Derived from CLI_ONLY_EXAMPLES so the `cliOnly` flag (list placement)
+ * can never diverge from App.tsx's dead-end routing: a command is flagged cliOnly
+ * iff selecting it routes to the CliOnlyScreen. Multi-word keys in CLI_ONLY_EXAMPLES
+ * (e.g. 'run eval') are subcommand paths, not top-level commands, so they never match.
*/
-const CLI_ONLY_COMMANDS = ['traces', 'pause', 'resume', 'stop', 'archive'] as const;
+const CLI_ONLY_COMMANDS = new Set(Object.keys(CLI_ONLY_EXAMPLES));
/**
* Commands hidden from TUI when inside an existing project.
@@ -53,6 +58,6 @@ export function getCommandsForUI(program: Command, options: GetCommandsOptions =
.filter(sub => !HIDDEN_SUBCOMMANDS.includes(sub.name() as (typeof HIDDEN_SUBCOMMANDS)[number]))
.map(sub => sub.name()),
disabled: false,
- cliOnly: CLI_ONLY_COMMANDS.includes(cmd.name() as (typeof CLI_ONLY_COMMANDS)[number]),
+ cliOnly: CLI_ONLY_COMMANDS.has(cmd.name()),
}));
}