diff --git a/src/cli.ts b/src/cli.ts index 6f249a3e..03883bc3 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -631,11 +631,16 @@ program .alias('check-deps') .description('Check Codeman tool dependencies (Node, Claude CLI, tmux, LibreOffice, MS Office)') .option('--json', 'Output structured JSON instead of a table') - .option('--category ', 'Only check one category (core|office|documents|media|other)') + .option('--category ', 'Only check one category (core|office|other)') .action(async (options) => { const { createRealHost, checkAll } = await import('./utils/dependency-checker.js'); const { renderTable, renderJson, computeExitCode } = await import('./utils/dependency-report.js'); - const { DEPENDENCY_REGISTRY } = await import('./config/dependency-registry.js'); + const { DEPENDENCY_REGISTRY, TOOL_CATEGORIES } = await import('./config/dependency-registry.js'); + + if (options.category && !(TOOL_CATEGORIES as readonly string[]).includes(options.category)) { + console.error(`Unknown category "${options.category}". Valid categories: ${TOOL_CATEGORIES.join(', ')}`); + process.exit(2); + } const host = createRealHost(); const registry = options.category diff --git a/src/config/dependency-registry.ts b/src/config/dependency-registry.ts index 2e4a6220..bc04c485 100644 --- a/src/config/dependency-registry.ts +++ b/src/config/dependency-registry.ts @@ -8,7 +8,11 @@ */ export type ProbeEnvironment = 'linux' | 'darwin' | 'win32' | 'wsl'; -export type ToolCategory = 'core' | 'office' | 'other'; + +/** The valid `--category` filter values; single source of truth for the type, the CLI + * help text, and CLI input validation. */ +export const TOOL_CATEGORIES = ['core', 'office', 'other'] as const; +export type ToolCategory = (typeof TOOL_CATEGORIES)[number]; /** Resolve a binary on the PATH and read its version. */ export interface PathResolver { @@ -49,7 +53,7 @@ export const DEPENDENCY_REGISTRY: ToolDependency[] = [ label: 'Node.js', category: 'core', required: true, - minVersion: '18.0.0', + minVersion: '22.0.0', resolvers: [{ match: ALL, resolver: { kind: 'path', bins: ['node'], versionArg: '--version' } }], installHint: { linux: 'https://nodejs.org', darwin: 'brew install node', wsl: 'https://nodejs.org' }, }, @@ -86,14 +90,6 @@ export const DEPENDENCY_REGISTRY: ToolDependency[] = [ usedBy: ['Codex sessions'], resolvers: [{ match: ALL, resolver: { kind: 'path', bins: ['codex'], versionArg: '--version' } }], }, - { - id: 'gemini', - label: 'Gemini CLI', - category: 'core', - required: false, - usedBy: ['Gemini sessions'], - resolvers: [{ match: ALL, resolver: { kind: 'path', bins: ['gemini'], versionArg: '--version' } }], - }, { id: 'libreoffice', label: 'LibreOffice', @@ -108,6 +104,22 @@ export const DEPENDENCY_REGISTRY: ToolDependency[] = [ ], installHint: { linux: 'sudo apt install libreoffice', darwin: 'brew install --cask libreoffice' }, }, + { + id: 'pdftoppm', + label: 'pdftoppm', + category: 'office', + required: false, + usedBy: ['document preview', 'PDF/Office first-page thumbnails'], + // poppler's pdftoppm prints its version to stderr; presence is what matters here. + resolvers: [ + { match: ['linux', 'darwin', 'wsl'], resolver: { kind: 'path', bins: ['pdftoppm'], versionArg: '-v' } }, + ], + installHint: { + linux: 'sudo apt install poppler-utils', + darwin: 'brew install poppler', + wsl: 'sudo apt install poppler-utils', + }, + }, { id: 'msoffice', label: 'MS Office', diff --git a/test/dependency-checker.test.ts b/test/dependency-checker.test.ts index cc82966c..b3bb5573 100644 --- a/test/dependency-checker.test.ts +++ b/test/dependency-checker.test.ts @@ -22,8 +22,8 @@ describe('DEPENDENCY_REGISTRY', () => { .map((t) => t.id) .sort(); expect(required).toEqual(['node', 'tmux']); - // all four agent CLIs are optional (Codeman runs any of them) - const agentClis = ['claude', 'opencode', 'codex', 'gemini']; + // all agent CLIs are optional (Codeman runs any of them) + const agentClis = ['claude', 'opencode', 'codex']; expect(DEPENDENCY_REGISTRY.filter((t) => agentClis.includes(t.id)).every((t) => t.required === false)).toBe(true); const office = DEPENDENCY_REGISTRY.filter((t) => t.category === 'office'); expect(office.every((t) => t.required === false)).toBe(true);