From cd6add215dc15bff8e18d6bead7b9b295945dec0 Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Wed, 24 Jun 2026 23:39:19 +0200 Subject: [PATCH] feat(cli): add init command with agent instruction generation --- src/cli/commands/init.ts | 41 ++++++++++++++++++++++++++++++++++++++++ src/cli/prompts.ts | 24 +++++++++++++++++++++++ tests/cli-init.test.ts | 24 +++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 src/cli/commands/init.ts create mode 100644 src/cli/prompts.ts create mode 100644 tests/cli-init.test.ts diff --git a/src/cli/commands/init.ts b/src/cli/commands/init.ts new file mode 100644 index 0000000..96ffb8f --- /dev/null +++ b/src/cli/commands/init.ts @@ -0,0 +1,41 @@ +import { mkdirSync, writeFileSync } from 'fs'; +import { join } from 'path'; +import { getAgentHubDir } from '../../core/paths.js'; +import { saveConfig, defaultConfig } from '../../core/config.js'; +import { agentsMd, claudeMd, codexMd, kimiMd } from '../../core/templates.js'; +import { askProjectName, askAgents, askDelegationMode } from '../prompts.js'; + +export async function init(cwd: string, options: { projectName?: string; yes?: boolean }): Promise { + const projectName = options.projectName ?? (options.yes ? getDefaultProjectName(cwd) : await askProjectName(getDefaultProjectName(cwd))); + + const { claude, codex, kimi } = options.yes + ? { claude: true, codex: true, kimi: true } + : await askAgents(); + + const mode = options.yes ? 'suggest' : await askDelegationMode(); + + const config = defaultConfig(projectName); + config.delegationMode = mode; + + const dir = getAgentHubDir(cwd); + mkdirSync(join(dir, 'tasks'), { recursive: true }); + mkdirSync(join(dir, 'handoffs'), { recursive: true }); + mkdirSync(join(dir, 'decisions'), { recursive: true }); + mkdirSync(join(dir, 'memory'), { recursive: true }); + mkdirSync(join(dir, 'status'), { recursive: true }); + + saveConfig(cwd, config); + + writeFileSync(join(cwd, 'AGENTS.md'), agentsMd(projectName), 'utf-8'); + if (claude) writeFileSync(join(cwd, 'CLAUDE.md'), claudeMd(), 'utf-8'); + if (codex) writeFileSync(join(cwd, 'CODEX.md'), codexMd(), 'utf-8'); + if (kimi) writeFileSync(join(cwd, 'KIMI.md'), kimiMd(), 'utf-8'); + + console.log(`AgentHub initialized for "${projectName}".`); + console.log('Run `agenthub status` to see the current project state.'); +} + +function getDefaultProjectName(cwd: string): string { + const parts = cwd.split(/[/\\]/); + return parts[parts.length - 1] || 'project'; +} diff --git a/src/cli/prompts.ts b/src/cli/prompts.ts new file mode 100644 index 0000000..dcc055e --- /dev/null +++ b/src/cli/prompts.ts @@ -0,0 +1,24 @@ +import { input, confirm, select } from '@inquirer/prompts'; + +export async function askProjectName(defaultName: string): Promise { + return input({ message: 'Project name:', default: defaultName }); +} + +export async function askAgents(): Promise<{ claude: boolean; codex: boolean; kimi: boolean }> { + const claude = await confirm({ message: 'Support Claude Code?', default: true }); + const codex = await confirm({ message: 'Support Codex CLI?', default: true }); + const kimi = await confirm({ message: 'Support Kimi Code CLI?', default: true }); + return { claude, codex, kimi }; +} + +export async function askDelegationMode(): Promise<'manual' | 'suggest' | 'auto'> { + return select({ + message: 'Default delegation mode:', + choices: [ + { name: 'suggest', value: 'suggest' }, + { name: 'manual', value: 'manual' }, + { name: 'auto', value: 'auto' }, + ], + default: 'suggest', + }); +} diff --git a/tests/cli-init.test.ts b/tests/cli-init.test.ts new file mode 100644 index 0000000..87defc5 --- /dev/null +++ b/tests/cli-init.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { mkdtempSync, rmSync, existsSync } from 'fs'; +import { tmpdir } from 'os'; +import { join } from 'path'; +import { init } from '../src/cli/commands/init.js'; + +describe('cli init', () => { + let cwd: string; + + beforeEach(() => { + cwd = mkdtempSync(join(tmpdir(), 'agenthub-')); + }); + + afterEach(() => { + rmSync(cwd, { recursive: true, force: true }); + }); + + it('initializes project with --yes', async () => { + await init(cwd, { yes: true, projectName: 'test-proj' }); + expect(existsSync(join(cwd, '.agenthub', 'agenthub.config.json'))).toBe(true); + expect(existsSync(join(cwd, 'AGENTS.md'))).toBe(true); + expect(existsSync(join(cwd, 'CLAUDE.md'))).toBe(true); + }); +});