feat(cli): refresh help and shell completion UX

- add a shared command catalog for root help and completion routing

- replace shell-local completion logic with a hidden __complete backend

- add topic help and parity tests for router, help, and completion coverage
This commit is contained in:
Tam Nhu Tran committed 2026-04-03 00:53:10 -04:00
1 parent 3eb55d0e33
commit 24ba2abe10
15 files changed
+1227 -2118

No files matched your search

@@ -0,0 +1,27 @@
import { describe, expect, test } from 'bun:test';
import { ROOT_COMMAND_CATALOG, getAllRootCommandTokens } from '../../../src/commands/command-catalog';
import { ROOT_COMMAND_ROUTES } from '../../../src/commands/root-command-router';
describe('command catalog', () => {
test('covers every routed root command and alias', () => {
const catalogTokens = new Set(getAllRootCommandTokens());
for (const route of ROOT_COMMAND_ROUTES) {
expect(catalogTokens.has(route.name)).toBe(true);
for (const alias of route.aliases || []) {
expect(catalogTokens.has(alias)).toBe(true);
}
}
});
test('keeps hidden operational hooks out of the public help surface', () => {
const hiddenCommands = ROOT_COMMAND_CATALOG.filter((entry) => entry.visibility === 'hidden').map(
(entry) => entry.name
);
expect(hiddenCommands).toContain('--install');
expect(hiddenCommands).toContain('--uninstall');
expect(hiddenCommands).toContain('__complete');
});
});
@@ -0,0 +1,132 @@
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
import fs from 'fs';
import os from 'os';
import path from 'path';
import { getCompletionSuggestions } from '../../../src/commands/completion-backend';
const originalHome = process.env.HOME;
const originalUserProfile = process.env.USERPROFILE;
const originalCcsHome = process.env.CCS_HOME;
let tempHome = '';
function suggestionValues(
tokensBeforeCurrent: string[],
current = '',
shell: 'bash' | 'fish' | 'powershell' | 'zsh' = 'bash'
): string[] {
return getCompletionSuggestions({ tokensBeforeCurrent, current, shell }).map((entry) => entry.value);
}
beforeEach(() => {
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-completion-'));
fs.mkdirSync(path.join(tempHome, '.ccs'), { recursive: true });
fs.writeFileSync(
path.join(tempHome, '.ccs', 'config.json'),
JSON.stringify(
{
profiles: {
localglm: '~/.ccs/localglm.settings.json',
},
cliproxy: {
'my-codex': {
provider: 'codex',
settings: '~/.ccs/my-codex.settings.json',
},
},
},
null,
2
)
);
fs.writeFileSync(
path.join(tempHome, '.ccs', 'profiles.json'),
JSON.stringify(
{
profiles: {
work: { type: 'account', created: '2026-04-02T00:00:00.000Z' },
},
default: 'work',
},
null,
2
)
);
process.env.HOME = tempHome;
process.env.USERPROFILE = tempHome;
process.env.CCS_HOME = tempHome;
});
afterEach(() => {
if (originalHome === undefined) delete process.env.HOME;
else process.env.HOME = originalHome;
if (originalUserProfile === undefined) delete process.env.USERPROFILE;
else process.env.USERPROFILE = originalUserProfile;
if (originalCcsHome === undefined) delete process.env.CCS_HOME;
else process.env.CCS_HOME = originalCcsHome;
fs.rmSync(tempHome, { recursive: true, force: true });
});
describe('completion backend', () => {
test('includes current root commands and dynamic profiles at the top level', () => {
const values = suggestionValues([]);
expect(values).toContain('config');
expect(values).toContain('docker');
expect(values).toContain('tokens');
expect(values).toContain('migrate');
expect(values).toContain('cursor');
expect(values).toContain('copilot');
expect(values).toContain('gemini');
expect(values).toContain('localglm');
expect(values).toContain('work');
expect(values).toContain('my-codex');
expect(values).not.toContain('__complete');
expect(values).not.toContain('--install');
expect(values).not.toContain('--uninstall');
});
test('suggests help topics from the help command', () => {
const values = suggestionValues(['help']);
expect(values).toContain('profiles');
expect(values).toContain('providers');
expect(values).toContain('completion');
expect(values).toContain('targets');
expect(values).not.toContain('api');
expect(values).not.toContain('__complete');
});
test('suggests lifecycle subcommands for api', () => {
const values = suggestionValues(['api']);
expect(values).toEqual(expect.arrayContaining(['create', 'discover', 'copy', 'export', 'import', 'remove']));
});
test('suggests account profiles for auth show', () => {
const values = suggestionValues(['auth', 'show']);
expect(values).toContain('work');
expect(values).toContain('--json');
});
test('suggests cliproxy variants for variant-scoped commands', () => {
const values = suggestionValues(['cliproxy', 'edit']);
expect(values).toContain('my-codex');
});
test('suggests env format values after the format flag', () => {
const values = suggestionValues(['env', '--format']);
expect(values).toEqual(expect.arrayContaining(['openai', 'anthropic', 'raw', 'claude-extension']));
});
test('includes live doctor and cliproxy flags from the shared catalog', () => {
expect(suggestionValues(['doctor'])).toEqual(expect.arrayContaining(['--fix', '-f']));
expect(suggestionValues(['cliproxy'])).toEqual(expect.arrayContaining(['remove', '--backend']));
});
test('filters suggestions by the current token prefix', () => {
const values = suggestionValues([], 'do');
expect(values).toEqual(expect.arrayContaining(['docker', 'doctor']));
expect(values).not.toContain('tokens');
});
});
+47 -122
View File
@@ -1,152 +1,77 @@
import { describe, expect, test } from 'bun:test';
import { showApiCommandHelp } from '../../../src/commands/api-command/help';
import { handleHelpCommand } from '../../../src/commands/help-command';
import {
handleHelpCommand,
handleHelpRoute,
getRootHelpVisibleCommands,
} from '../../../src/commands/help-command';
function stripAnsi(input: string): string {
return input.replace(/\u001b\[[0-9;]*m/g, '');
}
async function renderLines(
render: (writeLine: (line: string) => void) => Promise<void>
): Promise<string> {
const lines: string[] = [];
await render((line) => lines.push(line));
return stripAnsi(lines.join('\n'));
}
describe('help command parity', () => {
test('root help documents cliproxy provider filter under quota command', async () => {
const lines: string[] = [];
await handleHelpCommand((line) => lines.push(line));
test('root help stays within the compact line budget', async () => {
const rendered = await renderLines((writeLine) => handleHelpCommand(writeLine));
const visibleLines = rendered.split('\n').filter((line) => line.trim().length > 0);
const rendered = stripAnsi(lines.join('\n'));
expect(rendered.includes('ccs cliproxy status [provider]')).toBe(false);
expect(rendered.includes('ccs cliproxy status')).toBe(true);
expect(rendered.includes('ccs cliproxy quota --provider <name>')).toBe(true);
expect(rendered.includes('ccs llamacpp')).toBe(true);
expect(visibleLines.length).toBeLessThanOrEqual(90);
expect(rendered.includes('ccs help <topic>')).toBe(true);
expect(rendered.includes('ccs help completion')).toBe(true);
});
test('root help documents llama.cpp as a local API profile', async () => {
const lines: string[] = [];
await handleHelpCommand((line) => lines.push(line));
test('root help covers every public root command once the catalog is updated', async () => {
const rendered = await renderLines((writeLine) => handleHelpCommand(writeLine));
const rendered = stripAnsi(lines.join('\n'));
expect(rendered.includes('ccs llamacpp')).toBe(true);
expect(rendered.includes('http://127.0.0.1:8080')).toBe(true);
for (const command of getRootHelpVisibleCommands()) {
expect(rendered.includes(command)).toBe(true);
}
});
test('root help no longer markets glmt as a supported profile', async () => {
const lines: string[] = [];
await handleHelpCommand((line) => lines.push(line));
const rendered = stripAnsi(lines.join('\n'));
test('root help no longer markets deprecated glmt directly', async () => {
const rendered = await renderLines((writeLine) => handleHelpCommand(writeLine));
expect(rendered.includes('ccs glmt')).toBe(false);
expect(rendered.includes('ccs glm')).toBe(true);
});
test('root help documents Claude IDE extension setup surfaces', async () => {
const lines: string[] = [];
await handleHelpCommand((line) => lines.push(line));
test('providers topic lists built-in OAuth provider shortcuts', async () => {
const rendered = await renderLines((writeLine) => handleHelpRoute(['providers'], writeLine));
const rendered = stripAnsi(lines.join('\n'));
expect(rendered.includes('Claude IDE Extension setup page')).toBe(true);
expect(rendered.includes('ccs env <profile> --format claude-extension --ide vscode')).toBe(
true
);
expect(rendered.includes('ccs env <profile> --format claude-extension --ide windsurf')).toBe(
true
);
expect(rendered.includes('Built-in OAuth Providers')).toBe(true);
expect(rendered.includes('ccs cliproxy --help')).toBe(true);
expect(rendered.includes('gemini')).toBe(true);
expect(rendered.includes('codex')).toBe(true);
expect(rendered.includes('ghcp')).toBe(true);
});
test('root help documents dashboard host binding example', async () => {
const lines: string[] = [];
await handleHelpCommand((line) => lines.push(line));
test('completion topic documents install and verification paths', async () => {
const rendered = await renderLines((writeLine) => handleHelpRoute(['completion'], writeLine));
const rendered = stripAnsi(lines.join('\n'));
expect(rendered.includes('ccs config --host 0.0.0.0')).toBe(true);
expect(rendered.includes('Force all-interface binding for remote devices')).toBe(true);
expect(rendered.includes('ccs --shell-completion')).toBe(true);
expect(rendered.includes('ccs help <TAB>')).toBe(true);
expect(rendered.includes('--force')).toBe(true);
});
test('root help documents docker deployment commands', async () => {
const lines: string[] = [];
await handleHelpCommand((line) => lines.push(line));
test('api topic delegates to command-specific help', async () => {
const rendered = await renderLines((writeLine) => handleHelpRoute(['api'], writeLine));
const rendered = stripAnsi(lines.join('\n'));
expect(rendered.includes('ccs docker --help')).toBe(true);
expect(rendered.includes('ccs docker up --host <ssh>')).toBe(true);
expect(rendered.includes('ccs docker update')).toBe(true);
});
test('root help documents official channels native-only scope and process-env tokens', async () => {
const lines: string[] = [];
await handleHelpCommand((line) => lines.push(line));
const rendered = stripAnsi(lines.join('\n'));
expect(rendered.includes('Dashboard -> Settings -> Channels (fastest path)')).toBe(true);
expect(
rendered.includes(
'Fastest path: turn on the channel, save the token if needed, then run ccs.'
)
).toBe(true);
expect(rendered.includes('ccs codex')).toBe(true);
expect(rendered.includes('ccs --target codex')).toBe(true);
expect(
rendered.includes('Current-process TELEGRAM_BOT_TOKEN / DISCORD_BOT_TOKEN also work')
).toBe(true);
});
test('root help documents bare cursor as the runtime entrypoint', async () => {
const lines: string[] = [];
await handleHelpCommand((line) => lines.push(line));
const rendered = stripAnsi(lines.join('\n'));
expect(rendered.includes('ccs cursor <cmd>')).toBe(false);
expect(rendered.includes('Run Claude via Cursor local proxy')).toBe(true);
});
test('root help explains Claude [1m] as an explicit CCS suffix with upstream limits', async () => {
const lines: string[] = [];
await handleHelpCommand((line) => lines.push(line));
const rendered = stripAnsi(lines.join('\n'));
expect(
rendered.includes('Claude models: plain by default, opt-in with --1m or saved [1m]')
).toBe(true);
expect(rendered.includes('CCS only controls the saved [1m] suffix.')).toBe(true);
expect(rendered.includes('return 429 extra-usage errors for long-context requests')).toBe(true);
});
test('root help documents native Codex runtime alias and runtime-only scope', async () => {
const lines: string[] = [];
await handleHelpCommand((line) => lines.push(line));
const rendered = stripAnsi(lines.join('\n'));
expect(rendered.includes('ccs-codex <profile> [args]')).toBe(true);
expect(rendered.includes('ccsx <profile> [args]')).toBe(true);
expect(rendered.includes('ccsxp [args]')).toBe(true);
expect(rendered.includes('ccs --target codex')).toBe(true);
expect(rendered.includes('ccsxp "your prompt"')).toBe(true);
expect(rendered.includes('ccs codex-api --target codex')).toBe(true);
expect(rendered.includes('Compatible -> Codex CLI')).toBe(true);
expect(rendered.includes('CLIPROXY_API_KEY')).toBe(true);
expect(rendered.includes('codex (runtime-only)')).toBe(true);
});
test('api help documents create-time Claude [1m] flags and entitlement warning', async () => {
const lines: string[] = [];
await showApiCommandHelp((line) => lines.push(line));
const rendered = stripAnsi(lines.join('\n'));
expect(rendered.includes('--1m / --no-1m')).toBe(true);
expect(rendered.includes('CCS API Management')).toBe(true);
expect(rendered.includes('ccs api create --preset anthropic --1m')).toBe(true);
expect(rendered.includes('Plain Claude model IDs stay on standard context by default.')).toBe(
true
);
expect(rendered.includes('some accounts can still return 429 for long-context requests')).toBe(
true
);
expect(rendered.includes('ccs api discover --register')).toBe(true);
});
test('api help documents Codex bridge runtime launch separately from persisted targets', async () => {
const lines: string[] = [];
await showApiCommandHelp((line) => lines.push(line));
test('unknown help target shows an actionable fallback', async () => {
const rendered = await renderLines((writeLine) => handleHelpRoute(['unknown-topic'], writeLine));
const rendered = stripAnsi(lines.join('\n'));
expect(rendered.includes('ccs api create codex-api --cliproxy-provider codex')).toBe(true);
expect(rendered.includes('ccs codex-api --target codex')).toBe(true);
expect(rendered.includes('Default target: claude or droid (create)')).toBe(true);
expect(rendered.includes('Unknown help topic or command: unknown-topic')).toBe(true);
expect(rendered.includes('Available help topics:')).toBe(true);
process.exitCode = 0;
});
});
@@ -27,7 +27,7 @@ beforeEach(() => {
},
}));
mock.module('../../../src/commands/api-command', () => ({
mock.module('../../../src/commands/api-command/index', () => ({
handleApiCommand: async (args: string[]) => {
calls.push(`api:${args.join(' ')}`);
},
@@ -42,6 +42,12 @@ beforeEach(() => {
mock.module('../../../src/commands/tokens-command', () => ({
handleTokensCommand: async () => 37,
}));
mock.module('../../../src/commands/completion-backend', () => ({
handleCompletionCommand: async (args: string[]) => {
calls.push(`complete:${args.join(' ')}`);
},
}));
});
afterEach(() => {
@@ -120,4 +126,12 @@ describe('root-command-router', () => {
await expect(tryHandleRootCommand(['tokens', 'list'])).rejects.toThrow('process.exit(37)');
});
it('routes hidden completion queries through the completion backend', async () => {
const tryHandleRootCommand = await loadTryHandleRootCommand();
await expect(tryHandleRootCommand(['__complete', '--shell', 'bash', '--current', 'do'])).resolves.toBe(true);
expect(calls).toEqual(['complete:--shell bash --current do']);
});
});