diff --git a/packages/cli/src/commands/activity.ts b/packages/cli/src/commands/activity.ts index 350929ce..773ad8a8 100644 --- a/packages/cli/src/commands/activity.ts +++ b/packages/cli/src/commands/activity.ts @@ -1,5 +1,5 @@ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors, warn } from '../onboarding/index.js'; import { ActivityLog } from '@skillkit/core'; export class ActivityCommand extends Command { @@ -42,16 +42,16 @@ export class ActivityCommand extends Command { } if (activities.length === 0) { - console.log(chalk.yellow('No activity recorded')); + warn('No activity recorded'); console.log( - chalk.dim( + colors.muted( 'Activity is recorded when skills are active during git commits.' ) ); return 0; } - console.log(chalk.cyan(' Recent Skill Activity\n')); + console.log(colors.cyan(' Recent Skill Activity\n')); const displayed = activities.slice(0, limit); @@ -61,10 +61,10 @@ export class ActivityCommand extends Command { const files = activity.filesChanged.join(', '); const ago = formatTimeAgo(activity.committedAt); - console.log(` ${chalk.bold(shortSha)} ${activity.message}`); - console.log(` Skills: ${chalk.green(skills)}`); + console.log(` ${colors.bold(shortSha)} ${activity.message}`); + console.log(` Skills: ${colors.success(skills)}`); console.log( - ` Files: ${files} ${chalk.dim(`(${ago})`)}` + ` Files: ${files} ${colors.muted(`(${ago})`)}` ); console.log(); } diff --git a/packages/cli/src/commands/agent.ts b/packages/cli/src/commands/agent.ts index b93325eb..42aff836 100644 --- a/packages/cli/src/commands/agent.ts +++ b/packages/cli/src/commands/agent.ts @@ -4,7 +4,7 @@ * Manage custom AI sub-agents (e.g., .claude/agents/*.md) */ -import chalk from 'chalk'; +import { colors } from '../onboarding/index.js'; import { Command, Option } from 'clipanion'; import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; import { join, basename } from 'node:path'; @@ -67,7 +67,7 @@ export class AgentCommand extends Command { }); async execute(): Promise { - console.log(chalk.cyan('Agent management commands:\n')); + console.log(colors.cyan('Agent management commands:\n')); console.log(' agent list List all installed agents'); console.log(' agent show Show agent details'); console.log(' agent create Create a new agent'); @@ -76,7 +76,7 @@ export class AgentCommand extends Command { console.log(' agent sync Sync agents to target AI agent'); console.log(' agent validate [path] Validate agent definitions'); console.log(); - console.log(chalk.dim('Run `skillkit agent --help` for more info')); + console.log(colors.muted('Run `skillkit agent --help` for more info')); return 0; } } @@ -136,18 +136,18 @@ export class AgentListCommand extends Command { } if (agents.length === 0) { - console.log(chalk.yellow('No agents found')); - console.log(chalk.dim('Create an agent with: skillkit agent create ')); + console.log(colors.warning('No agents found')); + console.log(colors.muted('Create an agent with: skillkit agent create ')); return 0; } - console.log(chalk.cyan(`Installed agents (${agents.length}):\n`)); + console.log(colors.cyan(`Installed agents (${agents.length}):\n`)); const projectAgents = agents.filter((a: CustomAgent) => a.location === 'project'); const globalAgents = agents.filter((a: CustomAgent) => a.location === 'global'); if (projectAgents.length > 0) { - console.log(chalk.blue('Project agents:')); + console.log(colors.info('Project agents:')); for (const agent of projectAgents) { printAgent(agent); } @@ -155,7 +155,7 @@ export class AgentListCommand extends Command { } if (globalAgents.length > 0) { - console.log(chalk.dim('Global agents:')); + console.log(colors.muted('Global agents:')); for (const agent of globalAgents) { printAgent(agent); } @@ -163,7 +163,7 @@ export class AgentListCommand extends Command { } console.log( - chalk.dim(`${projectAgents.length} project, ${globalAgents.length} global`) + colors.muted(`${projectAgents.length} project, ${globalAgents.length} global`) ); return 0; @@ -187,47 +187,47 @@ export class AgentShowCommand extends Command { const agent = findAgent(this.name, searchDirs); if (!agent) { - console.log(chalk.red(`Agent not found: ${this.name}`)); + console.log(colors.error(`Agent not found: ${this.name}`)); return 1; } - console.log(chalk.cyan(`Agent: ${agent.name}\n`)); - console.log(`${chalk.dim('Description:')} ${agent.description}`); - console.log(`${chalk.dim('Location:')} ${agent.location} (${agent.path})`); - console.log(`${chalk.dim('Enabled:')} ${agent.enabled ? chalk.green('yes') : chalk.red('no')}`); + console.log(colors.cyan(`Agent: ${agent.name}\n`)); + console.log(`${colors.muted('Description:')} ${agent.description}`); + console.log(`${colors.muted('Location:')} ${agent.location} (${agent.path})`); + console.log(`${colors.muted('Enabled:')} ${agent.enabled ? colors.success('yes') : colors.error('no')}`); const fm = agent.frontmatter; if (fm.model) { - console.log(`${chalk.dim('Model:')} ${fm.model}`); + console.log(`${colors.muted('Model:')} ${fm.model}`); } if (fm.permissionMode) { - console.log(`${chalk.dim('Permission Mode:')} ${fm.permissionMode}`); + console.log(`${colors.muted('Permission Mode:')} ${fm.permissionMode}`); } if (fm.context) { - console.log(`${chalk.dim('Context:')} ${fm.context}`); + console.log(`${colors.muted('Context:')} ${fm.context}`); } if (fm.disallowedTools && fm.disallowedTools.length > 0) { - console.log(`${chalk.dim('Disallowed Tools:')} ${fm.disallowedTools.join(', ')}`); + console.log(`${colors.muted('Disallowed Tools:')} ${fm.disallowedTools.join(', ')}`); } if (fm.skills && fm.skills.length > 0) { - console.log(`${chalk.dim('Skills:')} ${fm.skills.join(', ')}`); + console.log(`${colors.muted('Skills:')} ${fm.skills.join(', ')}`); } if (fm.hooks && fm.hooks.length > 0) { - console.log(`${chalk.dim('Hooks:')} ${fm.hooks.length} defined`); + console.log(`${colors.muted('Hooks:')} ${fm.hooks.length} defined`); } if (fm.tags && fm.tags.length > 0) { - console.log(`${chalk.dim('Tags:')} ${fm.tags.join(', ')}`); + console.log(`${colors.muted('Tags:')} ${fm.tags.join(', ')}`); } if (fm.author) { - console.log(`${chalk.dim('Author:')} ${fm.author}`); + console.log(`${colors.muted('Author:')} ${fm.author}`); } if (fm.version) { - console.log(`${chalk.dim('Version:')} ${fm.version}`); + console.log(`${colors.muted('Version:')} ${fm.version}`); } console.log(); - console.log(chalk.dim('Content preview:')); - console.log(chalk.dim('─'.repeat(40))); + console.log(colors.muted('Content preview:')); + console.log(colors.muted('─'.repeat(40))); const preview = agent.content.slice(0, 500); console.log(preview + (agent.content.length > 500 ? '\n...' : '')); @@ -264,8 +264,8 @@ export class AgentCreateCommand extends Command { async execute(): Promise { const namePattern = /^[a-z0-9]+(-[a-z0-9]+)*$/; if (!namePattern.test(this.name)) { - console.log(chalk.red('Invalid agent name: must be lowercase alphanumeric with hyphens')); - console.log(chalk.dim('Examples: my-agent, code-reviewer, security-expert')); + console.log(colors.error('Invalid agent name: must be lowercase alphanumeric with hyphens')); + console.log(colors.muted('Examples: my-agent, code-reviewer, security-expert')); return 1; } @@ -282,7 +282,7 @@ export class AgentCreateCommand extends Command { const agentPath = join(targetDir, `${this.name}.md`); if (existsSync(agentPath)) { - console.log(chalk.red(`Agent already exists: ${agentPath}`)); + console.log(colors.error(`Agent already exists: ${agentPath}`)); return 1; } @@ -291,10 +291,10 @@ export class AgentCreateCommand extends Command { writeFileSync(agentPath, content); - console.log(chalk.green(`Created agent: ${agentPath}`)); + console.log(colors.success(`Created agent: ${agentPath}`)); console.log(); - console.log(chalk.dim('Edit the file to customize the agent system prompt.')); - console.log(chalk.dim(`Invoke with: @${this.name}`)); + console.log(colors.muted('Edit the file to customize the agent system prompt.')); + console.log(colors.muted(`Invoke with: @${this.name}`)); return 0; } @@ -360,23 +360,23 @@ export class AgentTranslateCommand extends Command { : join(process.cwd(), this.source); if (!existsSync(sourcePath)) { - console.log(chalk.red(`Source path not found: ${sourcePath}`)); + console.log(colors.error(`Source path not found: ${sourcePath}`)); return 1; } agents = discoverAgentsFromPath(sourcePath, this.recursive); if (agents.length === 0) { - console.log(chalk.yellow(`No agents found in: ${sourcePath}`)); + console.log(colors.warning(`No agents found in: ${sourcePath}`)); if (!this.recursive) { - console.log(chalk.dim('Tip: Use --recursive to scan subdirectories')); + console.log(colors.muted('Tip: Use --recursive to scan subdirectories')); } return 0; } } else if (this.name) { const agent = findAgent(this.name, searchDirs); if (!agent) { - console.log(chalk.red(`Agent not found: ${this.name}`)); + console.log(colors.error(`Agent not found: ${this.name}`)); return 1; } agents = [agent]; @@ -387,13 +387,13 @@ export class AgentTranslateCommand extends Command { } if (agents.length === 0) { - console.log(chalk.yellow('No agents found to translate')); + console.log(colors.warning('No agents found to translate')); return 0; } const outputDir = this.output || getAgentTargetDirectory(process.cwd(), targetAgent); - console.log(chalk.cyan(`Translating ${agents.length} agent(s) to ${targetAgent} format...\n`)); + console.log(colors.cyan(`Translating ${agents.length} agent(s) to ${targetAgent} format...\n`)); let successCount = 0; let errorCount = 0; @@ -403,7 +403,7 @@ export class AgentTranslateCommand extends Command { const result = translateAgent(agent, targetAgent, { addMetadata: true }); if (!result.success) { - console.log(chalk.red(`✗ ${agent.name}: Translation failed`)); + console.log(colors.error(`✗ ${agent.name}: Translation failed`)); errorCount++; continue; } @@ -411,15 +411,15 @@ export class AgentTranslateCommand extends Command { const outputPath = join(outputDir, result.filename); if (this.dryRun) { - console.log(chalk.blue(`Would write: ${outputPath}`)); + console.log(colors.info(`Would write: ${outputPath}`)); if (result.warnings.length > 0) { for (const warning of result.warnings) { - console.log(chalk.yellow(` ⚠ ${warning}`)); + console.log(colors.warning(` ⚠ ${warning}`)); } } if (result.incompatible.length > 0) { for (const incompat of result.incompatible) { - console.log(chalk.dim(` ○ ${incompat}`)); + console.log(colors.muted(` ○ ${incompat}`)); } } } else { @@ -427,21 +427,21 @@ export class AgentTranslateCommand extends Command { mkdirSync(outputDir, { recursive: true }); } writeFileSync(outputPath, result.content); - console.log(chalk.green(`✓ ${agent.name} → ${outputPath}`)); + console.log(colors.success(`✓ ${agent.name} → ${outputPath}`)); } successCount++; } catch (error) { - console.log(chalk.red(`✗ ${agent.name}: ${error instanceof Error ? error.message : 'Unknown error'}`)); + console.log(colors.error(`✗ ${agent.name}: ${error instanceof Error ? error.message : 'Unknown error'}`)); errorCount++; } } console.log(); if (this.dryRun) { - console.log(chalk.dim(`Would translate ${successCount} agent(s)`)); + console.log(colors.muted(`Would translate ${successCount} agent(s)`)); } else { - console.log(chalk.dim(`Translated ${successCount} agent(s)${errorCount > 0 ? `, ${errorCount} failed` : ''}`)); + console.log(colors.muted(`Translated ${successCount} agent(s)${errorCount > 0 ? `, ${errorCount} failed` : ''}`)); } return errorCount > 0 ? 1 : 0; @@ -468,7 +468,7 @@ export class AgentSyncCommand extends Command { const agents = findAllAgents(searchDirs); if (agents.length === 0) { - console.log(chalk.yellow('No agents found to sync')); + console.log(colors.warning('No agents found to sync')); return 0; } @@ -476,12 +476,12 @@ export class AgentSyncCommand extends Command { ? this.agent.split(',').map(a => a.trim() as AgentType) : ['claude-code' as AgentType]; - console.log(chalk.cyan(`Syncing ${agents.length} agent(s)...\n`)); + console.log(colors.cyan(`Syncing ${agents.length} agent(s)...\n`)); for (const targetAgent of targetAgents) { const outputDir = getAgentTargetDirectory(process.cwd(), targetAgent); - console.log(chalk.blue(`→ ${targetAgent} (${outputDir})`)); + console.log(colors.info(`→ ${targetAgent} (${outputDir})`)); if (!existsSync(outputDir)) { mkdirSync(outputDir, { recursive: true }); @@ -492,15 +492,15 @@ export class AgentSyncCommand extends Command { if (result.success) { const outputPath = join(outputDir, result.filename); writeFileSync(outputPath, result.content); - console.log(chalk.green(` ✓ ${agent.name}`)); + console.log(colors.success(` ✓ ${agent.name}`)); } else { - console.log(chalk.red(` ✗ ${agent.name}`)); + console.log(colors.error(` ✗ ${agent.name}`)); } } } console.log(); - console.log(chalk.dim('Sync complete')); + console.log(colors.muted('Sync complete')); return 0; } @@ -535,11 +535,11 @@ export class AgentValidateCommand extends Command { const agents = findAllAgents(searchDirs); if (agents.length === 0) { - console.log(chalk.yellow('No agents found')); + console.log(colors.warning('No agents found')); return 0; } - console.log(chalk.cyan(`Validating ${agents.length} agent(s)...\n`)); + console.log(colors.cyan(`Validating ${agents.length} agent(s)...\n`)); for (const agent of agents) { const result = validateAgent(agent.path); @@ -547,7 +547,7 @@ export class AgentValidateCommand extends Command { if (!result.valid) hasErrors = true; } } else { - console.log(chalk.yellow('Specify a path or use --all to validate all agents')); + console.log(colors.warning('Specify a path or use --all to validate all agents')); return 1; } @@ -556,10 +556,10 @@ export class AgentValidateCommand extends Command { } function printAgent(agent: CustomAgent): void { - const status = agent.enabled ? chalk.green('✓') : chalk.red('○'); - const name = agent.enabled ? agent.name : chalk.dim(agent.name); - const model = agent.frontmatter.model ? chalk.blue(`[${agent.frontmatter.model}]`) : ''; - const desc = chalk.dim(truncate(agent.description, 40)); + const status = agent.enabled ? colors.success('✓') : colors.error('○'); + const name = agent.enabled ? agent.name : colors.muted(agent.name); + const model = agent.frontmatter.model ? colors.info(`[${agent.frontmatter.model}]`) : ''; + const desc = colors.muted(truncate(agent.description, 40)); console.log(` ${status} ${name} ${model}`); if (agent.description) { @@ -572,17 +572,17 @@ function printValidationResult( result: { valid: boolean; errors: string[]; warnings: string[] } ): void { if (result.valid) { - console.log(chalk.green(`✓ ${name}`)); + console.log(colors.success(`✓ ${name}`)); for (const warning of result.warnings) { - console.log(chalk.yellow(` ⚠ ${warning}`)); + console.log(colors.warning(` ⚠ ${warning}`)); } } else { - console.log(chalk.red(`✗ ${name}`)); - for (const error of result.errors) { - console.log(chalk.red(` • ${error}`)); + console.log(colors.error(`✗ ${name}`)); + for (const err of result.errors) { + console.log(colors.error(` • ${err}`)); } for (const warning of result.warnings) { - console.log(chalk.yellow(` ⚠ ${warning}`)); + console.log(colors.warning(` ⚠ ${warning}`)); } } } @@ -671,15 +671,15 @@ export class AgentInstallCommand extends Command { } if (!this.name) { - console.log(chalk.yellow('Please specify an agent name or use --all')); - console.log(chalk.dim('Run `skillkit agent available` to see available agents')); + console.log(colors.warning('Please specify an agent name or use --all')); + console.log(colors.muted('Run `skillkit agent available` to see available agents')); return 1; } const agent = getBundledAgent(this.name); if (!agent) { - console.log(chalk.red(`Bundled agent not found: ${this.name}`)); - console.log(chalk.dim('Run `skillkit agent available` to see available agents')); + console.log(colors.error(`Bundled agent not found: ${this.name}`)); + console.log(colors.muted('Run `skillkit agent available` to see available agents')); return 1; } @@ -689,11 +689,11 @@ export class AgentInstallCommand extends Command { }); if (result.success) { - console.log(chalk.green(`✓ Installed: ${agent.name}`)); - console.log(chalk.dim(` Path: ${result.path}`)); - console.log(chalk.dim(` Invoke with: @${agent.id}`)); + console.log(colors.success(`✓ Installed: ${agent.name}`)); + console.log(colors.muted(` Path: ${result.path}`)); + console.log(colors.muted(` Invoke with: @${agent.id}`)); } else { - console.log(chalk.red(`✗ Failed: ${result.message}`)); + console.log(colors.error(`✗ Failed: ${result.message}`)); return 1; } @@ -702,7 +702,7 @@ export class AgentInstallCommand extends Command { private async installAll(): Promise { const agents = getBundledAgents(); - console.log(chalk.cyan(`Installing ${agents.length} bundled agents...\n`)); + console.log(colors.cyan(`Installing ${agents.length} bundled agents...\n`)); let successCount = 0; let skipCount = 0; @@ -715,20 +715,20 @@ export class AgentInstallCommand extends Command { }); if (result.success) { - console.log(chalk.green(` ✓ ${agent.name}`)); + console.log(colors.success(` ✓ ${agent.name}`)); successCount++; } else if (result.message.includes('already exists')) { - console.log(chalk.yellow(` ○ ${agent.name} (already installed)`)); + console.log(colors.warning(` ○ ${agent.name} (already installed)`)); skipCount++; } else { - console.log(chalk.red(` ✗ ${agent.name}: ${result.message}`)); + console.log(colors.error(` ✗ ${agent.name}: ${result.message}`)); errorCount++; } } console.log(); console.log( - chalk.dim( + colors.muted( `Installed: ${successCount}, Skipped: ${skipCount}, Errors: ${errorCount}` ) ); @@ -781,10 +781,10 @@ export class AgentAvailableCommand extends Command { if (agents.length === 0) { if (this.installed) { - console.log(chalk.yellow('No bundled agents installed')); - console.log(chalk.dim('Run `skillkit agent install ` to install')); + console.log(colors.warning('No bundled agents installed')); + console.log(colors.muted('Run `skillkit agent install ` to install')); } else { - console.log(chalk.green('All bundled agents are already installed!')); + console.log(colors.success('All bundled agents are already installed!')); } return 0; } @@ -793,7 +793,7 @@ export class AgentAvailableCommand extends Command { ? 'Installed Bundled Agents' : 'Available Bundled Agents'; - console.log(chalk.cyan(`${title} (${agents.length}):\n`)); + console.log(colors.cyan(`${title} (${agents.length}):\n`)); const categories = new Map(); for (const agent of agents) { @@ -805,20 +805,20 @@ export class AgentAvailableCommand extends Command { } for (const [category, catAgents] of categories) { - console.log(chalk.blue(` ${formatCategoryName(category)}`)); + console.log(colors.info(` ${formatCategoryName(category)}`)); for (const agent of catAgents) { const installed = isAgentInstalled(agent.id); - const status = installed ? chalk.green('✓') : chalk.dim('○'); - const model = agent.model ? chalk.blue(`[${agent.model}]`) : ''; - console.log(` ${status} ${chalk.bold(agent.id)} ${model}`); - console.log(` ${chalk.dim(agent.description)}`); + const status = installed ? colors.success('✓') : colors.muted('○'); + const model = agent.model ? colors.info(`[${agent.model}]`) : ''; + console.log(` ${status} ${colors.bold(agent.id)} ${model}`); + console.log(` ${colors.muted(agent.description)}`); } console.log(); } if (!this.installed) { - console.log(chalk.dim('Install with: skillkit agent install ')); - console.log(chalk.dim('Install all: skillkit agent install --all')); + console.log(colors.muted('Install with: skillkit agent install ')); + console.log(colors.muted('Install all: skillkit agent install --all')); } return 0; @@ -877,20 +877,20 @@ export class AgentFromSkillCommand extends Command { const skill = skills.find((s: Skill) => s.name === this.skillName); if (!skill) { - console.log(chalk.red(`Skill not found: ${this.skillName}`)); - console.log(chalk.dim('Available skills:')); + console.log(colors.error(`Skill not found: ${this.skillName}`)); + console.log(colors.muted('Available skills:')); for (const s of skills.slice(0, 10)) { - console.log(chalk.dim(` - ${s.name}`)); + console.log(colors.muted(` - ${s.name}`)); } if (skills.length > 10) { - console.log(chalk.dim(` ... and ${skills.length - 10} more`)); + console.log(colors.muted(` ... and ${skills.length - 10} more`)); } return 1; } const skillContent = readSkillContent(skill.path); if (!skillContent) { - console.log(chalk.red(`Could not read skill content: ${skill.path}`)); + console.log(colors.error(`Could not read skill content: ${skill.path}`)); return 1; } @@ -901,8 +901,8 @@ export class AgentFromSkillCommand extends Command { if (this.model) { const validModels = ['sonnet', 'opus', 'haiku', 'inherit']; if (!validModels.includes(this.model)) { - console.log(chalk.red(`Invalid model: ${this.model}`)); - console.log(chalk.dim(`Valid options: ${validModels.join(', ')}`)); + console.log(colors.error(`Invalid model: ${this.model}`)); + console.log(colors.muted(`Valid options: ${validModels.join(', ')}`)); return 1; } options.model = this.model as 'sonnet' | 'opus' | 'haiku' | 'inherit'; @@ -911,8 +911,8 @@ export class AgentFromSkillCommand extends Command { if (this.permission) { const validModes = ['default', 'plan', 'auto-edit', 'full-auto', 'bypassPermissions']; if (!validModes.includes(this.permission)) { - console.log(chalk.red(`Invalid permission mode: ${this.permission}`)); - console.log(chalk.dim(`Valid options: ${validModes.join(', ')}`)); + console.log(colors.error(`Invalid permission mode: ${this.permission}`)); + console.log(colors.muted(`Valid options: ${validModes.join(', ')}`)); return 1; } options.permissionMode = this.permission as AgentPermissionMode; @@ -928,16 +928,16 @@ export class AgentFromSkillCommand extends Command { if (this.output) { const sanitized = sanitizeFilename(this.output); if (!sanitized) { - console.log(chalk.red(`Invalid output filename: ${this.output}`)); - console.log(chalk.dim('Filename must contain only alphanumeric characters, hyphens, and underscores')); + console.log(colors.error(`Invalid output filename: ${this.output}`)); + console.log(colors.muted('Filename must contain only alphanumeric characters, hyphens, and underscores')); return 1; } filename = `${sanitized}.md`; } else { const sanitized = sanitizeFilename(skill.name); if (!sanitized) { - console.log(chalk.red(`Invalid skill name for filename: ${skill.name}`)); - console.log(chalk.dim('Skill name must contain only alphanumeric characters, hyphens, and underscores')); + console.log(colors.error(`Invalid skill name for filename: ${skill.name}`)); + console.log(colors.muted('Skill name must contain only alphanumeric characters, hyphens, and underscores')); return 1; } filename = `${sanitized}.md`; @@ -946,11 +946,11 @@ export class AgentFromSkillCommand extends Command { const outputPath = join(targetDir, filename); if (this.dryRun) { - console.log(chalk.cyan('Preview (dry run):\n')); - console.log(chalk.dim(`Would write to: ${outputPath}`)); - console.log(chalk.dim('─'.repeat(50))); + console.log(colors.cyan('Preview (dry run):\n')); + console.log(colors.muted(`Would write to: ${outputPath}`)); + console.log(colors.muted('─'.repeat(50))); console.log(content); - console.log(chalk.dim('─'.repeat(50))); + console.log(colors.muted('─'.repeat(50))); return 0; } @@ -959,18 +959,18 @@ export class AgentFromSkillCommand extends Command { } if (existsSync(outputPath)) { - console.log(chalk.yellow(`Overwriting existing file: ${outputPath}`)); + console.log(colors.warning(`Overwriting existing file: ${outputPath}`)); } writeFileSync(outputPath, content); - console.log(chalk.green(`Created subagent: ${outputPath}`)); + console.log(colors.success(`Created subagent: ${outputPath}`)); console.log(); - console.log(chalk.dim(`Invoke with: @${skill.name}`)); + console.log(colors.muted(`Invoke with: @${skill.name}`)); if (!this.inline) { - console.log(chalk.dim(`Skills referenced: ${skill.name}`)); + console.log(colors.muted(`Skills referenced: ${skill.name}`)); } else { - console.log(chalk.dim('Skill content embedded inline')); + console.log(colors.muted('Skill content embedded inline')); } return 0; diff --git a/packages/cli/src/commands/agents-md.ts b/packages/cli/src/commands/agents-md.ts index e0f9eb40..afab35aa 100644 --- a/packages/cli/src/commands/agents-md.ts +++ b/packages/cli/src/commands/agents-md.ts @@ -1,6 +1,6 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; -import chalk from 'chalk'; +import { colors, warn, success, error } from '../onboarding/index.js'; import { Command } from 'clipanion'; import { AgentsMdGenerator, AgentsMdParser } from '@skillkit/core'; @@ -40,7 +40,7 @@ export class AgentsMdInitCommand extends Command { const agentsPath = join(projectPath, 'AGENTS.md'); if (existsSync(agentsPath)) { - console.log(chalk.yellow('AGENTS.md already exists. Use `skillkit agents sync` to update.')); + warn('AGENTS.md already exists. Use `skillkit agents sync` to update.'); return 1; } @@ -48,15 +48,15 @@ export class AgentsMdInitCommand extends Command { const generator = new AgentsMdGenerator({ projectPath }); const result = generator.generate(); - console.log(chalk.dim('Preview:')); + console.log(colors.muted('Preview:')); console.log(''); console.log(result.content); writeFileSync(agentsPath, result.content, 'utf-8'); - console.log(chalk.green(`Created ${agentsPath}`)); + success(`Created ${agentsPath}`); return 0; } catch (err) { - console.log(chalk.red(`Failed to generate AGENTS.md: ${err instanceof Error ? err.message : String(err)}`)); + error(`Failed to generate AGENTS.md: ${err instanceof Error ? err.message : String(err)}`); return 1; } } @@ -74,7 +74,7 @@ export class AgentsMdSyncCommand extends Command { const agentsPath = join(projectPath, 'AGENTS.md'); if (!existsSync(agentsPath)) { - console.log(chalk.yellow('No AGENTS.md found. Run `skillkit agents init` first.')); + warn('No AGENTS.md found. Run `skillkit agents init` first.'); return 1; } @@ -83,7 +83,7 @@ export class AgentsMdSyncCommand extends Command { const parser = new AgentsMdParser(); if (!parser.hasManagedSections(existing)) { - console.log(chalk.yellow('No managed sections found in AGENTS.md. Nothing to update.')); + warn('No managed sections found in AGENTS.md. Nothing to update.'); return 0; } @@ -93,10 +93,10 @@ export class AgentsMdSyncCommand extends Command { const updated = parser.updateManagedSections(existing, managedSections); writeFileSync(agentsPath, updated, 'utf-8'); - console.log(chalk.green(`Updated ${managedSections.length} managed section(s) in AGENTS.md`)); + success(`Updated ${managedSections.length} managed section(s) in AGENTS.md`); return 0; } catch (err) { - console.log(chalk.red(`Failed to sync AGENTS.md: ${err instanceof Error ? err.message : String(err)}`)); + error(`Failed to sync AGENTS.md: ${err instanceof Error ? err.message : String(err)}`); return 1; } } @@ -113,7 +113,7 @@ export class AgentsMdShowCommand extends Command { const agentsPath = join(process.cwd(), 'AGENTS.md'); if (!existsSync(agentsPath)) { - console.log(chalk.yellow('No AGENTS.md found. Run `skillkit agents init` to create one.')); + warn('No AGENTS.md found. Run `skillkit agents init` to create one.'); return 1; } diff --git a/packages/cli/src/commands/ai.ts b/packages/cli/src/commands/ai.ts index 8853ff1d..b57a044e 100644 --- a/packages/cli/src/commands/ai.ts +++ b/packages/cli/src/commands/ai.ts @@ -1,8 +1,7 @@ import { Command, Option } from 'clipanion'; import { resolve } from 'node:path'; import { promises as fs } from 'fs'; -import chalk from 'chalk'; -import ora from 'ora'; +import { colors, spinner } from '../onboarding/index.js'; import { type SearchableSkill, type SkillExample, @@ -88,7 +87,7 @@ export class AICommand extends Command { const providerName = manager.getProviderName(); if (providerName === 'mock') { console.log( - chalk.yellow( + colors.warning( '⚠ Using mock AI provider (limited functionality)\n' + ' Set ANTHROPIC_API_KEY or OPENAI_API_KEY for real AI features\n' ) @@ -110,7 +109,7 @@ export class AICommand extends Command { return this.handleProviders(); default: console.error( - chalk.red(`Unknown subcommand: ${this.subcommand}\n`) + colors.error(`Unknown subcommand: ${this.subcommand}\n`) ); console.log('Valid subcommands: search, generate, similar, wizard, providers'); return 1; @@ -120,19 +119,20 @@ export class AICommand extends Command { private async handleSearch(manager: AIManager): Promise { const query = this.query || this.skillName; if (!query) { - console.error(chalk.red('Search query required (--query or positional argument)')); + console.error(colors.error('Search query required (--query or positional argument)')); return 1; } const skills = await this.loadSkills(); if (skills.length === 0) { console.log( - chalk.yellow('No skills found. Run "skillkit recommend --update" first.') + colors.warning('No skills found. Run "skillkit recommend --update" first.') ); return 0; } - const spinner = ora('Searching with AI...').start(); + const s = spinner(); + s.start('Searching with AI...'); try { const results = await manager.searchSkills(query, skills, { @@ -143,7 +143,7 @@ export class AICommand extends Command { includeReasoning: !this.json, }); - spinner.stop(); + s.stop(); if (this.json) { console.log(JSON.stringify(results, null, 2)); @@ -151,37 +151,37 @@ export class AICommand extends Command { } if (results.length === 0) { - console.log(chalk.yellow(`No skills found matching "${query}"`)); + console.log(colors.warning(`No skills found matching "${query}"`)); return 0; } - console.log(chalk.cyan(`\nAI Search Results (${results.length} found):\n`)); + console.log(colors.cyan(`\nAI Search Results (${results.length} found):\n`)); for (const result of results) { const score = Math.round(result.relevance * 100); const scoreColor = - score >= 70 ? chalk.green : score >= 50 ? chalk.yellow : chalk.dim; + score >= 70 ? colors.success : score >= 50 ? colors.warning : colors.muted; console.log( - ` ${scoreColor(`${score}%`)} ${chalk.bold(result.skill.name)}` + ` ${scoreColor(`${score}%`)} ${colors.bold(result.skill.name)}` ); if (result.skill.description) { - console.log(` ${chalk.dim(result.skill.description)}`); + console.log(` ${colors.muted(result.skill.description)}`); } if (result.reasoning) { - console.log(` ${chalk.dim('→ ' + result.reasoning)}`); + console.log(` ${colors.muted('→ ' + result.reasoning)}`); } console.log(); } return 0; - } catch (error) { - spinner.fail('Search failed'); + } catch (err) { + s.stop(colors.error('Search failed')); console.error( - chalk.red(error instanceof Error ? error.message : String(error)) + colors.error(err instanceof Error ? err.message : String(err)) ); return 1; } @@ -190,7 +190,7 @@ export class AICommand extends Command { private async handleGenerate(manager: AIManager): Promise { const description = this.description; if (!description) { - console.error(chalk.red('Description required (--description)')); + console.error(colors.error('Description required (--description)')); return 1; } @@ -201,7 +201,7 @@ export class AICommand extends Command { const code = await fs.readFile(codePath, 'utf-8'); codeExamples = [code]; } catch { - console.error(chalk.red(`Failed to read code file: ${codePath}`)); + console.error(colors.error(`Failed to read code file: ${codePath}`)); return 1; } } @@ -213,7 +213,8 @@ export class AICommand extends Command { targetAgent: this.targetAgent, }; - const spinner = ora('Generating skill with AI...').start(); + const s = spinner(); + s.start('Generating skill with AI...'); try { const generated = await manager.generateSkill(example, { @@ -222,13 +223,13 @@ export class AICommand extends Command { includeDocumentation: true, }); - spinner.stop(); + s.stop(); const validation = manager.validateGenerated(generated); if (!validation.valid) { - console.log(chalk.yellow('\n⚠ Generated skill has validation warnings:')); - for (const error of validation.errors) { - console.log(chalk.dim(` • ${error}`)); + console.log(colors.warning('\n⚠ Generated skill has validation warnings:')); + for (const err of validation.errors) { + console.log(colors.muted(` • ${err}`)); } console.log(); } @@ -238,41 +239,41 @@ export class AICommand extends Command { return 0; } - console.log(chalk.green('\n✓ Generated skill successfully\n')); - console.log(chalk.cyan('Name:') + ` ${generated.name}`); - console.log(chalk.cyan('Description:') + ` ${generated.description}`); + console.log(colors.success('\n✓ Generated skill successfully\n')); + console.log(colors.cyan('Name:') + ` ${generated.name}`); + console.log(colors.cyan('Description:') + ` ${generated.description}`); console.log( - chalk.cyan('Tags:') + ` ${generated.tags.join(', ')}` + colors.cyan('Tags:') + ` ${generated.tags.join(', ')}` ); console.log( - chalk.cyan('Confidence:') + + colors.cyan('Confidence:') + ` ${Math.round(generated.confidence * 100)}%` ); if (generated.reasoning) { - console.log(chalk.cyan('Reasoning:') + ` ${generated.reasoning}`); + console.log(colors.cyan('Reasoning:') + ` ${generated.reasoning}`); } - console.log('\n' + chalk.dim('Content:')); - console.log(chalk.dim('─'.repeat(60))); + console.log('\n' + colors.muted('Content:')); + console.log(colors.muted('─'.repeat(60))); console.log(generated.content); - console.log(chalk.dim('─'.repeat(60))); + console.log(colors.muted('─'.repeat(60))); if (this.output) { const outputPath = resolve(this.output); await fs.writeFile(outputPath, generated.content, 'utf-8'); - console.log(chalk.green(`\n✓ Saved to: ${outputPath}`)); + console.log(colors.success(`\n✓ Saved to: ${outputPath}`)); } else { console.log( - chalk.dim('\nSave with: --output ') + colors.muted('\nSave with: --output ') ); } return 0; - } catch (error) { - spinner.fail('Generation failed'); + } catch (err) { + s.stop(colors.error('Generation failed')); console.error( - chalk.red(error instanceof Error ? error.message : String(error)) + colors.error(err instanceof Error ? err.message : String(err)) ); return 1; } @@ -281,14 +282,14 @@ export class AICommand extends Command { private async handleSimilar(manager: AIManager): Promise { const skillName = this.skillName; if (!skillName) { - console.error(chalk.red('Skill name required')); + console.error(colors.error('Skill name required')); return 1; } const skills = await this.loadSkills(); if (skills.length === 0) { console.log( - chalk.yellow('No skills found. Run "skillkit recommend --update" first.') + colors.warning('No skills found. Run "skillkit recommend --update" first.') ); return 0; } @@ -298,11 +299,12 @@ export class AICommand extends Command { ); if (!targetSkill) { - console.error(chalk.red(`Skill not found: ${skillName}`)); + console.error(colors.error(`Skill not found: ${skillName}`)); return 1; } - const spinner = ora('Finding similar skills...').start(); + const s = spinner(); + s.start('Finding similar skills...'); try { const results = await manager.findSimilar(targetSkill, skills, { @@ -313,7 +315,7 @@ export class AICommand extends Command { includeReasoning: !this.json, }); - spinner.stop(); + s.stop(); if (this.json) { console.log(JSON.stringify(results, null, 2)); @@ -322,40 +324,40 @@ export class AICommand extends Command { if (results.length === 0) { console.log( - chalk.yellow(`No similar skills found for "${skillName}"`) + colors.warning(`No similar skills found for "${skillName}"`) ); return 0; } console.log( - chalk.cyan(`\nSkills similar to "${skillName}" (${results.length} found):\n`) + colors.cyan(`\nSkills similar to "${skillName}" (${results.length} found):\n`) ); for (const result of results) { const score = Math.round(result.relevance * 100); const scoreColor = - score >= 70 ? chalk.green : score >= 50 ? chalk.yellow : chalk.dim; + score >= 70 ? colors.success : score >= 50 ? colors.warning : colors.muted; console.log( - ` ${scoreColor(`${score}%`)} ${chalk.bold(result.skill.name)}` + ` ${scoreColor(`${score}%`)} ${colors.bold(result.skill.name)}` ); if (result.skill.description) { - console.log(` ${chalk.dim(result.skill.description)}`); + console.log(` ${colors.muted(result.skill.description)}`); } if (result.reasoning) { - console.log(` ${chalk.dim('→ ' + result.reasoning)}`); + console.log(` ${colors.muted('→ ' + result.reasoning)}`); } console.log(); } return 0; - } catch (error) { - spinner.fail('Search failed'); + } catch (err) { + s.stop(colors.error('Search failed')); console.error( - chalk.red(error instanceof Error ? error.message : String(error)) + colors.error(err instanceof Error ? err.message : String(err)) ); return 1; } @@ -377,8 +379,8 @@ export class AICommand extends Command { } private async handleWizard(): Promise { - console.log(chalk.cyan('\nLaunching Smart Generate Wizard...\n')); - console.log(chalk.dim('For the full wizard experience, use: skillkit generate\n')); + console.log(colors.cyan('\nLaunching Smart Generate Wizard...\n')); + console.log(colors.muted('For the full wizard experience, use: skillkit generate\n')); const { GenerateCommand } = await import('./generate.js'); const generateCmd = new GenerateCommand(); @@ -389,31 +391,31 @@ export class AICommand extends Command { const detected = detectProviders(); const defaultProvider = getDefaultProvider(); - console.log(chalk.cyan('\nAvailable LLM Providers:\n')); + console.log(colors.cyan('\nAvailable LLM Providers:\n')); for (const provider of detected) { const isDefault = provider.provider === defaultProvider; const status = provider.configured - ? chalk.green('✓ Configured') - : chalk.dim('○ Not configured'); - const defaultBadge = isDefault ? chalk.yellow(' (default)') : ''; + ? colors.success('✓ Configured') + : colors.muted('○ Not configured'); + const defaultBadge = isDefault ? colors.warning(' (default)') : ''; console.log(` ${provider.displayName}${defaultBadge}`); console.log(` ${status}`); if (provider.envVar) { - console.log(` ${chalk.dim(`Set ${provider.envVar} to configure`)}`); + console.log(` ${colors.muted(`Set ${provider.envVar} to configure`)}`); } console.log(); } if (defaultProvider === 'mock') { - console.log(` Mock Provider${chalk.yellow(' (default)')}`); - console.log(` ${chalk.green('✓ Always available')}`); - console.log(` ${chalk.dim('Basic functionality without API keys')}`); + console.log(` Mock Provider${colors.warning(' (default)')}`); + console.log(` ${colors.success('✓ Always available')}`); + console.log(` ${colors.muted('Basic functionality without API keys')}`); console.log(); } - console.log(chalk.dim('Use "skillkit generate --provider " to use a specific provider\n')); + console.log(colors.muted('Use "skillkit generate --provider " to use a specific provider\n')); return 0; } diff --git a/packages/cli/src/commands/audit.ts b/packages/cli/src/commands/audit.ts index 6b87d2f9..9edb2d30 100644 --- a/packages/cli/src/commands/audit.ts +++ b/packages/cli/src/commands/audit.ts @@ -2,7 +2,7 @@ import { Command, Option } from 'clipanion'; import { resolve } from 'node:path'; import { promises as fs } from 'fs'; import path from 'path'; -import chalk from 'chalk'; +import { colors, warn, success } from '../onboarding/index.js'; import { AuditLogger, type AuditQuery, type AuditEventType } from '@skillkit/core'; export class AuditCommand extends Command { @@ -104,7 +104,7 @@ export class AuditCommand extends Command { case 'clear': return await this.handleClear(logger); default: - console.error(chalk.red(`Unknown subcommand: ${this.subcommand}\n`)); + console.error(colors.error(`Unknown subcommand: ${this.subcommand}\n`)); console.log('Valid subcommands: log, export, stats, clear'); return 1; } @@ -123,37 +123,37 @@ export class AuditCommand extends Command { } if (events.length === 0) { - console.log(chalk.yellow('No audit events found')); + warn('No audit events found'); return 0; } - console.log(chalk.cyan(`\nAudit Log (${events.length} entries):\n`)); + console.log(colors.cyan(`\nAudit Log (${events.length} entries):\n`)); for (const event of events) { - const statusIcon = event.success ? chalk.green('✓') : chalk.red('✗'); + const statusIcon = event.success ? colors.success('✓') : colors.error('✗'); const timestamp = event.timestamp.toLocaleString(); console.log( - `${statusIcon} ${chalk.dim(timestamp)} ${chalk.bold(event.type)}` + `${statusIcon} ${colors.muted(timestamp)} ${colors.bold(event.type)}` ); console.log( - ` ${event.action} on ${chalk.cyan(event.resource)}` + ` ${event.action} on ${colors.cyan(event.resource)}` ); if (event.user) { - console.log(` ${chalk.dim('User:')} ${event.user}`); + console.log(` ${colors.muted('User:')} ${event.user}`); } if (event.duration) { - console.log(` ${chalk.dim('Duration:')} ${event.duration}ms`); + console.log(` ${colors.muted('Duration:')} ${event.duration}ms`); } if (event.error) { - console.log(` ${chalk.red('Error:')} ${event.error}`); + console.log(` ${colors.error('Error:')} ${event.error}`); } if (event.details && Object.keys(event.details).length > 0) { - console.log(` ${chalk.dim('Details:')} ${JSON.stringify(event.details)}`); + console.log(` ${colors.muted('Details:')} ${JSON.stringify(event.details)}`); } console.log(); @@ -171,7 +171,7 @@ export class AuditCommand extends Command { if (this.output) { const outputPath = resolve(this.output); await fs.writeFile(outputPath, content, 'utf-8'); - console.log(chalk.green(`✓ Exported audit log to: ${outputPath}`)); + success(`✓ Exported audit log to: ${outputPath}`); } else { console.log(content); } @@ -187,14 +187,14 @@ export class AuditCommand extends Command { return 0; } - console.log(chalk.cyan('\nAudit Statistics:\n')); - console.log(`Total Events: ${chalk.bold(stats.totalEvents.toString())}`); + console.log(colors.cyan('\nAudit Statistics:\n')); + console.log(`Total Events: ${colors.bold(stats.totalEvents.toString())}`); console.log( - `Success Rate: ${chalk.bold(`${(stats.successRate * 100).toFixed(1)}%`)}` + `Success Rate: ${colors.bold(`${(stats.successRate * 100).toFixed(1)}%`)}` ); if (Object.keys(stats.eventsByType).length > 0) { - console.log(chalk.cyan('\nEvents by Type:')); + console.log(colors.cyan('\nEvents by Type:')); const sorted = Object.entries(stats.eventsByType).sort( ([, a], [, b]) => b - a ); @@ -204,21 +204,21 @@ export class AuditCommand extends Command { } if (stats.topResources.length > 0) { - console.log(chalk.cyan('\nTop Resources:')); + console.log(colors.cyan('\nTop Resources:')); for (const { resource, count } of stats.topResources) { console.log(` ${resource.padEnd(40)} ${count}`); } } if (stats.recentErrors.length > 0) { - console.log(chalk.cyan(`\nRecent Errors (${stats.recentErrors.length}):`)); - for (const error of stats.recentErrors.slice(0, 5)) { - const timestamp = error.timestamp.toLocaleString(); + console.log(colors.cyan(`\nRecent Errors (${stats.recentErrors.length}):`)); + for (const err of stats.recentErrors.slice(0, 5)) { + const timestamp = err.timestamp.toLocaleString(); console.log( - ` ${chalk.red('✗')} ${chalk.dim(timestamp)} ${error.type}` + ` ${colors.error('✗')} ${colors.muted(timestamp)} ${err.type}` ); - if (error.error) { - console.log(` ${chalk.dim(error.error)}`); + if (err.error) { + console.log(` ${colors.muted(err.error)}`); } } } @@ -229,7 +229,7 @@ export class AuditCommand extends Command { private async handleClear(logger: AuditLogger): Promise { if (!this.days) { - console.error(chalk.red('--days option required')); + console.error(colors.error('--days option required')); return 1; } @@ -239,11 +239,7 @@ export class AuditCommand extends Command { const cleared = await logger.clear(cutoffDate); - console.log( - chalk.green( - `✓ Cleared ${cleared} audit entries older than ${daysAgo} days` - ) - ); + success(`✓ Cleared ${cleared} audit entries older than ${daysAgo} days`); return 0; } diff --git a/packages/cli/src/commands/cicd.ts b/packages/cli/src/commands/cicd.ts index 18d38e51..9e9b1cd7 100644 --- a/packages/cli/src/commands/cicd.ts +++ b/packages/cli/src/commands/cicd.ts @@ -1,5 +1,5 @@ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors } from '../onboarding/index.js'; import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; @@ -247,7 +247,7 @@ export class CICDCommand extends Command { ? ['github', 'gitlab', 'circleci'] : [this.provider as CICDProvider]; - console.log(chalk.cyan('Initializing CI/CD workflows...')); + console.log(colors.cyan('Initializing CI/CD workflows...')); console.log(); let success = true; @@ -256,29 +256,29 @@ export class CICDCommand extends Command { for (const provider of providers) { const result = this.createWorkflow(projectPath, provider); if (result.skipped) { - console.log(chalk.yellow(` ${result.message}`)); + console.log(colors.warning(` ${result.message}`)); continue; } if (!result.success) { success = false; - console.error(chalk.red(` ${result.message}`)); + console.error(colors.error(` ${result.message}`)); continue; } created = true; - console.log(chalk.green(` ✓ ${result.message}`)); + console.log(colors.success(` ✓ ${result.message}`)); } if (success) { console.log(); console.log( created - ? chalk.green('CI/CD workflows initialized successfully!') - : chalk.yellow('CI/CD workflows already exist; nothing to do.') + ? colors.success('CI/CD workflows initialized successfully!') + : colors.warning('CI/CD workflows already exist; nothing to do.') ); console.log(); if (created) { - console.log(chalk.dim('The workflows will run on push/PR to validate your skills.')); - console.log(chalk.dim('Commit the generated files to enable CI/CD.')); + console.log(colors.muted('The workflows will run on push/PR to validate your skills.')); + console.log(colors.muted('Commit the generated files to enable CI/CD.')); } } diff --git a/packages/cli/src/commands/context.ts b/packages/cli/src/commands/context.ts index 362e1ec7..3ce083aa 100644 --- a/packages/cli/src/commands/context.ts +++ b/packages/cli/src/commands/context.ts @@ -1,7 +1,7 @@ import { Command, Option } from 'clipanion'; import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; -import chalk from 'chalk'; +import { colors } from '../onboarding/index.js'; import { type AgentType, type ProjectContext, @@ -103,8 +103,8 @@ export class ContextCommand extends Command { case 'agents': return this.listAgents(); default: - console.error(chalk.red(`Unknown action: ${action}`)); - console.log(chalk.gray('Available actions: init, show, export, import, sync, detect, agents')); + console.error(colors.error(`Unknown action: ${action}`)); + console.log(colors.muted('Available actions: init, show, export, import, sync, detect, agents')); return 1; } } @@ -116,16 +116,16 @@ export class ContextCommand extends Command { const manager = new ContextManager(process.cwd()); if (manager.exists() && !this.force) { - console.log(chalk.yellow('Context already exists. Use --force to reinitialize.')); + console.log(colors.warning('Context already exists. Use --force to reinitialize.')); return this.showContext(); } - console.log(chalk.cyan('Initializing project context...\n')); + console.log(colors.cyan('Initializing project context...\n')); const context = manager.init({ force: this.force }); - console.log(chalk.green('✓ Context initialized\n')); - console.log(chalk.gray(` Location: .skillkit/context.yaml\n`)); + console.log(colors.success('✓ Context initialized\n')); + console.log(colors.muted(` Location: .skillkit/context.yaml\n`)); // Show summary this.printContextSummary(context); @@ -134,9 +134,9 @@ export class ContextCommand extends Command { const sync = createContextSync(process.cwd()); const detected = sync.detectAgents(); if (detected.length > 0) { - console.log(chalk.cyan('\nDetected agents:')); + console.log(colors.cyan('\nDetected agents:')); for (const agent of detected) { - console.log(` ${chalk.green('•')} ${agent}`); + console.log(` ${colors.success('•')} ${agent}`); } // Update context with detected agents @@ -146,7 +146,7 @@ export class ContextCommand extends Command { }); } - console.log(chalk.gray('\nRun `skillkit context sync` to sync skills to all agents.')); + console.log(colors.muted('\nRun `skillkit context sync` to sync skills to all agents.')); return 0; } @@ -159,7 +159,7 @@ export class ContextCommand extends Command { const context = manager.load(); if (!context) { - console.log(chalk.yellow('No context found. Run `skillkit context init` first.')); + console.log(colors.warning('No context found. Run `skillkit context init` first.')); return 1; } @@ -171,8 +171,8 @@ export class ContextCommand extends Command { this.printContextSummary(context); if (this.verbose) { - console.log(chalk.gray('\nFull context:')); - console.log(chalk.gray(JSON.stringify(context, null, 2))); + console.log(colors.muted('\nFull context:')); + console.log(colors.muted(JSON.stringify(context, null, 2))); } return 0; @@ -186,7 +186,7 @@ export class ContextCommand extends Command { const context = manager.get(); if (!context) { - console.error(chalk.red('No context found. Run `skillkit context init` first.')); + console.error(colors.error('No context found. Run `skillkit context init` first.')); return 1; } @@ -200,12 +200,12 @@ export class ContextCommand extends Command { if (this.output) { const outputPath = resolve(this.output); if (existsSync(outputPath) && !this.force) { - console.error(chalk.red(`File exists: ${outputPath}. Use --force to overwrite.`)); + console.error(colors.error(`File exists: ${outputPath}. Use --force to overwrite.`)); return 1; } writeFileSync(outputPath, content, 'utf-8'); - console.log(chalk.green(`✓ Context exported to ${outputPath}`)); + console.log(colors.success(`✓ Context exported to ${outputPath}`)); } else { // Print to stdout console.log(content); @@ -219,13 +219,13 @@ export class ContextCommand extends Command { */ private async importContext(): Promise { if (!this.input) { - console.error(chalk.red('Error: --input/-i file path is required')); + console.error(colors.error('Error: --input/-i file path is required')); return 1; } const inputPath = resolve(this.input); if (!existsSync(inputPath)) { - console.error(chalk.red(`File not found: ${inputPath}`)); + console.error(colors.error(`File not found: ${inputPath}`)); return 1; } @@ -238,11 +238,11 @@ export class ContextCommand extends Command { overwrite: this.force, }); - console.log(chalk.green('✓ Context imported successfully')); + console.log(colors.success('✓ Context imported successfully')); this.printContextSummary(context); return 0; } catch (error) { - console.error(chalk.red(`Import failed: ${error}`)); + console.error(colors.error(`Import failed: ${error}`)); return 1; } } @@ -255,13 +255,13 @@ export class ContextCommand extends Command { const context = manager.get(); if (!context) { - console.log(chalk.yellow('No context found. Initializing...')); + console.log(colors.warning('No context found. Initializing...')); manager.init(); } const sync = createContextSync(process.cwd()); - console.log(chalk.cyan('Syncing skills across agents...\n')); + console.log(colors.cyan('Syncing skills across agents...\n')); // Determine target agents const agents = this.agent ? [this.agent as AgentType] : undefined; @@ -274,33 +274,33 @@ export class ContextCommand extends Command { // Print results for (const result of report.results) { - const status = result.success ? chalk.green('✓') : chalk.red('✗'); + const status = result.success ? colors.success('✓') : colors.error('✗'); console.log(`${status} ${result.agent}: ${result.skillsSynced} synced, ${result.skillsSkipped} skipped`); if (this.verbose && result.files.length > 0) { for (const file of result.files) { - console.log(chalk.gray(` → ${file}`)); + console.log(colors.muted(` → ${file}`)); } } if (result.warnings.length > 0) { for (const warning of result.warnings) { - console.log(chalk.yellow(` ⚠ ${warning}`)); + console.log(colors.warning(` ⚠ ${warning}`)); } } if (result.errors.length > 0) { for (const error of result.errors) { - console.log(chalk.red(` ✗ ${error}`)); + console.log(colors.error(` ✗ ${error}`)); } } } console.log(); - console.log(chalk.bold(`Summary: ${report.successfulAgents}/${report.totalAgents} agents, ${report.totalSkills} skills`)); + console.log(colors.bold(`Summary: ${report.successfulAgents}/${report.totalAgents} agents, ${report.totalSkills} skills`)); if (this.dryRun) { - console.log(chalk.gray('\n(Dry run - no files were written)')); + console.log(colors.muted('\n(Dry run - no files were written)')); } return report.successfulAgents === report.totalAgents ? 0 : 1; @@ -310,86 +310,86 @@ export class ContextCommand extends Command { * Detect project stack */ private async detectProject(): Promise { - console.log(chalk.cyan('Analyzing project...\n')); + console.log(colors.cyan('Analyzing project...\n')); const stack = analyzeProject(process.cwd()); const tags = getStackTags(stack); // Languages if (stack.languages.length > 0) { - console.log(chalk.bold('Languages:')); + console.log(colors.bold('Languages:')); for (const lang of stack.languages) { const version = lang.version ? ` (${lang.version})` : ''; - console.log(` ${chalk.green('•')} ${lang.name}${version}`); + console.log(` ${colors.success('•')} ${lang.name}${version}`); } console.log(); } // Frameworks if (stack.frameworks.length > 0) { - console.log(chalk.bold('Frameworks:')); + console.log(colors.bold('Frameworks:')); for (const fw of stack.frameworks) { const version = fw.version ? ` (${fw.version})` : ''; - console.log(` ${chalk.green('•')} ${fw.name}${version}`); + console.log(` ${colors.success('•')} ${fw.name}${version}`); } console.log(); } // Libraries if (stack.libraries.length > 0) { - console.log(chalk.bold('Libraries:')); + console.log(colors.bold('Libraries:')); for (const lib of stack.libraries) { const version = lib.version ? ` (${lib.version})` : ''; - console.log(` ${chalk.green('•')} ${lib.name}${version}`); + console.log(` ${colors.success('•')} ${lib.name}${version}`); } console.log(); } // Styling if (stack.styling.length > 0) { - console.log(chalk.bold('Styling:')); + console.log(colors.bold('Styling:')); for (const style of stack.styling) { - console.log(` ${chalk.green('•')} ${style.name}`); + console.log(` ${colors.success('•')} ${style.name}`); } console.log(); } // Testing if (stack.testing.length > 0) { - console.log(chalk.bold('Testing:')); + console.log(colors.bold('Testing:')); for (const test of stack.testing) { - console.log(` ${chalk.green('•')} ${test.name}`); + console.log(` ${colors.success('•')} ${test.name}`); } console.log(); } // Databases if (stack.databases.length > 0) { - console.log(chalk.bold('Databases:')); + console.log(colors.bold('Databases:')); for (const db of stack.databases) { - console.log(` ${chalk.green('•')} ${db.name}`); + console.log(` ${colors.success('•')} ${db.name}`); } console.log(); } // Tools if (stack.tools.length > 0) { - console.log(chalk.bold('Tools:')); + console.log(colors.bold('Tools:')); for (const tool of stack.tools) { - console.log(` ${chalk.green('•')} ${tool.name}`); + console.log(` ${colors.success('•')} ${tool.name}`); } console.log(); } // Tags if (tags.length > 0) { - console.log(chalk.bold('Recommended skill tags:')); - console.log(` ${chalk.cyan(tags.join(', '))}`); + console.log(colors.bold('Recommended skill tags:')); + console.log(` ${colors.cyan(tags.join(', '))}`); console.log(); } if (this.json) { - console.log(chalk.gray('\nJSON:')); + console.log(colors.muted('\nJSON:')); console.log(JSON.stringify(stack, null, 2)); } @@ -405,27 +405,27 @@ export class ContextCommand extends Command { const status = sync.checkStatus(); const adapters = getAllAdapters(); - console.log(chalk.bold('\nAgent Status:\n')); + console.log(colors.bold('\nAgent Status:\n')); for (const [agent, info] of Object.entries(status)) { const adapter = adapters.find(a => a.type === agent); const name = adapter?.name || agent; const isDetected = detected.includes(agent as AgentType); - const statusIcon = info.hasSkills ? chalk.green('●') : isDetected ? chalk.yellow('○') : chalk.gray('○'); - const skillInfo = info.skillCount > 0 ? chalk.gray(` (${info.skillCount} skills)`) : ''; + const statusIcon = info.hasSkills ? colors.success('●') : isDetected ? colors.warning('○') : colors.muted('○'); + const skillInfo = info.skillCount > 0 ? colors.muted(` (${info.skillCount} skills)`) : ''; - console.log(` ${statusIcon} ${name.padEnd(20)} ${chalk.gray(agent)}${skillInfo}`); + console.log(` ${statusIcon} ${name.padEnd(20)} ${colors.muted(agent)}${skillInfo}`); if (this.verbose && info.skills.length > 0) { for (const skill of info.skills) { - console.log(chalk.gray(` └─ ${skill}`)); + console.log(colors.muted(` └─ ${skill}`)); } } } console.log(); - console.log(chalk.gray('Legend: ● has skills, ○ detected/configured, ○ not detected')); + console.log(colors.muted('Legend: ● has skills, ○ detected/configured, ○ not detected')); console.log(); return 0; @@ -435,8 +435,8 @@ export class ContextCommand extends Command { * Print context summary */ private printContextSummary(context: ProjectContext): void { - console.log(chalk.bold('Project:')); - console.log(` Name: ${chalk.cyan(context.project.name)}`); + console.log(colors.bold('Project:')); + console.log(` Name: ${colors.cyan(context.project.name)}`); if (context.project.type) { console.log(` Type: ${context.project.type}`); } @@ -460,32 +460,30 @@ export class ContextCommand extends Command { } if (stackItems.length > 0) { - console.log(`\n${chalk.bold('Stack:')} ${stackItems.join(', ')}`); + console.log(`\n${colors.bold('Stack:')} ${stackItems.join(', ')}`); - // Show top items const topFrameworks = context.stack.frameworks.slice(0, 3).map(f => f.name); if (topFrameworks.length > 0) { - console.log(` Frameworks: ${chalk.cyan(topFrameworks.join(', '))}`); + console.log(` Frameworks: ${colors.cyan(topFrameworks.join(', '))}`); } const topLibs = context.stack.libraries.slice(0, 3).map(l => l.name); if (topLibs.length > 0) { - console.log(` Libraries: ${chalk.cyan(topLibs.join(', '))}`); + console.log(` Libraries: ${colors.cyan(topLibs.join(', '))}`); } } // Skills summary if (context.skills) { - console.log(`\n${chalk.bold('Skills:')}`); + console.log(`\n${colors.bold('Skills:')}`); console.log(` Installed: ${context.skills.installed?.length || 0}`); console.log(` Auto-sync: ${context.skills.autoSync ? 'enabled' : 'disabled'}`); } - // Agents summary if (context.agents) { - console.log(`\n${chalk.bold('Agents:')}`); + console.log(`\n${colors.bold('Agents:')}`); if (context.agents.primary) { - console.log(` Primary: ${chalk.cyan(context.agents.primary)}`); + console.log(` Primary: ${colors.cyan(context.agents.primary)}`); } if (context.agents.synced?.length) { console.log(` Synced: ${context.agents.synced.join(', ')}`); diff --git a/packages/cli/src/commands/create.ts b/packages/cli/src/commands/create.ts index d0e26c36..cc81e90f 100644 --- a/packages/cli/src/commands/create.ts +++ b/packages/cli/src/commands/create.ts @@ -1,6 +1,6 @@ import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; -import chalk from 'chalk'; +import { colors, success, error, step } from '../onboarding/index.js'; import { Command, Option } from 'clipanion'; export class CreateCommand extends Command { @@ -41,8 +41,8 @@ export class CreateCommand extends Command { const skillName = this.name.toLowerCase(); if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(skillName)) { - console.error(chalk.red('Invalid skill name')); - console.error(chalk.dim('Must be lowercase alphanumeric with hyphens (e.g., my-skill)')); + error('Invalid skill name'); + console.log(colors.muted('Must be lowercase alphanumeric with hyphens (e.g., my-skill)')); return 1; } @@ -50,7 +50,7 @@ export class CreateCommand extends Command { const skillDir = join(parentDir, skillName); if (existsSync(skillDir)) { - console.error(chalk.red(`Directory already exists: ${skillDir}`)); + error(`Directory already exists: ${skillDir}`); return 1; } @@ -78,24 +78,24 @@ export class CreateCommand extends Command { writeFileSync(join(assetsDir, '.gitkeep'), ''); } - console.log(chalk.green(`Created skill: ${skillName}`)); + success(`Created skill: ${skillName}`); console.log(); - console.log(chalk.dim('Structure:')); - console.log(chalk.dim(` ${skillDir}/`)); - console.log(chalk.dim(' ├── SKILL.md')); - if (this.full || this.references) console.log(chalk.dim(' ├── references/')); - if (this.full || this.scripts) console.log(chalk.dim(' ├── scripts/')); - if (this.full || this.assets) console.log(chalk.dim(' └── assets/')); + console.log(colors.muted('Structure:')); + console.log(colors.muted(` ${skillDir}/`)); + console.log(colors.muted(' ├── SKILL.md')); + if (this.full || this.references) console.log(colors.muted(' ├── references/')); + if (this.full || this.scripts) console.log(colors.muted(' ├── scripts/')); + if (this.full || this.assets) console.log(colors.muted(' └── assets/')); console.log(); - console.log(chalk.cyan('Next steps:')); - console.log(chalk.dim(' 1. Edit SKILL.md with your instructions')); - console.log(chalk.dim(' 2. Validate: skillkit validate ' + skillDir)); - console.log(chalk.dim(' 3. Test: skillkit read ' + skillName)); + step('Next steps:'); + console.log(colors.muted(' 1. Edit SKILL.md with your instructions')); + console.log(colors.muted(' 2. Validate: skillkit validate ' + skillDir)); + console.log(colors.muted(' 3. Test: skillkit read ' + skillName)); return 0; - } catch (error) { - console.error(chalk.red('Failed to create skill')); - console.error(chalk.dim(error instanceof Error ? error.message : String(error))); + } catch (err) { + error('Failed to create skill'); + console.log(colors.muted(err instanceof Error ? err.message : String(err))); return 1; } } diff --git a/packages/cli/src/commands/guideline.ts b/packages/cli/src/commands/guideline.ts index 542cf292..ed8db587 100644 --- a/packages/cli/src/commands/guideline.ts +++ b/packages/cli/src/commands/guideline.ts @@ -1,5 +1,5 @@ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors } from '../onboarding/index.js'; import { getAllGuidelines, getEnabledGuidelines, @@ -31,7 +31,7 @@ export class GuidelineCommand extends Command { }); async execute(): Promise { - console.log(chalk.cyan('Guideline commands:\n')); + console.log(colors.cyan('Guideline commands:\n')); console.log(' guideline list List all guidelines'); console.log(' guideline show Show guideline content'); console.log(' guideline enable Enable a guideline'); @@ -70,7 +70,7 @@ export class GuidelineListCommand extends Command { } const title = this.enabled ? 'Enabled Guidelines' : 'All Guidelines'; - console.log(chalk.cyan(`${title} (${guidelines.length}):\n`)); + console.log(colors.cyan(`${title} (${guidelines.length}):\n`)); const byCategory = new Map(); for (const guideline of guidelines) { @@ -81,22 +81,22 @@ export class GuidelineListCommand extends Command { } for (const [category, catGuidelines] of byCategory) { - console.log(chalk.blue(` ${formatCategoryName(category)}`)); + console.log(colors.info(` ${formatCategoryName(category)}`)); for (const guideline of catGuidelines) { const enabled = isGuidelineEnabled(guideline.id); - const status = enabled ? chalk.green('●') : chalk.dim('○'); - const custom = isBuiltinGuideline(guideline.id) ? '' : chalk.dim(' (custom)'); - const priority = chalk.dim(`[${guideline.priority}]`); + const status = enabled ? colors.success('●') : colors.muted('○'); + const custom = isBuiltinGuideline(guideline.id) ? '' : colors.muted(' (custom)'); + const priority = colors.muted(`[${guideline.priority}]`); - console.log(` ${status} ${chalk.bold(guideline.id)} ${priority}${custom}`); - console.log(` ${chalk.dim(guideline.description)}`); + console.log(` ${status} ${colors.bold(guideline.id)} ${priority}${custom}`); + console.log(` ${colors.muted(guideline.description)}`); } console.log(); } - console.log(chalk.dim('Enable with: skillkit guideline enable ')); - console.log(chalk.dim('Show content: skillkit guideline show ')); + console.log(colors.muted('Enable with: skillkit guideline enable ')); + console.log(colors.muted('Show content: skillkit guideline show ')); return 0; } @@ -116,20 +116,20 @@ export class GuidelineShowCommand extends Command { const guideline = getGuideline(this.id); if (!guideline) { - console.log(chalk.red(`Guideline not found: ${this.id}`)); + console.log(colors.error(`Guideline not found: ${this.id}`)); return 1; } const enabled = isGuidelineEnabled(this.id); - const status = enabled ? chalk.green('enabled') : chalk.dim('disabled'); + const status = enabled ? colors.success('enabled') : colors.muted('disabled'); - console.log(chalk.cyan(`Guideline: ${guideline.name}\n`)); + console.log(colors.cyan(`Guideline: ${guideline.name}\n`)); console.log(`ID: ${guideline.id}`); console.log(`Category: ${guideline.category}`); console.log(`Priority: ${guideline.priority}`); console.log(`Status: ${status}`); console.log(); - console.log(chalk.bold('Content:')); + console.log(colors.bold('Content:')); console.log(guideline.content); return 0; @@ -150,11 +150,11 @@ export class GuidelineEnableCommand extends Command { const success = enableGuideline(this.id); if (!success) { - console.log(chalk.red(`Guideline not found: ${this.id}`)); + console.log(colors.error(`Guideline not found: ${this.id}`)); return 1; } - console.log(chalk.green(`✓ Enabled guideline: ${this.id}`)); + console.log(colors.success(`✓ Enabled guideline: ${this.id}`)); return 0; } } @@ -173,11 +173,11 @@ export class GuidelineDisableCommand extends Command { const success = disableGuideline(this.id); if (!success) { - console.log(chalk.yellow(`Guideline not found or already disabled: ${this.id}`)); + console.log(colors.warning(`Guideline not found or already disabled: ${this.id}`)); return 0; } - console.log(chalk.green(`✓ Disabled guideline: ${this.id}`)); + console.log(colors.success(`✓ Disabled guideline: ${this.id}`)); return 0; } } @@ -216,7 +216,7 @@ export class GuidelineCreateCommand extends Command { async execute(): Promise { if (isBuiltinGuideline(this.id)) { - console.log(chalk.red(`Cannot create: ${this.id} is a built-in guideline`)); + console.log(colors.error(`Cannot create: ${this.id} is a built-in guideline`)); return 1; } @@ -234,8 +234,8 @@ export class GuidelineCreateCommand extends Command { addCustomGuideline(guideline); enableGuideline(this.id); - console.log(chalk.green(`✓ Created guideline: ${this.id}`)); - console.log(chalk.dim('Edit ~/.skillkit/guidelines.yaml to customize content')); + console.log(colors.success(`✓ Created guideline: ${this.id}`)); + console.log(colors.muted('Edit ~/.skillkit/guidelines.yaml to customize content')); return 0; } @@ -253,18 +253,18 @@ export class GuidelineRemoveCommand extends Command { async execute(): Promise { if (isBuiltinGuideline(this.id)) { - console.log(chalk.red(`Cannot remove built-in guideline: ${this.id}`)); + console.log(colors.error(`Cannot remove built-in guideline: ${this.id}`)); return 1; } const removed = removeCustomGuideline(this.id); if (!removed) { - console.log(chalk.yellow(`Guideline not found: ${this.id}`)); + console.log(colors.warning(`Guideline not found: ${this.id}`)); return 1; } - console.log(chalk.green(`✓ Removed guideline: ${this.id}`)); + console.log(colors.success(`✓ Removed guideline: ${this.id}`)); return 0; } } diff --git a/packages/cli/src/commands/handoff.ts b/packages/cli/src/commands/handoff.ts index 90e24bbb..c443b6ba 100644 --- a/packages/cli/src/commands/handoff.ts +++ b/packages/cli/src/commands/handoff.ts @@ -1,6 +1,6 @@ import { Command, Option } from 'clipanion'; import { writeFileSync } from 'node:fs'; -import chalk from 'chalk'; +import { colors } from '../onboarding/index.js'; import { SessionHandoff } from '@skillkit/core'; export class SessionHandoffCommand extends Command { @@ -41,9 +41,9 @@ export class SessionHandoffCommand extends Command { if (this.out) { try { writeFileSync(this.out, output, 'utf-8'); - console.log(chalk.green(`Handoff saved to ${this.out}`)); + console.log(colors.success(`Handoff saved to ${this.out}`)); } catch (error) { - console.error(chalk.red(`Failed to write file: ${(error as Error).message}`)); + console.error(colors.error(`Failed to write file: ${(error as Error).message}`)); return 1; } } else { diff --git a/packages/cli/src/commands/hook.ts b/packages/cli/src/commands/hook.ts index b2c92f18..da25699b 100644 --- a/packages/cli/src/commands/hook.ts +++ b/packages/cli/src/commands/hook.ts @@ -5,7 +5,7 @@ */ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors } from '../onboarding/index.js'; import { createHookManager, type HookEvent, type InjectionMode } from '@skillkit/core'; import { getHookTemplates, @@ -60,12 +60,12 @@ export class HookCommand extends Command { case 'info': return await this.showHookInfo(projectPath); default: - this.context.stderr.write(chalk.red(`Unknown action: ${this.action}\n`)); + this.context.stderr.write(colors.error(`Unknown action: ${this.action}\n`)); this.context.stderr.write('Available actions: list, add, remove, enable, disable, generate, info\n'); return 1; } } catch (err) { - this.context.stderr.write(chalk.red(`✗ ${err instanceof Error ? err.message : 'Unknown error'}\n`)); + this.context.stderr.write(colors.error(`✗ ${err instanceof Error ? err.message : 'Unknown error'}\n`)); return 1; } } @@ -76,11 +76,11 @@ export class HookCommand extends Command { if (hooks.length === 0) { this.context.stdout.write('No hooks configured.\n'); - this.context.stdout.write(chalk.gray('Run `skillkit hook add ` to add a hook.\n')); + this.context.stdout.write(colors.muted('Run `skillkit hook add ` to add a hook.\n')); return 0; } - this.context.stdout.write(chalk.cyan('Configured Hooks:\n\n')); + this.context.stdout.write(colors.cyan('Configured Hooks:\n\n')); // Group by event const eventGroups = new Map(); @@ -91,15 +91,15 @@ export class HookCommand extends Command { } for (const [event, eventHooks] of eventGroups) { - this.context.stdout.write(chalk.yellow(`${event}:\n`)); + this.context.stdout.write(colors.warning(`${event}:\n`)); for (const hook of eventHooks) { - const status = hook.enabled ? chalk.green('●') : chalk.gray('○'); - this.context.stdout.write(` ${status} ${chalk.white(hook.id.slice(0, 8))} → ${hook.skills.join(', ')}\n`); + const status = hook.enabled ? colors.success('●') : colors.muted('○'); + this.context.stdout.write(` ${status} ${colors.primary(hook.id.slice(0, 8))} → ${hook.skills.join(', ')}\n`); if (hook.matcher) { - this.context.stdout.write(chalk.gray(` Pattern: ${hook.matcher}\n`)); + this.context.stdout.write(colors.muted(` Pattern: ${hook.matcher}\n`)); } if (this.verbose) { - this.context.stdout.write(chalk.gray(` Inject: ${hook.inject}, Priority: ${hook.priority || 0}\n`)); + this.context.stdout.write(colors.muted(` Inject: ${hook.inject}, Priority: ${hook.priority || 0}\n`)); } } } @@ -110,14 +110,14 @@ export class HookCommand extends Command { private async addHook(projectPath: string): Promise { if (!this.target) { - this.context.stderr.write(chalk.red('Event type required.\n')); + this.context.stderr.write(colors.error('Event type required.\n')); this.context.stderr.write('Usage: skillkit hook add \n'); this.context.stderr.write('Events: session:start, session:end, file:save, file:open, task:start, commit:pre, commit:post, error:occur, test:fail, build:fail\n'); return 1; } if (!this.skill) { - this.context.stderr.write(chalk.red('Skill name required.\n')); + this.context.stderr.write(colors.error('Skill name required.\n')); this.context.stderr.write('Usage: skillkit hook add \n'); return 1; } @@ -133,7 +133,7 @@ export class HookCommand extends Command { ]; if (!validEvents.includes(event)) { - this.context.stderr.write(chalk.red(`Invalid event: ${this.target}\n`)); + this.context.stderr.write(colors.error(`Invalid event: ${this.target}\n`)); this.context.stderr.write(`Valid events: ${validEvents.join(', ')}\n`); return 1; } @@ -151,7 +151,7 @@ export class HookCommand extends Command { manager.save(); - this.context.stdout.write(chalk.green(`✓ Hook added: ${hook.id.slice(0, 8)}\n`)); + this.context.stdout.write(colors.success(`✓ Hook added: ${hook.id.slice(0, 8)}\n`)); this.context.stdout.write(` Event: ${event}\n`); this.context.stdout.write(` Skill: ${this.skill}\n`); if (this.pattern) { @@ -163,7 +163,7 @@ export class HookCommand extends Command { private async removeHook(projectPath: string): Promise { if (!this.target) { - this.context.stderr.write(chalk.red('Hook ID required.\n')); + this.context.stderr.write(colors.error('Hook ID required.\n')); this.context.stderr.write('Usage: skillkit hook remove \n'); return 1; } @@ -174,20 +174,20 @@ export class HookCommand extends Command { // Find hook by ID prefix const hook = hooks.find((h) => h.id.startsWith(this.target!)); if (!hook) { - this.context.stderr.write(chalk.red(`Hook not found: ${this.target}\n`)); + this.context.stderr.write(colors.error(`Hook not found: ${this.target}\n`)); return 1; } manager.unregisterHook(hook.id); manager.save(); - this.context.stdout.write(chalk.green(`✓ Hook removed: ${hook.id.slice(0, 8)}\n`)); + this.context.stdout.write(colors.success(`✓ Hook removed: ${hook.id.slice(0, 8)}\n`)); return 0; } private async enableHook(projectPath: string): Promise { if (!this.target) { - this.context.stderr.write(chalk.red('Hook ID required.\n')); + this.context.stderr.write(colors.error('Hook ID required.\n')); return 1; } @@ -196,20 +196,20 @@ export class HookCommand extends Command { const hook = hooks.find((h) => h.id.startsWith(this.target!)); if (!hook) { - this.context.stderr.write(chalk.red(`Hook not found: ${this.target}\n`)); + this.context.stderr.write(colors.error(`Hook not found: ${this.target}\n`)); return 1; } manager.enableHook(hook.id); manager.save(); - this.context.stdout.write(chalk.green(`✓ Hook enabled: ${hook.id.slice(0, 8)}\n`)); + this.context.stdout.write(colors.success(`✓ Hook enabled: ${hook.id.slice(0, 8)}\n`)); return 0; } private async disableHook(projectPath: string): Promise { if (!this.target) { - this.context.stderr.write(chalk.red('Hook ID required.\n')); + this.context.stderr.write(colors.error('Hook ID required.\n')); return 1; } @@ -218,14 +218,14 @@ export class HookCommand extends Command { const hook = hooks.find((h) => h.id.startsWith(this.target!)); if (!hook) { - this.context.stderr.write(chalk.red(`Hook not found: ${this.target}\n`)); + this.context.stderr.write(colors.error(`Hook not found: ${this.target}\n`)); return 1; } manager.disableHook(hook.id); manager.save(); - this.context.stdout.write(chalk.yellow(`○ Hook disabled: ${hook.id.slice(0, 8)}\n`)); + this.context.stdout.write(colors.warning(`○ Hook disabled: ${hook.id.slice(0, 8)}\n`)); return 0; } @@ -241,7 +241,7 @@ export class HookCommand extends Command { const agent = this.agent || 'claude-code'; const generated = manager.generateAgentHooks(agent as any); - this.context.stdout.write(chalk.cyan(`Generated hooks for ${agent}:\n\n`)); + this.context.stdout.write(colors.cyan(`Generated hooks for ${agent}:\n\n`)); if (typeof generated === 'string') { this.context.stdout.write(generated); @@ -255,7 +255,7 @@ export class HookCommand extends Command { private async showHookInfo(projectPath: string): Promise { if (!this.target) { - this.context.stderr.write(chalk.red('Hook ID required.\n')); + this.context.stderr.write(colors.error('Hook ID required.\n')); return 1; } @@ -264,11 +264,11 @@ export class HookCommand extends Command { const hook = hooks.find((h) => h.id.startsWith(this.target!)); if (!hook) { - this.context.stderr.write(chalk.red(`Hook not found: ${this.target}\n`)); + this.context.stderr.write(colors.error(`Hook not found: ${this.target}\n`)); return 1; } - this.context.stdout.write(chalk.cyan(`\nHook: ${hook.id}\n`)); + this.context.stdout.write(colors.cyan(`\nHook: ${hook.id}\n`)); this.context.stdout.write(`Event: ${hook.event}\n`); this.context.stdout.write(`Skills: ${hook.skills.join(', ')}\n`); this.context.stdout.write(`Enabled: ${hook.enabled ? 'Yes' : 'No'}\n`); @@ -317,7 +317,7 @@ export class HookTemplateListCommand extends Command { return 0; } - console.log(chalk.cyan(`Hook Templates (${templates.length}):\n`)); + console.log(colors.cyan(`Hook Templates (${templates.length}):\n`)); const byCategory = new Map(); for (const template of templates) { @@ -328,17 +328,17 @@ export class HookTemplateListCommand extends Command { } for (const [category, catTemplates] of byCategory) { - console.log(chalk.blue(` ${formatCategory(category)}`)); + console.log(colors.info(` ${formatCategory(category)}`)); for (const template of catTemplates) { - const blocking = template.blocking ? chalk.yellow(' [blocking]') : ''; - console.log(` ${chalk.bold(template.id)}${blocking}`); - console.log(` ${chalk.dim(template.description)}`); + const blocking = template.blocking ? colors.warning(' [blocking]') : ''; + console.log(` ${colors.bold(template.id)}${blocking}`); + console.log(` ${colors.muted(template.description)}`); console.log(` Event: ${template.event}`); } console.log(); } - console.log(chalk.dim('Apply with: skillkit hook template apply ')); + console.log(colors.muted('Apply with: skillkit hook template apply ')); return 0; } @@ -361,8 +361,8 @@ export class HookTemplateApplyCommand extends Command { const template = getHookTemplate(this.id); if (!template) { - console.log(chalk.red(`Template not found: ${this.id}`)); - console.log(chalk.dim('Run `skillkit hook template list` to see available templates')); + console.log(colors.error(`Template not found: ${this.id}`)); + console.log(colors.muted('Run `skillkit hook template list` to see available templates')); return 1; } @@ -385,12 +385,12 @@ export class HookTemplateApplyCommand extends Command { manager.save(); - console.log(chalk.green(`✓ Applied template: ${template.name}`)); + console.log(colors.success(`✓ Applied template: ${template.name}`)); console.log(` Hook ID: ${hook.id.slice(0, 8)}`); console.log(` Event: ${template.event}`); - console.log(` Command: ${chalk.dim(template.command)}`); + console.log(` Command: ${colors.muted(template.command)}`); if (template.blocking) { - console.log(chalk.yellow(` Blocking: yes`)); + console.log(colors.warning(` Blocking: yes`)); } return 0; @@ -411,11 +411,11 @@ export class HookTemplateShowCommand extends Command { const template = getHookTemplate(this.id); if (!template) { - console.log(chalk.red(`Template not found: ${this.id}`)); + console.log(colors.error(`Template not found: ${this.id}`)); return 1; } - console.log(chalk.cyan(`Template: ${template.name}\n`)); + console.log(colors.cyan(`Template: ${template.name}\n`)); console.log(`ID: ${template.id}`); console.log(`Category: ${template.category}`); console.log(`Description: ${template.description}`); @@ -426,7 +426,7 @@ export class HookTemplateShowCommand extends Command { console.log(`Blocking: ${template.blocking ? 'yes' : 'no'}`); console.log(`Timeout: ${template.timeout || 30000}ms`); console.log(); - console.log(chalk.bold('Command:')); + console.log(colors.bold('Command:')); console.log(` ${template.command}`); return 0; diff --git a/packages/cli/src/commands/install.ts b/packages/cli/src/commands/install.ts index 594477c2..7fdca2b3 100644 --- a/packages/cli/src/commands/install.ts +++ b/packages/cli/src/commands/install.ts @@ -133,512 +133,490 @@ export class InstallCommand extends Command { const s = spinner(); try { - // Show welcome logo for interactive mode if (isInteractive && !this.quiet) { welcome(); } - let providerAdapter = detectProvider(this.source); - let result: { - success: boolean; - path?: string; - tempRoot?: string; - error?: string; - skills?: string[]; - discoveredSkills?: Array<{ - name: string; - dirName: string; - path: string; - }>; - } | null = null; - - const isUrl = - this.source.startsWith("http://") || this.source.startsWith("https://"); - if (isUrl && !this.provider && !providerAdapter) { - s.start("Checking for well-known skills..."); - const wellKnown = new WellKnownProvider(); - const discovery = await wellKnown.discoverFromUrl(this.source); - if (discovery.success) { - s.stop( - `Found ${discovery.skills?.length || 0} skill(s) via well-known discovery`, - ); - providerAdapter = wellKnown; - result = discovery; - } else { - s.stop("No well-known skills found"); - warn(`No well-known skills found at ${this.source}`); - console.log( - colors.muted("You can save this URL as a skill instead:"), - ); - console.log(colors.muted(` skillkit save ${this.source}`)); - return 1; - } - } + const { providerAdapter, cloneResult } = await this.resolveSource(s); + if (!providerAdapter || !cloneResult) return 1; - if (this.provider) { - providerAdapter = getProvider(this.provider as GitProvider); - } + const discoveredSkills = cloneResult.discoveredSkills || []; - if (!providerAdapter) { - error(`Could not detect provider for: ${this.source}`); - console.log(colors.muted("Use --provider flag or specify source as:")); - console.log( - colors.muted( - " GitHub: owner/repo or https://github.com/owner/repo", - ), - ); - console.log( - colors.muted(" Skills.sh: skills.sh/owner/repo/skill-name"), - ); - console.log( - colors.muted( - " GitLab: gitlab:owner/repo or https://gitlab.com/owner/repo", - ), - ); - console.log(colors.muted(" Bitbucket: bitbucket:owner/repo")); - console.log(colors.muted(" Local: ./path or ~/path")); - return 1; + if (this.list) { + return this.listSkills(discoveredSkills, cloneResult); } - if (!result) { - s.start(`Fetching from ${providerAdapter.name}...`); - result = await providerAdapter.clone(this.source, "", { depth: 1 }); + const skillSelection = await this.selectSkills(discoveredSkills, isInteractive); + if (!skillSelection.skills) return skillSelection.exitCode; + const skillsToInstall = skillSelection.skills; - if (!result.success || !result.path) { - s.stop(colors.error(result.error || "Failed to fetch source")); - return 1; - } + const agentSelection = await this.selectAgents(isInteractive); + if (!agentSelection.agents) return agentSelection.exitCode; + const targetAgents = agentSelection.agents; + + const installMethod = await this.selectMethod(isInteractive, targetAgents); + if (!installMethod) return 0; - s.stop(`Found ${result.skills?.length || 0} skill(s)`); + await this.runSecurityScan(skillsToInstall, cloneResult); + + if (isInteractive && !this.yes) { + const confirmed = await this.confirmInstall(skillsToInstall, targetAgents); + if (!confirmed) return 0; } - const cloneResult = result!; - const discoveredSkills = cloneResult.discoveredSkills || []; + const installResults = await this.performInstall( + skillsToInstall, targetAgents, installMethod, providerAdapter, cloneResult, s, + ); - // List mode - just show skills and exit - if (this.list) { - if (discoveredSkills.length === 0) { - warn("No skills found in this repository"); - } else { - console.log(""); - console.log(colors.bold("Available skills:")); - console.log(""); - for (const skill of discoveredSkills) { - const quality = evaluateSkillDirectory(skill.path); - const qualityBadge = quality - ? ` ${formatQualityBadge(quality.overall)}` - : ""; - console.log( - ` ${colors.success(symbols.stepActive)} ${colors.primary(skill.name)}${qualityBadge}`, - ); - } - console.log(""); - console.log( - colors.muted(`Total: ${discoveredSkills.length} skill(s)`), - ); - console.log( - colors.muted("To install: skillkit install --skill=name"), - ); - } + this.cleanupTemp(cloneResult); + this.updateAgentsMd(installResults.length); + await this.showResults(installResults, targetAgents, providerAdapter, isInteractive); - const cleanupPath = cloneResult.tempRoot || cloneResult.path; - if ( - !isLocalPath(this.source) && - cleanupPath && - existsSync(cleanupPath) - ) { - rmSync(cleanupPath, { recursive: true, force: true }); - } + return 0; + } catch (err) { + s.stop(colors.error("Installation failed")); + console.log(colors.muted(err instanceof Error ? err.message : String(err))); + return 1; + } + } - return 0; + private async resolveSource(s: ReturnType): Promise<{ + providerAdapter: ReturnType | null; + cloneResult: { + success: boolean; + path?: string; + tempRoot?: string; + error?: string; + skills?: string[]; + discoveredSkills?: Array<{ name: string; dirName: string; path: string }>; + } | null; + }> { + let providerAdapter = detectProvider(this.source); + let result: { + success: boolean; + path?: string; + tempRoot?: string; + error?: string; + skills?: string[]; + discoveredSkills?: Array<{ name: string; dirName: string; path: string }>; + } | null = null; + + const isUrl = this.source.startsWith("http://") || this.source.startsWith("https://"); + if (isUrl && !this.provider && !providerAdapter) { + s.start("Checking for well-known skills..."); + const wellKnown = new WellKnownProvider(); + const discovery = await wellKnown.discoverFromUrl(this.source); + if (discovery.success) { + s.stop(`Found ${discovery.skills?.length || 0} skill(s) via well-known discovery`); + providerAdapter = wellKnown; + result = discovery; + } else { + s.stop("No well-known skills found"); + warn(`No well-known skills found at ${this.source}`); + console.log(colors.muted("You can save this URL as a skill instead:")); + console.log(colors.muted(` skillkit save ${this.source}`)); + return { providerAdapter: null, cloneResult: null }; } + } - let skillsToInstall = discoveredSkills; + if (this.provider) { + providerAdapter = getProvider(this.provider as GitProvider); + } - // Non-interactive: use --skills filter - if (this.skills) { - const requestedSkills = this.skills.split(",").map((s) => s.trim()); - const available = discoveredSkills.map((s) => s.name); - const notFound = requestedSkills.filter((s) => !available.includes(s)); + if (!providerAdapter) { + error(`Could not detect provider for: ${this.source}`); + console.log(colors.muted("Use --provider flag or specify source as:")); + console.log(colors.muted(" GitHub: owner/repo or https://github.com/owner/repo")); + console.log(colors.muted(" Skills.sh: skills.sh/owner/repo/skill-name")); + console.log(colors.muted(" GitLab: gitlab:owner/repo or https://gitlab.com/owner/repo")); + console.log(colors.muted(" Bitbucket: bitbucket:owner/repo")); + console.log(colors.muted(" Local: ./path or ~/path")); + return { providerAdapter: null, cloneResult: null }; + } - if (notFound.length > 0) { - error(`Skills not found: ${notFound.join(", ")}`); - console.log(colors.muted(`Available: ${available.join(", ")}`)); - return 1; - } + if (!result) { + s.start(`Fetching from ${providerAdapter.name}...`); + result = await providerAdapter.clone(this.source, "", { depth: 1 }); - skillsToInstall = discoveredSkills.filter((s) => - requestedSkills.includes(s.name), - ); - } else if (this.all || this.yes) { - skillsToInstall = discoveredSkills; - } else if (isInteractive && discoveredSkills.length > 1) { - step(`Source: ${colors.cyan(this.source)}`); + if (!result.success || !result.path) { + s.stop(colors.error(result.error || "Failed to fetch source")); + return { providerAdapter: null, cloneResult: null }; + } - const skillResult = await quickSkillSelect({ - skills: discoveredSkills.map((s) => ({ name: s.name })), - }); + s.stop(`Found ${result.skills?.length || 0} skill(s)`); + } - if (isCancel(skillResult)) { - cancel("Installation cancelled"); - return 0; - } + return { providerAdapter, cloneResult: result }; + } - const selected = (skillResult as { skills: string[] }).skills; - skillsToInstall = discoveredSkills.filter((s) => - selected.includes(s.name), - ); + private listSkills( + discoveredSkills: Array<{ name: string; path: string }>, + cloneResult: { tempRoot?: string; path?: string }, + ): number { + if (discoveredSkills.length === 0) { + warn("No skills found in this repository"); + } else { + console.log(""); + console.log(colors.bold("Available skills:")); + console.log(""); + for (const skill of discoveredSkills) { + const quality = evaluateSkillDirectory(skill.path); + const qualityBadge = quality ? ` ${formatQualityBadge(quality.overall)}` : ""; + console.log(` ${colors.success(symbols.stepActive)} ${colors.primary(skill.name)}${qualityBadge}`); } + console.log(""); + console.log(colors.muted(`Total: ${discoveredSkills.length} skill(s)`)); + console.log(colors.muted("To install: skillkit install --skill=name")); + } - if (skillsToInstall.length === 0) { - warn("No skills to install"); - return 0; + this.cleanupTemp(cloneResult); + return 0; + } + + private async selectSkills( + discoveredSkills: Array<{ name: string; dirName: string; path: string }>, + isInteractive: boolean, + ): Promise<{ skills: Array<{ name: string; dirName: string; path: string }> | null; exitCode: number }> { + if (this.skills) { + const requestedSkills = this.skills.split(",").map((s) => s.trim()); + const available = discoveredSkills.map((s) => s.name); + const notFound = requestedSkills.filter((s) => !available.includes(s)); + + if (notFound.length > 0) { + error(`Skills not found: ${notFound.join(", ")}`); + console.log(colors.muted(`Available: ${available.join(", ")}`)); + return { skills: null, exitCode: 1 }; } - // Determine target agents - let targetAgents: AgentType[]; + return { skills: discoveredSkills.filter((s) => requestedSkills.includes(s.name)), exitCode: 0 }; + } - if (this.agent && this.agent.length > 0) { - const allValid = getAllAdapters().map((a) => a.type); - const invalid = this.agent.filter( - (a) => !allValid.includes(a as AgentType), - ); - if (invalid.length > 0) { - error(`Unknown agent(s): ${invalid.join(", ")}`); - console.log(colors.muted(`Available: ${allValid.join(", ")}`)); - return 1; - } - targetAgents = this.agent as AgentType[]; - } else if (isInteractive) { - const allAgentTypes = getAllAdapters().map((a) => a.type); - const lastAgents = getLastAgents(); + if (this.all || this.yes) { + return { skills: discoveredSkills, exitCode: 0 }; + } - step(`Detected ${allAgentTypes.length} agents`); + if (isInteractive && discoveredSkills.length > 1) { + step(`Source: ${colors.cyan(this.source)}`); - const agentResult = await quickAgentSelect({ - message: "Install to", - agents: allAgentTypes, - lastSelected: lastAgents, - }); + const skillResult = await quickSkillSelect({ + skills: discoveredSkills.map((s) => ({ name: s.name })), + }); - if (isCancel(agentResult)) { - cancel("Installation cancelled"); - return 0; - } + if (isCancel(skillResult)) { + cancel("Installation cancelled"); + return { skills: null, exitCode: 0 }; + } - targetAgents = (agentResult as { agents: string[] }) - .agents as AgentType[]; + const selected = (skillResult as { skills: string[] }).skills; + const result = discoveredSkills.filter((s) => selected.includes(s.name)); + if (result.length === 0) { + warn("No skills to install"); + return { skills: null, exitCode: 0 }; + } + return { skills: result, exitCode: 0 }; + } - // Save selection for next time - saveLastAgents(targetAgents); - } else { - // Non-interactive: use detected agent - const detectedAgent = await detectAgent(); - targetAgents = [detectedAgent]; + return { skills: discoveredSkills, exitCode: 0 }; + } + + private async selectAgents(isInteractive: boolean): Promise<{ agents: AgentType[] | null; exitCode: number }> { + if (this.agent && this.agent.length > 0) { + const allValid = getAllAdapters().map((a) => a.type); + const invalid = this.agent.filter((a) => !allValid.includes(a as AgentType)); + if (invalid.length > 0) { + error(`Unknown agent(s): ${invalid.join(", ")}`); + console.log(colors.muted(`Available: ${allValid.join(", ")}`)); + return { agents: null, exitCode: 1 }; } + return { agents: this.agent as AgentType[], exitCode: 0 }; + } - // Interactive: select installation method - let installMethod: "symlink" | "copy" = "copy"; + if (isInteractive) { + const allAgentTypes = getAllAdapters().map((a) => a.type); + const lastAgents = getLastAgents(); - if (isInteractive && targetAgents.length > 1) { - const methodResult = await selectInstallMethod({}); + step(`Detected ${allAgentTypes.length} agents`); - if (isCancel(methodResult)) { - cancel("Installation cancelled"); - return 0; - } + const agentResult = await quickAgentSelect({ + message: "Install to", + agents: allAgentTypes, + lastSelected: lastAgents, + }); - installMethod = methodResult as "symlink" | "copy"; + if (isCancel(agentResult)) { + cancel("Installation cancelled"); + return { agents: null, exitCode: 0 }; } - // Check for low-quality skills and warn - const lowQualitySkills: Array<{ - name: string; - score: number; - warnings: string[]; - }> = []; - for (const skill of skillsToInstall) { - const quality = evaluateSkillDirectory(skill.path); - if (quality && quality.overall < 60) { - lowQualitySkills.push({ - name: skill.name, - score: quality.overall, - warnings: quality.warnings.slice(0, 2), - }); - } - } + const targetAgents = (agentResult as { agents: string[] }).agents as AgentType[]; + saveLastAgents(targetAgents); + return { agents: targetAgents, exitCode: 0 }; + } - if (this.scan) { - const scanner = new SkillScanner({ failOnSeverity: Severity.HIGH }); - for (const skill of skillsToInstall) { - const scanResult = await scanner.scan(skill.path); - - if (scanResult.verdict === "fail" && !this.force) { - error(`Security scan FAILED for "${skill.name}"`); - console.log(formatSummary(scanResult)); - console.log( - colors.muted( - "Use --force to install anyway, or --no-scan to skip scanning", - ), - ); - - const cleanupPath = cloneResult.tempRoot || cloneResult.path; - if ( - !isLocalPath(this.source) && - cleanupPath && - existsSync(cleanupPath) - ) { - rmSync(cleanupPath, { recursive: true, force: true }); - } - return 1; - } + const detectedAgent = await detectAgent(); + return { agents: [detectedAgent], exitCode: 0 }; + } - if (scanResult.verdict === "warn" && !this.quiet) { - warn( - `Security warnings for "${skill.name}" (${scanResult.stats.medium} medium, ${scanResult.stats.low} low)`, - ); - } - } + private async selectMethod( + isInteractive: boolean, + targetAgents: AgentType[], + ): Promise<"symlink" | "copy" | null> { + if (isInteractive && targetAgents.length > 1) { + const methodResult = await selectInstallMethod({}); + + if (isCancel(methodResult)) { + cancel("Installation cancelled"); + return null; } - // Confirm installation - if (isInteractive && !this.yes) { - console.log(""); + return methodResult as "symlink" | "copy"; + } + return "copy"; + } - // Show low-quality warning if any - if (lowQualitySkills.length > 0) { - console.log( - colors.warning( - `${symbols.warning} Warning: ${lowQualitySkills.length} skill(s) have low quality scores (< 60)`, - ), - ); - for (const lq of lowQualitySkills) { - const grade = getQualityGradeFromScore(lq.score); - const warningText = - lq.warnings.length > 0 ? ` - ${lq.warnings.join(", ")}` : ""; - console.log( - colors.muted(` - ${lq.name} [${grade}]${warningText}`), - ); - } - console.log(""); - } + private async runSecurityScan( + skillsToInstall: Array<{ name: string; path: string }>, + cloneResult: { tempRoot?: string; path?: string }, + ): Promise { + if (!this.scan) return; + + const scanner = new SkillScanner({ failOnSeverity: Severity.HIGH }); + for (const skill of skillsToInstall) { + const scanResult = await scanner.scan(skill.path); + + if (scanResult.verdict === "fail" && !this.force) { + error(`Security scan FAILED for "${skill.name}"`); + console.log(formatSummary(scanResult)); + console.log(colors.muted("Use --force to install anyway, or --no-scan to skip scanning")); + this.cleanupTemp(cloneResult); + throw new Error(`Security scan failed for ${skill.name}`); + } - const agentDisplay = - targetAgents.length <= 3 - ? targetAgents.map(formatAgent).join(", ") - : `${targetAgents.slice(0, 2).map(formatAgent).join(", ")} +${targetAgents.length - 2} more`; + if (scanResult.verdict === "warn" && !this.quiet) { + warn(`Security warnings for "${skill.name}" (${scanResult.stats.medium} medium, ${scanResult.stats.low} low)`); + } + } + } - const confirmResult = await confirm({ - message: `Install ${skillsToInstall.length} skill(s) to ${agentDisplay}?`, - initialValue: true, - }); + private async confirmInstall( + skillsToInstall: Array<{ name: string; path: string }>, + targetAgents: AgentType[], + ): Promise { + console.log(""); - if (isCancel(confirmResult) || !confirmResult) { - cancel("Installation cancelled"); - return 0; - } + const lowQualitySkills = skillsToInstall + .map((skill) => { + const quality = evaluateSkillDirectory(skill.path); + return quality && quality.overall < 60 + ? { name: skill.name, score: quality.overall, warnings: quality.warnings.slice(0, 2) } + : null; + }) + .filter(Boolean) as Array<{ name: string; score: number; warnings: string[] }>; + + if (lowQualitySkills.length > 0) { + console.log( + colors.warning(`${symbols.warning} Warning: ${lowQualitySkills.length} skill(s) have low quality scores (< 60)`), + ); + for (const lq of lowQualitySkills) { + const grade = getQualityGradeFromScore(lq.score); + const warningText = lq.warnings.length > 0 ? ` - ${lq.warnings.join(", ")}` : ""; + console.log(colors.muted(` - ${lq.name} [${grade}]${warningText}`)); } + console.log(""); + } - // Perform installation - let totalInstalled = 0; - const installResults: InstallResult[] = []; + const agentDisplay = + targetAgents.length <= 3 + ? targetAgents.map(formatAgent).join(", ") + : `${targetAgents.slice(0, 2).map(formatAgent).join(", ")} +${targetAgents.length - 2} more`; - for (const skill of skillsToInstall) { - const skillName = skill.name; - const sourcePath = skill.path; - const installedAgents: string[] = []; - let primaryPath: string | null = null; + const confirmResult = await confirm({ + message: `Install ${skillsToInstall.length} skill(s) to ${agentDisplay}?`, + initialValue: true, + }); - for (const agentType of targetAgents) { - const adapter = getAdapter(agentType); - const installDir = getInstallDir(this.global, agentType); + if (isCancel(confirmResult) || !confirmResult) { + cancel("Installation cancelled"); + return false; + } + return true; + } - if (!existsSync(installDir)) { - mkdirSync(installDir, { recursive: true }); - } + private async performInstall( + skillsToInstall: Array<{ name: string; dirName: string; path: string }>, + targetAgents: AgentType[], + installMethod: "symlink" | "copy", + providerAdapter: ReturnType, + cloneResult: { tempRoot?: string; path?: string }, + s: ReturnType, + ): Promise { + const installResults: InstallResult[] = []; + + for (const skill of skillsToInstall) { + const skillName = skill.name; + const sourcePath = skill.path; + const installedAgents: string[] = []; + let primaryPath: string | null = null; + + for (const agentType of targetAgents) { + const adapter = getAdapter(agentType); + const installDir = getInstallDir(this.global, agentType); + + if (!existsSync(installDir)) { + mkdirSync(installDir, { recursive: true }); + } - const targetPath = join(installDir, skillName); + const targetPath = join(installDir, skillName); - if (existsSync(targetPath) && !this.force) { - if (!this.quiet) { - warn( - `Skipping ${skillName} for ${adapter.name} (already exists, use --force)`, - ); - } - continue; + if (existsSync(targetPath) && !this.force) { + if (!this.quiet) { + warn(`Skipping ${skillName} for ${adapter.name} (already exists, use --force)`); } + continue; + } - const securityRoot = cloneResult.tempRoot || cloneResult.path || ""; - if (!securityRoot || !isPathInside(sourcePath, securityRoot)) { - error(`Skipping ${skillName} (path traversal detected)`); - continue; - } + const securityRoot = cloneResult.tempRoot || cloneResult.path || ""; + if (!securityRoot || !isPathInside(sourcePath, securityRoot)) { + error(`Skipping ${skillName} (path traversal detected)`); + continue; + } - const isSymlinkMode = - installMethod === "symlink" && targetAgents.length > 1; - const useSymlink = isSymlinkMode && primaryPath !== null; + const isSymlinkMode = installMethod === "symlink" && targetAgents.length > 1; + const useSymlink = isSymlinkMode && primaryPath !== null; - s.start( - `Installing ${skillName} to ${adapter.name}${useSymlink ? " (symlink)" : ""}...`, - ); + s.start(`Installing ${skillName} to ${adapter.name}${useSymlink ? " (symlink)" : ""}...`); - try { - if (existsSync(targetPath)) { - rmSync(targetPath, { recursive: true, force: true }); - } + try { + if (existsSync(targetPath)) { + rmSync(targetPath, { recursive: true, force: true }); + } - if (useSymlink && primaryPath) { - symlinkSync(primaryPath, targetPath, "dir"); - } else { - cpSync(sourcePath, targetPath, { - recursive: true, - dereference: true, - }); - if (isSymlinkMode && primaryPath === null) { - primaryPath = targetPath; - } + if (useSymlink && primaryPath) { + symlinkSync(primaryPath, targetPath, "dir"); + } else { + cpSync(sourcePath, targetPath, { recursive: true, dereference: true }); + if (isSymlinkMode && primaryPath === null) { + primaryPath = targetPath; + } - // Auto-install npm dependencies if package.json exists - const packageJsonPath = join(targetPath, "package.json"); - if (existsSync(packageJsonPath)) { - s.stop(`Installed ${skillName} to ${adapter.name}`); - s.start(`Installing npm dependencies for ${skillName}...`); - try { - await execFileAsync("npm", ["install", "--omit=dev"], { - cwd: targetPath, - }); - s.stop(`Installed dependencies for ${skillName}`); - } catch (npmErr) { - s.stop( - colors.warning(`Dependencies failed for ${skillName}`), - ); - console.log( - colors.muted("Run manually: npm install in " + targetPath), - ); - } - s.start(`Finishing ${skillName} installation...`); + const packageJsonPath = join(targetPath, "package.json"); + if (existsSync(packageJsonPath)) { + s.stop(`Installed ${skillName} to ${adapter.name}`); + s.start(`Installing npm dependencies for ${skillName}...`); + try { + await execFileAsync("npm", ["install", "--omit=dev"], { cwd: targetPath }); + s.stop(`Installed dependencies for ${skillName}`); + } catch { + s.stop(colors.warning(`Dependencies failed for ${skillName}`)); + console.log(colors.muted("Run manually: npm install in " + targetPath)); } + s.start(`Finishing ${skillName} installation...`); } - - const metadata: SkillMetadata = { - name: skillName, - description: "", - source: this.source, - sourceType: providerAdapter.type, - subpath: skillName, - installedAt: new Date().toISOString(), - enabled: true, - }; - saveSkillMetadata(targetPath, metadata); - - installedAgents.push(agentType); - s.stop( - `Installed ${skillName} to ${adapter.name}${useSymlink ? " (symlink)" : ""}`, - ); - } catch (err) { - s.stop( - colors.error(`Failed to install ${skillName} to ${adapter.name}`), - ); - console.log( - colors.muted(err instanceof Error ? err.message : String(err)), - ); } - } - if (installedAgents.length > 0) { - totalInstalled++; - installResults.push({ - skillName, - method: installMethod, - agents: installedAgents, - path: join( - getInstallDir(this.global, installedAgents[0] as AgentType), - skillName, - ), - }); + const metadata: SkillMetadata = { + name: skillName, + description: "", + source: this.source, + sourceType: providerAdapter!.type, + subpath: skillName, + installedAt: new Date().toISOString(), + enabled: true, + }; + saveSkillMetadata(targetPath, metadata); + + installedAgents.push(agentType); + s.stop(`Installed ${skillName} to ${adapter.name}${useSymlink ? " (symlink)" : ""}`); + } catch (err) { + s.stop(colors.error(`Failed to install ${skillName} to ${adapter.name}`)); + console.log(colors.muted(err instanceof Error ? err.message : String(err))); } } - // Cleanup temp directory - const cleanupPath = cloneResult.tempRoot || cloneResult.path; - if (!isLocalPath(this.source) && cleanupPath && existsSync(cleanupPath)) { - rmSync(cleanupPath, { recursive: true, force: true }); + if (installedAgents.length > 0) { + installResults.push({ + skillName, + method: installMethod, + agents: installedAgents, + path: join(getInstallDir(this.global, installedAgents[0] as AgentType), skillName), + }); } + } - if (totalInstalled > 0) { - try { - const agentsMdPath = join(process.cwd(), "AGENTS.md"); - if (existsSync(agentsMdPath)) { - const parser = new AgentsMdParser(); - const existing = readFileSync(agentsMdPath, "utf-8"); - if (parser.hasManagedSections(existing)) { - const gen = new AgentsMdGenerator({ projectPath: process.cwd() }); - const genResult = gen.generate(); - const updated = parser.updateManagedSections( - existing, - genResult.sections.filter((s) => s.managed), - ); - writeFileSync(agentsMdPath, updated, "utf-8"); - } - } - } catch { - warn("Failed to update AGENTS.md"); - } - } + return installResults; + } - // Show summary - if (totalInstalled > 0) { - if (isInteractive) { - showInstallSummary({ - totalSkills: totalInstalled, - totalAgents: targetAgents.length, - results: installResults, - source: this.source, - }); + private cleanupTemp(cloneResult: { tempRoot?: string; path?: string }): void { + const cleanupPath = cloneResult.tempRoot || cloneResult.path; + if (!isLocalPath(this.source) && cleanupPath && existsSync(cleanupPath)) { + rmSync(cleanupPath, { recursive: true, force: true }); + } + } - await this.showSkillsShStats( - installResults.map((r) => r.skillName), - providerAdapter, + private updateAgentsMd(totalInstalled: number): void { + if (totalInstalled === 0) return; + try { + const agentsMdPath = join(process.cwd(), "AGENTS.md"); + if (existsSync(agentsMdPath)) { + const parser = new AgentsMdParser(); + const existing = readFileSync(agentsMdPath, "utf-8"); + if (parser.hasManagedSections(existing)) { + const gen = new AgentsMdGenerator({ projectPath: process.cwd() }); + const genResult = gen.generate(); + const updated = parser.updateManagedSections( + existing, + genResult.sections.filter((s) => s.managed), ); + writeFileSync(agentsMdPath, updated, "utf-8"); + } + } + } catch { + warn("Failed to update AGENTS.md"); + } + } - outro("Installation complete!"); + private async showResults( + installResults: InstallResult[], + targetAgents: AgentType[], + providerAdapter: ReturnType, + isInteractive: boolean, + ): Promise { + if (installResults.length > 0) { + if (isInteractive) { + showInstallSummary({ + totalSkills: installResults.length, + totalAgents: targetAgents.length, + results: installResults, + source: this.source, + }); - if (!this.yes) { - showNextSteps({ - skillNames: installResults.map((r) => r.skillName), - agentTypes: targetAgents, - syncNeeded: true, - }); + await this.showSkillsShStats( + installResults.map((r) => r.skillName), + providerAdapter!, + ); - this.showProTips(installResults.map((r) => r.skillName)); - } - } else { - success( - `Installed ${totalInstalled} skill(s) to ${targetAgents.length} agent(s)`, - ); - for (const r of installResults) { - console.log( - colors.muted( - ` ${symbols.success} ${r.skillName} ${symbols.arrowRight} ${r.agents.map(getAgentIcon).join(" ")}`, - ), - ); - } - console.log(""); + outro("Installation complete!"); + + if (!this.yes) { + showNextSteps({ + skillNames: installResults.map((r) => r.skillName), + agentTypes: targetAgents, + syncNeeded: true, + }); + this.showProTips(installResults.map((r) => r.skillName)); + } + } else { + success(`Installed ${installResults.length} skill(s) to ${targetAgents.length} agent(s)`); + for (const r of installResults) { console.log( - colors.muted("Run `skillkit sync` to update agent configs"), + colors.muted(` ${symbols.success} ${r.skillName} ${symbols.arrowRight} ${r.agents.map(getAgentIcon).join(" ")}`), ); } - } else { - warn("No skills were installed"); + console.log(""); + console.log(colors.muted("Run `skillkit sync` to update agent configs")); } - - return 0; - } catch (err) { - s.stop(colors.error("Installation failed")); - console.log( - colors.muted(err instanceof Error ? err.message : String(err)), - ); - return 1; + } else { + warn("No skills were installed"); } } diff --git a/packages/cli/src/commands/learn.ts b/packages/cli/src/commands/learn.ts index b99baef0..132908d0 100644 --- a/packages/cli/src/commands/learn.ts +++ b/packages/cli/src/commands/learn.ts @@ -1,6 +1,6 @@ import { Command, Option } from 'clipanion'; import { resolve } from 'node:path'; -import chalk from 'chalk'; +import { colors } from '../onboarding/index.js'; import { analyzeGitHistory, loadPatternStore, @@ -73,7 +73,7 @@ export class LearnCommand extends Command { return this.showPatterns(); } - console.log(chalk.cyan('Analyzing git history for learnable patterns...\n')); + console.log(colors.cyan('Analyzing git history for learnable patterns...\n')); const result = analyzeGitHistory(projectPath, { commits: this.commits ? parseInt(this.commits) : 100, @@ -88,8 +88,8 @@ export class LearnCommand extends Command { this.printAnalysisResult(result); if (result.patterns.length === 0) { - console.log(chalk.yellow('\nNo learnable patterns found.')); - console.log(chalk.dim('Try analyzing more commits or a different date range.')); + console.log(colors.warning('\nNo learnable patterns found.')); + console.log(colors.muted('Try analyzing more commits or a different date range.')); return 0; } @@ -105,12 +105,12 @@ export class LearnCommand extends Command { } console.log(); - console.log(chalk.green(`✓ Extracted ${added} patterns`)); + console.log(colors.success(`✓ Extracted ${added} patterns`)); if (!this.approve) { - console.log(chalk.dim('Patterns are pending approval. Use:')); - console.log(chalk.dim(' skillkit pattern approve - to approve')); - console.log(chalk.dim(' skillkit pattern status - to view all')); + console.log(colors.muted('Patterns are pending approval. Use:')); + console.log(colors.muted(' skillkit pattern approve - to approve')); + console.log(colors.muted(' skillkit pattern status - to view all')); } return 0; @@ -119,21 +119,21 @@ export class LearnCommand extends Command { private printAnalysisResult(result: GitAnalysisResult): void { const { summary, dateRange, languages, frameworks } = result; - console.log(chalk.bold('Analysis Summary')); + console.log(colors.bold('Analysis Summary')); console.log(` Commits analyzed: ${summary.totalCommits}`); console.log(` Files changed: ${summary.totalFilesChanged}`); console.log(` Date range: ${dateRange.from || 'N/A'} to ${dateRange.to || 'N/A'}`); console.log(); if (languages.length > 0) { - console.log(` Languages: ${chalk.cyan(languages.join(', '))}`); + console.log(` Languages: ${colors.cyan(languages.join(', '))}`); } if (frameworks.length > 0) { - console.log(` Frameworks: ${chalk.cyan(frameworks.join(', '))}`); + console.log(` Frameworks: ${colors.cyan(frameworks.join(', '))}`); } console.log(); - console.log(chalk.bold('Commit Categories')); + console.log(colors.bold('Commit Categories')); console.log(` Error fixes: ${summary.errorFixes}`); console.log(` Refactors: ${summary.refactors}`); console.log(` Features: ${summary.features}`); @@ -141,15 +141,15 @@ export class LearnCommand extends Command { console.log(` Tests: ${summary.tests}`); console.log(); - console.log(chalk.bold(`Patterns Extracted: ${result.patterns.length}`)); + console.log(colors.bold(`Patterns Extracted: ${result.patterns.length}`)); for (const pattern of result.patterns.slice(0, 5)) { - const confidence = chalk.blue(`${(pattern.confidence * 100).toFixed(0)}%`); - console.log(` ${chalk.dim('○')} ${pattern.title} [${pattern.category}] ${confidence}`); + const confidence = colors.info(`${(pattern.confidence * 100).toFixed(0)}%`); + console.log(` ${colors.muted('○')} ${pattern.title} [${pattern.category}] ${confidence}`); } if (result.patterns.length > 5) { - console.log(chalk.dim(` ... and ${result.patterns.length - 5} more`)); + console.log(colors.muted(` ... and ${result.patterns.length - 5} more`)); } } @@ -162,18 +162,18 @@ export class LearnCommand extends Command { } if (patterns.length === 0) { - console.log(chalk.yellow('No patterns stored yet.')); - console.log(chalk.dim('Run `skillkit learn` to extract patterns from git history.')); + console.log(colors.warning('No patterns stored yet.')); + console.log(colors.muted('Run `skillkit learn` to extract patterns from git history.')); return 0; } - console.log(chalk.cyan(`Stored Patterns (${patterns.length}):\n`)); + console.log(colors.cyan(`Stored Patterns (${patterns.length}):\n`)); const approved = patterns.filter(p => p.approved); const pending = patterns.filter(p => !p.approved); if (approved.length > 0) { - console.log(chalk.green('Approved:')); + console.log(colors.success('Approved:')); for (const pattern of approved) { this.printPattern(pattern); } @@ -181,7 +181,7 @@ export class LearnCommand extends Command { } if (pending.length > 0) { - console.log(chalk.yellow('Pending Approval:')); + console.log(colors.warning('Pending Approval:')); for (const pattern of pending) { this.printPattern(pattern); } @@ -191,11 +191,11 @@ export class LearnCommand extends Command { } private printPattern(pattern: LearnedPattern): void { - const confidence = chalk.blue(`${(pattern.confidence * 100).toFixed(0)}%`); - const status = pattern.approved ? chalk.green('✓') : chalk.yellow('○'); - console.log(` ${status} ${chalk.bold(pattern.id)}`); + const confidence = colors.info(`${(pattern.confidence * 100).toFixed(0)}%`); + const status = pattern.approved ? colors.success('✓') : colors.warning('○'); + console.log(` ${status} ${colors.bold(pattern.id)}`); console.log(` ${pattern.title} [${pattern.category}] ${confidence}`); - console.log(chalk.dim(` ${truncate(pattern.problem, 60)}`)); + console.log(colors.muted(` ${truncate(pattern.problem, 60)}`)); } } @@ -226,18 +226,18 @@ export class PatternStatusCommand extends Command { return 0; } - console.log(chalk.cyan('Pattern Statistics\n')); + console.log(colors.cyan('Pattern Statistics\n')); console.log(`Total patterns: ${stats.total}`); console.log(); - console.log(chalk.bold('By Confidence:')); + console.log(colors.bold('By Confidence:')); console.log(` High (>70%): ${stats.byConfidenceRange.high}`); console.log(` Medium (40-70%): ${stats.byConfidenceRange.medium}`); console.log(` Low (<40%): ${stats.byConfidenceRange.low}`); console.log(); if (stats.byDomain.size > 0) { - console.log(chalk.bold('By Domain:')); + console.log(colors.bold('By Domain:')); for (const [domain, count] of stats.byDomain) { console.log(` ${domain}: ${count}`); } @@ -245,7 +245,7 @@ export class PatternStatusCommand extends Command { } if (stats.mostUsed) { - console.log(chalk.bold('Most Used Pattern:')); + console.log(colors.bold('Most Used Pattern:')); console.log(` ${stats.mostUsed.title} (${stats.mostUsed.useCount} uses)`); } @@ -276,7 +276,7 @@ export class PatternFeedbackCommand extends Command { async execute(): Promise { if (!this.success && !this.failure) { - console.log(chalk.yellow('Please specify --success or --failure')); + console.log(colors.warning('Please specify --success or --failure')); return 1; } @@ -285,12 +285,12 @@ export class PatternFeedbackCommand extends Command { : recordFailure(this.id); if (!result) { - console.log(chalk.red(`Pattern not found: ${this.id}`)); + console.log(colors.error(`Pattern not found: ${this.id}`)); return 1; } - const change = result.change === 'increased' ? chalk.green('↑') : chalk.red('↓'); - console.log(chalk.green(`✓ Feedback recorded`)); + const change = result.change === 'increased' ? colors.success('↑') : colors.error('↓'); + console.log(colors.success(`✓ Feedback recorded`)); console.log(` Confidence: ${(result.previousConfidence * 100).toFixed(0)}% ${change} ${(result.newConfidence * 100).toFixed(0)}%`); console.log(` Total uses: ${result.pattern.useCount}`); @@ -312,11 +312,11 @@ export class PatternApproveCommand extends Command { const pattern = approvePattern(this.id); if (!pattern) { - console.log(chalk.red(`Pattern not found: ${this.id}`)); + console.log(colors.error(`Pattern not found: ${this.id}`)); return 1; } - console.log(chalk.green(`✓ Approved: ${pattern.title}`)); + console.log(colors.success(`✓ Approved: ${pattern.title}`)); return 0; } } @@ -335,11 +335,11 @@ export class PatternRejectCommand extends Command { const removed = rejectPattern(this.id); if (!removed) { - console.log(chalk.red(`Pattern not found: ${this.id}`)); + console.log(colors.error(`Pattern not found: ${this.id}`)); return 1; } - console.log(chalk.green(`✓ Removed pattern: ${this.id}`)); + console.log(colors.success(`✓ Removed pattern: ${this.id}`)); return 0; } } @@ -371,7 +371,7 @@ export class PatternExportCommand extends Command { const patterns = this.approvedOnly ? getApprovedPatterns() : getAllPatterns(); if (patterns.length === 0) { - console.log(chalk.yellow('No patterns to export')); + console.log(colors.warning('No patterns to export')); return 0; } @@ -386,7 +386,7 @@ export class PatternExportCommand extends Command { if (this.output) { writeFileSync(this.output, content); - console.log(chalk.green(`✓ Exported ${patterns.length} patterns to ${this.output}`)); + console.log(colors.success(`✓ Exported ${patterns.length} patterns to ${this.output}`)); } else { console.log(content); } @@ -407,7 +407,7 @@ export class PatternImportCommand extends Command { async execute(): Promise { if (!existsSync(this.file)) { - console.log(chalk.red(`File not found: ${this.file}`)); + console.log(colors.error(`File not found: ${this.file}`)); return 1; } @@ -415,7 +415,7 @@ export class PatternImportCommand extends Command { const patterns = importPatternsFromJson(content); if (patterns.length === 0) { - console.log(chalk.yellow('No patterns found in file')); + console.log(colors.warning('No patterns found in file')); return 1; } @@ -423,7 +423,7 @@ export class PatternImportCommand extends Command { addPattern(pattern); } - console.log(chalk.green(`✓ Imported ${patterns.length} patterns`)); + console.log(colors.success(`✓ Imported ${patterns.length} patterns`)); return 0; } } @@ -451,21 +451,21 @@ export class PatternClusterCommand extends Command { const patterns = getApprovedPatterns(); if (patterns.length === 0) { - console.log(chalk.yellow('No approved patterns to cluster')); - console.log(chalk.dim('Approve patterns first with: skillkit pattern approve ')); + console.log(colors.warning('No approved patterns to cluster')); + console.log(colors.muted('Approve patterns first with: skillkit pattern approve ')); return 0; } const clusters = clusterPatterns(patterns); - console.log(chalk.cyan(`Pattern Clusters (${clusters.size}):\n`)); + console.log(colors.cyan(`Pattern Clusters (${clusters.size}):\n`)); for (const [category, categoryPatterns] of clusters) { - console.log(chalk.bold(`${category.replace('_', ' ')} (${categoryPatterns.length}):`)); + console.log(colors.bold(`${category.replace('_', ' ')} (${categoryPatterns.length}):`)); for (const pattern of categoryPatterns) { - const confidence = chalk.blue(`${(pattern.confidence * 100).toFixed(0)}%`); - console.log(` ${chalk.dim('○')} ${pattern.title} ${confidence}`); + const confidence = colors.info(`${(pattern.confidence * 100).toFixed(0)}%`); + console.log(` ${colors.muted('○')} ${pattern.title} ${confidence}`); } console.log(); @@ -476,7 +476,7 @@ export class PatternClusterCommand extends Command { if (skill) { const filepath = saveGeneratedSkill(skill); - console.log(chalk.green(` → Generated skill: ${filepath}`)); + console.log(colors.success(` → Generated skill: ${filepath}`)); } } } diff --git a/packages/cli/src/commands/lineage.ts b/packages/cli/src/commands/lineage.ts index cdf191d7..2c8826a2 100644 --- a/packages/cli/src/commands/lineage.ts +++ b/packages/cli/src/commands/lineage.ts @@ -1,5 +1,5 @@ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors } from '../onboarding/index.js'; import { SkillLineage } from '@skillkit/core'; export class LineageCommand extends Command { @@ -55,8 +55,8 @@ export class LineageCommand extends Command { } if (data.skills.length === 0) { - console.log(chalk.yellow('No skill lineage found.')); - console.log(chalk.dim('Lineage is built from skill execution history and activity logs.')); + console.log(colors.warning('No skill lineage found.')); + console.log(colors.muted('Lineage is built from skill execution history and activity logs.')); return 0; } diff --git a/packages/cli/src/commands/memory.ts b/packages/cli/src/commands/memory.ts index 0da6fca5..b499712e 100644 --- a/packages/cli/src/commands/memory.ts +++ b/packages/cli/src/commands/memory.ts @@ -1,5 +1,5 @@ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors, error, warn } from '../onboarding/index.js'; import { ObservationStore, LearningStore, @@ -158,8 +158,8 @@ export class MemoryCommand extends Command { case 'index': return this.showIndex(); default: - console.error(chalk.red(`Unknown action: ${action}`)); - console.log(chalk.gray('Available actions: status, search, list, show, compress, export, import, clear, add, rate, sync-claude, index, config')); + error(`Unknown action: ${action}`); + console.log(colors.muted('Available actions: status, search, list, show, compress, export, import, clear, add, rate, sync-claude, index, config')); return 1; } } @@ -194,32 +194,32 @@ export class MemoryCommand extends Command { return 0; } - console.log(chalk.bold('\nMemory Status\n')); + console.log(colors.bold('\nMemory Status\n')); // Session observations - console.log(chalk.cyan('Session:')); + console.log(colors.cyan('Session:')); console.log(` Observations: ${sessionObservations}`); if (sessionId) { - console.log(` Session ID: ${chalk.gray(sessionId.slice(0, 8))}`); + console.log(` Session ID: ${colors.muted(sessionId.slice(0, 8))}`); } console.log(); // Project learnings - console.log(chalk.cyan('Project:')); + console.log(colors.cyan('Project:')); console.log(` Learnings: ${projectLearnings}`); - console.log(` Path: ${chalk.gray(status.projectMemoryExists ? paths.projectMemoryDir : 'Not initialized')}`); + console.log(` Path: ${colors.muted(status.projectMemoryExists ? paths.projectMemoryDir : 'Not initialized')}`); console.log(); // Global learnings - console.log(chalk.cyan('Global:')); + console.log(colors.cyan('Global:')); console.log(` Learnings: ${globalLearnings}`); - console.log(` Path: ${chalk.gray(status.globalMemoryExists ? paths.globalMemoryDir : 'Not initialized')}`); + console.log(` Path: ${colors.muted(status.globalMemoryExists ? paths.globalMemoryDir : 'Not initialized')}`); console.log(); // Recommendations if (sessionObservations >= 50) { - console.log(chalk.yellow('💡 You have many uncompressed observations. Consider running:')); - console.log(chalk.gray(' skillkit memory compress')); + warn('💡 You have many uncompressed observations. Consider running:'); + console.log(colors.muted(' skillkit memory compress')); } return 0; @@ -231,8 +231,8 @@ export class MemoryCommand extends Command { private async searchMemories(): Promise { const query = this.arg; if (!query) { - console.error(chalk.red('Error: Search query is required')); - console.log(chalk.gray('Usage: skillkit memory search "your query"')); + error('Error: Search query is required'); + console.log(colors.muted('Usage: skillkit memory search "your query"')); return 1; } @@ -244,7 +244,7 @@ export class MemoryCommand extends Command { if (this.limit) { const parsed = parseInt(this.limit, 10); if (isNaN(parsed) || parsed <= 0) { - console.log(chalk.red('Invalid --limit value. Must be a positive number.')); + console.log(colors.error('Invalid --limit value. Must be a positive number.')); return 1; } maxLearnings = parsed; @@ -269,15 +269,15 @@ export class MemoryCommand extends Command { } if (results.length === 0) { - console.log(chalk.yellow(`No memories found for: "${query}"`)); + console.log(colors.warning(`No memories found for: "${query}"`)); return 0; } - console.log(chalk.bold(`\nFound ${results.length} memories:\n`)); + console.log(colors.bold(`\nFound ${results.length} memories:\n`)); for (const { learning, relevanceScore, matchedBy } of results) { - console.log(`${chalk.cyan('●')} ${chalk.bold(learning.title)}`); - console.log(` ID: ${chalk.gray(learning.id.slice(0, 8))}`); + console.log(`${colors.cyan('●')} ${colors.bold(learning.title)}`); + console.log(` ID: ${colors.muted(learning.id.slice(0, 8))}`); console.log(` Relevance: ${this.formatScore(relevanceScore)}%`); console.log(` Tags: ${learning.tags.join(', ')}`); console.log(` Scope: ${learning.scope}`); @@ -290,7 +290,7 @@ export class MemoryCommand extends Command { // Show excerpt const excerpt = learning.content.slice(0, 100); - console.log(` ${chalk.gray(excerpt)}${learning.content.length > 100 ? '...' : ''}`); + console.log(` ${colors.muted(excerpt)}${learning.content.length > 100 ? '...' : ''}`); console.log(); } @@ -327,12 +327,12 @@ export class MemoryCommand extends Command { } const scope = this.global ? 'Global' : 'Project'; - console.log(chalk.bold(`\n${scope} Learnings (${learnings.length}):\n`)); + console.log(colors.bold(`\n${scope} Learnings (${learnings.length}):\n`)); if (learnings.length === 0) { - console.log(chalk.gray('No learnings found.')); - console.log(chalk.gray('\nCapture learnings by running skills or add manually:')); - console.log(chalk.gray(' skillkit memory add --title "..." --content "..."')); + console.log(colors.muted('No learnings found.')); + console.log(colors.muted('\nCapture learnings by running skills or add manually:')); + console.log(colors.muted(' skillkit memory add --title "..." --content "..."')); return 0; } @@ -341,12 +341,12 @@ export class MemoryCommand extends Command { ? ` [${this.formatScore(learning.effectiveness)}%]` : ''; - console.log(`${chalk.cyan('●')} ${learning.title}${chalk.green(effectiveness)}`); - console.log(` ${chalk.gray(learning.id.slice(0, 8))} | ${learning.tags.join(', ')} | ${learning.useCount} uses`); + console.log(`${colors.cyan('●')} ${learning.title}${colors.success(effectiveness)}`); + console.log(` ${colors.muted(learning.id.slice(0, 8))} | ${learning.tags.join(', ')} | ${learning.useCount} uses`); if (this.verbose) { const excerpt = learning.content.slice(0, 80); - console.log(` ${chalk.gray(excerpt)}${learning.content.length > 80 ? '...' : ''}`); + console.log(` ${colors.muted(excerpt)}${learning.content.length > 80 ? '...' : ''}`); } } @@ -360,8 +360,8 @@ export class MemoryCommand extends Command { private async showLearning(): Promise { const id = this.arg; if (!id) { - console.error(chalk.red('Error: Learning ID is required')); - console.log(chalk.gray('Usage: skillkit memory show ')); + error('Error: Learning ID is required'); + console.log(colors.muted('Usage: skillkit memory show ')); return 1; } @@ -385,7 +385,7 @@ export class MemoryCommand extends Command { } if (!learning) { - console.error(chalk.red(`Learning not found: ${id}`)); + error(`Learning not found: ${id}`); return 1; } @@ -394,29 +394,29 @@ export class MemoryCommand extends Command { return 0; } - console.log(chalk.bold(`\n${learning.title}\n`)); - console.log(chalk.gray(`ID: ${learning.id}`)); - console.log(chalk.gray(`Scope: ${learning.scope}`)); - console.log(chalk.gray(`Source: ${learning.source}`)); - console.log(chalk.gray(`Tags: ${learning.tags.join(', ')}`)); + console.log(colors.bold(`\n${learning.title}\n`)); + console.log(colors.muted(`ID: ${learning.id}`)); + console.log(colors.muted(`Scope: ${learning.scope}`)); + console.log(colors.muted(`Source: ${learning.source}`)); + console.log(colors.muted(`Tags: ${learning.tags.join(', ')}`)); if (learning.frameworks?.length) { - console.log(chalk.gray(`Frameworks: ${learning.frameworks.join(', ')}`)); + console.log(colors.muted(`Frameworks: ${learning.frameworks.join(', ')}`)); } if (learning.patterns?.length) { - console.log(chalk.gray(`Patterns: ${learning.patterns.join(', ')}`)); + console.log(colors.muted(`Patterns: ${learning.patterns.join(', ')}`)); } - console.log(chalk.gray(`Created: ${new Date(learning.createdAt).toLocaleString()}`)); - console.log(chalk.gray(`Updated: ${new Date(learning.updatedAt).toLocaleString()}`)); - console.log(chalk.gray(`Uses: ${learning.useCount}`)); + console.log(colors.muted(`Created: ${new Date(learning.createdAt).toLocaleString()}`)); + console.log(colors.muted(`Updated: ${new Date(learning.updatedAt).toLocaleString()}`)); + console.log(colors.muted(`Uses: ${learning.useCount}`)); if (learning.effectiveness !== undefined) { - console.log(chalk.gray(`Effectiveness: ${this.formatScore(learning.effectiveness)}%`)); + console.log(colors.muted(`Effectiveness: ${this.formatScore(learning.effectiveness)}%`)); } - console.log(chalk.bold('\nContent:\n')); + console.log(colors.bold('\nContent:\n')); console.log(learning.content); console.log(); @@ -436,11 +436,11 @@ export class MemoryCommand extends Command { const observations = observationStore.getAll(); if (observations.length === 0) { - console.log(chalk.yellow('No observations to compress.')); + console.log(colors.warning('No observations to compress.')); return 0; } - console.log(chalk.cyan(`Found ${observations.length} observations to compress...\n`)); + console.log(colors.cyan(`Found ${observations.length} observations to compress...\n`)); const compressor = createMemoryCompressor(projectPath); const compressionOptions = { @@ -449,17 +449,17 @@ export class MemoryCommand extends Command { }; if (this.dryRun) { - console.log(chalk.gray('(Dry run - no changes will be made)\n')); + console.log(colors.muted('(Dry run - no changes will be made)\n')); // For dry-run, only compress without storing const result = await compressor.compress(observations, compressionOptions); - console.log(chalk.green(`✓ Would compress ${result.stats.inputCount} observations into ${result.stats.outputCount} learnings\n`)); + console.log(colors.success(`✓ Would compress ${result.stats.inputCount} observations into ${result.stats.outputCount} learnings\n`)); if (result.learnings.length > 0) { - console.log(chalk.bold('Learnings that would be created:')); + console.log(colors.bold('Learnings that would be created:')); for (const learning of result.learnings) { - console.log(` ${chalk.cyan('●')} ${learning.title}`); + console.log(` ${colors.cyan('●')} ${learning.title}`); console.log(` Tags: ${learning.tags.join(', ')}`); } console.log(); @@ -471,12 +471,12 @@ export class MemoryCommand extends Command { // Actual compression with storage const { learnings, result } = await compressor.compressAndStore(observations, compressionOptions); - console.log(chalk.green(`✓ Compressed ${result.stats.inputCount} observations into ${result.stats.outputCount} learnings\n`)); + console.log(colors.success(`✓ Compressed ${result.stats.inputCount} observations into ${result.stats.outputCount} learnings\n`)); if (learnings.length > 0) { - console.log(chalk.bold('New Learnings:')); + console.log(colors.bold('New Learnings:')); for (const learning of learnings) { - console.log(` ${chalk.cyan('●')} ${learning.title}`); + console.log(` ${colors.cyan('●')} ${learning.title}`); console.log(` Tags: ${learning.tags.join(', ')}`); } console.log(); @@ -485,7 +485,7 @@ export class MemoryCommand extends Command { if (result.processedObservationIds.length > 0) { // Remove processed observations const deleted = observationStore.deleteMany(result.processedObservationIds); - console.log(chalk.gray(`Cleared ${deleted} processed observations.`)); + console.log(colors.muted(`Cleared ${deleted} processed observations.`)); } return 0; @@ -497,8 +497,8 @@ export class MemoryCommand extends Command { private async exportLearning(): Promise { const id = this.arg; if (!id) { - console.error(chalk.red('Error: Learning ID is required')); - console.log(chalk.gray('Usage: skillkit memory export --name my-skill')); + error('Error: Learning ID is required'); + console.log(colors.muted('Usage: skillkit memory export --name my-skill')); return 1; } @@ -519,7 +519,7 @@ export class MemoryCommand extends Command { } if (!learning) { - console.error(chalk.red(`Learning not found: ${id}`)); + error(`Learning not found: ${id}`); return 1; } @@ -530,7 +530,7 @@ export class MemoryCommand extends Command { const skillContent = this.generateSkillContent(learning, skillName); if (this.dryRun) { - console.log(chalk.gray('(Dry run preview)\n')); + console.log(colors.muted('(Dry run preview)\n')); console.log(skillContent); return 0; } @@ -547,8 +547,8 @@ export class MemoryCommand extends Command { writeFileSync(outputPath, skillContent, 'utf-8'); - console.log(chalk.green(`✓ Exported learning as skill: ${skillName}`)); - console.log(chalk.gray(` Path: ${outputPath}`)); + console.log(colors.success(`✓ Exported learning as skill: ${skillName}`)); + console.log(colors.muted(` Path: ${outputPath}`)); return 0; } @@ -559,8 +559,8 @@ export class MemoryCommand extends Command { private async importMemories(): Promise { const inputPath = this.input || this.arg; if (!inputPath) { - console.error(chalk.red('Error: Input path is required')); - console.log(chalk.gray('Usage: skillkit memory import --input ')); + error('Error: Input path is required'); + console.log(colors.muted('Usage: skillkit memory import --input ')); return 1; } @@ -569,7 +569,7 @@ export class MemoryCommand extends Command { const fullPath = resolve(inputPath); if (!existsSync(fullPath)) { - console.error(chalk.red(`File not found: ${fullPath}`)); + error(`File not found: ${fullPath}`); return 1; } @@ -585,14 +585,14 @@ export class MemoryCommand extends Command { const data = parseYaml(content) as { learnings?: Learning[] }; if (!data.learnings || !Array.isArray(data.learnings)) { - console.error(chalk.red('Invalid memory file format')); + error('Invalid memory file format'); return 1; } let imported = 0; for (const learning of data.learnings) { if (this.dryRun) { - console.log(chalk.gray(`Would import: ${learning.title}`)); + console.log(colors.muted(`Would import: ${learning.title}`)); } else { store.add({ source: 'imported', @@ -608,14 +608,14 @@ export class MemoryCommand extends Command { } if (this.dryRun) { - console.log(chalk.gray(`\n(Dry run - ${data.learnings.length} learnings would be imported)`)); + console.log(colors.muted(`\n(Dry run - ${data.learnings.length} learnings would be imported)`)); } else { - console.log(chalk.green(`✓ Imported ${imported} learnings`)); + console.log(colors.success(`✓ Imported ${imported} learnings`)); } return 0; - } catch (error) { - console.error(chalk.red(`Import failed: ${error}`)); + } catch (err) { + error(`Import failed: ${err}`); return 1; } } @@ -630,7 +630,7 @@ export class MemoryCommand extends Command { const observationStore = new ObservationStore(projectPath); const learningStore = new LearningStore('project', projectPath); - console.log(chalk.gray('(Dry run preview)\n')); + console.log(colors.muted('(Dry run preview)\n')); console.log(`Would clear ${observationStore.count()} observations`); if (!this.keepLearnings) { console.log(`Would clear ${learningStore.count()} learnings`); @@ -640,12 +640,12 @@ export class MemoryCommand extends Command { const observationStore = new ObservationStore(projectPath); observationStore.clear(); - console.log(chalk.green('✓ Cleared session observations')); + console.log(colors.success('✓ Cleared session observations')); if (!this.keepLearnings) { const learningStore = new LearningStore('project', projectPath); learningStore.clear(); - console.log(chalk.green('✓ Cleared project learnings')); + console.log(colors.success('✓ Cleared project learnings')); } return 0; @@ -656,14 +656,14 @@ export class MemoryCommand extends Command { */ private async addLearning(): Promise { if (!this.title) { - console.error(chalk.red('Error: --title is required')); - console.log(chalk.gray('Usage: skillkit memory add --title "..." --content "..."')); + error('Error: --title is required'); + console.log(colors.muted('Usage: skillkit memory add --title "..." --content "..."')); return 1; } if (!this.content) { - console.error(chalk.red('Error: --content is required')); - console.log(chalk.gray('Usage: skillkit memory add --title "..." --content "..."')); + error('Error: --content is required'); + console.log(colors.muted('Usage: skillkit memory add --title "..." --content "..."')); return 1; } @@ -682,8 +682,8 @@ export class MemoryCommand extends Command { tags, }); - console.log(chalk.green(`✓ Added learning: ${learning.title}`)); - console.log(chalk.gray(` ID: ${learning.id}`)); + console.log(colors.success(`✓ Added learning: ${learning.title}`)); + console.log(colors.muted(` ID: ${learning.id}`)); return 0; } @@ -694,15 +694,15 @@ export class MemoryCommand extends Command { private async rateLearning(): Promise { const id = this.arg; if (!id) { - console.error(chalk.red('Error: Learning ID is required')); - console.log(chalk.gray('Usage: skillkit memory rate ')); + error('Error: Learning ID is required'); + console.log(colors.muted('Usage: skillkit memory rate ')); return 1; } const rating = parseInt(this.ratingArg || '0', 10); if (isNaN(rating) || rating < 0 || rating > 100) { - console.error(chalk.red('Error: Rating must be 0-100')); - console.log(chalk.gray('Usage: skillkit memory rate ')); + error('Error: Rating must be 0-100'); + console.log(colors.muted('Usage: skillkit memory rate ')); return 1; } @@ -737,12 +737,12 @@ export class MemoryCommand extends Command { } if (!learning) { - console.error(chalk.red(`Learning not found: ${id}`)); + error(`Learning not found: ${id}`); return 1; } store.setEffectiveness(learning.id, rating); - console.log(chalk.green(`✓ Rated "${learning.title}" as ${rating}% effective`)); + console.log(colors.success(`✓ Rated "${learning.title}" as ${rating}% effective`)); return 0; } @@ -759,14 +759,14 @@ export class MemoryCommand extends Command { return 0; } - console.log(chalk.bold('\nMemory Configuration\n')); + console.log(colors.bold('\nMemory Configuration\n')); - console.log(chalk.cyan('Paths:')); - console.log(` Project observations: ${chalk.gray(paths.observationsFile)}`); - console.log(` Project learnings: ${chalk.gray(paths.learningsFile)}`); - console.log(` Project index: ${chalk.gray(paths.indexFile)}`); - console.log(` Global learnings: ${chalk.gray(paths.globalLearningsFile)}`); - console.log(` Global index: ${chalk.gray(paths.globalIndexFile)}`); + console.log(colors.cyan('Paths:')); + console.log(` Project observations: ${colors.muted(paths.observationsFile)}`); + console.log(` Project learnings: ${colors.muted(paths.learningsFile)}`); + console.log(` Project index: ${colors.muted(paths.indexFile)}`); + console.log(` Global learnings: ${colors.muted(paths.globalLearningsFile)}`); + console.log(` Global index: ${colors.muted(paths.globalIndexFile)}`); console.log(); return 0; @@ -780,7 +780,7 @@ export class MemoryCommand extends Command { if (this.global) { if (this.dryRun) { - console.log(chalk.gray('(Dry run - previewing global CLAUDE.md sync)\n')); + console.log(colors.muted('(Dry run - previewing global CLAUDE.md sync)\n')); } const result = this.dryRun @@ -792,21 +792,21 @@ export class MemoryCommand extends Command { const learnings = globalStore.getAll() .filter((l) => (l.effectiveness ?? 0) >= 60 || l.useCount >= 3) .slice(0, 20); - console.log(chalk.cyan(`Would add ${learnings.length} learnings to global CLAUDE.md`)); + console.log(colors.cyan(`Would add ${learnings.length} learnings to global CLAUDE.md`)); for (const l of learnings.slice(0, 5)) { - console.log(` ${chalk.gray('●')} ${l.title}`); + console.log(` ${colors.muted('●')} ${l.title}`); } if (learnings.length > 5) { - console.log(chalk.gray(` ... and ${learnings.length - 5} more`)); + console.log(colors.muted(` ... and ${learnings.length - 5} more`)); } return 0; } if (result.updated) { - console.log(chalk.green(`✓ Updated global CLAUDE.md with ${result.learningsAdded} learnings`)); - console.log(chalk.gray(` Path: ${result.path}`)); + console.log(colors.success(`✓ Updated global CLAUDE.md with ${result.learningsAdded} learnings`)); + console.log(colors.muted(` Path: ${result.path}`)); } else { - console.log(chalk.yellow('No learnings to sync to global CLAUDE.md')); + console.log(colors.warning('No learnings to sync to global CLAUDE.md')); } return 0; @@ -817,28 +817,28 @@ export class MemoryCommand extends Command { if (this.dryRun) { const preview = updater.preview({ minEffectiveness: 60 }); - console.log(chalk.gray('(Dry run preview)\n')); + console.log(colors.muted('(Dry run preview)\n')); if (!preview.wouldUpdate) { - console.log(chalk.yellow('No learnings to sync to CLAUDE.md')); + console.log(colors.warning('No learnings to sync to CLAUDE.md')); return 0; } - console.log(chalk.cyan(`Would add ${preview.learnings.length} learnings to CLAUDE.md\n`)); + console.log(colors.cyan(`Would add ${preview.learnings.length} learnings to CLAUDE.md\n`)); for (const learning of preview.learnings.slice(0, 5)) { - console.log(` ${chalk.gray('●')} ${learning.title}`); + console.log(` ${colors.muted('●')} ${learning.title}`); } if (preview.learnings.length > 5) { - console.log(chalk.gray(` ... and ${preview.learnings.length - 5} more`)); + console.log(colors.muted(` ... and ${preview.learnings.length - 5} more`)); } - console.log(chalk.bold('\nFormatted section preview:')); - console.log(chalk.gray('─'.repeat(50))); + console.log(colors.bold('\nFormatted section preview:')); + console.log(colors.muted('─'.repeat(50))); console.log(preview.formattedSection.slice(0, 500)); if (preview.formattedSection.length > 500) { - console.log(chalk.gray('...')); + console.log(colors.muted('...')); } return 0; @@ -847,17 +847,17 @@ export class MemoryCommand extends Command { const result = updater.update({ minEffectiveness: 60 }); if (result.updated) { - console.log(chalk.green(`✓ Updated CLAUDE.md with ${result.learningsAdded} learnings`)); - console.log(chalk.gray(` Path: ${result.path}`)); + console.log(colors.success(`✓ Updated CLAUDE.md with ${result.learningsAdded} learnings`)); + console.log(colors.muted(` Path: ${result.path}`)); if (this.verbose && result.learningSummaries.length > 0) { - console.log(chalk.cyan('\nLearnings added:')); + console.log(colors.cyan('\nLearnings added:')); for (const title of result.learningSummaries.slice(0, 10)) { - console.log(` ${chalk.gray('●')} ${title}`); + console.log(` ${colors.muted('●')} ${title}`); } } } else { - console.log(chalk.yellow('No learnings to sync to CLAUDE.md')); + console.log(colors.warning('No learnings to sync to CLAUDE.md')); } return 0; @@ -874,7 +874,7 @@ export class MemoryCommand extends Command { if (this.limit) { const parsed = parseInt(this.limit, 10); if (isNaN(parsed) || parsed <= 0) { - console.log(chalk.red('Invalid --limit value. Must be a positive number.')); + console.log(colors.error('Invalid --limit value. Must be a positive number.')); return 1; } maxResults = parsed; @@ -887,10 +887,10 @@ export class MemoryCommand extends Command { return 0; } - console.log(chalk.bold(`\nMemory Index (${index.length} entries)\n`)); + console.log(colors.bold(`\nMemory Index (${index.length} entries)\n`)); if (index.length === 0) { - console.log(chalk.gray('No learnings found.')); + console.log(colors.muted('No learnings found.')); return 0; } @@ -901,17 +901,17 @@ export class MemoryCommand extends Command { const effectiveness = entry.effectiveness !== undefined ? ` [${this.formatScore(entry.effectiveness)}%]` : ''; - const scope = entry.scope === 'global' ? chalk.magenta('[G]') : chalk.blue('[P]'); + const scope = entry.scope === 'global' ? colors.magenta('[G]') : colors.info('[P]'); - console.log(`${scope} ${chalk.gray(entry.id.slice(0, 8))} ${entry.title}${chalk.green(effectiveness)}`); + console.log(`${scope} ${colors.muted(entry.id.slice(0, 8))} ${entry.title}${colors.success(effectiveness)}`); console.log(` Tags: ${entry.tags.join(', ')} | Uses: ${entry.useCount}`); } if (index.length > displayLimit) { - console.log(chalk.gray(`\n... and ${index.length - displayLimit} more (use --limit to show more)`)); + console.log(colors.muted(`\n... and ${index.length - displayLimit} more (use --limit to show more)`)); } - console.log(chalk.gray('\nUse "skillkit memory show " to view full details')); + console.log(colors.muted('\nUse "skillkit memory show " to view full details')); return 0; } @@ -920,9 +920,9 @@ export class MemoryCommand extends Command { * Format relevance/effectiveness score with color */ private formatScore(score: number): string { - if (score >= 80) return chalk.green(score.toString()); - if (score >= 50) return chalk.yellow(score.toString()); - return chalk.red(score.toString()); + if (score >= 80) return colors.success(score.toString()); + if (score >= 50) return colors.warning(score.toString()); + return colors.error(score.toString()); } /** diff --git a/packages/cli/src/commands/mesh.ts b/packages/cli/src/commands/mesh.ts index 3dc8fbeb..3ebd5309 100644 --- a/packages/cli/src/commands/mesh.ts +++ b/packages/cli/src/commands/mesh.ts @@ -1,5 +1,5 @@ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors, error } from '../onboarding/index.js'; export class MeshCommand extends Command { static override paths = [['mesh']]; @@ -72,8 +72,8 @@ export class MeshCommand extends Command { case 'peer': return this.handlePeer(); default: - console.error(chalk.red(`Unknown action: ${action}`)); - console.log(chalk.gray('Available actions: init, add, remove, list, health, discover, status, security, peer')); + error(`Unknown action: ${action}`); + console.log(colors.muted('Available actions: init, add, remove, list, health, discover, status, security, peer')); return 1; } } @@ -86,10 +86,10 @@ export class MeshCommand extends Command { const localConfig = hostsFile.localHost; const localIP = getLocalIPAddress(); - console.log(chalk.green('✓ Mesh network initialized\n')); + console.log(colors.success('✓ Mesh network initialized\n')); - console.log(chalk.bold('Local Host Configuration:')); - console.log(` ID: ${chalk.cyan(localConfig.id.slice(0, 8))}`); + console.log(colors.bold('Local Host Configuration:')); + console.log(` ID: ${colors.cyan(localConfig.id.slice(0, 8))}`); console.log(` Name: ${localConfig.name}`); console.log(` Address: ${localIP}:${localConfig.port}`); @@ -102,20 +102,20 @@ export class MeshCommand extends Command { } console.log(); - console.log(chalk.gray('Other hosts can connect to this machine using:')); - console.log(chalk.gray(` skillkit mesh add ${localIP}:${localConfig.port}`)); + console.log(colors.muted('Other hosts can connect to this machine using:')); + console.log(colors.muted(` skillkit mesh add ${localIP}:${localConfig.port}`)); return 0; } catch (err: any) { - console.error(chalk.red(`Failed to initialize mesh: ${err.message}`)); + error(`Failed to initialize mesh: ${err.message}`); return 1; } } private async addHost(): Promise { if (!this.arg) { - console.error(chalk.red('Error: Host address is required')); - console.log(chalk.gray('Usage: skillkit mesh add
[:port]')); + error('Error: Host address is required'); + console.log(colors.muted('Usage: skillkit mesh add
[:port]')); return 1; } @@ -127,7 +127,7 @@ export class MeshCommand extends Command { const portValue = portStr ?? this.port; const port = portValue ? Number(portValue) : DEFAULT_PORT; if (!Number.isInteger(port) || port < 1 || port > 65535) { - console.error(chalk.red(`Invalid port: ${portValue}`)); + error(`Invalid port: ${portValue}`); return 1; } @@ -142,23 +142,23 @@ export class MeshCommand extends Command { await addKnownHost(host); - console.log(chalk.green(`✓ Added host: ${host.name}`)); - console.log(` ID: ${chalk.gray(host.id.slice(0, 8))}`); + console.log(colors.success(`✓ Added host: ${host.name}`)); + console.log(` ID: ${colors.muted(host.id.slice(0, 8))}`); console.log(` Address: ${host.address}:${host.port}`); - console.log(chalk.gray('\nRun "skillkit mesh health" to verify connectivity.')); + console.log(colors.muted('\nRun "skillkit mesh health" to verify connectivity.')); return 0; } catch (err: any) { - console.error(chalk.red(`Failed to add host: ${err.message}`)); + error(`Failed to add host: ${err.message}`); return 1; } } private async removeHost(): Promise { if (!this.arg) { - console.error(chalk.red('Error: Host ID is required')); - console.log(chalk.gray('Usage: skillkit mesh remove ')); + error('Error: Host ID is required'); + console.log(colors.muted('Usage: skillkit mesh remove ')); return 1; } @@ -169,22 +169,22 @@ export class MeshCommand extends Command { const host = hosts.find(h => h.id === this.arg || h.id.startsWith(this.arg!) || h.name === this.arg); if (!host) { - console.error(chalk.red(`Host not found: ${this.arg}`)); + error(`Host not found: ${this.arg}`); return 1; } const removed = await removeKnownHost(host.id); if (removed) { - console.log(chalk.green(`✓ Removed host: ${host.name}`)); + console.log(colors.success(`✓ Removed host: ${host.name}`)); } else { - console.error(chalk.red('Failed to remove host')); + error('Failed to remove host'); return 1; } return 0; } catch (err: any) { - console.error(chalk.red(`Failed to remove host: ${err.message}`)); + error(`Failed to remove host: ${err.message}`); return 1; } } @@ -202,23 +202,23 @@ export class MeshCommand extends Command { return 0; } - console.log(chalk.bold('\nLocal Host:\n')); - console.log(` ${chalk.cyan('●')} ${localConfig.name} ${chalk.green('[local]')}`); - console.log(` ID: ${chalk.gray(localConfig.id.slice(0, 8))}`); + console.log(colors.bold('\nLocal Host:\n')); + console.log(` ${colors.cyan('●')} ${localConfig.name} ${colors.success('[local]')}`); + console.log(` ID: ${colors.muted(localConfig.id.slice(0, 8))}`); console.log(` Address: ${localIP}:${localConfig.port}`); - console.log(chalk.bold('\nKnown Hosts:\n')); + console.log(colors.bold('\nKnown Hosts:\n')); if (hosts.length === 0) { - console.log(chalk.gray(' No hosts configured.')); - console.log(chalk.gray(' Add hosts with: skillkit mesh add
')); + console.log(colors.muted(' No hosts configured.')); + console.log(colors.muted(' Add hosts with: skillkit mesh add
')); } else { for (const host of hosts) { - const statusIcon = host.status === 'online' ? chalk.green('●') : host.status === 'offline' ? chalk.red('●') : chalk.gray('○'); - const statusLabel = host.status === 'online' ? chalk.green('[online]') : host.status === 'offline' ? chalk.red('[offline]') : chalk.gray('[unknown]'); + const statusIcon = host.status === 'online' ? colors.success('●') : host.status === 'offline' ? colors.error('●') : colors.muted('○'); + const statusLabel = host.status === 'online' ? colors.success('[online]') : host.status === 'offline' ? colors.error('[offline]') : colors.muted('[unknown]'); console.log(` ${statusIcon} ${host.name} ${statusLabel}`); - console.log(` ID: ${chalk.gray(host.id.slice(0, 8))}`); + console.log(` ID: ${colors.muted(host.id.slice(0, 8))}`); console.log(` Address: ${host.address}:${host.port}`); if (host.tailscaleIP) { console.log(` Tailscale: ${host.tailscaleIP}`); @@ -235,9 +235,9 @@ export class MeshCommand extends Command { const tailscaleHosts = await discoverTailscaleHosts(localConfig.port); if (tailscaleHosts.length > 0) { - console.log(chalk.bold('\nTailscale Peers:\n')); + console.log(colors.bold('\nTailscale Peers:\n')); for (const host of tailscaleHosts) { - console.log(` ${chalk.blue('◆')} ${host.name}`); + console.log(` ${colors.info('◆')} ${host.name}`); console.log(` IP: ${host.tailscaleIP}`); } } @@ -247,7 +247,7 @@ export class MeshCommand extends Command { console.log(); return 0; } catch (err: any) { - console.error(chalk.red(`Failed to list hosts: ${err.message}`)); + error(`Failed to list hosts: ${err.message}`); return 1; } } @@ -259,12 +259,12 @@ export class MeshCommand extends Command { const hosts = await getKnownHosts(); if (hosts.length === 0) { - console.log(chalk.yellow('No hosts configured.')); - console.log(chalk.gray('Add hosts with: skillkit mesh add
')); + console.log(colors.warning('No hosts configured.')); + console.log(colors.muted('Add hosts with: skillkit mesh add
')); return 0; } - console.log(chalk.bold('\nChecking host health...\n')); + console.log(colors.bold('\nChecking host health...\n')); const timeout = this.timeout ? parseInt(this.timeout, 10) : undefined; const results = await checkAllHostsHealth({ timeout }); @@ -281,10 +281,10 @@ export class MeshCommand extends Command { const name = host?.name || result.hostId.slice(0, 8); if (result.status === 'online') { - console.log(` ${chalk.green('✓')} ${name} - ${chalk.green('online')} (${result.latencyMs}ms)`); + console.log(` ${colors.success('✓')} ${name} - ${colors.success('online')} (${result.latencyMs}ms)`); } else { hasFailures = true; - console.log(` ${chalk.red('✗')} ${name} - ${chalk.red('offline')} ${result.error ? `(${result.error})` : ''}`); + console.log(` ${colors.error('✗')} ${name} - ${colors.error('offline')} ${result.error ? `(${result.error})` : ''}`); } } @@ -296,7 +296,7 @@ export class MeshCommand extends Command { return hasFailures ? 1 : 0; } catch (err: any) { - console.error(chalk.red(`Failed to check health: ${err.message}`)); + error(`Failed to check health: ${err.message}`); return 1; } } @@ -305,18 +305,18 @@ export class MeshCommand extends Command { try { const { discoverOnce, isTailscaleAvailable, discoverTailscaleHosts, getLocalHostConfig } = await import('@skillkit/mesh'); - console.log(chalk.bold('\nDiscovering hosts on local network...\n')); + console.log(colors.bold('\nDiscovering hosts on local network...\n')); const timeout = this.timeout ? parseInt(this.timeout, 10) : 5000; const localHosts = await discoverOnce(timeout); if (localHosts.length === 0) { - console.log(chalk.gray(' No hosts discovered on local network.')); + console.log(colors.muted(' No hosts discovered on local network.')); } else { - console.log(chalk.cyan('Local Network:')); + console.log(colors.cyan('Local Network:')); for (const host of localHosts) { - console.log(` ${chalk.green('●')} ${host.name}`); + console.log(` ${colors.success('●')} ${host.name}`); console.log(` Address: ${host.address}:${host.port}`); } } @@ -328,23 +328,23 @@ export class MeshCommand extends Command { const tailscaleHosts = await discoverTailscaleHosts(localConfig.port); if (tailscaleHosts.length > 0) { - console.log(chalk.cyan('\nTailscale Network:')); + console.log(colors.cyan('\nTailscale Network:')); for (const host of tailscaleHosts) { - console.log(` ${chalk.blue('◆')} ${host.name}`); + console.log(` ${colors.info('◆')} ${host.name}`); console.log(` Tailscale IP: ${host.tailscaleIP}`); } } else { - console.log(chalk.gray('\n No Tailscale peers found.')); + console.log(colors.muted('\n No Tailscale peers found.')); } } else { - console.log(chalk.gray('\n Tailscale not available.')); + console.log(colors.muted('\n Tailscale not available.')); } } console.log(); return 0; } catch (err: any) { - console.error(chalk.red(`Failed to discover hosts: ${err.message}`)); + error(`Failed to discover hosts: ${err.message}`); return 1; } } @@ -373,31 +373,31 @@ export class MeshCommand extends Command { return 0; } - console.log(chalk.bold('\nMesh Network Status\n')); + console.log(colors.bold('\nMesh Network Status\n')); - console.log(chalk.cyan('Local Host:')); + console.log(colors.cyan('Local Host:')); console.log(` Name: ${localConfig.name}`); - console.log(` ID: ${chalk.gray(localConfig.id.slice(0, 8))}`); + console.log(` ID: ${colors.muted(localConfig.id.slice(0, 8))}`); console.log(` Address: ${localIP}:${localConfig.port}`); if (hasTailscale) { const tailscaleIP = await getTailscaleIP(); if (tailscaleIP) { - console.log(` Tailscale: ${chalk.blue(tailscaleIP)}`); + console.log(` Tailscale: ${colors.info(tailscaleIP)}`); } } console.log(); - console.log(chalk.cyan('Network:')); + console.log(colors.cyan('Network:')); console.log(` Known hosts: ${hosts.length}`); console.log(` Online: ${onlineHosts}`); console.log(` Local peers: ${localPeers.length}`); - console.log(` Tailscale: ${hasTailscale ? chalk.green('available') : chalk.gray('not available')}`); + console.log(` Tailscale: ${hasTailscale ? colors.success('available') : colors.muted('not available')}`); console.log(); return 0; } catch (err: any) { - console.error(chalk.red(`Failed to get status: ${err.message}`)); + error(`Failed to get status: ${err.message}`); return 1; } } @@ -411,8 +411,8 @@ export class MeshCommand extends Command { case 'status': return this.showSecurityStatus(); default: - console.error(chalk.red(`Unknown security action: ${subAction}`)); - console.log(chalk.gray('Available actions: init, status')); + error(`Unknown security action: ${subAction}`); + console.log(colors.muted('Available actions: init, status')); return 1; } } @@ -421,33 +421,33 @@ export class MeshCommand extends Command { try { const { SecureKeystore, TLSManager, getLocalHostConfig, describeSecurityLevel, DEFAULT_SECURITY_CONFIG } = await import('@skillkit/mesh'); - console.log(chalk.bold('\nInitializing mesh security...\n')); + console.log(colors.bold('\nInitializing mesh security...\n')); const keystore = new SecureKeystore(); const identity = await keystore.loadOrCreateIdentity(); - console.log(chalk.green('✓ Identity created/loaded')); - console.log(` Fingerprint: ${chalk.cyan(identity.fingerprint)}`); - console.log(` Public Key: ${chalk.gray(identity.publicKeyHex.slice(0, 32))}...`); + console.log(colors.success('✓ Identity created/loaded')); + console.log(` Fingerprint: ${colors.cyan(identity.fingerprint)}`); + console.log(` Public Key: ${colors.muted(identity.publicKeyHex.slice(0, 32))}...`); const localConfig = await getLocalHostConfig(); const tlsManager = new TLSManager(); const certInfo = await tlsManager.loadOrCreateCertificate(localConfig.id, 'localhost'); - console.log(chalk.green('\n✓ TLS certificate generated')); - console.log(` Fingerprint: ${chalk.gray(certInfo.fingerprint.slice(0, 16))}...`); + console.log(colors.success('\n✓ TLS certificate generated')); + console.log(` Fingerprint: ${colors.muted(certInfo.fingerprint.slice(0, 16))}...`); console.log(` Valid until: ${certInfo.notAfter.toLocaleDateString()}`); - console.log(chalk.green('\n✓ Security initialized')); - console.log(` Mode: ${chalk.cyan(describeSecurityLevel(DEFAULT_SECURITY_CONFIG))}`); + console.log(colors.success('\n✓ Security initialized')); + console.log(` Mode: ${colors.cyan(describeSecurityLevel(DEFAULT_SECURITY_CONFIG))}`); - console.log(chalk.gray('\nOther hosts can trust this peer using:')); - console.log(chalk.gray(` skillkit mesh peer trust ${identity.fingerprint}`)); + console.log(colors.muted('\nOther hosts can trust this peer using:')); + console.log(colors.muted(` skillkit mesh peer trust ${identity.fingerprint}`)); console.log(); return 0; } catch (err: any) { - console.error(chalk.red(`Failed to initialize security: ${err.message}`)); + error(`Failed to initialize security: ${err.message}`); return 1; } } @@ -465,7 +465,7 @@ export class MeshCommand extends Command { const keystore = new SecureKeystore(); - console.log(chalk.bold('\nMesh Security Status\n')); + console.log(colors.bold('\nMesh Security Status\n')); const hasIdentity = await keystore.hasIdentity(); @@ -474,47 +474,47 @@ export class MeshCommand extends Command { const trustedPeers = await keystore.getTrustedPeers(); const revokedPeers = await keystore.getRevokedFingerprints(); - console.log(chalk.cyan('Identity:')); - console.log(` Fingerprint: ${chalk.green(identity.fingerprint)}`); - console.log(` Public Key: ${chalk.gray(identity.publicKeyHex.slice(0, 32))}...`); + console.log(colors.cyan('Identity:')); + console.log(` Fingerprint: ${colors.success(identity.fingerprint)}`); + console.log(` Public Key: ${colors.muted(identity.publicKeyHex.slice(0, 32))}...`); - console.log(chalk.cyan('\nTrust:')); + console.log(colors.cyan('\nTrust:')); console.log(` Trusted peers: ${trustedPeers.length}`); console.log(` Revoked peers: ${revokedPeers.length}`); if (this.verbose && trustedPeers.length > 0) { - console.log(chalk.cyan('\nTrusted Peers:')); + console.log(colors.cyan('\nTrusted Peers:')); for (const peer of trustedPeers) { - console.log(` ${chalk.green('●')} ${peer.name || peer.fingerprint.slice(0, 16)}`); - console.log(` Fingerprint: ${chalk.gray(peer.fingerprint)}`); + console.log(` ${colors.success('●')} ${peer.name || peer.fingerprint.slice(0, 16)}`); + console.log(` Fingerprint: ${colors.muted(peer.fingerprint)}`); console.log(` Added: ${new Date(peer.addedAt).toLocaleDateString()}`); } } } else { - console.log(chalk.yellow('Identity: Not initialized')); - console.log(chalk.gray('Run "skillkit mesh security init" to initialize.')); + console.log(colors.warning('Identity: Not initialized')); + console.log(colors.muted('Run "skillkit mesh security init" to initialize.')); } const localConfig = await getLocalHostConfig(); const tlsManager = new TLSManager(); const hasCert = await tlsManager.hasCertificate(localConfig.id); - console.log(chalk.cyan('\nTLS:')); + console.log(colors.cyan('\nTLS:')); if (hasCert) { const certInfo = await tlsManager.loadCertificate(localConfig.id); - console.log(` Certificate: ${chalk.green('Available')}`); + console.log(` Certificate: ${colors.success('Available')}`); if (certInfo) { - console.log(` Fingerprint: ${chalk.gray(certInfo.fingerprint.slice(0, 16))}...`); + console.log(` Fingerprint: ${colors.muted(certInfo.fingerprint.slice(0, 16))}...`); } } else { - console.log(` Certificate: ${chalk.yellow('Not generated')}`); + console.log(` Certificate: ${colors.warning('Not generated')}`); } - console.log(chalk.cyan('\nConfiguration:')); - console.log(` Security level: ${chalk.cyan(describeSecurityLevel(DEFAULT_SECURITY_CONFIG))}`); + console.log(colors.cyan('\nConfiguration:')); + console.log(` Security level: ${colors.cyan(describeSecurityLevel(DEFAULT_SECURITY_CONFIG))}`); console.log(` Discovery mode: ${DEFAULT_SECURITY_CONFIG.discovery.mode}`); console.log(` Transport encryption: ${DEFAULT_SECURITY_CONFIG.transport.encryption}`); - console.log(` Auth required: ${DEFAULT_SECURITY_CONFIG.transport.requireAuth ? chalk.green('yes') : chalk.gray('no')}`); + console.log(` Auth required: ${DEFAULT_SECURITY_CONFIG.transport.requireAuth ? colors.success('yes') : colors.muted('no')}`); if (this.json) { const identity = hasIdentity ? await keystore.loadOrCreateIdentity() : null; @@ -530,7 +530,7 @@ export class MeshCommand extends Command { console.log(); return 0; } catch (err: any) { - console.error(chalk.red(`Failed to get security status: ${err.message}`)); + error(`Failed to get security status: ${err.message}`); return 1; } } @@ -539,8 +539,8 @@ export class MeshCommand extends Command { const subAction = this.arg; if (!subAction) { - console.error(chalk.red('Error: Peer action is required')); - console.log(chalk.gray('Usage: skillkit mesh peer [fingerprint]')); + error('Error: Peer action is required'); + console.log(colors.muted('Usage: skillkit mesh peer [fingerprint]')); return 1; } @@ -552,8 +552,8 @@ export class MeshCommand extends Command { case 'list': return this.listPeers(); default: - console.error(chalk.red(`Unknown peer action: ${subAction}`)); - console.log(chalk.gray('Available actions: trust, revoke, list')); + error(`Unknown peer action: ${subAction}`); + console.log(colors.muted('Available actions: trust, revoke, list')); return 1; } } @@ -562,8 +562,8 @@ export class MeshCommand extends Command { const fingerprint = this.subArg; if (!fingerprint) { - console.error(chalk.red('Error: Fingerprint is required')); - console.log(chalk.gray('Usage: skillkit mesh peer trust [--name ]')); + error('Error: Fingerprint is required'); + console.log(colors.muted('Usage: skillkit mesh peer trust [--name ]')); return 1; } @@ -574,17 +574,17 @@ export class MeshCommand extends Command { const isRevoked = await keystore.isRevoked(fingerprint); if (isRevoked) { - console.log(chalk.yellow(`Peer ${fingerprint.slice(0, 8)} was previously revoked. Removing from revoked list...`)); + console.log(colors.warning(`Peer ${fingerprint.slice(0, 8)} was previously revoked. Removing from revoked list...`)); } await keystore.addTrustedPeer(fingerprint, '', this.name); - console.log(chalk.green(`✓ Trusted peer: ${this.name || fingerprint.slice(0, 16)}`)); - console.log(` Fingerprint: ${chalk.gray(fingerprint)}`); + console.log(colors.success(`✓ Trusted peer: ${this.name || fingerprint.slice(0, 16)}`)); + console.log(` Fingerprint: ${colors.muted(fingerprint)}`); return 0; } catch (err: any) { - console.error(chalk.red(`Failed to trust peer: ${err.message}`)); + error(`Failed to trust peer: ${err.message}`); return 1; } } @@ -593,8 +593,8 @@ export class MeshCommand extends Command { const fingerprint = this.subArg; if (!fingerprint) { - console.error(chalk.red('Error: Fingerprint is required')); - console.log(chalk.gray('Usage: skillkit mesh peer revoke ')); + error('Error: Fingerprint is required'); + console.log(colors.muted('Usage: skillkit mesh peer revoke ')); return 1; } @@ -604,12 +604,12 @@ export class MeshCommand extends Command { const keystore = new SecureKeystore(); await keystore.revokePeer(fingerprint); - console.log(chalk.green(`✓ Revoked peer: ${fingerprint.slice(0, 16)}`)); - console.log(chalk.gray('This peer will no longer be trusted for mesh communication.')); + console.log(colors.success(`✓ Revoked peer: ${fingerprint.slice(0, 16)}`)); + console.log(colors.muted('This peer will no longer be trusted for mesh communication.')); return 0; } catch (err: any) { - console.error(chalk.red(`Failed to revoke peer: ${err.message}`)); + error(`Failed to revoke peer: ${err.message}`); return 1; } } @@ -627,34 +627,34 @@ export class MeshCommand extends Command { return 0; } - console.log(chalk.bold('\nPeer Trust Status\n')); + console.log(colors.bold('\nPeer Trust Status\n')); if (this.trusted || trustedPeers.length > 0) { - console.log(chalk.cyan('Trusted Peers:')); + console.log(colors.cyan('Trusted Peers:')); if (trustedPeers.length === 0) { - console.log(chalk.gray(' No trusted peers.')); + console.log(colors.muted(' No trusted peers.')); } else { for (const peer of trustedPeers) { - console.log(` ${chalk.green('●')} ${peer.name || 'Unknown'}`); - console.log(` Fingerprint: ${chalk.gray(peer.fingerprint)}`); + console.log(` ${colors.success('●')} ${peer.name || 'Unknown'}`); + console.log(` Fingerprint: ${colors.muted(peer.fingerprint)}`); console.log(` Added: ${new Date(peer.addedAt).toLocaleDateString()}`); } } } if (!this.trusted && revokedFingerprints.length > 0) { - console.log(chalk.cyan('\nRevoked Peers:')); + console.log(colors.cyan('\nRevoked Peers:')); for (const fp of revokedFingerprints) { - console.log(` ${chalk.red('●')} ${chalk.gray(fp)}`); + console.log(` ${colors.error('●')} ${colors.muted(fp)}`); } } - console.log(chalk.gray(`\nTotal: ${trustedPeers.length} trusted, ${revokedFingerprints.length} revoked`)); + console.log(colors.muted(`\nTotal: ${trustedPeers.length} trusted, ${revokedFingerprints.length} revoked`)); console.log(); return 0; } catch (err: any) { - console.error(chalk.red(`Failed to list peers: ${err.message}`)); + error(`Failed to list peers: ${err.message}`); return 1; } } diff --git a/packages/cli/src/commands/message.ts b/packages/cli/src/commands/message.ts index d3647616..7faa066f 100644 --- a/packages/cli/src/commands/message.ts +++ b/packages/cli/src/commands/message.ts @@ -1,5 +1,5 @@ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors, error } from '../onboarding/index.js'; export class MessageCommand extends Command { static override paths = [['message'], ['msg']]; @@ -66,8 +66,8 @@ export class MessageCommand extends Command { case 'status': return this.showStatus(); default: - console.error(chalk.red(`Unknown action: ${action}`)); - console.log(chalk.gray('Available actions: send, inbox, read, reply, archive, forward, sent, status')); + error(`Unknown action: ${action}`); + console.log(colors.muted('Available actions: send, inbox, read, reply, archive, forward, sent, status')); return 1; } } @@ -79,14 +79,14 @@ export class MessageCommand extends Command { private async sendMessage(): Promise { const to = this.arg; if (!to) { - console.error(chalk.red('Error: Recipient is required')); - console.log(chalk.gray('Usage: skillkit message send --body "..."')); + error('Error: Recipient is required'); + console.log(colors.muted('Usage: skillkit message send --body "..."')); return 1; } if (!this.body) { - console.error(chalk.red('Error: Message body is required')); - console.log(chalk.gray('Usage: skillkit message send --body "..."')); + error('Error: Message body is required'); + console.log(colors.muted('Usage: skillkit message send --body "..."')); return 1; } @@ -105,20 +105,20 @@ export class MessageCommand extends Command { }); if (result.delivered) { - console.log(chalk.green(`✓ Message sent to ${to}`)); - console.log(` ID: ${chalk.gray(result.messageId.slice(0, 8))}`); + console.log(colors.success(`✓ Message sent to ${to}`)); + console.log(` ID: ${colors.muted(result.messageId.slice(0, 8))}`); console.log(` Via: ${result.via}`); } else { - console.error(chalk.red(`✗ Failed to deliver message`)); + error(`✗ Failed to deliver message`); if (result.error) { - console.error(chalk.gray(` Error: ${result.error}`)); + console.error(colors.muted(` Error: ${result.error}`)); } return 1; } return 0; } catch (err: any) { - console.error(chalk.red(`Failed to send message: ${err.message}`)); + error(`Failed to send message: ${err.message}`); return 1; } } @@ -147,33 +147,33 @@ export class MessageCommand extends Command { const summary = await service.getInboxSummary(); - console.log(chalk.bold(`\nInbox (${summary.unread} unread / ${summary.total} total)\n`)); + console.log(colors.bold(`\nInbox (${summary.unread} unread / ${summary.total} total)\n`)); if (messages.length === 0) { - console.log(chalk.gray(' No messages.')); + console.log(colors.muted(' No messages.')); return 0; } for (const message of messages) { const isUnread = message.status === 'unread'; const priorityIcon = this.getPriorityIcon(message.priority); - const statusIcon = isUnread ? chalk.cyan('●') : chalk.gray('○'); + const statusIcon = isUnread ? colors.cyan('●') : colors.muted('○'); - console.log(`${statusIcon} ${priorityIcon} ${isUnread ? chalk.bold(message.subject) : message.subject}`); - console.log(` ID: ${chalk.gray(message.id.slice(0, 8))} | From: ${message.from} | ${this.formatDate(message.createdAt)}`); + console.log(`${statusIcon} ${priorityIcon} ${isUnread ? colors.bold(message.subject) : message.subject}`); + console.log(` ID: ${colors.muted(message.id.slice(0, 8))} | From: ${message.from} | ${this.formatDate(message.createdAt)}`); if (this.verbose) { const bodyPreview = typeof message.body === 'string' ? message.body.slice(0, 80) : JSON.stringify(message.body).slice(0, 80); - console.log(` ${chalk.gray(bodyPreview)}${bodyPreview.length >= 80 ? '...' : ''}`); + console.log(` ${colors.muted(bodyPreview)}${bodyPreview.length >= 80 ? '...' : ''}`); } } console.log(); return 0; } catch (err: any) { - console.error(chalk.red(`Failed to show inbox: ${err.message}`)); + error(`Failed to show inbox: ${err.message}`); return 1; } } @@ -181,8 +181,8 @@ export class MessageCommand extends Command { private async readMessage(): Promise { const id = this.arg; if (!id) { - console.error(chalk.red('Error: Message ID is required')); - console.log(chalk.gray('Usage: skillkit message read ')); + error('Error: Message ID is required'); + console.log(colors.muted('Usage: skillkit message read ')); return 1; } @@ -205,13 +205,13 @@ export class MessageCommand extends Command { } } - console.error(chalk.red(`Message not found: ${id}`)); + error(`Message not found: ${id}`); return 1; } return this.displayMessage(message); } catch (err: any) { - console.error(chalk.red(`Failed to read message: ${err.message}`)); + error(`Failed to read message: ${err.message}`); return 1; } } @@ -222,22 +222,22 @@ export class MessageCommand extends Command { return 0; } - console.log(chalk.bold(`\n${message.subject}\n`)); + console.log(colors.bold(`\n${message.subject}\n`)); console.log(`From: ${message.from}`); console.log(`To: ${message.to}`); console.log(`Date: ${new Date(message.createdAt).toLocaleString()}`); console.log(`Priority: ${message.priority}`); console.log(`Type: ${message.type}`); - console.log(`ID: ${chalk.gray(message.id)}`); + console.log(`ID: ${colors.muted(message.id)}`); if (message.replyTo) { - console.log(`Reply to: ${chalk.gray(message.replyTo)}`); + console.log(`Reply to: ${colors.muted(message.replyTo)}`); } if (message.threadId) { - console.log(`Thread: ${chalk.gray(message.threadId)}`); + console.log(`Thread: ${colors.muted(message.threadId)}`); } - console.log(chalk.bold('\nBody:\n')); + console.log(colors.bold('\nBody:\n')); if (typeof message.body === 'string') { console.log(message.body); } else { @@ -251,14 +251,14 @@ export class MessageCommand extends Command { private async replyMessage(): Promise { const id = this.arg; if (!id) { - console.error(chalk.red('Error: Message ID is required')); - console.log(chalk.gray('Usage: skillkit message reply --body "..."')); + error('Error: Message ID is required'); + console.log(colors.muted('Usage: skillkit message reply --body "..."')); return 1; } if (!this.body) { - console.error(chalk.red('Error: Reply body is required')); - console.log(chalk.gray('Usage: skillkit message reply --body "..."')); + error('Error: Reply body is required'); + console.log(colors.muted('Usage: skillkit message reply --body "..."')); return 1; } @@ -276,7 +276,7 @@ export class MessageCommand extends Command { if (found) { messageId = found.id; } else { - console.error(chalk.red(`Message not found: ${id}`)); + error(`Message not found: ${id}`); return 1; } } @@ -284,19 +284,19 @@ export class MessageCommand extends Command { const result = await service.reply(messageId, this.body); if (result.delivered) { - console.log(chalk.green('✓ Reply sent')); - console.log(` ID: ${chalk.gray(result.messageId.slice(0, 8))}`); + console.log(colors.success('✓ Reply sent')); + console.log(` ID: ${colors.muted(result.messageId.slice(0, 8))}`); } else { - console.error(chalk.red('✗ Failed to send reply')); + error('✗ Failed to send reply'); if (result.error) { - console.error(chalk.gray(` Error: ${result.error}`)); + console.error(colors.muted(` Error: ${result.error}`)); } return 1; } return 0; } catch (err: any) { - console.error(chalk.red(`Failed to reply: ${err.message}`)); + error(`Failed to reply: ${err.message}`); return 1; } } @@ -304,8 +304,8 @@ export class MessageCommand extends Command { private async archiveMessage(): Promise { const id = this.arg; if (!id) { - console.error(chalk.red('Error: Message ID is required')); - console.log(chalk.gray('Usage: skillkit message archive ')); + error('Error: Message ID is required'); + console.log(colors.muted('Usage: skillkit message archive ')); return 1; } @@ -323,7 +323,7 @@ export class MessageCommand extends Command { if (found) { messageId = found.id; } else { - console.error(chalk.red(`Message not found: ${id}`)); + error(`Message not found: ${id}`); return 1; } } @@ -331,15 +331,15 @@ export class MessageCommand extends Command { const archived = await service.archive(messageId); if (archived) { - console.log(chalk.green('✓ Message archived')); + console.log(colors.success('✓ Message archived')); } else { - console.error(chalk.red('✗ Failed to archive message')); + error('✗ Failed to archive message'); return 1; } return 0; } catch (err: any) { - console.error(chalk.red(`Failed to archive: ${err.message}`)); + error(`Failed to archive: ${err.message}`); return 1; } } @@ -349,14 +349,14 @@ export class MessageCommand extends Command { const to = this.arg2; if (!id) { - console.error(chalk.red('Error: Message ID is required')); - console.log(chalk.gray('Usage: skillkit message forward ')); + error('Error: Message ID is required'); + console.log(colors.muted('Usage: skillkit message forward ')); return 1; } if (!to) { - console.error(chalk.red('Error: Recipient is required')); - console.log(chalk.gray('Usage: skillkit message forward ')); + error('Error: Recipient is required'); + console.log(colors.muted('Usage: skillkit message forward ')); return 1; } @@ -374,7 +374,7 @@ export class MessageCommand extends Command { if (found) { messageId = found.id; } else { - console.error(chalk.red(`Message not found: ${id}`)); + error(`Message not found: ${id}`); return 1; } } @@ -382,19 +382,19 @@ export class MessageCommand extends Command { const result = await service.forward(messageId, to, this.body); if (result.delivered) { - console.log(chalk.green(`✓ Message forwarded to ${to}`)); - console.log(` ID: ${chalk.gray(result.messageId.slice(0, 8))}`); + console.log(colors.success(`✓ Message forwarded to ${to}`)); + console.log(` ID: ${colors.muted(result.messageId.slice(0, 8))}`); } else { - console.error(chalk.red('✗ Failed to forward message')); + error('✗ Failed to forward message'); if (result.error) { - console.error(chalk.gray(` Error: ${result.error}`)); + console.error(colors.muted(` Error: ${result.error}`)); } return 1; } return 0; } catch (err: any) { - console.error(chalk.red(`Failed to forward: ${err.message}`)); + error(`Failed to forward: ${err.message}`); return 1; } } @@ -418,24 +418,24 @@ export class MessageCommand extends Command { return 0; } - console.log(chalk.bold(`\nSent Messages (${messages.length})\n`)); + console.log(colors.bold(`\nSent Messages (${messages.length})\n`)); if (messages.length === 0) { - console.log(chalk.gray(' No sent messages.')); + console.log(colors.muted(' No sent messages.')); return 0; } for (const message of messages) { const priorityIcon = this.getPriorityIcon(message.priority); - console.log(`${chalk.gray('○')} ${priorityIcon} ${message.subject}`); - console.log(` ID: ${chalk.gray(message.id.slice(0, 8))} | To: ${message.to} | ${this.formatDate(message.createdAt)}`); + console.log(`${colors.muted('○')} ${priorityIcon} ${message.subject}`); + console.log(` ID: ${colors.muted(message.id.slice(0, 8))} | To: ${message.to} | ${this.formatDate(message.createdAt)}`); } console.log(); return 0; } catch (err: any) { - console.error(chalk.red(`Failed to show sent: ${err.message}`)); + error(`Failed to show sent: ${err.message}`); return 1; } } @@ -461,26 +461,26 @@ export class MessageCommand extends Command { return 0; } - console.log(chalk.bold('\nMessaging Status\n')); + console.log(colors.bold('\nMessaging Status\n')); - console.log(chalk.cyan('Agent:')); + console.log(colors.cyan('Agent:')); console.log(` ID: ${agentId}`); console.log(); - console.log(chalk.cyan('Inbox:')); + console.log(colors.cyan('Inbox:')); console.log(` Total: ${summary.total}`); console.log(` Unread: ${summary.unread}`); console.log(` By priority: urgent=${summary.byPriority.urgent}, high=${summary.byPriority.high}, normal=${summary.byPriority.normal}, low=${summary.byPriority.low}`); console.log(); - console.log(chalk.cyan('Storage:')); + console.log(colors.cyan('Storage:')); console.log(` Sent: ${sent.length}`); console.log(` Archived: ${archived.length}`); console.log(); return 0; } catch (err: any) { - console.error(chalk.red(`Failed to get status: ${err.message}`)); + error(`Failed to get status: ${err.message}`); return 1; } } @@ -488,13 +488,13 @@ export class MessageCommand extends Command { private getPriorityIcon(priority: string): string { switch (priority) { case 'urgent': - return chalk.red('!!!!'); + return colors.error('!!!!'); case 'high': - return chalk.yellow('!!!'); + return colors.warning('!!!'); case 'normal': - return chalk.gray('!'); + return colors.muted('!'); case 'low': - return chalk.gray('·'); + return colors.muted('·'); default: return ''; } diff --git a/packages/cli/src/commands/methodology.ts b/packages/cli/src/commands/methodology.ts index 7f795bb8..2461e7e2 100644 --- a/packages/cli/src/commands/methodology.ts +++ b/packages/cli/src/commands/methodology.ts @@ -5,7 +5,7 @@ */ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors } from '../onboarding/index.js'; import { createMethodologyManager, createMethodologyLoader } from '@skillkit/core'; export class MethodologyCommand extends Command { @@ -50,12 +50,12 @@ export class MethodologyCommand extends Command { case 'installed': return await this.listInstalled(projectPath); default: - this.context.stderr.write(chalk.red(`Unknown action: ${this.action}\n`)); + this.context.stderr.write(colors.error(`Unknown action: ${this.action}\n`)); this.context.stderr.write('Available actions: list, install, uninstall, sync, search, info, installed\n'); return 1; } } catch (err) { - this.context.stderr.write(chalk.red(`✗ ${err instanceof Error ? err.message : 'Unknown error'}\n`)); + this.context.stderr.write(colors.error(`✗ ${err instanceof Error ? err.message : 'Unknown error'}\n`)); return 1; } } @@ -69,18 +69,18 @@ export class MethodologyCommand extends Command { return 0; } - this.context.stdout.write(chalk.cyan('Available Methodology Packs:\n\n')); + this.context.stdout.write(colors.cyan('Available Methodology Packs:\n\n')); for (const pack of packs) { - this.context.stdout.write(chalk.green(` ${pack.name}`) + ` v${pack.version}\n`); - this.context.stdout.write(chalk.gray(` ${pack.description}\n`)); + this.context.stdout.write(colors.success(` ${pack.name}`) + ` v${pack.version}\n`); + this.context.stdout.write(colors.muted(` ${pack.description}\n`)); this.context.stdout.write(` Skills: ${pack.skills.join(', ')}\n`); this.context.stdout.write(` Tags: ${pack.tags.join(', ')}\n`); this.context.stdout.write('\n'); } this.context.stdout.write(`Total: ${packs.length} packs\n`); - this.context.stdout.write(chalk.gray('\nRun `skillkit methodology install` to install all packs.\n')); + this.context.stdout.write(colors.muted('\nRun `skillkit methodology install` to install all packs.\n')); return 0; } @@ -92,31 +92,31 @@ export class MethodologyCommand extends Command { // Check if it's a skill ID (pack/skill) or pack name if (this.target.includes('/')) { // Install single skill - this.context.stdout.write(`Installing skill: ${chalk.cyan(this.target)}...\n`); + this.context.stdout.write(`Installing skill: ${colors.cyan(this.target)}...\n`); if (this.dryRun) { - this.context.stdout.write(chalk.yellow('[dry-run] Would install skill.\n')); + this.context.stdout.write(colors.warning('[dry-run] Would install skill.\n')); return 0; } const result = await manager.installSkill(this.target); if (result.success) { - this.context.stdout.write(chalk.green(`✓ Skill installed: ${this.target}\n`)); + this.context.stdout.write(colors.success(`✓ Skill installed: ${this.target}\n`)); } else { for (const failed of result.failed) { - this.context.stderr.write(chalk.red(`✗ ${failed.name}: ${failed.error}\n`)); + this.context.stderr.write(colors.error(`✗ ${failed.name}: ${failed.error}\n`)); } return 1; } } else { // Install single pack - this.context.stdout.write(`Installing pack: ${chalk.cyan(this.target)}...\n`); + this.context.stdout.write(`Installing pack: ${colors.cyan(this.target)}...\n`); if (this.dryRun) { const pack = await loader.loadPack(this.target); if (pack) { - this.context.stdout.write(chalk.yellow(`[dry-run] Would install ${pack.skills.length} skills.\n`)); + this.context.stdout.write(colors.warning(`[dry-run] Would install ${pack.skills.length} skills.\n`)); } return 0; } @@ -124,15 +124,15 @@ export class MethodologyCommand extends Command { const result = await manager.installPack(this.target); if (result.success) { - this.context.stdout.write(chalk.green(`✓ Pack "${this.target}" installed!\n`)); + this.context.stdout.write(colors.success(`✓ Pack "${this.target}" installed!\n`)); this.context.stdout.write(` Installed: ${result.installed.length} skills\n`); if (result.skipped.length > 0) { this.context.stdout.write(` Skipped (already installed): ${result.skipped.length}\n`); } } else { - this.context.stderr.write(chalk.red(`✗ Failed to install pack\n`)); + this.context.stderr.write(colors.error(`✗ Failed to install pack\n`)); for (const failed of result.failed) { - this.context.stderr.write(chalk.red(` - ${failed.name}: ${failed.error}\n`)); + this.context.stderr.write(colors.error(` - ${failed.name}: ${failed.error}\n`)); } return 1; } @@ -145,31 +145,31 @@ export class MethodologyCommand extends Command { const packs = await loader.loadAllPacks(); let totalSkills = 0; for (const pack of packs) { - this.context.stdout.write(chalk.yellow(`[dry-run] Would install ${pack.name} (${pack.skills.length} skills)\n`)); + this.context.stdout.write(colors.warning(`[dry-run] Would install ${pack.name} (${pack.skills.length} skills)\n`)); totalSkills += pack.skills.length; } - this.context.stdout.write(chalk.yellow(`\n[dry-run] Would install ${totalSkills} skills total.\n`)); + this.context.stdout.write(colors.warning(`\n[dry-run] Would install ${totalSkills} skills total.\n`)); return 0; } const result = await manager.installAllPacks(); if (result.success) { - this.context.stdout.write(chalk.green('\n✓ All methodology packs installed!\n')); + this.context.stdout.write(colors.success('\n✓ All methodology packs installed!\n')); this.context.stdout.write(` Installed: ${result.installed.length} skills\n`); if (result.skipped.length > 0) { this.context.stdout.write(` Skipped (already installed): ${result.skipped.length}\n`); } } else { - this.context.stdout.write(chalk.yellow('\n⚠ Some skills failed to install:\n')); + this.context.stdout.write(colors.warning('\n⚠ Some skills failed to install:\n')); for (const failed of result.failed) { - this.context.stderr.write(chalk.red(` - ${failed.name}: ${failed.error}\n`)); + this.context.stderr.write(colors.error(` - ${failed.name}: ${failed.error}\n`)); } this.context.stdout.write(`\n Installed: ${result.installed.length} skills\n`); } } - this.context.stdout.write(chalk.gray('\nRun `skillkit methodology sync` to sync to detected agents.\n')); + this.context.stdout.write(colors.muted('\nRun `skillkit methodology sync` to sync to detected agents.\n')); return 0; } @@ -177,18 +177,18 @@ export class MethodologyCommand extends Command { const manager = createMethodologyManager({ projectPath }); if (!this.target) { - this.context.stderr.write(chalk.red('Pack name required for uninstall.\n')); + this.context.stderr.write(colors.error('Pack name required for uninstall.\n')); this.context.stderr.write('Usage: skillkit methodology uninstall \n'); return 1; } if (this.dryRun) { - this.context.stdout.write(chalk.yellow(`[dry-run] Would uninstall pack: ${this.target}\n`)); + this.context.stdout.write(colors.warning(`[dry-run] Would uninstall pack: ${this.target}\n`)); return 0; } await manager.uninstallPack(this.target); - this.context.stdout.write(chalk.green(`✓ Pack "${this.target}" uninstalled.\n`)); + this.context.stdout.write(colors.success(`✓ Pack "${this.target}" uninstalled.\n`)); return 0; } @@ -199,23 +199,23 @@ export class MethodologyCommand extends Command { if (this.dryRun) { const installed = manager.listInstalledSkills(); - this.context.stdout.write(chalk.yellow(`[dry-run] Would sync ${installed.length} skills.\n`)); + this.context.stdout.write(colors.warning(`[dry-run] Would sync ${installed.length} skills.\n`)); return 0; } const result = await manager.syncAll(); if (result.synced.length > 0) { - this.context.stdout.write(chalk.green('✓ Sync complete!\n')); + this.context.stdout.write(colors.success('✓ Sync complete!\n')); for (const sync of result.synced) { this.context.stdout.write(` ${sync.skill} → ${sync.agents.join(', ')}\n`); } } if (result.failed.length > 0) { - this.context.stdout.write(chalk.yellow('\n⚠ Some syncs failed:\n')); + this.context.stdout.write(colors.warning('\n⚠ Some syncs failed:\n')); for (const fail of result.failed) { - this.context.stderr.write(chalk.red(` ${fail.skill} (${fail.agent}): ${fail.error}\n`)); + this.context.stderr.write(colors.error(` ${fail.skill} (${fail.agent}): ${fail.error}\n`)); } } @@ -230,7 +230,7 @@ export class MethodologyCommand extends Command { const loader = createMethodologyLoader(); if (!this.target) { - this.context.stderr.write(chalk.red('Search query required.\n')); + this.context.stderr.write(colors.error('Search query required.\n')); this.context.stderr.write('Usage: skillkit methodology search \n'); return 1; } @@ -242,11 +242,11 @@ export class MethodologyCommand extends Command { return 0; } - this.context.stdout.write(chalk.cyan(`Found ${skills.length} skills matching "${this.target}":\n\n`)); + this.context.stdout.write(colors.cyan(`Found ${skills.length} skills matching "${this.target}":\n\n`)); for (const skill of skills) { - this.context.stdout.write(chalk.green(` ${skill.id}`) + ` v${skill.version}\n`); - this.context.stdout.write(chalk.gray(` ${skill.description || 'No description'}\n`)); + this.context.stdout.write(colors.success(` ${skill.id}`) + ` v${skill.version}\n`); + this.context.stdout.write(colors.muted(` ${skill.description || 'No description'}\n`)); if (skill.tags.length > 0) { this.context.stdout.write(` Tags: ${skill.tags.join(', ')}\n`); } @@ -263,7 +263,7 @@ export class MethodologyCommand extends Command { const loader = createMethodologyLoader(); if (!this.target) { - this.context.stderr.write(chalk.red('Pack name required.\n')); + this.context.stderr.write(colors.error('Pack name required.\n')); this.context.stderr.write('Usage: skillkit methodology info \n'); return 1; } @@ -271,11 +271,11 @@ export class MethodologyCommand extends Command { const pack = await loader.loadPack(this.target); if (!pack) { - this.context.stderr.write(chalk.red(`Pack not found: ${this.target}\n`)); + this.context.stderr.write(colors.error(`Pack not found: ${this.target}\n`)); return 1; } - this.context.stdout.write(chalk.cyan(`\nPack: ${pack.name}\n`)); + this.context.stdout.write(colors.cyan(`\nPack: ${pack.name}\n`)); this.context.stdout.write(`Version: ${pack.version}\n`); this.context.stdout.write(`Description: ${pack.description}\n`); this.context.stdout.write(`Tags: ${pack.tags.join(', ')}\n`); @@ -283,12 +283,12 @@ export class MethodologyCommand extends Command { this.context.stdout.write(`License: ${pack.license || 'Unknown'}\n`); this.context.stdout.write(`Compatibility: ${pack.compatibility.join(', ')}\n`); - this.context.stdout.write(chalk.cyan(`\nSkills (${pack.skills.length}):\n`)); + this.context.stdout.write(colors.cyan(`\nSkills (${pack.skills.length}):\n`)); const skills = await loader.loadPackSkills(this.target); for (const skill of skills) { - this.context.stdout.write(chalk.green(` ${skill.id.split('/')[1]}\n`)); - this.context.stdout.write(chalk.gray(` ${skill.description || 'No description'}\n`)); + this.context.stdout.write(colors.success(` ${skill.id.split('/')[1]}\n`)); + this.context.stdout.write(colors.muted(` ${skill.description || 'No description'}\n`)); if (this.verbose) { if (skill.metadata.triggers) { this.context.stdout.write(` Triggers: ${skill.metadata.triggers.join(', ')}\n`); @@ -302,7 +302,7 @@ export class MethodologyCommand extends Command { } } - this.context.stdout.write(chalk.gray(`\nRun \`skillkit methodology install ${this.target}\` to install this pack.\n`)); + this.context.stdout.write(colors.muted(`\nRun \`skillkit methodology install ${this.target}\` to install this pack.\n`)); return 0; } @@ -314,30 +314,30 @@ export class MethodologyCommand extends Command { if (installedPacks.length === 0 && installedSkills.length === 0) { this.context.stdout.write('No methodology skills installed.\n'); - this.context.stdout.write(chalk.gray('Run `skillkit methodology install` to install methodology packs.\n')); + this.context.stdout.write(colors.muted('Run `skillkit methodology install` to install methodology packs.\n')); return 0; } if (installedPacks.length > 0) { - this.context.stdout.write(chalk.cyan('Installed Packs:\n')); + this.context.stdout.write(colors.cyan('Installed Packs:\n')); for (const pack of installedPacks) { - this.context.stdout.write(chalk.green(` ${pack.name}`) + ` v${pack.version}\n`); + this.context.stdout.write(colors.success(` ${pack.name}`) + ` v${pack.version}\n`); this.context.stdout.write(` Skills: ${pack.skills.length}\n`); } this.context.stdout.write('\n'); } if (installedSkills.length > 0) { - this.context.stdout.write(chalk.cyan('Installed Skills:\n')); + this.context.stdout.write(colors.cyan('Installed Skills:\n')); for (const skill of installedSkills) { const syncStatus = skill.syncedAgents.length > 0 - ? chalk.green(`synced to ${skill.syncedAgents.length} agents`) - : chalk.yellow('not synced'); - this.context.stdout.write(` ${chalk.green(skill.id)} v${skill.version} [${syncStatus}]\n`); + ? colors.success(`synced to ${skill.syncedAgents.length} agents`) + : colors.warning('not synced'); + this.context.stdout.write(` ${colors.success(skill.id)} v${skill.version} [${syncStatus}]\n`); } } - this.context.stdout.write(chalk.gray(`\nTotal: ${installedSkills.length} skills installed.\n`)); + this.context.stdout.write(colors.muted(`\nTotal: ${installedSkills.length} skills installed.\n`)); return 0; } } diff --git a/packages/cli/src/commands/pause.ts b/packages/cli/src/commands/pause.ts index e4feed13..45a6414d 100644 --- a/packages/cli/src/commands/pause.ts +++ b/packages/cli/src/commands/pause.ts @@ -1,6 +1,6 @@ import { Command, Option } from 'clipanion'; import { resolve } from 'node:path'; -import chalk from 'chalk'; +import { colors, warn, success, error } from '../onboarding/index.js'; import { SessionManager } from '@skillkit/core'; /** @@ -36,35 +36,35 @@ export class PauseCommand extends Command { const state = manager.get(); if (!state) { - console.log(chalk.yellow('No active session found.')); + warn('No active session found.'); return 1; } if (!state.currentExecution) { - console.log(chalk.yellow('No skill execution in progress.')); + warn('No skill execution in progress.'); return 1; } if (state.currentExecution.status === 'paused') { - console.log(chalk.yellow('Execution is already paused.')); - console.log(chalk.dim('Resume with: skillkit resume')); + warn('Execution is already paused.'); + console.log(colors.muted('Resume with: skillkit resume')); return 0; } - const success = manager.pause(); + const paused = manager.pause(); - if (success) { + if (paused) { const exec = state.currentExecution; - console.log(chalk.green('✓ Execution paused')); + success('✓ Execution paused'); console.log(); - console.log(` Skill: ${chalk.bold(exec.skillName)}`); + console.log(` Skill: ${colors.bold(exec.skillName)}`); console.log(` Progress: ${exec.currentStep}/${exec.totalSteps} tasks completed`); console.log(); - console.log(chalk.dim('Resume with: skillkit resume')); - console.log(chalk.dim('View status: skillkit status')); + console.log(colors.muted('Resume with: skillkit resume')); + console.log(colors.muted('View status: skillkit status')); return 0; } else { - console.log(chalk.red('Failed to pause execution.')); + error('Failed to pause execution.'); return 1; } } diff --git a/packages/cli/src/commands/plugin.ts b/packages/cli/src/commands/plugin.ts index e6b4d969..84f974e9 100644 --- a/packages/cli/src/commands/plugin.ts +++ b/packages/cli/src/commands/plugin.ts @@ -8,7 +8,7 @@ import { Command, Option } from 'clipanion'; import { join, isAbsolute, resolve, sep } from 'node:path'; import { homedir } from 'node:os'; import { existsSync, mkdirSync, copyFileSync, cpSync, rmSync } from 'node:fs'; -import chalk from 'chalk'; +import { colors } from '../onboarding/index.js'; import { createPluginManager, loadPlugin, loadPluginsFromDirectory } from '@skillkit/core'; export class PluginCommand extends Command { @@ -67,12 +67,12 @@ export class PluginCommand extends Command { case 'info': return this.pluginInfo(pluginManager); default: - this.context.stderr.write(chalk.red(`Unknown action: ${this.action}\n`)); + this.context.stderr.write(colors.error(`Unknown action: ${this.action}\n`)); this.context.stderr.write('Available actions: list, install, uninstall, enable, disable, info\n'); return 1; } } catch (err) { - this.context.stderr.write(chalk.red(`✗ ${err instanceof Error ? err.message : 'Unknown error'}\n`)); + this.context.stderr.write(colors.error(`✗ ${err instanceof Error ? err.message : 'Unknown error'}\n`)); return 1; } } @@ -86,17 +86,17 @@ export class PluginCommand extends Command { return 0; } - this.context.stdout.write(chalk.cyan(`Installed Plugins (${plugins.length}):\n\n`)); + this.context.stdout.write(colors.cyan(`Installed Plugins (${plugins.length}):\n\n`)); for (const plugin of plugins) { const enabled = pluginManager.isPluginEnabled(plugin.name); const status = enabled - ? chalk.green('enabled') - : chalk.gray('disabled'); + ? colors.success('enabled') + : colors.muted('disabled'); - this.context.stdout.write(chalk.cyan(` ${plugin.name}`) + ` v${plugin.version} [${status}]\n`); + this.context.stdout.write(colors.cyan(` ${plugin.name}`) + ` v${plugin.version} [${status}]\n`); if (plugin.description) { - this.context.stdout.write(chalk.gray(` ${plugin.description}\n`)); + this.context.stdout.write(colors.muted(` ${plugin.description}\n`)); } } @@ -106,7 +106,7 @@ export class PluginCommand extends Command { const commands = pluginManager.getAllCommands(); if (translators.size > 0 || providers.size > 0 || commands.length > 0) { - this.context.stdout.write(chalk.cyan('\nRegistered Extensions:\n')); + this.context.stdout.write(colors.cyan('\nRegistered Extensions:\n')); if (translators.size > 0) { this.context.stdout.write(` Translators: ${Array.from(translators.keys()).join(', ')}\n`); } @@ -140,7 +140,7 @@ export class PluginCommand extends Command { private async installPlugin(pluginManager: ReturnType): Promise { if (!this.source) { - this.context.stderr.write(chalk.red('--source is required for install\n')); + this.context.stderr.write(colors.error('--source is required for install\n')); return 1; } @@ -156,7 +156,7 @@ export class PluginCommand extends Command { // Validate plugin name from metadata const pluginName = plugin.metadata.name; if (!this.isValidPluginName(pluginName)) { - this.context.stderr.write(chalk.red(`Invalid plugin name: ${pluginName}\n`)); + this.context.stderr.write(colors.error(`Invalid plugin name: ${pluginName}\n`)); return 1; } @@ -184,7 +184,7 @@ export class PluginCommand extends Command { const resolvedTarget = resolve(targetDir); const resolvedPluginsDir = resolve(pluginsDir); if (!resolvedTarget.startsWith(resolvedPluginsDir + sep)) { - this.context.stderr.write(chalk.red('Invalid plugin name\n')); + this.context.stderr.write(colors.error('Invalid plugin name\n')); return 1; } @@ -217,12 +217,12 @@ export class PluginCommand extends Command { copyFileSync(resolvedSource, join(targetDir, destFileName)); } - this.context.stdout.write(chalk.dim(` Copied to ${targetDir}\n`)); + this.context.stdout.write(colors.muted(` Copied to ${targetDir}\n`)); } await pluginManager.register(plugin); - this.context.stdout.write(chalk.green(`✓ Plugin "${plugin.metadata.name}" installed!\n`)); + this.context.stdout.write(colors.success(`✓ Plugin "${plugin.metadata.name}" installed!\n`)); this.context.stdout.write(` Version: ${plugin.metadata.version}\n`); if (plugin.metadata.description) { this.context.stdout.write(` ${plugin.metadata.description}\n`); @@ -244,13 +244,13 @@ export class PluginCommand extends Command { private async uninstallPlugin(pluginManager: ReturnType): Promise { if (!this.name) { - this.context.stderr.write(chalk.red('--name is required for uninstall\n')); + this.context.stderr.write(colors.error('--name is required for uninstall\n')); return 1; } // Validate plugin name to prevent path traversal attacks if (!this.isValidPluginName(this.name)) { - this.context.stderr.write(chalk.red('Invalid plugin name\n')); + this.context.stderr.write(colors.error('Invalid plugin name\n')); return 1; } @@ -269,57 +269,57 @@ export class PluginCommand extends Command { const resolvedPluginDir = resolve(pluginDir); const resolvedPluginsDir = resolve(pluginsDir); if (!resolvedPluginDir.startsWith(resolvedPluginsDir + sep)) { - this.context.stderr.write(chalk.red('Invalid plugin name\n')); + this.context.stderr.write(colors.error('Invalid plugin name\n')); return 1; } if (existsSync(pluginDir)) { rmSync(pluginDir, { recursive: true, force: true }); - this.context.stdout.write(chalk.dim(` Removed ${pluginDir}\n`)); + this.context.stdout.write(colors.muted(` Removed ${pluginDir}\n`)); } - this.context.stdout.write(chalk.green(`✓ Plugin "${this.name}" uninstalled.\n`)); + this.context.stdout.write(colors.success(`✓ Plugin "${this.name}" uninstalled.\n`)); return 0; } private enablePlugin(pluginManager: ReturnType): number { if (!this.name) { - this.context.stderr.write(chalk.red('--name is required for enable\n')); + this.context.stderr.write(colors.error('--name is required for enable\n')); return 1; } pluginManager.enablePlugin(this.name); - this.context.stdout.write(chalk.green(`✓ Plugin "${this.name}" enabled.\n`)); + this.context.stdout.write(colors.success(`✓ Plugin "${this.name}" enabled.\n`)); return 0; } private disablePlugin(pluginManager: ReturnType): number { if (!this.name) { - this.context.stderr.write(chalk.red('--name is required for disable\n')); + this.context.stderr.write(colors.error('--name is required for disable\n')); return 1; } pluginManager.disablePlugin(this.name); - this.context.stdout.write(chalk.green(`✓ Plugin "${this.name}" disabled.\n`)); + this.context.stdout.write(colors.success(`✓ Plugin "${this.name}" disabled.\n`)); return 0; } private pluginInfo(pluginManager: ReturnType): number { if (!this.name) { - this.context.stderr.write(chalk.red('--name is required for info\n')); + this.context.stderr.write(colors.error('--name is required for info\n')); return 1; } const plugin = pluginManager.getPlugin(this.name); if (!plugin) { - this.context.stderr.write(chalk.red(`Plugin "${this.name}" not found.\n`)); + this.context.stderr.write(colors.error(`Plugin "${this.name}" not found.\n`)); return 1; } const { metadata } = plugin; const enabled = pluginManager.isPluginEnabled(this.name); - this.context.stdout.write(chalk.cyan(`${metadata.name}`) + ` v${metadata.version}\n`); + this.context.stdout.write(colors.cyan(`${metadata.name}`) + ` v${metadata.version}\n`); this.context.stdout.write(`Status: ${enabled ? 'enabled' : 'disabled'}\n`); if (metadata.description) { this.context.stdout.write(`Description: ${metadata.description}\n`); diff --git a/packages/cli/src/commands/primer.ts b/packages/cli/src/commands/primer.ts index 4dca64e5..e1d6516a 100644 --- a/packages/cli/src/commands/primer.ts +++ b/packages/cli/src/commands/primer.ts @@ -1,6 +1,6 @@ import { Command, Option } from 'clipanion'; import { resolve } from 'node:path'; -import chalk from 'chalk'; +import { colors, warn, error, step } from '../onboarding/index.js'; import { type AgentType, AgentType as AgentTypeSchema, @@ -96,7 +96,7 @@ export class PrimerCommand extends Command { } private async runLearn(projectPath: string): Promise { - console.log(chalk.cyan('Analyzing codebase and extracting patterns...\n')); + step('Analyzing codebase and extracting patterns...\n'); const analysis = analyzePrimer(projectPath); if (this.verbose) { @@ -104,7 +104,7 @@ export class PrimerCommand extends Command { console.log(); } - console.log(chalk.bold('Analyzing git history for patterns...\n')); + console.log(colors.bold('Analyzing git history for patterns...\n')); const gitResult = analyzeGitHistory(projectPath, { commits: this.commits ? parseInt(this.commits) : 100, @@ -116,35 +116,35 @@ export class PrimerCommand extends Command { console.log(); if (gitResult.patterns.length === 0) { - console.log(chalk.yellow('No learnable patterns found.')); + warn('No learnable patterns found.'); return 0; } - console.log(chalk.bold(`Extracted ${gitResult.patterns.length} patterns:\n`)); + console.log(colors.bold(`Extracted ${gitResult.patterns.length} patterns:\n`)); for (const pattern of gitResult.patterns.slice(0, 10)) { - const confidence = chalk.blue(`${(pattern.confidence * 100).toFixed(0)}%`); - console.log(` ${chalk.dim('○')} ${pattern.title} [${pattern.category}] ${confidence}`); + const confidence = colors.info(`${(pattern.confidence * 100).toFixed(0)}%`); + console.log(` ${colors.muted('○')} ${pattern.title} [${pattern.category}] ${confidence}`); addPattern(pattern); } if (gitResult.patterns.length > 10) { - console.log(chalk.dim(` ... and ${gitResult.patterns.length - 10} more saved`)); + console.log(colors.muted(` ... and ${gitResult.patterns.length - 10} more saved`)); for (const pattern of gitResult.patterns.slice(10)) { addPattern(pattern); } } console.log(); - console.log(chalk.green(`✓ Saved ${gitResult.patterns.length} patterns`)); - console.log(chalk.dim('View with: skillkit learn --show')); - console.log(chalk.dim('Approve with: skillkit pattern approve ')); + console.log(colors.success(`✓ Saved ${gitResult.patterns.length} patterns`)); + console.log(colors.muted('View with: skillkit learn --show')); + console.log(colors.muted('Approve with: skillkit pattern approve ')); return 0; } private async runAnalysis(projectPath: string): Promise { - console.log(chalk.cyan('Analyzing codebase...\n')); + step('Analyzing codebase...\n'); try { const analysis = analyzePrimer(projectPath); @@ -156,8 +156,8 @@ export class PrimerCommand extends Command { this.printAnalysis(analysis); return 0; - } catch (error) { - console.error(chalk.red('Analysis failed:'), error instanceof Error ? error.message : error); + } catch (err) { + error('Analysis failed: ' + (err instanceof Error ? err.message : String(err))); return 1; } } @@ -165,7 +165,7 @@ export class PrimerCommand extends Command { private async runGenerate(projectPath: string): Promise { const agents = this.parseAgents(); - console.log(chalk.cyan('Analyzing codebase and generating AI instructions...\n')); + step('Analyzing codebase and generating AI instructions...\n'); try { const result = generatePrimer(projectPath, { @@ -183,37 +183,37 @@ export class PrimerCommand extends Command { } if (result.generated.length === 0) { - console.log(chalk.yellow('No instruction files generated.')); + warn('No instruction files generated.'); if (result.errors.length > 0) { - for (const error of result.errors) { - console.log(chalk.red(` Error: ${error}`)); + for (const errMsg of result.errors) { + console.log(colors.error(` Error: ${errMsg}`)); } } return 1; } - console.log(chalk.bold('Generated Instruction Files:\n')); + console.log(colors.bold('Generated Instruction Files:\n')); for (const instruction of result.generated) { - const status = this.dryRun ? chalk.yellow('(dry-run)') : chalk.green('created'); - console.log(` ${chalk.green('●')} ${chalk.bold(instruction.agent)}`); - console.log(` ${chalk.gray(instruction.filepath)} ${status}`); + const status = this.dryRun ? colors.warning('(dry-run)') : colors.success('created'); + console.log(` ${colors.success('●')} ${colors.bold(instruction.agent)}`); + console.log(` ${colors.muted(instruction.filepath)} ${status}`); } console.log(); if (result.warnings.length > 0) { - console.log(chalk.yellow('Warnings:')); - for (const warning of result.warnings) { - console.log(` ${chalk.yellow('⚠')} ${warning}`); + console.log(colors.warning('Warnings:')); + for (const warningMsg of result.warnings) { + console.log(` ${colors.warning('⚠')} ${warningMsg}`); } console.log(); } if (result.errors.length > 0) { - console.log(chalk.red('Errors:')); - for (const error of result.errors) { - console.log(` ${chalk.red('✗')} ${error}`); + console.log(colors.error('Errors:')); + for (const errMsg of result.errors) { + console.log(` ${colors.error('✗')} ${errMsg}`); } console.log(); } @@ -222,15 +222,15 @@ export class PrimerCommand extends Command { ? `Would generate ${result.generated.length} instruction file(s)` : `Generated ${result.generated.length} instruction file(s)`; - console.log(chalk.bold(summary)); + console.log(colors.bold(summary)); if (this.dryRun) { - console.log(chalk.gray('\n(Dry run - no files were written)')); + console.log(colors.muted('\n(Dry run - no files were written)')); } return result.success ? 0 : 1; - } catch (error) { - console.error(chalk.red('Generation failed:'), error instanceof Error ? error.message : error); + } catch (err) { + error('Generation failed: ' + (err instanceof Error ? err.message : String(err))); return 1; } } @@ -246,7 +246,7 @@ export class PrimerCommand extends Command { if (result.success) { agents.push(result.data); } else { - console.warn(chalk.yellow(`Unknown agent: ${part}`)); + warn(`Unknown agent: ${part}`); } } @@ -256,8 +256,8 @@ export class PrimerCommand extends Command { private printAnalysis(analysis: ReturnType): void { const { project, languages, packageManagers, stack, structure, conventions, ci, docker, buildCommands, importantFiles } = analysis; - console.log(chalk.bold('Project Information')); - console.log(` Name: ${chalk.cyan(project.name)}`); + console.log(colors.bold('Project Information')); + console.log(` Name: ${colors.cyan(project.name)}`); if (project.description) { console.log(` Description: ${project.description}`); } @@ -270,67 +270,67 @@ export class PrimerCommand extends Command { console.log(); if (languages.length > 0) { - console.log(chalk.bold('Languages')); + console.log(colors.bold('Languages')); for (const lang of languages) { const version = lang.version ? ` (${lang.version})` : ''; - console.log(` ${chalk.green('●')} ${lang.name}${version}`); + console.log(` ${colors.success('●')} ${lang.name}${version}`); } console.log(); } if (packageManagers.length > 0) { - console.log(chalk.bold('Package Managers')); - console.log(` ${chalk.cyan(packageManagers.join(', '))}`); + console.log(colors.bold('Package Managers')); + console.log(` ${colors.cyan(packageManagers.join(', '))}`); console.log(); } if (stack.frameworks.length > 0) { - console.log(chalk.bold('Frameworks')); + console.log(colors.bold('Frameworks')); for (const fw of stack.frameworks) { const version = fw.version ? ` (${fw.version})` : ''; - console.log(` ${chalk.green('●')} ${fw.name}${version}`); + console.log(` ${colors.success('●')} ${fw.name}${version}`); } console.log(); } if (stack.libraries.length > 0) { - console.log(chalk.bold('Libraries')); + console.log(colors.bold('Libraries')); for (const lib of stack.libraries.slice(0, 10)) { const version = lib.version ? ` (${lib.version})` : ''; - console.log(` ${chalk.green('●')} ${lib.name}${version}`); + console.log(` ${colors.success('●')} ${lib.name}${version}`); } if (stack.libraries.length > 10) { - console.log(` ${chalk.gray(`...and ${stack.libraries.length - 10} more`)}`); + console.log(` ${colors.muted(`...and ${stack.libraries.length - 10} more`)}`); } console.log(); } if (stack.styling.length > 0) { - console.log(chalk.bold('Styling')); + console.log(colors.bold('Styling')); for (const style of stack.styling) { - console.log(` ${chalk.green('●')} ${style.name}`); + console.log(` ${colors.success('●')} ${style.name}`); } console.log(); } if (stack.testing.length > 0) { - console.log(chalk.bold('Testing')); + console.log(colors.bold('Testing')); for (const test of stack.testing) { - console.log(` ${chalk.green('●')} ${test.name}`); + console.log(` ${colors.success('●')} ${test.name}`); } console.log(); } if (stack.databases.length > 0) { - console.log(chalk.bold('Databases')); + console.log(colors.bold('Databases')); for (const db of stack.databases) { - console.log(` ${chalk.green('●')} ${db.name}`); + console.log(` ${colors.success('●')} ${db.name}`); } console.log(); } if (structure) { - console.log(chalk.bold('Project Structure')); + console.log(colors.bold('Project Structure')); if (structure.type) { console.log(` Type: ${structure.type}`); } @@ -350,7 +350,7 @@ export class PrimerCommand extends Command { } if (conventions && Object.keys(conventions).some(k => conventions[k as keyof typeof conventions] !== undefined)) { - console.log(chalk.bold('Code Conventions')); + console.log(colors.bold('Code Conventions')); if (conventions.indentation) { console.log(` Indentation: ${conventions.indentation}`); } @@ -367,7 +367,7 @@ export class PrimerCommand extends Command { } if (ci && ci.hasCI) { - console.log(chalk.bold('CI/CD')); + console.log(colors.bold('CI/CD')); console.log(` Provider: ${ci.provider}`); if (ci.hasCD) { console.log(` Deployment: Yes`); @@ -376,7 +376,7 @@ export class PrimerCommand extends Command { } if (docker && (docker.hasDockerfile || docker.hasCompose)) { - console.log(chalk.bold('Docker')); + console.log(colors.bold('Docker')); if (docker.hasDockerfile) { console.log(` Dockerfile: Yes`); if (docker.baseImage) { @@ -390,35 +390,35 @@ export class PrimerCommand extends Command { } if (buildCommands && Object.keys(buildCommands).some(k => buildCommands[k as keyof typeof buildCommands])) { - console.log(chalk.bold('Build Commands')); + console.log(colors.bold('Build Commands')); if (buildCommands.install) { - console.log(` Install: ${chalk.gray(buildCommands.install)}`); + console.log(` Install: ${colors.muted(buildCommands.install)}`); } if (buildCommands.dev) { - console.log(` Dev: ${chalk.gray(buildCommands.dev)}`); + console.log(` Dev: ${colors.muted(buildCommands.dev)}`); } if (buildCommands.build) { - console.log(` Build: ${chalk.gray(buildCommands.build)}`); + console.log(` Build: ${colors.muted(buildCommands.build)}`); } if (buildCommands.test) { - console.log(` Test: ${chalk.gray(buildCommands.test)}`); + console.log(` Test: ${colors.muted(buildCommands.test)}`); } console.log(); } if (importantFiles.length > 0 && this.verbose) { - console.log(chalk.bold('Important Files')); + console.log(colors.bold('Important Files')); for (const file of importantFiles.slice(0, 15)) { - console.log(` ${chalk.gray(file)}`); + console.log(` ${colors.muted(file)}`); } if (importantFiles.length > 15) { - console.log(` ${chalk.gray(`...and ${importantFiles.length - 15} more`)}`); + console.log(` ${colors.muted(`...and ${importantFiles.length - 15} more`)}`); } console.log(); } - console.log(chalk.bold('Available Agents')); - console.log(chalk.gray(` Use --all-agents to generate for all ${Object.keys(AGENT_CONFIG).length} agents`)); - console.log(chalk.gray(` Use --agent to generate for specific agents`)); + console.log(colors.bold('Available Agents')); + console.log(colors.muted(` Use --all-agents to generate for all ${Object.keys(AGENT_CONFIG).length} agents`)); + console.log(colors.muted(` Use --agent to generate for specific agents`)); } } diff --git a/packages/cli/src/commands/profile.ts b/packages/cli/src/commands/profile.ts index ef97d777..3ec0daa2 100644 --- a/packages/cli/src/commands/profile.ts +++ b/packages/cli/src/commands/profile.ts @@ -1,5 +1,5 @@ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors, warn, success, error } from '../onboarding/index.js'; import { getActiveProfile, setActiveProfile, @@ -35,48 +35,48 @@ export class ProfileCommand extends Command { const profile = getProfile(this.name as ProfileName); if (!profile) { - console.log(chalk.red(`Profile not found: ${this.name}`)); - console.log(chalk.dim('Run `skillkit profile list` to see available profiles')); + error(`Profile not found: ${this.name}`); + console.log(colors.muted('Run `skillkit profile list` to see available profiles')); return 1; } setActiveProfile(this.name as ProfileName); - console.log(chalk.green(`✓ Switched to ${profile.name} mode`)); - console.log(chalk.dim(` Focus: ${profile.focus}`)); + success(`✓ Switched to ${profile.name} mode`); + console.log(colors.muted(` Focus: ${profile.focus}`)); return 0; } const active = getActiveProfile(); const profile = getProfile(active); - console.log(chalk.cyan(`Current Profile: ${active}\n`)); + console.log(colors.cyan(`Current Profile: ${active}\n`)); if (profile) { console.log(`Description: ${profile.description}`); console.log(`Focus: ${profile.focus}`); console.log(); - console.log(chalk.bold('Behaviors:')); + console.log(colors.bold('Behaviors:')); for (const behavior of profile.behaviors) { console.log(` • ${behavior}`); } console.log(); - console.log(chalk.bold('Priorities:')); + console.log(colors.bold('Priorities:')); console.log(` ${profile.priorities.join(' > ')}`); if (profile.preferredTools?.length) { console.log(); - console.log(chalk.bold('Preferred Tools:')); + console.log(colors.bold('Preferred Tools:')); console.log(` ${profile.preferredTools.join(', ')}`); } if (profile.avoidTools?.length) { - console.log(chalk.bold('Avoid Tools:')); + console.log(colors.bold('Avoid Tools:')); console.log(` ${profile.avoidTools.join(', ')}`); } } console.log(); - console.log(chalk.dim('Switch with: skillkit profile ')); + console.log(colors.muted('Switch with: skillkit profile ')); return 0; } @@ -103,21 +103,21 @@ export class ProfileListCommand extends Command { return 0; } - console.log(chalk.cyan('Available Profiles:\n')); + console.log(colors.cyan('Available Profiles:\n')); for (const profile of profiles) { const isActive = profile.name === active; - const marker = isActive ? chalk.green('●') : chalk.dim('○'); - const name = isActive ? chalk.bold(profile.name) : profile.name; - const type = isBuiltinProfile(profile.name) ? '' : chalk.dim(' (custom)'); + const marker = isActive ? colors.success('●') : colors.muted('○'); + const name = isActive ? colors.bold(profile.name) : profile.name; + const type = isBuiltinProfile(profile.name) ? '' : colors.muted(' (custom)'); console.log(` ${marker} ${name}${type}`); - console.log(` ${chalk.dim(profile.description)}`); + console.log(` ${colors.muted(profile.description)}`); console.log(` Focus: ${profile.focus}`); console.log(); } - console.log(chalk.dim('Switch with: skillkit profile ')); + console.log(colors.muted('Switch with: skillkit profile ')); return 0; } @@ -149,7 +149,7 @@ export class ProfileCreateCommand extends Command { async execute(): Promise { if (isBuiltinProfile(this.name as ProfileName)) { - console.log(chalk.red(`Cannot create profile: ${this.name} is a built-in profile`)); + error(`Cannot create profile: ${this.name} is a built-in profile`); return 1; } @@ -163,8 +163,8 @@ export class ProfileCreateCommand extends Command { addCustomProfile(profile); - console.log(chalk.green(`✓ Created profile: ${this.name}`)); - console.log(chalk.dim('Edit ~/.skillkit/profiles.yaml to customize')); + success(`✓ Created profile: ${this.name}`); + console.log(colors.muted('Edit ~/.skillkit/profiles.yaml to customize')); return 0; } @@ -182,18 +182,18 @@ export class ProfileRemoveCommand extends Command { async execute(): Promise { if (isBuiltinProfile(this.name as ProfileName)) { - console.log(chalk.red(`Cannot remove built-in profile: ${this.name}`)); + error(`Cannot remove built-in profile: ${this.name}`); return 1; } const removed = removeCustomProfile(this.name as ProfileName); if (!removed) { - console.log(chalk.yellow(`Profile not found: ${this.name}`)); + warn(`Profile not found: ${this.name}`); return 1; } - console.log(chalk.green(`✓ Removed profile: ${this.name}`)); + success(`✓ Removed profile: ${this.name}`); return 0; } diff --git a/packages/cli/src/commands/publish.ts b/packages/cli/src/commands/publish.ts index fc528afd..42c9259d 100644 --- a/packages/cli/src/commands/publish.ts +++ b/packages/cli/src/commands/publish.ts @@ -7,7 +7,7 @@ import { statSync, } from "node:fs"; import { join, basename, dirname, resolve, sep } from "node:path"; -import chalk from "chalk"; +import { colors, warn, success, error, step } from "../onboarding/index.js"; import { Command, Option } from "clipanion"; import { generateWellKnownIndex, @@ -136,19 +136,19 @@ export class PublishCommand extends Command { const basePath = this.skillPath || process.cwd(); const outputDir = this.output || basePath; - console.log(chalk.cyan("Generating well-known skills structure...\n")); + step("Generating well-known skills structure...\n"); const discoveredSkills = this.discoverSkills(basePath); if (discoveredSkills.length === 0) { - console.error(chalk.red("No skills found")); + error("No skills found"); console.error( - chalk.dim("Skills must contain a SKILL.md file with frontmatter"), + colors.muted("Skills must contain a SKILL.md file with frontmatter"), ); return 1; } - console.log(chalk.white(`Found ${discoveredSkills.length} skill(s):\n`)); + console.log(colors.primary(`Found ${discoveredSkills.length} skill(s):\n`)); const wellKnownSkills: WellKnownSkill[] = []; const validSkills: Array<{ @@ -161,20 +161,18 @@ export class PublishCommand extends Command { for (const skill of discoveredSkills) { const safeName = sanitizeSkillName(skill.name); if (!safeName) { - console.log( - chalk.yellow( - ` ${chalk.yellow("⚠")} Skipping "${skill.name}" (invalid name - must be alphanumeric with hyphens/underscores)`, - ), + warn( + ` ${colors.warning("⚠")} Skipping "${skill.name}" (invalid name - must be alphanumeric with hyphens/underscores)`, ); continue; } const files = this.getSkillFiles(skill.path); - console.log(chalk.dim(` ${chalk.green("●")} ${safeName}`)); + console.log(colors.muted(` ${colors.success("●")} ${safeName}`)); console.log( - chalk.dim(` Description: ${skill.description || "No description"}`), + colors.muted(` Description: ${skill.description || "No description"}`), ); - console.log(chalk.dim(` Files: ${files.join(", ")}`)); + console.log(colors.muted(` Files: ${files.join(", ")}`)); validSkills.push({ name: skill.name, @@ -193,24 +191,20 @@ export class PublishCommand extends Command { for (const skill of validSkills) { const scanResult = await scanner.scan(skill.path); if (scanResult.verdict === "fail") { - console.error( - chalk.red(`\nSecurity scan FAILED for "${skill.safeName}"`), - ); + error(`\nSecurity scan FAILED for "${skill.safeName}"`); console.error(formatSummary(scanResult)); - console.error(chalk.dim("Fix security issues before publishing.")); + console.error(colors.muted("Fix security issues before publishing.")); return 1; } if (scanResult.verdict === "warn") { - console.log( - chalk.yellow( - ` Security warnings for "${skill.safeName}" (${scanResult.findings.length} findings)`, - ), + warn( + ` Security warnings for "${skill.safeName}" (${scanResult.findings.length} findings)`, ); } } if (validSkills.length === 0) { - console.error(chalk.red("\nNo valid skills to publish")); + error("\nNo valid skills to publish"); return 1; } @@ -218,11 +212,11 @@ export class PublishCommand extends Command { if (this.format === "mintlify") { if (this.dryRun) { - console.log(chalk.yellow("Dry run - not writing files\n")); - console.log(chalk.white("Would generate (Mintlify format):")); + warn("Dry run - not writing files\n"); + console.log(colors.primary("Would generate (Mintlify format):")); for (const skill of validSkills) { console.log( - chalk.dim( + colors.muted( ` ${outputDir}/.well-known/skills/${skill.safeName}/skill.md`, ), ); @@ -241,7 +235,7 @@ export class PublishCommand extends Command { const resolvedDir = resolve(mintlifyDir); if (!resolvedDir.startsWith(resolvedOutput + sep)) { console.log( - chalk.red(`Skipping ${skill.safeName} (path traversal detected)`), + colors.error(`Skipping ${skill.safeName} (path traversal detected)`), ); continue; } @@ -253,26 +247,26 @@ export class PublishCommand extends Command { } } - console.log(chalk.green("Generated Mintlify well-known structure:\n")); + success("Generated Mintlify well-known structure:\n"); for (const skill of validSkills) { console.log( - chalk.dim( + colors.muted( ` ${outputDir}/.well-known/skills/${skill.safeName}/skill.md`, ), ); } console.log(""); - console.log(chalk.cyan("Next steps:")); + step("Next steps:"); console.log( - chalk.dim(" 1. Deploy the .well-known directory to your web server"), + colors.muted(" 1. Deploy the .well-known directory to your web server"), ); console.log( - chalk.dim( + colors.muted( " 2. Users can install via: skillkit install https://your-domain.com", ), ); console.log( - chalk.dim( + colors.muted( " 3. Skills auto-discovered from /.well-known/skills/{name}/skill.md", ), ); @@ -280,20 +274,20 @@ export class PublishCommand extends Command { } if (this.dryRun) { - console.log(chalk.yellow("Dry run - not writing files\n")); - console.log(chalk.white("Would generate:")); - console.log(chalk.dim(` ${outputDir}/.well-known/skills/index.json`)); + warn("Dry run - not writing files\n"); + console.log(colors.primary("Would generate:")); + console.log(colors.muted(` ${outputDir}/.well-known/skills/index.json`)); for (const skill of wellKnownSkills) { for (const file of skill.files) { console.log( - chalk.dim( + colors.muted( ` ${outputDir}/.well-known/skills/${skill.name}/${file}`, ), ); } } console.log(""); - console.log(chalk.white("index.json preview:")); + console.log(colors.primary("index.json preview:")); console.log( JSON.stringify(generateWellKnownIndex(wellKnownSkills), null, 2), ); @@ -309,9 +303,7 @@ export class PublishCommand extends Command { const resolvedWellKnownDir = resolve(wellKnownDir); if (!resolvedSkillDir.startsWith(resolvedWellKnownDir + sep)) { - console.log( - chalk.yellow(` Skipping "${skill.name}" (path traversal detected)`), - ); + warn(` Skipping "${skill.name}" (path traversal detected)`); continue; } @@ -333,24 +325,24 @@ export class PublishCommand extends Command { JSON.stringify(index, null, 2), ); - console.log(chalk.green("Generated well-known structure:\n")); - console.log(chalk.dim(` ${wellKnownDir}/index.json`)); + success("Generated well-known structure:\n"); + console.log(colors.muted(` ${wellKnownDir}/index.json`)); for (const skill of wellKnownSkills) { - console.log(chalk.dim(` ${wellKnownDir}/${skill.name}/`)); + console.log(colors.muted(` ${wellKnownDir}/${skill.name}/`)); } console.log(""); - console.log(chalk.cyan("Next steps:")); + step("Next steps:"); console.log( - chalk.dim(" 1. Deploy the .well-known directory to your web server"), + colors.muted(" 1. Deploy the .well-known directory to your web server"), ); console.log( - chalk.dim( + colors.muted( " 2. Users can install via: skillkit add https://your-domain.com", ), ); console.log( - chalk.dim( + colors.muted( " 3. Skills auto-discovered from /.well-known/skills/index.json", ), ); @@ -456,14 +448,14 @@ export class PublishSubmitCommand extends Command { const skillMdPath = this.findSkillMd(skillPath); if (!skillMdPath) { - console.error(chalk.red("No SKILL.md found")); + error("No SKILL.md found"); console.error( - chalk.dim("Run this command from a directory containing SKILL.md"), + colors.muted("Run this command from a directory containing SKILL.md"), ); return 1; } - console.log(chalk.cyan("Submitting skill to SkillKit marketplace...\n")); + step("Submitting skill to SkillKit marketplace...\n"); const content = readFileSync(skillMdPath, "utf-8"); const frontmatter = this.parseFrontmatter(content); @@ -472,9 +464,9 @@ export class PublishSubmitCommand extends Command { const repoInfo = await this.getRepoInfo(dirname(skillMdPath)); if (!repoInfo) { - console.error(chalk.red("Not a git repository or no remote configured")); + error("Not a git repository or no remote configured"); console.error( - chalk.dim( + colors.muted( "Your skill must be in a git repository with a GitHub remote", ), ); @@ -483,8 +475,8 @@ export class PublishSubmitCommand extends Command { const skillSlug = this.slugify(skillName); if (!skillSlug) { - console.error(chalk.red("Skill name produces an empty slug.")); - console.error(chalk.dim("Please pass --name with letters or numbers.")); + error("Skill name produces an empty slug."); + console.error(colors.muted("Please pass --name with letters or numbers.")); return 1; } @@ -498,16 +490,16 @@ export class PublishSubmitCommand extends Command { tags: frontmatter.tags || ["general"], }; - console.log(chalk.white("Skill details:")); - console.log(chalk.dim(` ID: ${skillEntry.id}`)); - console.log(chalk.dim(` Name: ${skillEntry.name}`)); - console.log(chalk.dim(` Description: ${skillEntry.description}`)); - console.log(chalk.dim(` Source: ${skillEntry.source}`)); - console.log(chalk.dim(` Tags: ${skillEntry.tags.join(", ")}`)); + console.log(colors.primary("Skill details:")); + console.log(colors.muted(` ID: ${skillEntry.id}`)); + console.log(colors.muted(` Name: ${skillEntry.name}`)); + console.log(colors.muted(` Description: ${skillEntry.description}`)); + console.log(colors.muted(` Source: ${skillEntry.source}`)); + console.log(colors.muted(` Tags: ${skillEntry.tags.join(", ")}`)); console.log(); if (this.dryRun) { - console.log(chalk.yellow("Dry run - not submitting")); + warn("Dry run - not submitting"); console.log(JSON.stringify(skillEntry, null, 2)); return 0; } @@ -517,7 +509,7 @@ export class PublishSubmitCommand extends Command { const issueBodyEncoded = encodeURIComponent(issueBody); const issueUrl = `https://github.com/rohitg00/skillkit/issues/new?title=${issueTitle}&body=${issueBodyEncoded}&labels=skill-submission,publish`; - console.log(chalk.green("Opening GitHub to submit your skill...\n")); + success("Opening GitHub to submit your skill...\n"); try { const { execFileSync } = await import("node:child_process"); @@ -533,12 +525,12 @@ export class PublishSubmitCommand extends Command { : [issueUrl]; execFileSync(cmd, args, { stdio: "ignore" }); - console.log(chalk.green("GitHub issue page opened!")); - console.log(chalk.dim("Review and submit the issue.")); + success("GitHub issue page opened!"); + console.log(colors.muted("Review and submit the issue.")); } catch { - console.log(chalk.yellow("Could not open browser automatically.")); - console.log(chalk.dim("Please open this URL manually:\n")); - console.log(chalk.cyan(issueUrl)); + warn("Could not open browser automatically."); + console.log(colors.muted("Please open this URL manually:\n")); + console.log(colors.cyan(issueUrl)); } return 0; diff --git a/packages/cli/src/commands/read.ts b/packages/cli/src/commands/read.ts index 6a3283d4..604cee3c 100644 --- a/packages/cli/src/commands/read.ts +++ b/packages/cli/src/commands/read.ts @@ -1,4 +1,4 @@ -import chalk from 'chalk'; +import { colors, warn, error } from '../onboarding/index.js'; import { Command, Option } from 'clipanion'; import { findSkill, readSkillContent } from '@skillkit/core'; import { getSearchDirs } from '../helpers.js'; @@ -30,7 +30,7 @@ export class ReadCommand extends Command { .filter(s => s.length > 0); if (skillNames.length === 0) { - console.error(chalk.red('No skill names provided')); + error('No skill names provided'); return 1; } @@ -40,16 +40,16 @@ export class ReadCommand extends Command { const skill = findSkill(skillName, searchDirs); if (!skill) { - console.error(chalk.red(`Skill not found: ${skillName}`)); - console.error(chalk.dim('Available directories:')); - searchDirs.forEach(d => console.error(chalk.dim(` - ${d}`))); + error(`Skill not found: ${skillName}`); + console.log(colors.muted('Available directories:')); + searchDirs.forEach(d => console.log(colors.muted(` - ${d}`))); exitCode = 1; continue; } if (!skill.enabled) { - console.error(chalk.yellow(`Skill disabled: ${skillName}`)); - console.error(chalk.dim('Enable with: skillkit enable ' + skillName)); + warn(`Skill disabled: ${skillName}`); + console.log(colors.muted('Enable with: skillkit enable ' + skillName)); exitCode = 1; continue; } @@ -57,7 +57,7 @@ export class ReadCommand extends Command { const content = readSkillContent(skill.path); if (!content) { - console.error(chalk.red(`Could not read SKILL.md for: ${skillName}`)); + error(`Could not read SKILL.md for: ${skillName}`); exitCode = 1; continue; } diff --git a/packages/cli/src/commands/resume.ts b/packages/cli/src/commands/resume.ts index 7016c4e1..4751ca0c 100644 --- a/packages/cli/src/commands/resume.ts +++ b/packages/cli/src/commands/resume.ts @@ -1,6 +1,6 @@ import { Command, Option } from 'clipanion'; import { resolve } from 'node:path'; -import chalk from 'chalk'; +import { colors, warn, success, error } from '../onboarding/index.js'; import { SessionManager } from '@skillkit/core'; /** @@ -36,47 +36,46 @@ export class ResumeCommand extends Command { const state = manager.get(); if (!state) { - console.log(chalk.yellow('No active session found.')); + warn('No active session found.'); return 1; } if (!state.currentExecution) { - console.log(chalk.yellow('No skill execution to resume.')); - console.log(chalk.dim('Start a new execution with: skillkit run ')); + warn('No skill execution to resume.'); + console.log(colors.muted('Start a new execution with: skillkit run ')); return 1; } if (state.currentExecution.status !== 'paused') { if (state.currentExecution.status === 'running') { - console.log(chalk.yellow('Execution is already running.')); + warn('Execution is already running.'); } else { - console.log(chalk.yellow(`Execution is ${state.currentExecution.status}.`)); + warn(`Execution is ${state.currentExecution.status}.`); } return 1; } - const success = manager.resume(); + const resumed = manager.resume(); - if (success) { + if (resumed) { const exec = state.currentExecution; - console.log(chalk.green('✓ Execution resumed')); + success('✓ Execution resumed'); console.log(); - console.log(` Skill: ${chalk.bold(exec.skillName)}`); + console.log(` Skill: ${colors.bold(exec.skillName)}`); console.log(` Progress: ${exec.currentStep}/${exec.totalSteps} tasks`); console.log(); - // Show next task const nextTask = exec.tasks.find((t) => t.status === 'pending' || t.status === 'in_progress'); if (nextTask) { - console.log(` Next task: ${chalk.cyan(nextTask.name)}`); + console.log(` Next task: ${colors.cyan(nextTask.name)}`); } console.log(); - console.log(chalk.dim('The execution will continue from where it left off.')); - console.log(chalk.dim('View status: skillkit status')); + console.log(colors.muted('The execution will continue from where it left off.')); + console.log(colors.muted('View status: skillkit status')); return 0; } else { - console.log(chalk.red('Failed to resume execution.')); + error('Failed to resume execution.'); return 1; } } diff --git a/packages/cli/src/commands/run.ts b/packages/cli/src/commands/run.ts index ff900a4d..43b7913f 100644 --- a/packages/cli/src/commands/run.ts +++ b/packages/cli/src/commands/run.ts @@ -1,8 +1,7 @@ import { Command, Option } from 'clipanion'; import { resolve, join } from 'node:path'; import { existsSync, readFileSync, statSync } from 'node:fs'; -import chalk from 'chalk'; -import ora from 'ora'; +import { colors, step, success, error, spinner } from '../onboarding/index.js'; import { createExecutionEngine, discoverSkills, @@ -89,22 +88,20 @@ export class RunCommand extends Command { return 1; } - // Show skill info if (!this.json) { - console.log(chalk.cyan(`Executing skill: ${chalk.bold(skill.name)}`)); + step(`Executing skill: ${colors.bold(skill.name)}`); if (skill.description) { - console.log(chalk.dim(skill.description)); + console.log(colors.muted(skill.description)); } console.log(); } - // Dry run mode if (this.dryRun) { this.showDryRun(skill); return 0; } - const spinner = ora(); + const oraSpinner = spinner(); let currentTask = ''; // Create execution engine @@ -115,38 +112,38 @@ export class RunCommand extends Command { switch (event.type) { case 'task_start': currentTask = event.taskName || ''; - spinner.start(`Task ${(event.taskIndex || 0) + 1}/${event.totalTasks}: ${currentTask}`); + oraSpinner.start(`Task ${(event.taskIndex || 0) + 1}/${event.totalTasks}: ${currentTask}`); break; - case 'task_complete': - const icon = event.status === 'completed' ? chalk.green('✓') : chalk.red('✗'); - spinner.stopAndPersist({ - symbol: icon, - text: `Task ${(event.taskIndex || 0) + 1}/${event.totalTasks}: ${currentTask}`, - }); + case 'task_complete': { + const icon = event.status === 'completed' ? colors.success('✓') : colors.error('✗'); + oraSpinner.stop(); + console.log(`${icon} Task ${(event.taskIndex || 0) + 1}/${event.totalTasks}: ${currentTask}`); if (event.error && this.verbose) { - console.log(chalk.red(` Error: ${event.error}`)); + console.log(colors.error(` Error: ${event.error}`)); } break; + } case 'checkpoint': - spinner.info(`Checkpoint: ${event.message}`); + oraSpinner.stop(); + console.log(`Checkpoint: ${event.message}`); break; case 'verification': if (this.verbose) { - console.log(chalk.dim(` ${event.message}`)); + console.log(colors.muted(` ${event.message}`)); } break; case 'complete': console.log(); if (event.status === 'completed') { - console.log(chalk.green('✓ Skill execution completed')); + success('✓ Skill execution completed'); } else { - console.log(chalk.red(`✗ Skill execution ${event.status}`)); + error(`✗ Skill execution ${event.status}`); if (event.error) { - console.log(chalk.red(` Error: ${event.error}`)); + console.log(colors.error(` Error: ${event.error}`)); } } break; @@ -176,18 +173,17 @@ export class RunCommand extends Command { if (this.json) { console.log(JSON.stringify(result, null, 2)); } else { - // Show summary console.log(); - console.log(chalk.cyan('Summary:')); - console.log(` Duration: ${chalk.dim(this.formatDuration(result.durationMs || 0))}`); - console.log(` Tasks: ${chalk.dim(`${result.tasks.filter(t => t.status === 'completed').length}/${result.tasks.length} completed`)}`); + step('Summary:'); + console.log(` Duration: ${colors.muted(this.formatDuration(result.durationMs || 0))}`); + console.log(` Tasks: ${colors.muted(`${result.tasks.filter(t => t.status === 'completed').length}/${result.tasks.length} completed`)}`); if (result.filesModified.length > 0) { - console.log(` Files modified: ${chalk.dim(result.filesModified.length.toString())}`); + console.log(` Files modified: ${colors.muted(result.filesModified.length.toString())}`); } if (result.commits.length > 0) { - console.log(` Commits: ${chalk.dim(result.commits.join(', '))}`); + console.log(` Commits: ${colors.muted(result.commits.join(', '))}`); } } @@ -206,14 +202,14 @@ export class RunCommand extends Command { return skill; } - console.error(chalk.red(`Skill "${this.skillRef}" not found`)); - console.log(chalk.dim('Install skills with: skillkit install ')); + console.error(colors.error(`Skill "${this.skillRef}" not found`)); + console.log(colors.muted('Install skills with: skillkit install ')); return null; } private loadSkillFromFile(filePath: string): ExecutableSkill | null { if (!existsSync(filePath)) { - console.error(chalk.red(`File not found: ${filePath}`)); + console.error(colors.error(`File not found: ${filePath}`)); return null; } @@ -221,7 +217,6 @@ export class RunCommand extends Command { const content = readFileSync(filePath, 'utf-8'); const frontmatter = extractFrontmatter(content); - // Parse tasks from frontmatter if present const tasks = this.parseTasksFromFrontmatter(frontmatter); return { @@ -232,8 +227,8 @@ export class RunCommand extends Command { content, tasks, }; - } catch (error) { - console.error(chalk.red(`Failed to load skill: ${error}`)); + } catch (err) { + console.error(colors.error(`Failed to load skill: ${err}`)); return null; } } @@ -281,46 +276,46 @@ export class RunCommand extends Command { } private showDryRun(skill: ExecutableSkill): void { - console.log(chalk.cyan('Dry Run - Execution Plan')); + step('Dry Run - Execution Plan'); console.log(); - console.log(`Skill: ${chalk.bold(skill.name)}`); + console.log(`Skill: ${colors.bold(skill.name)}`); if (skill.description) { - console.log(`Description: ${chalk.dim(skill.description)}`); + console.log(`Description: ${colors.muted(skill.description)}`); } - console.log(`Source: ${chalk.dim(skill.source)}`); + console.log(`Source: ${colors.muted(skill.source)}`); console.log(); if (skill.tasks && skill.tasks.length > 0) { - console.log(chalk.cyan('Tasks:')); + step('Tasks:'); for (let i = 0; i < skill.tasks.length; i++) { const task = skill.tasks[i]; const typeLabel = this.getTaskTypeLabel(task.type); console.log(` ${i + 1}. ${task.name} ${typeLabel}`); if (task.action) { - console.log(` ${chalk.dim(task.action)}`); + console.log(` ${colors.muted(task.action)}`); } if (task.files && task.files.length > 0) { - console.log(` Files: ${chalk.dim(task.files.join(', '))}`); + console.log(` Files: ${colors.muted(task.files.join(', '))}`); } } } else { - console.log(chalk.dim('No structured tasks defined. Skill will be executed as a single unit.')); + console.log(colors.muted('No structured tasks defined. Skill will be executed as a single unit.')); } console.log(); - console.log(chalk.dim('This is a dry run. Remove --dry-run to execute.')); + console.log(colors.muted('This is a dry run. Remove --dry-run to execute.')); } private getTaskTypeLabel(type: string): string { switch (type) { case 'auto': - return chalk.green('[auto]'); + return colors.success('[auto]'); case 'checkpoint:human-verify': - return chalk.yellow('[verify]'); + return colors.warning('[verify]'); case 'checkpoint:decision': - return chalk.blue('[decision]'); + return colors.info('[decision]'); case 'checkpoint:human-action': - return chalk.magenta('[manual]'); + return colors.magenta('[manual]'); default: return ''; } diff --git a/packages/cli/src/commands/save.ts b/packages/cli/src/commands/save.ts index 169f25d9..5100c59c 100644 --- a/packages/cli/src/commands/save.ts +++ b/packages/cli/src/commands/save.ts @@ -1,11 +1,10 @@ import { cpSync, mkdirSync } from 'node:fs'; import { dirname, join } from 'node:path'; -import chalk from 'chalk'; +import { colors, warn, spinner } from '../onboarding/index.js'; import { Command, Option } from 'clipanion'; import { ContentExtractor, SkillGenerator, AutoTagger } from '@skillkit/core'; import type { AgentType } from '@skillkit/core'; import { getAdapter } from '@skillkit/agents'; -import { spinner } from '../onboarding/index.js'; export class SaveCommand extends Command { static override paths = [['save']]; @@ -47,11 +46,11 @@ export class SaveCommand extends Command { async execute(): Promise { const sources = [this.url, this.text, this.file].filter(Boolean); if (sources.length === 0) { - console.log(chalk.red('Provide a URL, --text, or --file')); + console.log(colors.error('Provide a URL, --text, or --file')); return 1; } if (sources.length > 1) { - console.log(chalk.red('Provide only one of: URL, --text, or --file')); + console.log(colors.error('Provide only one of: URL, --text, or --file')); return 1; } @@ -83,13 +82,13 @@ export class SaveCommand extends Command { global: this.global, }); - s.stop(chalk.green('Skill saved')); + s.stop(colors.success('Skill saved')); console.log(''); - console.log(chalk.bold(' Name: ') + chalk.cyan(result.name)); - console.log(chalk.bold(' Path: ') + chalk.dim(result.skillPath)); + console.log(colors.bold(' Name: ') + colors.cyan(result.name)); + console.log(colors.bold(' Path: ') + colors.muted(result.skillPath)); if (tags.length > 0) { - console.log(chalk.bold(' Tags: ') + tags.map(t => chalk.yellow(t)).join(', ')); + console.log(colors.bold(' Tags: ') + tags.map(t => colors.warning(t)).join(', ')); } if (this.agent) { @@ -102,9 +101,9 @@ export class SaveCommand extends Command { const targetDir = join(adapter.skillsDir, result.name); mkdirSync(targetDir, { recursive: true }); cpSync(skillDir, targetDir, { recursive: true }); - console.log(chalk.green(` Installed to ${agentName}: `) + chalk.dim(targetDir)); + console.log(colors.success(` Installed to ${agentName}: `) + colors.muted(targetDir)); } catch (err) { - console.log(chalk.yellow(` Skipped ${agentName}: ${err instanceof Error ? err.message : String(err)}`)); + warn(` Skipped ${agentName}: ${err instanceof Error ? err.message : String(err)}`); } } } @@ -112,8 +111,8 @@ export class SaveCommand extends Command { console.log(''); return 0; } catch (err) { - s.stop(chalk.red('Failed')); - console.log(chalk.red(err instanceof Error ? err.message : String(err))); + s.stop(colors.error('Failed')); + console.log(colors.error(err instanceof Error ? err.message : String(err))); return 1; } } diff --git a/packages/cli/src/commands/session.ts b/packages/cli/src/commands/session.ts index 76126ae6..616dda61 100644 --- a/packages/cli/src/commands/session.ts +++ b/packages/cli/src/commands/session.ts @@ -1,5 +1,5 @@ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors, warn, success, step } from '../onboarding/index.js'; import { loadSessionFile, saveSessionFile, @@ -32,7 +32,7 @@ export class SessionCommand extends Command { }); async execute(): Promise { - console.log(chalk.cyan('Session commands:\n')); + step('Session commands:\n'); console.log(' session status Show current session state'); console.log(' session start Start a new session'); console.log(' session load Load session from specific date'); @@ -65,8 +65,8 @@ export class SessionStatusCommand extends Command { const session = getMostRecentSession(); if (!session) { - console.log(chalk.yellow('No active session found')); - console.log(chalk.dim('Start one with: skillkit session start')); + warn('No active session found'); + console.log(colors.muted('Start one with: skillkit session start')); return 0; } @@ -80,7 +80,7 @@ export class SessionStatusCommand extends Command { } private printSession(session: SessionFile): void { - console.log(chalk.cyan(`Session: ${session.date}\n`)); + step(`Session: ${session.date}\n`); console.log(`Agent: ${session.agent}`); console.log(`Project: ${session.projectPath}`); console.log(`Started: ${session.startedAt}`); @@ -88,33 +88,33 @@ export class SessionStatusCommand extends Command { console.log(); if (session.completed.length > 0) { - console.log(chalk.green('Completed:')); + console.log(colors.success('Completed:')); for (const task of session.completed) { - console.log(` ${chalk.green('✓')} ${task}`); + console.log(` ${colors.success('✓')} ${task}`); } console.log(); } if (session.inProgress.length > 0) { - console.log(chalk.yellow('In Progress:')); + console.log(colors.warning('In Progress:')); for (const task of session.inProgress) { - console.log(` ${chalk.yellow('○')} ${task}`); + console.log(` ${colors.warning('○')} ${task}`); } console.log(); } if (session.notes.length > 0) { - console.log(chalk.blue('Notes for Next Session:')); - for (const note of session.notes) { - console.log(` ${chalk.dim('•')} ${note}`); + console.log(colors.info('Notes for Next Session:')); + for (const n of session.notes) { + console.log(` ${colors.muted('•')} ${n}`); } console.log(); } if (session.contextToLoad.length > 0) { - console.log(chalk.dim('Context to Load:')); + console.log(colors.muted('Context to Load:')); for (const ctx of session.contextToLoad) { - console.log(` ${chalk.dim('•')} ${ctx}`); + console.log(` ${colors.muted('•')} ${ctx}`); } } } @@ -142,8 +142,8 @@ export class SessionStartCommand extends Command { const session = createSessionFile(agent, projectPath); const filepath = saveSessionFile(session); - console.log(chalk.green(`✓ Session started: ${session.date}`)); - console.log(chalk.dim(` Saved to: ${filepath}`)); + success(`✓ Session started: ${session.date}`); + console.log(colors.muted(` Saved to: ${filepath}`)); return 0; } @@ -163,31 +163,31 @@ export class SessionLoadCommand extends Command { const session = this.date ? loadSessionFile(this.date) : getMostRecentSession(); if (!session) { - console.log(chalk.yellow('Session not found')); + warn('Session not found'); return 1; } - console.log(chalk.green(`✓ Loaded session: ${session.date}`)); + success(`✓ Loaded session: ${session.date}`); console.log(); if (session.notes.length > 0) { - console.log(chalk.cyan('Notes from previous session:')); - for (const note of session.notes) { - console.log(` ${chalk.dim('•')} ${note}`); + step('Notes from previous session:'); + for (const n of session.notes) { + console.log(` ${colors.muted('•')} ${n}`); } console.log(); } if (session.inProgress.length > 0) { - console.log(chalk.yellow('Tasks still in progress:')); + console.log(colors.warning('Tasks still in progress:')); for (const task of session.inProgress) { - console.log(` ${chalk.yellow('○')} ${task}`); + console.log(` ${colors.warning('○')} ${task}`); } console.log(); } if (session.contextToLoad.length > 0) { - console.log(chalk.dim('Context to load:')); + console.log(colors.muted('Context to load:')); for (const ctx of session.contextToLoad) { console.log(` ${ctx}`); } @@ -218,7 +218,7 @@ export class SessionListCommand extends Command { const sessions = listSessions(limit); if (sessions.length === 0) { - console.log(chalk.yellow('No sessions found')); + warn('No sessions found'); return 0; } @@ -227,13 +227,13 @@ export class SessionListCommand extends Command { return 0; } - console.log(chalk.cyan(`Recent Sessions (${sessions.length}):\n`)); + step(`Recent Sessions (${sessions.length}):\n`); for (const session of sessions) { const progress = `${session.completedCount}/${session.taskCount}`; - const notesIndicator = session.hasNotes ? chalk.blue(' [notes]') : ''; - console.log(` ${chalk.bold(session.date)} - ${session.agent}`); - console.log(` ${chalk.dim(session.projectPath)}`); + const notesIndicator = session.hasNotes ? colors.info(' [notes]') : ''; + console.log(` ${colors.bold(session.date)} - ${session.agent}`); + console.log(` ${colors.muted(session.projectPath)}`); console.log(` Tasks: ${progress}${notesIndicator}`); console.log(); } @@ -265,7 +265,7 @@ export class SessionNoteCommand extends Command { saveSessionFile(updated); - console.log(chalk.green('✓ Note added')); + success('✓ Note added'); return 0; } } @@ -297,7 +297,7 @@ export class SessionCompleteCommand extends Command { saveSessionFile(updated); - console.log(chalk.green(`✓ Completed: ${this.task}`)); + success(`✓ Completed: ${this.task}`); return 0; } } @@ -325,7 +325,7 @@ export class SessionInProgressCommand extends Command { saveSessionFile(updated); - console.log(chalk.yellow(`○ In progress: ${this.task}`)); + console.log(colors.warning(`○ In progress: ${this.task}`)); return 0; } } @@ -354,7 +354,7 @@ export class SessionSnapshotSaveCommand extends Command { const state = sessionMgr.get(); if (!state) { - console.log(chalk.yellow('No active session to snapshot')); + warn('No active session to snapshot'); return 1; } @@ -375,9 +375,9 @@ export class SessionSnapshotSaveCommand extends Command { } manager.save(this.name, state, observations, this.desc); - console.log(chalk.green(`\u2713 Snapshot saved: ${this.name}`)); + success(`\u2713 Snapshot saved: ${this.name}`); if (this.desc) { - console.log(chalk.dim(` ${this.desc}`)); + console.log(colors.muted(` ${this.desc}`)); } return 0; } @@ -398,7 +398,7 @@ export class SessionSnapshotRestoreCommand extends Command { const manager = new SnapshotManager(projectPath); if (!manager.exists(this.name)) { - console.log(chalk.red(`Snapshot "${this.name}" not found`)); + console.log(colors.error(`Snapshot "${this.name}" not found`)); return 1; } @@ -429,7 +429,7 @@ export class SessionSnapshotRestoreCommand extends Command { // Non-critical: session state restored even if observations fail } - console.log(chalk.green(`\u2713 Snapshot restored: ${this.name}`)); + success(`\u2713 Snapshot restored: ${this.name}`); return 0; } } @@ -457,18 +457,18 @@ export class SessionSnapshotListCommand extends Command { } if (snapshots.length === 0) { - console.log(chalk.yellow('No snapshots found')); - console.log(chalk.dim('Save one with: skillkit session snapshot save ')); + warn('No snapshots found'); + console.log(colors.muted('Save one with: skillkit session snapshot save ')); return 0; } - console.log(chalk.cyan(`Snapshots (${snapshots.length}):\n`)); + step(`Snapshots (${snapshots.length}):\n`); for (const snap of snapshots) { - console.log(` ${chalk.bold(snap.name)}`); + console.log(` ${colors.bold(snap.name)}`); console.log(` Created: ${snap.createdAt}`); if (snap.description) { - console.log(` ${chalk.dim(snap.description)}`); + console.log(` ${colors.muted(snap.description)}`); } console.log(` Skills in history: ${snap.skillCount}`); console.log(); @@ -493,11 +493,11 @@ export class SessionSnapshotDeleteCommand extends Command { const manager = new SnapshotManager(projectPath); if (!manager.delete(this.name)) { - console.log(chalk.red(`Snapshot "${this.name}" not found`)); + console.log(colors.error(`Snapshot "${this.name}" not found`)); return 1; } - console.log(chalk.green(`\u2713 Snapshot deleted: ${this.name}`)); + success(`\u2713 Snapshot deleted: ${this.name}`); return 0; } } diff --git a/packages/cli/src/commands/settings.ts b/packages/cli/src/commands/settings.ts index 5b8600f5..9a25b14f 100644 --- a/packages/cli/src/commands/settings.ts +++ b/packages/cli/src/commands/settings.ts @@ -1,5 +1,5 @@ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors, success, step } from '../onboarding/index.js'; import { loadConfig, saveConfig, type SkillkitConfig, type AgentType } from '@skillkit/core'; const VALID_AGENTS: AgentType[] = [ @@ -69,7 +69,7 @@ export class SettingsCommand extends Command { autoSync: true, }; saveConfig(defaultConfig, this.global); - console.log(chalk.green('Settings reset to defaults')); + success('Settings reset to defaults'); return 0; } @@ -81,7 +81,7 @@ export class SettingsCommand extends Command { 'enabledSkills', 'disabledSkills', 'marketplaceSources', 'defaultTimeout' ]; if (!knownKeys.includes(this.get)) { - console.error(chalk.red(`Unknown setting: ${this.get}`)); + console.error(colors.error(`Unknown setting: ${this.get}`)); return 1; } const value = this.getConfigValue(config, this.get); @@ -100,18 +100,18 @@ export class SettingsCommand extends Command { const value = valueParts.join('='); if (!key || !this.set.includes('=')) { - console.error(chalk.red('Invalid format. Use: --set key=value')); + console.error(colors.error('Invalid format. Use: --set key=value')); return 1; } const result = this.setConfigValue(config, key, value); if (!result.success) { - console.error(chalk.red(result.error)); + console.error(colors.error(result.error || 'Unknown error')); return 1; } saveConfig(config, this.global); - console.log(chalk.green(`${key} = ${value}`)); + success(`${key} = ${value}`); return 0; } @@ -119,8 +119,8 @@ export class SettingsCommand extends Command { if (this.json) { console.log(JSON.stringify(config, null, 2)); } else { - console.log(chalk.cyan('SkillKit Settings')); - console.log(chalk.dim('─'.repeat(40))); + step('SkillKit Settings'); + console.log(colors.muted('─'.repeat(40))); console.log(); const settings = [ @@ -132,25 +132,25 @@ export class SettingsCommand extends Command { ]; for (const setting of settings) { - console.log(` ${chalk.white(setting.label.padEnd(14))} ${chalk.dim(setting.value)}`); + console.log(` ${colors.primary(setting.label.padEnd(14))} ${colors.muted(setting.value)}`); } if (config.enabledSkills?.length) { console.log(); - console.log(` ${chalk.white('Enabled Skills'.padEnd(14))} ${chalk.dim(config.enabledSkills.join(', '))}`); + console.log(` ${colors.primary('Enabled Skills'.padEnd(14))} ${colors.muted(config.enabledSkills.join(', '))}`); } if (config.disabledSkills?.length) { - console.log(` ${chalk.white('Disabled Skills'.padEnd(14))} ${chalk.dim(config.disabledSkills.join(', '))}`); + console.log(` ${colors.primary('Disabled Skills'.padEnd(14))} ${colors.muted(config.disabledSkills.join(', '))}`); } if (config.marketplaceSources?.length) { - console.log(` ${chalk.white('Marketplaces'.padEnd(14))} ${chalk.dim(config.marketplaceSources.join(', '))}`); + console.log(` ${colors.primary('Marketplaces'.padEnd(14))} ${colors.muted(config.marketplaceSources.join(', '))}`); } console.log(); - console.log(chalk.dim('Use --set key=value to modify settings')); - console.log(chalk.dim('Available keys: agent, autoSync, cacheDir, skillsDir, defaultTimeout')); + console.log(colors.muted('Use --set key=value to modify settings')); + console.log(colors.muted('Available keys: agent, autoSync, cacheDir, skillsDir, defaultTimeout')); } return 0; diff --git a/packages/cli/src/commands/status.ts b/packages/cli/src/commands/status.ts index 8acd8a31..04dc0d3e 100644 --- a/packages/cli/src/commands/status.ts +++ b/packages/cli/src/commands/status.ts @@ -1,7 +1,7 @@ import { Command, Option } from 'clipanion'; import { resolve } from 'node:path'; import { existsSync } from 'node:fs'; -import chalk from 'chalk'; +import { colors } from '../onboarding/index.js'; import type { AgentType } from '@skillkit/core'; import { SessionManager, findAllSkills, loadConfig, getProjectConfigPath } from '@skillkit/core'; import { detectAgent, getAdapter } from '@skillkit/agents'; @@ -77,34 +77,34 @@ export class StatusCommand extends Command { // Show current execution if (state.currentExecution) { const exec = state.currentExecution; - const statusColor = exec.status === 'paused' ? chalk.yellow : chalk.green; + const statusColor = exec.status === 'paused' ? colors.warning : colors.success; - console.log(chalk.cyan('Current Execution:\n')); - console.log(` Skill: ${chalk.bold(exec.skillName)}`); - console.log(` Source: ${chalk.dim(exec.skillSource)}`); + console.log(colors.cyan('Current Execution:\n')); + console.log(` Skill: ${colors.bold(exec.skillName)}`); + console.log(` Source: ${colors.muted(exec.skillSource)}`); console.log(` Status: ${statusColor(exec.status)}`); console.log(` Progress: ${exec.currentStep}/${exec.totalSteps} tasks`); - console.log(` Started: ${chalk.dim(new Date(exec.startedAt).toLocaleString())}`); + console.log(` Started: ${colors.muted(new Date(exec.startedAt).toLocaleString())}`); if (exec.pausedAt) { - console.log(` Paused: ${chalk.dim(new Date(exec.pausedAt).toLocaleString())}`); + console.log(` Paused: ${colors.muted(new Date(exec.pausedAt).toLocaleString())}`); } - console.log(chalk.cyan('\n Tasks:')); + console.log(colors.cyan('\n Tasks:')); for (const task of exec.tasks) { const statusIcon = this.getStatusIcon(task.status); const statusText = this.getStatusColor(task.status)(task.status); console.log(` ${statusIcon} ${task.name} - ${statusText}`); if (task.error) { - console.log(` ${chalk.red('Error:')} ${task.error}`); + console.log(` ${colors.error('Error:')} ${task.error}`); } } if (exec.status === 'paused') { - console.log(chalk.yellow('\n Resume with: skillkit resume')); + console.log(colors.warning('\n Resume with: skillkit resume')); } } else { - console.log(chalk.dim('No active execution.')); + console.log(colors.muted('No active execution.')); } // Show history @@ -113,22 +113,22 @@ export class StatusCommand extends Command { const history = manager.getHistory(limit); if (history.length > 0) { - console.log(chalk.cyan('\nExecution History:\n')); + console.log(colors.cyan('\nExecution History:\n')); for (const entry of history) { - const statusColor = entry.status === 'completed' ? chalk.green : chalk.red; + const statusColor = entry.status === 'completed' ? colors.success : colors.error; const duration = this.formatDuration(entry.durationMs); - console.log(` ${statusColor('●')} ${chalk.bold(entry.skillName)}`); - console.log(` ${chalk.dim(entry.skillSource)} • ${duration}`); - console.log(` ${chalk.dim(new Date(entry.completedAt).toLocaleString())}`); + console.log(` ${statusColor('●')} ${colors.bold(entry.skillName)}`); + console.log(` ${colors.muted(entry.skillSource)} • ${duration}`); + console.log(` ${colors.muted(new Date(entry.completedAt).toLocaleString())}`); if (entry.commits.length > 0) { - console.log(` Commits: ${chalk.dim(entry.commits.join(', '))}`); + console.log(` Commits: ${colors.muted(entry.commits.join(', '))}`); } if (entry.error) { - console.log(` ${chalk.red('Error:')} ${entry.error}`); + console.log(` ${colors.error('Error:')} ${entry.error}`); } console.log(); @@ -138,12 +138,12 @@ export class StatusCommand extends Command { // Show decisions if (state.decisions.length > 0) { - console.log(chalk.cyan('Saved Decisions:\n')); + console.log(colors.cyan('Saved Decisions:\n')); for (const decision of state.decisions.slice(0, 5)) { - console.log(` ${chalk.dim(decision.key)}: ${decision.value}`); + console.log(` ${colors.muted(decision.key)}: ${decision.value}`); } if (state.decisions.length > 5) { - console.log(chalk.dim(` ... and ${state.decisions.length - 5} more`)); + console.log(colors.muted(` ... and ${state.decisions.length - 5} more`)); } } @@ -199,59 +199,59 @@ export class StatusCommand extends Command { private showOverview(overview: StatusOverview): void { console.log(''); - console.log(chalk.cyan(' Project Overview')); - console.log(` Agent: ${chalk.bold(overview.agent)}`); - console.log(` Config: ${overview.config ? chalk.green('skillkit.yaml') : chalk.dim('none (defaults)')}`); - console.log(` Version: ${chalk.bold(overview.version)}`); + console.log(colors.cyan(' Project Overview')); + console.log(` Agent: ${colors.bold(overview.agent)}`); + console.log(` Config: ${overview.config ? colors.success('skillkit.yaml') : colors.muted('none (defaults)')}`); + console.log(` Version: ${colors.bold(overview.version)}`); console.log(''); - console.log(chalk.cyan(` Skills (${overview.totalSkills} installed)`)); + console.log(colors.cyan(` Skills (${overview.totalSkills} installed)`)); console.log(` Project: ${overview.projectSkills} skills in ${overview.skillsDir}`); console.log(` Global: ${overview.globalSkills} skills`); console.log(''); - console.log(chalk.cyan(' Recent Activity')); + console.log(colors.cyan(' Recent Activity')); if (overview.recentHistory.length === 0) { - console.log(chalk.dim(' No recent executions.')); + console.log(colors.muted(' No recent executions.')); } else { for (const entry of overview.recentHistory) { - const statusColor = entry.status === 'completed' ? chalk.green : chalk.red; + const statusColor = entry.status === 'completed' ? colors.success : colors.error; console.log(` ${statusColor('\u25cf')} ${entry.skillName} - ${new Date(entry.completedAt).toLocaleDateString()}`); } } console.log(''); - console.log(chalk.dim(' Tip: Run "skillkit doctor" for a full health check.')); + console.log(colors.muted(' Tip: Run "skillkit doctor" for a full health check.')); console.log(''); } private getStatusIcon(status: string): string { switch (status) { case 'completed': - return chalk.green('✓'); + return colors.success('✓'); case 'failed': - return chalk.red('✗'); + return colors.error('✗'); case 'in_progress': - return chalk.blue('●'); + return colors.info('●'); case 'paused': - return chalk.yellow('⏸'); + return colors.warning('⏸'); default: - return chalk.dim('○'); + return colors.muted('○'); } } private getStatusColor(status: string): (text: string) => string { switch (status) { case 'completed': - return chalk.green; + return colors.success; case 'failed': - return chalk.red; + return colors.error; case 'in_progress': - return chalk.blue; + return colors.info; case 'paused': - return chalk.yellow; + return colors.warning; default: - return chalk.dim; + return colors.muted; } } diff --git a/packages/cli/src/commands/team.ts b/packages/cli/src/commands/team.ts index a9c9e9dd..04364a8f 100644 --- a/packages/cli/src/commands/team.ts +++ b/packages/cli/src/commands/team.ts @@ -5,7 +5,7 @@ */ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors } from '../onboarding/index.js'; import { createTeamManager, createSkillBundle, exportBundle, importBundle } from '@skillkit/core'; import { join } from 'node:path'; @@ -62,23 +62,23 @@ export class TeamCommand extends Command { case 'bundle-import': return await this.importSkillBundle(); default: - this.context.stderr.write(chalk.red(`Unknown action: ${this.action}\n`)); + this.context.stderr.write(colors.error(`Unknown action: ${this.action}\n`)); this.context.stderr.write('Available actions: init, share, import, list, sync, remove, bundle-create, bundle-export, bundle-import\n'); return 1; } } catch (err) { - this.context.stderr.write(chalk.red(`✗ ${err instanceof Error ? err.message : 'Unknown error'}\n`)); + this.context.stderr.write(colors.error(`✗ ${err instanceof Error ? err.message : 'Unknown error'}\n`)); return 1; } } private async initTeam(teamManager: ReturnType): Promise { if (!this.name) { - this.context.stderr.write(chalk.red('--name is required for init\n')); + this.context.stderr.write(colors.error('--name is required for init\n')); return 1; } if (!this.registry) { - this.context.stderr.write(chalk.red('--registry is required for init\n')); + this.context.stderr.write(colors.error('--registry is required for init\n')); return 1; } @@ -87,7 +87,7 @@ export class TeamCommand extends Command { registryUrl: this.registry, }); - this.context.stdout.write(chalk.green('✓ Team initialized!\n')); + this.context.stdout.write(colors.success('✓ Team initialized!\n')); this.context.stdout.write(` Team ID: ${config.teamId}\n`); this.context.stdout.write(` Registry: ${config.registryUrl}\n`); return 0; @@ -96,12 +96,12 @@ export class TeamCommand extends Command { private async shareSkill(teamManager: ReturnType): Promise { const config = teamManager.load(); if (!config) { - this.context.stderr.write(chalk.red('Team not initialized. Run `skillkit team init` first.\n')); + this.context.stderr.write(colors.error('Team not initialized. Run `skillkit team init` first.\n')); return 1; } if (!this.name) { - this.context.stderr.write(chalk.red('--name is required for share\n')); + this.context.stderr.write(colors.error('--name is required for share\n')); return 1; } @@ -111,7 +111,7 @@ export class TeamCommand extends Command { tags: this.tags?.split(',').map((t) => t.trim()), }); - this.context.stdout.write(chalk.green('✓ Skill shared!\n')); + this.context.stdout.write(colors.success('✓ Skill shared!\n')); this.context.stdout.write(` Name: ${shared.name}\n`); this.context.stdout.write(` Version: ${shared.version}\n`); this.context.stdout.write(` Source: ${shared.source}\n`); @@ -121,12 +121,12 @@ export class TeamCommand extends Command { private async importSkill(teamManager: ReturnType): Promise { const config = teamManager.load(); if (!config) { - this.context.stderr.write(chalk.red('Team not initialized. Run `skillkit team init` first.\n')); + this.context.stderr.write(colors.error('Team not initialized. Run `skillkit team init` first.\n')); return 1; } if (!this.name) { - this.context.stderr.write(chalk.red('--name is required for import\n')); + this.context.stderr.write(colors.error('--name is required for import\n')); return 1; } @@ -136,14 +136,14 @@ export class TeamCommand extends Command { }); if (!result.success) { - this.context.stderr.write(chalk.red(`✗ ${result.error}\n`)); + this.context.stderr.write(colors.error(`✗ ${result.error}\n`)); return 1; } if (this.dryRun) { - this.context.stdout.write(chalk.cyan(`[dry-run] Would import to: ${result.path}\n`)); + this.context.stdout.write(colors.cyan(`[dry-run] Would import to: ${result.path}\n`)); } else { - this.context.stdout.write(chalk.green(`✓ Skill imported to: ${result.path}\n`)); + this.context.stdout.write(colors.success(`✓ Skill imported to: ${result.path}\n`)); } return 0; } @@ -151,14 +151,14 @@ export class TeamCommand extends Command { private async listSkills(teamManager: ReturnType): Promise { const config = teamManager.load(); if (!config) { - this.context.stderr.write(chalk.red('Team not initialized. Run `skillkit team init` first.\n')); + this.context.stderr.write(colors.error('Team not initialized. Run `skillkit team init` first.\n')); return 1; } const skills = teamManager.listSharedSkills(); - this.context.stdout.write(chalk.cyan(`Team: ${config.teamName}\n`)); - this.context.stdout.write(chalk.gray(`Registry: ${config.registryUrl}\n\n`)); + this.context.stdout.write(colors.cyan(`Team: ${config.teamName}\n`)); + this.context.stdout.write(colors.muted(`Registry: ${config.registryUrl}\n\n`)); if (skills.length === 0) { this.context.stdout.write('No shared skills yet. Use `skillkit team share` to share a skill.\n'); @@ -167,9 +167,9 @@ export class TeamCommand extends Command { this.context.stdout.write(`Shared Skills (${skills.length}):\n`); for (const skill of skills) { - this.context.stdout.write(chalk.cyan(` ${skill.name}`) + ` v${skill.version}\n`); + this.context.stdout.write(colors.cyan(` ${skill.name}`) + ` v${skill.version}\n`); if (skill.description) { - this.context.stdout.write(chalk.gray(` ${skill.description}\n`)); + this.context.stdout.write(colors.muted(` ${skill.description}\n`)); } this.context.stdout.write(` by ${skill.author} | ${skill.downloads || 0} downloads\n`); } @@ -180,7 +180,7 @@ export class TeamCommand extends Command { private async syncTeam(teamManager: ReturnType): Promise { const config = teamManager.load(); if (!config) { - this.context.stderr.write(chalk.red('Team not initialized. Run `skillkit team init` first.\n')); + this.context.stderr.write(colors.error('Team not initialized. Run `skillkit team init` first.\n')); return 1; } @@ -188,7 +188,7 @@ export class TeamCommand extends Command { const result = await teamManager.sync(); - this.context.stdout.write(chalk.green('✓ Sync complete!\n')); + this.context.stdout.write(colors.success('✓ Sync complete!\n')); if (result.added.length > 0) { this.context.stdout.write(` Added: ${result.added.join(', ')}\n`); } @@ -205,39 +205,39 @@ export class TeamCommand extends Command { private async removeSkill(teamManager: ReturnType): Promise { const config = teamManager.load(); if (!config) { - this.context.stderr.write(chalk.red('Team not initialized. Run `skillkit team init` first.\n')); + this.context.stderr.write(colors.error('Team not initialized. Run `skillkit team init` first.\n')); return 1; } if (!this.name) { - this.context.stderr.write(chalk.red('--name is required for remove\n')); + this.context.stderr.write(colors.error('--name is required for remove\n')); return 1; } const removed = teamManager.removeSkill(this.name); if (!removed) { - this.context.stderr.write(chalk.red(`Skill "${this.name}" not found in team registry.\n`)); + this.context.stderr.write(colors.error(`Skill "${this.name}" not found in team registry.\n`)); return 1; } - this.context.stdout.write(chalk.green(`✓ Skill "${this.name}" removed from team registry.\n`)); + this.context.stdout.write(colors.success(`✓ Skill "${this.name}" removed from team registry.\n`)); return 0; } private async createBundle(teamManager: ReturnType): Promise { const config = teamManager.load(); if (!config) { - this.context.stderr.write(chalk.red('Team not initialized. Run `skillkit team init` first.\n')); + this.context.stderr.write(colors.error('Team not initialized. Run `skillkit team init` first.\n')); return 1; } if (!this.name) { - this.context.stderr.write(chalk.red('--name is required for bundle-create\n')); + this.context.stderr.write(colors.error('--name is required for bundle-create\n')); return 1; } if (!this.skills) { - this.context.stderr.write(chalk.red('--skills is required for bundle-create\n')); + this.context.stderr.write(colors.error('--skills is required for bundle-create\n')); return 1; } @@ -254,14 +254,14 @@ export class TeamCommand extends Command { try { bundle.addSkill(skillPath); addedCount++; - this.context.stdout.write(chalk.gray(` + ${skillName}\n`)); + this.context.stdout.write(colors.muted(` + ${skillName}\n`)); } catch (err) { - this.context.stderr.write(chalk.yellow(` ⚠ Skipping ${skillName}: ${err instanceof Error ? err.message : 'Unknown error'}\n`)); + this.context.stderr.write(colors.warning(` ⚠ Skipping ${skillName}: ${err instanceof Error ? err.message : 'Unknown error'}\n`)); } } if (addedCount === 0) { - this.context.stderr.write(chalk.red('No skills were added to the bundle.\n')); + this.context.stderr.write(colors.error('No skills were added to the bundle.\n')); return 1; } @@ -270,11 +270,11 @@ export class TeamCommand extends Command { const result = exportBundle(bundle, outputPath); if (!result.success) { - this.context.stderr.write(chalk.red(`✗ ${result.error}\n`)); + this.context.stderr.write(colors.error(`✗ ${result.error}\n`)); return 1; } - this.context.stdout.write(chalk.green(`\n✓ Bundle "${this.name}" created with ${addedCount} skills!\n`)); + this.context.stdout.write(colors.success(`\n✓ Bundle "${this.name}" created with ${addedCount} skills!\n`)); this.context.stdout.write(` Checksum: ${bundle.getChecksum()}\n`); this.context.stdout.write(` Output: ${result.path}\n`); return 0; @@ -283,17 +283,17 @@ export class TeamCommand extends Command { private async exportSkillBundle(teamManager: ReturnType): Promise { const config = teamManager.load(); if (!config) { - this.context.stderr.write(chalk.red('Team not initialized. Run `skillkit team init` first.\n')); + this.context.stderr.write(colors.error('Team not initialized. Run `skillkit team init` first.\n')); return 1; } if (!this.name) { - this.context.stderr.write(chalk.red('--name is required for bundle-export\n')); + this.context.stderr.write(colors.error('--name is required for bundle-export\n')); return 1; } if (!this.output) { - this.context.stderr.write(chalk.red('--output is required for bundle-export\n')); + this.context.stderr.write(colors.error('--output is required for bundle-export\n')); return 1; } @@ -303,7 +303,7 @@ export class TeamCommand extends Command { // Check if bundle exists const { existsSync, readFileSync, writeFileSync } = await import('node:fs'); if (!existsSync(bundlePath)) { - this.context.stderr.write(chalk.red(`Bundle "${this.name}" not found. Create it first with bundle-create.\n`)); + this.context.stderr.write(colors.error(`Bundle "${this.name}" not found. Create it first with bundle-create.\n`)); return 1; } @@ -311,19 +311,19 @@ export class TeamCommand extends Command { const content = readFileSync(bundlePath, 'utf-8'); writeFileSync(this.output, content, 'utf-8'); - this.context.stdout.write(chalk.green(`✓ Bundle exported to: ${this.output}\n`)); + this.context.stdout.write(colors.success(`✓ Bundle exported to: ${this.output}\n`)); return 0; } private async importSkillBundle(): Promise { if (!this.source) { - this.context.stderr.write(chalk.red('--source is required for bundle-import\n')); + this.context.stderr.write(colors.error('--source is required for bundle-import\n')); return 1; } const { existsSync } = await import('node:fs'); if (!existsSync(this.source)) { - this.context.stderr.write(chalk.red(`Bundle file not found: ${this.source}\n`)); + this.context.stderr.write(colors.error(`Bundle file not found: ${this.source}\n`)); return 1; } @@ -331,28 +331,28 @@ export class TeamCommand extends Command { const skillsDir = join(projectPath, 'skills'); if (this.dryRun) { - this.context.stdout.write(chalk.cyan('[dry-run] Would import bundle to: ' + skillsDir + '\n')); + this.context.stdout.write(colors.cyan('[dry-run] Would import bundle to: ' + skillsDir + '\n')); return 0; } const result = importBundle(this.source, skillsDir, { overwrite: this.overwrite }); if (!result.success && result.imported.length === 0) { - this.context.stderr.write(chalk.red('✗ Failed to import bundle:\n')); + this.context.stderr.write(colors.error('✗ Failed to import bundle:\n')); for (const error of result.errors) { - this.context.stderr.write(chalk.red(` - ${error}\n`)); + this.context.stderr.write(colors.error(` - ${error}\n`)); } return 1; } - this.context.stdout.write(chalk.green(`✓ Imported ${result.imported.length} skills from bundle!\n`)); + this.context.stdout.write(colors.success(`✓ Imported ${result.imported.length} skills from bundle!\n`)); for (const name of result.imported) { - this.context.stdout.write(chalk.gray(` + ${name}\n`)); + this.context.stdout.write(colors.muted(` + ${name}\n`)); } if (result.errors.length > 0) { - this.context.stdout.write(chalk.yellow('\nWarnings:\n')); + this.context.stdout.write(colors.warning('\nWarnings:\n')); for (const error of result.errors) { - this.context.stdout.write(chalk.yellow(` - ${error}\n`)); + this.context.stdout.write(colors.warning(` - ${error}\n`)); } } return 0; diff --git a/packages/cli/src/commands/test.ts b/packages/cli/src/commands/test.ts index a58c2b2f..de5aa3e1 100644 --- a/packages/cli/src/commands/test.ts +++ b/packages/cli/src/commands/test.ts @@ -1,7 +1,7 @@ import { Command, Option } from 'clipanion'; import { resolve, join } from 'node:path'; import { existsSync, readFileSync, readdirSync } from 'node:fs'; -import chalk from 'chalk'; +import { colors, warn, success, error, step } from '../onboarding/index.js'; import { runTestSuite, createTestSuiteFromFrontmatter, @@ -85,8 +85,8 @@ export class TestCommand extends Command { if (skillFiles.length === 0) { if (!this.json) { - console.log(chalk.yellow('No skills found with tests.')); - console.log(chalk.dim('Add tests to your skills using YAML frontmatter.')); + warn('No skills found with tests.'); + console.log(colors.muted('Add tests to your skills using YAML frontmatter.')); } else { console.log(JSON.stringify({ results: [], passed: true, total: 0 })); } @@ -100,13 +100,13 @@ export class TestCommand extends Command { if (filesToTest.length === 0) { if (!this.json) { - console.log(chalk.yellow(`No skills found matching "${this.skill}"`)); + warn(`No skills found matching "${this.skill}"`); } return 1; } if (!this.json) { - console.log(chalk.bold('Running skill tests...\n')); + console.log(colors.bold('Running skill tests...\n')); } // Parse tags @@ -126,7 +126,7 @@ export class TestCommand extends Command { } if (!this.json) { - console.log(chalk.blue(`Testing: ${suite.skillName}`)); + step(`Testing: ${suite.skillName}`); } const result = await runTestSuite(suite, { @@ -141,21 +141,21 @@ export class TestCommand extends Command { switch (event.type) { case 'test_start': - console.log(chalk.dim(` Running: ${event.testName}`)); + console.log(colors.muted(` Running: ${event.testName}`)); break; case 'test_end': if (event.passed) { - console.log(chalk.green(` ✓ ${event.testName}`)); + console.log(colors.success(` ✓ ${event.testName}`)); } else { - console.log(chalk.red(` ✗ ${event.testName}`)); + console.log(colors.error(` ✗ ${event.testName}`)); if (event.error) { - console.log(chalk.red(` ${event.error}`)); + console.log(colors.error(` ${event.error}`)); } } break; case 'assertion_end': if (this.verbose && !event.passed) { - console.log(chalk.red(` - ${event.assertionType}: ${event.error}`)); + console.log(colors.error(` - ${event.assertionType}: ${event.error}`)); } break; } @@ -169,7 +169,7 @@ export class TestCommand extends Command { } if (!this.json) { - const icon = result.passed ? chalk.green('✓') : chalk.red('✗'); + const icon = result.passed ? colors.success('✓') : colors.error('✗'); const status = result.passed ? 'PASSED' : 'FAILED'; console.log( `${icon} ${suite.skillName}: ${result.passedCount}/${result.tests.length} tests ${status} (${result.duration}ms)\n` @@ -203,21 +203,21 @@ export class TestCommand extends Command { const failedTests = results.reduce((acc, r) => acc + r.failedCount, 0); const skippedTests = results.reduce((acc, r) => acc + r.skippedCount, 0); - console.log(chalk.bold('Summary:')); + console.log(colors.bold('Summary:')); console.log(` Skills tested: ${results.length}`); console.log(` Total tests: ${totalTests}`); - console.log(chalk.green(` Passed: ${passedTests}`)); + console.log(colors.success(` Passed: ${passedTests}`)); if (failedTests > 0) { - console.log(chalk.red(` Failed: ${failedTests}`)); + console.log(colors.error(` Failed: ${failedTests}`)); } if (skippedTests > 0) { - console.log(chalk.yellow(` Skipped: ${skippedTests}`)); + console.log(colors.warning(` Skipped: ${skippedTests}`)); } if (allPassed) { - console.log(chalk.green('\n✓ All tests passed!')); + success('\n✓ All tests passed!'); } else { - console.log(chalk.red('\n✗ Some tests failed.')); + error('\n✗ Some tests failed.'); } } diff --git a/packages/cli/src/commands/timeline.ts b/packages/cli/src/commands/timeline.ts index b7108e5d..92519b3e 100644 --- a/packages/cli/src/commands/timeline.ts +++ b/packages/cli/src/commands/timeline.ts @@ -1,5 +1,5 @@ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors, warn, error } from '../onboarding/index.js'; import { SessionTimeline } from '@skillkit/core'; import type { TimelineEventType } from '@skillkit/core'; @@ -52,8 +52,8 @@ export class TimelineCommand extends Command { let types: TimelineEventType[] | undefined; if (this.type) { if (!validTypes.includes(this.type as TimelineEventType)) { - console.log(chalk.red(`Invalid event type: ${this.type}`)); - console.log(chalk.dim(`Valid types: ${validTypes.join(', ')}`)); + error(`Invalid event type: ${this.type}`); + console.log(colors.muted(`Valid types: ${validTypes.join(', ')}`)); return 1; } types = [this.type as TimelineEventType]; @@ -72,8 +72,8 @@ export class TimelineCommand extends Command { } if (data.events.length === 0) { - console.log(chalk.yellow('No timeline events found.')); - console.log(chalk.dim('Events are recorded during skill execution, git commits, and observations.')); + warn('No timeline events found.'); + console.log(colors.muted('Events are recorded during skill execution, git commits, and observations.')); return 0; } diff --git a/packages/cli/src/commands/workflow/create.ts b/packages/cli/src/commands/workflow/create.ts index 73cd561c..e69f1f45 100644 --- a/packages/cli/src/commands/workflow/create.ts +++ b/packages/cli/src/commands/workflow/create.ts @@ -1,6 +1,6 @@ import { Command, Option } from 'clipanion'; import { resolve } from 'node:path'; -import chalk from 'chalk'; +import { colors, success } from '../../onboarding/index.js'; import { createWorkflowTemplate, saveWorkflow, serializeWorkflow } from '@skillkit/core'; /** @@ -52,11 +52,11 @@ export class WorkflowCreateCommand extends Command { const filePath = saveWorkflow(targetPath, workflow); - console.log(chalk.green(`✓ Created workflow: ${chalk.bold(this.workflowName)}`)); - console.log(chalk.dim(` File: ${filePath}`)); + success(`✓ Created workflow: ${colors.bold(this.workflowName)}`); + console.log(colors.muted(` File: ${filePath}`)); console.log(); - console.log(chalk.dim('Edit the workflow file to add skills and configure waves.')); - console.log(chalk.dim('Run with: skillkit workflow run ' + this.workflowName)); + console.log(colors.muted('Edit the workflow file to add skills and configure waves.')); + console.log(colors.muted('Run with: skillkit workflow run ' + this.workflowName)); return 0; } diff --git a/packages/cli/src/commands/workflow/list.ts b/packages/cli/src/commands/workflow/list.ts index 67df9ba4..a9a6d688 100644 --- a/packages/cli/src/commands/workflow/list.ts +++ b/packages/cli/src/commands/workflow/list.ts @@ -1,6 +1,6 @@ import { Command, Option } from 'clipanion'; import { resolve } from 'node:path'; -import chalk from 'chalk'; +import { colors, warn, error, step } from '../../onboarding/index.js'; import { listWorkflows } from '@skillkit/core'; /** @@ -43,8 +43,8 @@ export class WorkflowListCommand extends Command { try { workflows = listWorkflows(targetPath); } catch (err) { - console.log(chalk.red('Failed to list workflows.')); - console.log(chalk.dim(String(err))); + error('Failed to list workflows.'); + console.log(colors.muted(String(err))); return 1; } @@ -54,40 +54,40 @@ export class WorkflowListCommand extends Command { } if (workflows.length === 0) { - console.log(chalk.yellow('No workflows found.')); - console.log(chalk.dim('Create a workflow with: skillkit workflow create')); - console.log(chalk.dim('Or add YAML files to .skillkit/workflows/')); + warn('No workflows found.'); + console.log(colors.muted('Create a workflow with: skillkit workflow create')); + console.log(colors.muted('Or add YAML files to .skillkit/workflows/')); return 0; } - console.log(chalk.cyan(`Available Workflows (${workflows.length}):\n`)); + step(`Available Workflows (${workflows.length}):\n`); for (const workflow of workflows) { - console.log(` ${chalk.bold(workflow.name)}`); + console.log(` ${colors.bold(workflow.name)}`); if (workflow.description) { - console.log(` ${chalk.dim(workflow.description)}`); + console.log(` ${colors.muted(workflow.description)}`); } if (this.verbose) { - console.log(` Version: ${chalk.dim(workflow.version || 'N/A')}`); - console.log(` Waves: ${chalk.dim(workflow.waves.length.toString())}`); + console.log(` Version: ${colors.muted(workflow.version || 'N/A')}`); + console.log(` Waves: ${colors.muted(workflow.waves.length.toString())}`); const totalSkills = workflow.waves.reduce( (sum, wave) => sum + wave.skills.length, 0 ); - console.log(` Total Skills: ${chalk.dim(totalSkills.toString())}`); + console.log(` Total Skills: ${colors.muted(totalSkills.toString())}`); if (workflow.tags && workflow.tags.length > 0) { - console.log(` Tags: ${chalk.dim(workflow.tags.join(', '))}`); + console.log(` Tags: ${colors.muted(workflow.tags.join(', '))}`); } } console.log(); } - console.log(chalk.dim('Run a workflow with: skillkit workflow run ')); + console.log(colors.muted('Run a workflow with: skillkit workflow run ')); return 0; } diff --git a/packages/cli/src/commands/workflow/pipeline.ts b/packages/cli/src/commands/workflow/pipeline.ts index 008205f9..be38808b 100644 --- a/packages/cli/src/commands/workflow/pipeline.ts +++ b/packages/cli/src/commands/workflow/pipeline.ts @@ -1,5 +1,5 @@ import { Command, Option } from 'clipanion'; -import chalk from 'chalk'; +import { colors, warn, success, error, step } from '../../onboarding/index.js'; import { BUILTIN_PIPELINES, getBuiltinPipeline, @@ -31,21 +31,21 @@ export class WorkflowPipelineCommand extends Command { async execute(): Promise { if (!this.pipeline) { - console.log(chalk.cyan('Usage: skillkit workflow pipeline \n')); + step('Usage: skillkit workflow pipeline \n'); console.log('Available pipelines:'); for (const p of BUILTIN_PIPELINES) { - console.log(` ${chalk.bold(p.id)} - ${p.description}`); + console.log(` ${colors.bold(p.id)} - ${p.description}`); } console.log(); - console.log(chalk.dim('Run: skillkit workflow pipeline list for details')); + console.log(colors.muted('Run: skillkit workflow pipeline list for details')); return 0; } const pipeline = getBuiltinPipeline(this.pipeline); if (!pipeline) { - console.log(chalk.red(`Pipeline not found: ${this.pipeline}`)); - console.log(chalk.dim('Run `skillkit workflow pipeline list` to see available pipelines')); + error(`Pipeline not found: ${this.pipeline}`); + console.log(colors.muted('Run `skillkit workflow pipeline list` to see available pipelines')); return 1; } @@ -57,46 +57,46 @@ export class WorkflowPipelineCommand extends Command { } private showPipeline(pipeline: AgentPipeline): number { - console.log(chalk.cyan(`Pipeline: ${pipeline.name}\n`)); + step(`Pipeline: ${pipeline.name}\n`); console.log(`Description: ${pipeline.description}`); console.log(); - console.log(chalk.bold('Stages:')); + console.log(colors.bold('Stages:')); for (let i = 0; i < pipeline.stages.length; i++) { const stage = pipeline.stages[i]; const arrow = i < pipeline.stages.length - 1 ? '→' : ''; - console.log(` ${i + 1}. ${chalk.bold(stage.name)} (@${stage.agent})`); - console.log(` ${chalk.dim(stage.description)}`); + console.log(` ${i + 1}. ${colors.bold(stage.name)} (@${stage.agent})`); + console.log(` ${colors.muted(stage.description)}`); if (arrow) { - console.log(` ${chalk.dim(arrow)}`); + console.log(` ${colors.muted(arrow)}`); } } console.log(); - console.log(chalk.dim('(dry-run mode - no execution)')); + console.log(colors.muted('(dry-run mode - no execution)')); return 0; } private async runPipeline(pipeline: AgentPipeline): Promise { - console.log(chalk.cyan(`Starting Pipeline: ${pipeline.name}\n`)); + step(`Starting Pipeline: ${pipeline.name}\n`); for (let i = 0; i < pipeline.stages.length; i++) { const stage = pipeline.stages[i]; const stageNum = `[${i + 1}/${pipeline.stages.length}]`; - console.log(`${chalk.blue(stageNum)} ${chalk.bold(stage.name)}`); + console.log(`${colors.info(stageNum)} ${colors.bold(stage.name)}`); console.log(` Agent: @${stage.agent}`); - console.log(` ${chalk.dim(stage.description)}`); + console.log(` ${colors.muted(stage.description)}`); console.log(); - console.log(chalk.yellow(` → Invoke @${stage.agent} for: ${stage.description}`)); - console.log(chalk.dim(' (Manual agent invocation required in current implementation)')); + warn(` → Invoke @${stage.agent} for: ${stage.description}`); + console.log(colors.muted(' (Manual agent invocation required in current implementation)')); console.log(); } - console.log(chalk.green('✓ Pipeline stages displayed')); - console.log(chalk.dim('Execute each stage by invoking the agents in order')); + success('✓ Pipeline stages displayed'); + console.log(colors.muted('Execute each stage by invoking the agents in order')); return 0; } @@ -122,16 +122,16 @@ export class WorkflowPipelineListCommand extends Command { return 0; } - console.log(chalk.cyan(`Available Pipelines (${pipelines.length}):\n`)); + step(`Available Pipelines (${pipelines.length}):\n`); for (const pipeline of pipelines) { - console.log(chalk.bold(` ${pipeline.id}`)); + console.log(colors.bold(` ${pipeline.id}`)); console.log(` ${pipeline.name}: ${pipeline.description}`); console.log(` Stages: ${pipeline.stages.map(s => s.name).join(' → ')}`); console.log(); } - console.log(chalk.dim('Run with: skillkit workflow pipeline ')); + console.log(colors.muted('Run with: skillkit workflow pipeline ')); return 0; } diff --git a/packages/cli/src/commands/workflow/run.ts b/packages/cli/src/commands/workflow/run.ts index ba307838..a828295e 100644 --- a/packages/cli/src/commands/workflow/run.ts +++ b/packages/cli/src/commands/workflow/run.ts @@ -1,7 +1,6 @@ import { Command, Option } from 'clipanion'; import { resolve } from 'node:path'; -import chalk from 'chalk'; -import ora from 'ora'; +import { colors, success, error as errorFn, spinner } from '../../onboarding/index.js'; import { loadWorkflowByName, loadWorkflow, @@ -87,25 +86,25 @@ export class WorkflowRunCommand extends Command { } else if (this.workflowName) { workflow = loadWorkflowByName(targetPath, this.workflowName); if (!workflow) { - console.error(chalk.red(`Workflow "${this.workflowName}" not found`)); - console.log(chalk.dim('List available workflows: skillkit workflow list')); + errorFn(`Workflow "${this.workflowName}" not found`); + console.log(colors.muted('List available workflows: skillkit workflow list')); return 1; } } else { - console.error(chalk.red('Please specify a workflow name or --file')); + errorFn('Please specify a workflow name or --file'); return 1; } - } catch (error) { - console.error(chalk.red(`Failed to load workflow: ${error}`)); + } catch (err) { + errorFn(`Failed to load workflow: ${err}`); return 1; } // Validate workflow const validation = validateWorkflow(workflow); if (!validation.valid) { - console.error(chalk.red('Invalid workflow:')); - for (const error of validation.errors) { - console.error(chalk.red(` • ${error}`)); + errorFn('Invalid workflow:'); + for (const validationError of validation.errors) { + errorFn(` • ${validationError}`); } return 1; } @@ -117,13 +116,13 @@ export class WorkflowRunCommand extends Command { } // Execute workflow - console.log(chalk.cyan(`Executing workflow: ${chalk.bold(workflow.name)}`)); + console.log(colors.cyan(`Executing workflow: ${colors.bold(workflow.name)}`)); if (workflow.description) { - console.log(chalk.dim(workflow.description)); + console.log(colors.muted(workflow.description)); } console.log(); - const spinner = ora(); + const spin = spinner(); let currentWave = -1; // Create the skill executor (real or simulated) @@ -132,7 +131,7 @@ export class WorkflowRunCommand extends Command { delay: 500, onExecute: (skillName) => { if (this.verbose && !this.json) { - console.log(chalk.dim(` [Simulated] ${skillName}`)); + console.log(colors.muted(` [Simulated] ${skillName}`)); } }, }) @@ -144,17 +143,17 @@ export class WorkflowRunCommand extends Command { if (this.verbose && !this.json) { switch (event.type) { case 'skill_found': - console.log(chalk.dim(` Found: ${event.message}`)); + console.log(colors.muted(` Found: ${event.message}`)); break; case 'skill_not_found': - console.log(chalk.red(` Not found: ${event.skillName}`)); + console.log(colors.error(` Not found: ${event.skillName}`)); break; case 'agent_selected': - console.log(chalk.dim(` Agent: ${event.agent}`)); + console.log(colors.muted(` Agent: ${event.agent}`)); break; case 'execution_complete': if (!event.success) { - console.log(chalk.red(` Error: ${event.error}`)); + console.log(colors.error(` Error: ${event.error}`)); } break; } @@ -171,38 +170,34 @@ export class WorkflowRunCommand extends Command { switch (event.type) { case 'wave_start': currentWave = event.waveIndex || 0; - spinner.start(`Wave ${currentWave + 1}: ${event.waveName || 'Executing...'}`); + spin.start(`Wave ${currentWave + 1}: ${event.waveName || 'Executing...'}`); break; case 'skill_start': if (this.verbose) { - spinner.text = `Wave ${currentWave + 1}: Running ${event.skillName}...`; + spin.message(`Wave ${currentWave + 1}: Running ${event.skillName}...`); } break; case 'skill_complete': if (this.verbose) { - const icon = event.status === 'completed' ? chalk.green('✓') : chalk.red('✗'); + const icon = event.status === 'completed' ? colors.success('✓') : colors.error('✗'); console.log(` ${icon} ${event.skillName}`); } break; case 'wave_complete': - const waveIcon = event.status === 'completed' ? chalk.green('✓') : chalk.red('✗'); - spinner.stopAndPersist({ - symbol: waveIcon, - text: `Wave ${(event.waveIndex || 0) + 1}: ${event.waveName || 'Complete'}`, - }); + spin.stop(`Wave ${(event.waveIndex || 0) + 1}: ${event.waveName || 'Complete'}`); break; case 'workflow_complete': console.log(); if (event.status === 'completed') { - console.log(chalk.green('✓ Workflow completed successfully')); + success('✓ Workflow completed successfully'); } else { - console.log(chalk.red(`✗ Workflow ${event.status}`)); + errorFn(`✗ Workflow ${event.status}`); if (event.error) { - console.log(chalk.red(` Error: ${event.error}`)); + errorFn(` Error: ${event.error}`); } } break; @@ -220,19 +215,19 @@ export class WorkflowRunCommand extends Command { } private showDryRun(workflow: { name: string; description?: string; waves: Array<{ name?: string; parallel: boolean; skills: Array }> }): void { - console.log(chalk.cyan('Dry Run - Workflow Execution Plan')); + console.log(colors.cyan('Dry Run - Workflow Execution Plan')); console.log(); - console.log(`Workflow: ${chalk.bold(workflow.name)}`); + console.log(`Workflow: ${colors.bold(workflow.name)}`); if (workflow.description) { - console.log(`Description: ${chalk.dim(workflow.description)}`); + console.log(`Description: ${colors.muted(workflow.description)}`); } console.log(); for (let i = 0; i < workflow.waves.length; i++) { const wave = workflow.waves[i]; - const modeLabel = wave.parallel ? chalk.blue('[parallel]') : chalk.yellow('[sequential]'); + const modeLabel = wave.parallel ? colors.info('[parallel]') : colors.warning('[sequential]'); - console.log(`${chalk.cyan(`Wave ${i + 1}`)}: ${wave.name || 'Unnamed'} ${modeLabel}`); + console.log(`${colors.cyan(`Wave ${i + 1}`)}: ${wave.name || 'Unnamed'} ${modeLabel}`); for (const skill of wave.skills) { const skillName = typeof skill === 'string' ? skill : skill.skill; @@ -242,7 +237,7 @@ export class WorkflowRunCommand extends Command { console.log(); } - console.log(chalk.dim('This is a dry run. No skills were executed.')); - console.log(chalk.dim('Remove --dry-run to execute the workflow.')); + console.log(colors.muted('This is a dry run. No skills were executed.')); + console.log(colors.muted('Remove --dry-run to execute the workflow.')); } }