cairn-work 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,108 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
2
+ import { detectAgents, getAgentName, isSupportedAgent } from './detect.js';
3
+ import { existsSync, mkdirSync, rmSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { tmpdir } from 'os';
6
+
7
+ describe('Agent Detection', () => {
8
+ let originalEnv;
9
+ let testHome;
10
+
11
+ beforeEach(() => {
12
+ // Save original environment
13
+ originalEnv = { ...process.env };
14
+
15
+ // Create temporary home directory for testing
16
+ testHome = join(tmpdir(), `cairn-test-${Date.now()}`);
17
+ mkdirSync(testHome, { recursive: true });
18
+ });
19
+
20
+ afterEach(() => {
21
+ // Restore environment
22
+ process.env = originalEnv;
23
+
24
+ // Clean up test directory
25
+ if (existsSync(testHome)) {
26
+ rmSync(testHome, { recursive: true, force: true });
27
+ }
28
+ });
29
+
30
+ test('detectAgents returns generic when no agents found', () => {
31
+ // Clear environment variables that might detect agents
32
+ delete process.env.ANTHROPIC_API_KEY;
33
+ delete process.env.CLAUDE_CODE_WORKSPACE;
34
+ delete process.env.CURSOR_WORKSPACE;
35
+ delete process.env.WINDSURF_WORKSPACE;
36
+
37
+ const result = detectAgents();
38
+
39
+ expect(result).toHaveProperty('primary');
40
+ expect(result).toHaveProperty('detected');
41
+ expect(Array.isArray(result.detected)).toBe(true);
42
+
43
+ // If running in environment with actual agents installed, primary might not be generic
44
+ // Just verify it returns a valid agent type
45
+ expect(isSupportedAgent(result.primary)).toBe(true);
46
+ });
47
+
48
+ test('detectAgents finds Clawdbot when .clawdbot exists', () => {
49
+ // This test only runs if .clawdbot actually exists
50
+ const clawdbotPath = join(process.env.HOME, '.clawdbot');
51
+ if (existsSync(clawdbotPath)) {
52
+ const result = detectAgents();
53
+ expect(result.detected).toContain('clawdbot');
54
+ }
55
+ });
56
+
57
+ test('detectAgents finds Claude Code when env var set', () => {
58
+ process.env.ANTHROPIC_API_KEY = 'test-key';
59
+
60
+ const result = detectAgents();
61
+
62
+ expect(result.detected).toContain('claude-code');
63
+ });
64
+
65
+ test('detectAgents finds Cursor when .cursorrules exists', () => {
66
+ // Only test if in actual Cursor environment
67
+ if (existsSync('.cursorrules')) {
68
+ const result = detectAgents();
69
+ expect(result.detected).toContain('cursor');
70
+ }
71
+ });
72
+
73
+ test('detectAgents handles multiple agents', () => {
74
+ process.env.ANTHROPIC_API_KEY = 'test-key';
75
+ process.env.CURSOR_WORKSPACE = 'test-workspace';
76
+
77
+ const result = detectAgents();
78
+
79
+ expect(result.detected.length).toBeGreaterThanOrEqual(1);
80
+ expect(result.primary).toBeTruthy();
81
+ });
82
+
83
+ test('getAgentName returns correct display names', () => {
84
+ expect(getAgentName('clawdbot')).toBe('Clawdbot');
85
+ expect(getAgentName('claude-code')).toBe('Claude Code');
86
+ expect(getAgentName('cursor')).toBe('Cursor');
87
+ expect(getAgentName('windsurf')).toBe('Windsurf');
88
+ expect(getAgentName('generic')).toBe('Generic AI Agent');
89
+ });
90
+
91
+ test('getAgentName returns input for unknown agent', () => {
92
+ expect(getAgentName('unknown-agent')).toBe('unknown-agent');
93
+ });
94
+
95
+ test('isSupportedAgent validates known agents', () => {
96
+ expect(isSupportedAgent('clawdbot')).toBe(true);
97
+ expect(isSupportedAgent('claude-code')).toBe(true);
98
+ expect(isSupportedAgent('cursor')).toBe(true);
99
+ expect(isSupportedAgent('windsurf')).toBe(true);
100
+ expect(isSupportedAgent('generic')).toBe(true);
101
+ });
102
+
103
+ test('isSupportedAgent rejects unknown agents', () => {
104
+ expect(isSupportedAgent('unknown')).toBe(false);
105
+ expect(isSupportedAgent('invalid')).toBe(false);
106
+ expect(isSupportedAgent('')).toBe(false);
107
+ });
108
+ });
@@ -0,0 +1,60 @@
1
+ import { join, dirname } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import chalk from 'chalk';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+
8
+ /**
9
+ * Generic agent setup - just provide instructions
10
+ */
11
+ export async function setupGeneric(workspacePath) {
12
+ // For generic agents, we can't auto-configure
13
+ // Just provide clear instructions
14
+ console.log(chalk.yellow('ℹ'), 'Generic agent detected - manual setup required');
15
+ return true;
16
+ }
17
+
18
+ /**
19
+ * Get setup instructions for generic agents
20
+ */
21
+ export function getGenericInstructions(workspacePath) {
22
+ const skillPath = join(dirname(__dirname), '../skills/agent-skill.template.md');
23
+
24
+ let output = '\n';
25
+ output += chalk.bold('Manual Setup Required') + '\n\n';
26
+ output += 'Your Cairn workspace: ' + chalk.cyan(workspacePath) + '\n\n';
27
+ output += chalk.bold('To configure your AI agent:') + '\n\n';
28
+ output += '1. Share this with your agent:\n';
29
+ output += ' ' + chalk.dim('─────────────────────────────────────') + '\n';
30
+ output += ' ' + chalk.yellow('I\'m using Cairn for project management.') + '\n';
31
+ output += ' ' + chalk.yellow('My workspace is at: ' + workspacePath) + '\n';
32
+ output += ' ' + chalk.yellow('Please read the agent skill documentation at:') + '\n';
33
+ output += ' ' + chalk.yellow(skillPath) + '\n';
34
+ output += ' ' + chalk.dim('─────────────────────────────────────') + '\n\n';
35
+ output += '2. ' + chalk.bold('Or copy the skill to your agent\'s context:') + '\n';
36
+ output += ' - For AI coding assistants: Add ' + chalk.cyan(skillPath) + ' to your workspace\n';
37
+ output += ' - For chat agents: Share the skill content in your first message\n\n';
38
+ output += '3. ' + chalk.bold('Test it:') + '\n';
39
+ output += ' Ask your agent:\n';
40
+ output += ' ' + chalk.yellow('"Help me create a project called Launch My App"') + '\n\n';
41
+ output += chalk.bold('Supported agents with auto-setup:') + '\n';
42
+ output += '- Clawdbot (detected via ~/.clawdbot/)\n';
43
+ output += '- Claude Code (workspace-based)\n';
44
+ output += '- Cursor (uses .cursorrules)\n';
45
+ output += '- Windsurf (workspace-based)\n\n';
46
+ output += chalk.dim('If you install one of these agents later, run:') + '\n';
47
+ output += chalk.dim(' cairn onboard --force') + '\n';
48
+
49
+ return output;
50
+ }
51
+
52
+ /**
53
+ * Verify generic setup (always returns guidance)
54
+ */
55
+ export function verifyGeneric() {
56
+ return {
57
+ success: true,
58
+ message: 'Manual setup - verify agent can read workspace'
59
+ };
60
+ }
@@ -0,0 +1,158 @@
1
+ import { existsSync, mkdirSync, writeFileSync, readdirSync } from 'fs';
2
+ import { join } from 'path';
3
+ import chalk from 'chalk';
4
+ import inquirer from 'inquirer';
5
+ import { resolveWorkspace } from '../setup/workspace.js';
6
+
7
+ function slugify(text) {
8
+ return text
9
+ .toLowerCase()
10
+ .replace(/[^a-z0-9]+/g, '-')
11
+ .replace(/^-+|-+$/g, '');
12
+ }
13
+
14
+ function getToday() {
15
+ return new Date().toISOString().split('T')[0];
16
+ }
17
+
18
+ function getDueDate(daysFromNow = 7) {
19
+ const date = new Date();
20
+ date.setDate(date.getDate() + daysFromNow);
21
+ return date.toISOString().split('T')[0];
22
+ }
23
+
24
+ export default async function create(type, name, options) {
25
+ const workspacePath = resolveWorkspace();
26
+
27
+ if (!workspacePath) {
28
+ console.error(chalk.red('Error:'), 'No workspace found. Run:', chalk.cyan('cairn init'));
29
+ process.exit(1);
30
+ }
31
+
32
+ const slug = slugify(name);
33
+
34
+ switch (type) {
35
+ case 'project':
36
+ await createProject(workspacePath, name, slug, options);
37
+ break;
38
+ case 'task':
39
+ await createTask(workspacePath, name, slug, options);
40
+ break;
41
+ default:
42
+ console.error(chalk.red('Error:'), `Unknown type: ${type}`);
43
+ console.log(chalk.dim('Valid types: project, task'));
44
+ process.exit(1);
45
+ }
46
+ }
47
+
48
+ async function createProject(workspacePath, name, slug, options) {
49
+ const projectPath = join(workspacePath, 'projects', slug);
50
+
51
+ if (existsSync(projectPath)) {
52
+ console.error(chalk.red('Error:'), `Project already exists: ${slug}`);
53
+ process.exit(1);
54
+ }
55
+
56
+ // Create folders
57
+ mkdirSync(projectPath, { recursive: true });
58
+ mkdirSync(join(projectPath, 'tasks'), { recursive: true });
59
+
60
+ // Create charter.md
61
+ const charterPath = join(projectPath, 'charter.md');
62
+ const charter = `---
63
+ title: ${name}
64
+ description: ${options.description || name}
65
+ status: active
66
+ priority: 2
67
+ created: ${getToday()}
68
+ due: ${options.due || getDueDate(30)}
69
+ owner: ${options.assignee || 'me'}
70
+ default_autonomy: draft
71
+ budget: 100
72
+ spent: 0
73
+ ---
74
+
75
+ ## Why This Matters
76
+
77
+ ${options.objective || '[Describe why this project is important]'}
78
+
79
+ ## Success Criteria
80
+
81
+ - [ ] [Define what success looks like]
82
+ - [ ] [Add measurable outcomes]
83
+ - [ ] [Specify completion criteria]
84
+
85
+ ## Context
86
+
87
+ [Add relevant background, constraints, or dependencies]
88
+
89
+ ## Work Log
90
+
91
+ ### ${getToday()} - Created
92
+ Project created via Cairn CLI.
93
+ `;
94
+
95
+ writeFileSync(charterPath, charter);
96
+
97
+ console.log(chalk.green('āœ“'), `Project created: ${chalk.cyan(slug)}`);
98
+ console.log(chalk.dim(` Location: ${projectPath}`));
99
+ console.log(chalk.dim(` Charter: ${charterPath}`));
100
+ console.log();
101
+ console.log(chalk.dim('Next:'), `cairn create task "Task Name" --project ${slug}`);
102
+ }
103
+
104
+ async function createTask(workspacePath, name, slug, options) {
105
+ const projectSlug = options.project;
106
+
107
+ if (!projectSlug) {
108
+ console.error(chalk.red('Error:'), 'Missing --project flag');
109
+ console.log(chalk.dim('Example:'), chalk.cyan(`cairn create task "${name}" --project my-project`));
110
+ process.exit(1);
111
+ }
112
+
113
+ const projectPath = join(workspacePath, 'projects', projectSlug);
114
+ if (!existsSync(projectPath)) {
115
+ console.error(chalk.red('Error:'), `Project not found: ${projectSlug}`);
116
+ process.exit(1);
117
+ }
118
+
119
+ const tasksDir = join(projectPath, 'tasks');
120
+ if (!existsSync(tasksDir)) {
121
+ mkdirSync(tasksDir, { recursive: true });
122
+ }
123
+
124
+ const taskPath = join(tasksDir, `${slug}.md`);
125
+
126
+ if (existsSync(taskPath)) {
127
+ console.error(chalk.red('Error:'), `Task already exists: ${slug}`);
128
+ process.exit(1);
129
+ }
130
+
131
+ // Create task file
132
+ const task = `---
133
+ title: ${name}
134
+ description: ${options.description || name}
135
+ assignee: ${options.assignee || 'you'}
136
+ status: ${options.status || 'pending'}
137
+ created: ${getToday()}
138
+ due: ${options.due || getDueDate(7)}
139
+ autonomy: draft
140
+ spend: 0
141
+ artifacts: []
142
+ ---
143
+
144
+ ## Objective
145
+
146
+ ${options.objective || '[Describe what needs to be accomplished]'}
147
+
148
+ ## Work Log
149
+
150
+ ### ${getToday()} - Created
151
+ Task created via Cairn CLI.
152
+ `;
153
+
154
+ writeFileSync(taskPath, task);
155
+
156
+ console.log(chalk.green('āœ“'), `Task created: ${chalk.cyan(slug)}`);
157
+ console.log(chalk.dim(` Location: ${taskPath}`));
158
+ }
@@ -0,0 +1,137 @@
1
+ import { existsSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+
8
+ import { detectAgents, getAgentName } from '../agents/detect.js';
9
+ import { verifyClawdbot } from '../agents/clawdbot.js';
10
+ import { verifyClaudeCode } from '../agents/claude-code.js';
11
+ import { verifyCursor } from '../agents/cursor.js';
12
+ import { validateWorkspace, createWorkspace, createWelcomeFile, resolveWorkspace } from '../setup/workspace.js';
13
+
14
+ export default async function doctor() {
15
+ console.log(chalk.bold.cyan('\nšŸ”ļø Cairn Doctor\n'));
16
+ console.log(chalk.dim('Checking workspace health...\n'));
17
+
18
+ const workspacePath = resolveWorkspace() || join(homedir(), 'cairn');
19
+ let issues = [];
20
+ let warnings = [];
21
+
22
+ // Check 1: Workspace exists
23
+ const spinner = ora('Checking workspace').start();
24
+ if (!existsSync(workspacePath)) {
25
+ spinner.fail('Workspace not found');
26
+ issues.push({
27
+ problem: `Workspace not found at ${workspacePath}`,
28
+ fix: 'Run: cairn init'
29
+ });
30
+ } else {
31
+ const { valid, missing } = validateWorkspace(workspacePath);
32
+ if (!valid) {
33
+ spinner.warn('Workspace incomplete');
34
+ warnings.push({
35
+ problem: `Missing folders: ${missing.join(', ')}`,
36
+ fix: 'Run: cairn init --path ' + workspacePath
37
+ });
38
+ } else {
39
+ spinner.succeed('Workspace structure valid');
40
+ }
41
+ }
42
+
43
+ // Check 2: Agent configuration
44
+ const agentSpinner = ora('Checking agent configuration').start();
45
+ const { primary, detected } = detectAgents();
46
+
47
+ if (detected.length === 0) {
48
+ agentSpinner.info('No specific agent detected');
49
+ warnings.push({
50
+ problem: 'No AI agent auto-detected',
51
+ fix: 'Run: cairn onboard --agent <type>'
52
+ });
53
+ } else {
54
+ agentSpinner.succeed(`Detected: ${detected.map(getAgentName).join(', ')}`);
55
+
56
+ // Verify each detected agent
57
+ for (const agent of detected) {
58
+ const verifySpinner = ora(`Verifying ${getAgentName(agent)}`).start();
59
+ let result;
60
+
61
+ switch (agent) {
62
+ case 'clawdbot':
63
+ result = verifyClawdbot();
64
+ break;
65
+ case 'claude-code':
66
+ result = verifyClaudeCode();
67
+ break;
68
+ case 'cursor':
69
+ result = verifyCursor();
70
+ break;
71
+ default:
72
+ result = { success: true, message: 'No verification available' };
73
+ }
74
+
75
+ if (result.success) {
76
+ verifySpinner.succeed(result.message);
77
+ } else {
78
+ verifySpinner.fail(result.message);
79
+ issues.push({
80
+ problem: `${getAgentName(agent)}: ${result.message}`,
81
+ fix: `Run: cairn onboard --agent ${agent} --force`
82
+ });
83
+ }
84
+ }
85
+ }
86
+
87
+ // Check 3: README exists
88
+ const readmeSpinner = ora('Checking README').start();
89
+ const readmePath = join(workspacePath, 'README.md');
90
+ if (existsSync(readmePath)) {
91
+ readmeSpinner.succeed('README exists');
92
+ } else {
93
+ readmeSpinner.warn('README missing');
94
+ warnings.push({
95
+ problem: 'Welcome README not found',
96
+ fix: 'Will be created automatically'
97
+ });
98
+ if (existsSync(workspacePath)) {
99
+ createWelcomeFile(workspacePath);
100
+ console.log(chalk.green('āœ“'), 'README created');
101
+ }
102
+ }
103
+
104
+ // Summary
105
+ console.log();
106
+ console.log(chalk.bold('Summary:\n'));
107
+
108
+ if (issues.length === 0 && warnings.length === 0) {
109
+ console.log(chalk.green('āœ“'), chalk.bold('Everything looks good!'));
110
+ console.log();
111
+ console.log(chalk.dim('Your Cairn workspace is healthy.'));
112
+ console.log(chalk.dim('Location:'), chalk.cyan(workspacePath));
113
+ console.log();
114
+ return;
115
+ }
116
+
117
+ if (issues.length > 0) {
118
+ console.log(chalk.red.bold(`${issues.length} issue(s) found:\n`));
119
+ for (const issue of issues) {
120
+ console.log(chalk.red('āœ—'), issue.problem);
121
+ console.log(chalk.dim(' Fix:'), chalk.cyan(issue.fix));
122
+ console.log();
123
+ }
124
+ }
125
+
126
+ if (warnings.length > 0) {
127
+ console.log(chalk.yellow.bold(`${warnings.length} warning(s):\n`));
128
+ for (const warning of warnings) {
129
+ console.log(chalk.yellow('⚠'), warning.problem);
130
+ console.log(chalk.dim(' Fix:'), chalk.cyan(warning.fix));
131
+ console.log();
132
+ }
133
+ }
134
+
135
+ console.log(chalk.dim('Run'), chalk.cyan('cairn --help'), chalk.dim('for more commands.'));
136
+ console.log();
137
+ }
@@ -0,0 +1,28 @@
1
+ import chalk from 'chalk';
2
+
3
+ import { createWorkspace, createWelcomeFile, workspaceExists } from '../setup/workspace.js';
4
+
5
+ export default async function init(options) {
6
+ console.log(chalk.bold.cyan('\nšŸ”ļø Initializing Cairn\n'));
7
+
8
+ const workspacePath = options.path || process.cwd();
9
+
10
+ if (workspaceExists(workspacePath)) {
11
+ console.log(chalk.yellow('⚠'), `Workspace already exists at ${workspacePath}`);
12
+ console.log(chalk.dim('Use --path to specify a different location'));
13
+ return;
14
+ }
15
+
16
+ // Create workspace
17
+ createWorkspace(workspacePath);
18
+ createWelcomeFile(workspacePath);
19
+
20
+ console.log();
21
+ console.log(chalk.green('āœ“'), `Workspace created at ${chalk.cyan(workspacePath)}`);
22
+ console.log();
23
+ console.log(chalk.dim('Next steps:'));
24
+ console.log(chalk.dim(' • Create a project: cairn create project "My Project"'));
25
+ console.log(chalk.dim(' • Configure agent: cairn onboard'));
26
+ console.log(chalk.dim(' • Check health: cairn doctor'));
27
+ console.log();
28
+ }
@@ -0,0 +1,141 @@
1
+ import { homedir } from 'os';
2
+ import { join } from 'path';
3
+ import chalk from 'chalk';
4
+ import inquirer from 'inquirer';
5
+ import ora from 'ora';
6
+
7
+ import { detectAgents, getAgentName } from '../agents/detect.js';
8
+ import { setupClawdbot, getClawdbotInstructions } from '../agents/clawdbot.js';
9
+ import { setupClaudeCode, getClaudeCodeInstructions } from '../agents/claude-code.js';
10
+ import { setupCursor, getCursorInstructions } from '../agents/cursor.js';
11
+ import { setupGeneric, getGenericInstructions } from '../agents/generic.js';
12
+ import { createWorkspace, createWelcomeFile, workspaceExists } from '../setup/workspace.js';
13
+
14
+ export default async function onboard(options) {
15
+ console.log(chalk.bold.cyan('\nšŸ”ļø Cairn Onboarding\n'));
16
+
17
+ // Prompt for workspace path (if not provided)
18
+ let workspacePath = options.path;
19
+ if (!workspacePath) {
20
+ const currentDir = process.cwd();
21
+ const cairnDir = join(homedir(), 'cairn');
22
+
23
+ const { location } = await inquirer.prompt([{
24
+ type: 'list',
25
+ name: 'location',
26
+ message: 'Where should Cairn store your project files?',
27
+ choices: [
28
+ { name: `Here (${currentDir})`, value: currentDir },
29
+ { name: `~/cairn`, value: cairnDir },
30
+ { name: 'Somewhere else...', value: '__custom__' }
31
+ ]
32
+ }]);
33
+
34
+ if (location === '__custom__') {
35
+ const { customPath } = await inquirer.prompt([{
36
+ type: 'input',
37
+ name: 'customPath',
38
+ message: 'Enter the full path:'
39
+ }]);
40
+ workspacePath = customPath;
41
+ } else {
42
+ workspacePath = location;
43
+ }
44
+ }
45
+
46
+ // Check if already set up
47
+ if (workspaceExists(workspacePath) && !options.force) {
48
+ const { proceed } = await inquirer.prompt([{
49
+ type: 'confirm',
50
+ name: 'proceed',
51
+ message: `Workspace already exists at ${workspacePath}. Re-run onboarding?`,
52
+ default: false
53
+ }]);
54
+
55
+ if (!proceed) {
56
+ console.log(chalk.yellow('\nOnboarding cancelled.'));
57
+ return;
58
+ }
59
+ }
60
+
61
+ // Step 1: Detect agents
62
+ let agentType = options.agent;
63
+
64
+ if (!agentType) {
65
+ const spinner = ora('Detecting AI agents').start();
66
+ const { primary, detected } = detectAgents();
67
+ spinner.stop();
68
+
69
+ if (detected.length > 1) {
70
+ console.log(chalk.green('āœ“'), `Detected agents: ${detected.map(getAgentName).join(', ')}`);
71
+
72
+ const { selected } = await inquirer.prompt([{
73
+ type: 'list',
74
+ name: 'selected',
75
+ message: 'Multiple agents detected. Which one do you want to configure?',
76
+ choices: detected.map(type => ({
77
+ name: getAgentName(type),
78
+ value: type
79
+ }))
80
+ }]);
81
+
82
+ agentType = selected;
83
+ } else if (detected.length === 1) {
84
+ agentType = primary;
85
+ console.log(chalk.green('āœ“'), `Detected: ${getAgentName(agentType)}`);
86
+ } else {
87
+ agentType = 'generic';
88
+ console.log(chalk.yellow('ℹ'), 'No specific agent detected - using generic setup');
89
+ }
90
+ }
91
+
92
+ // Step 2: Create workspace
93
+ console.log();
94
+ createWorkspace(workspacePath);
95
+ createWelcomeFile(workspacePath);
96
+
97
+ // Step 3: Set up agent
98
+ console.log();
99
+ const setupSpinner = ora(`Configuring ${getAgentName(agentType)}`).start();
100
+
101
+ try {
102
+ switch (agentType) {
103
+ case 'clawdbot':
104
+ await setupClawdbot(workspacePath);
105
+ setupSpinner.succeed(`${getAgentName(agentType)} configured`);
106
+ console.log(getClawdbotInstructions(workspacePath));
107
+ break;
108
+
109
+ case 'claude-code':
110
+ await setupClaudeCode(workspacePath);
111
+ setupSpinner.succeed(`${getAgentName(agentType)} configured`);
112
+ console.log(getClaudeCodeInstructions(workspacePath));
113
+ break;
114
+
115
+ case 'cursor':
116
+ await setupCursor(workspacePath);
117
+ setupSpinner.succeed(`${getAgentName(agentType)} configured`);
118
+ console.log(getCursorInstructions(workspacePath));
119
+ break;
120
+
121
+ case 'generic':
122
+ default:
123
+ await setupGeneric(workspacePath);
124
+ setupSpinner.stop();
125
+ console.log(getGenericInstructions(workspacePath));
126
+ break;
127
+ }
128
+ } catch (error) {
129
+ setupSpinner.fail('Setup failed');
130
+ console.error(chalk.red('Error:'), error.message);
131
+ process.exit(1);
132
+ }
133
+
134
+ // Success!
135
+ console.log(chalk.bold.green('\nšŸŽ‰ Onboarding complete!\n'));
136
+ console.log(chalk.dim('Next steps:'));
137
+ console.log(chalk.dim(' 1. Test your agent with: "Help me create a project"'));
138
+ console.log(chalk.dim(' 2. Read the docs: cairn --help'));
139
+ console.log(chalk.dim(' 3. Create your first project: cairn create project "My Project"'));
140
+ console.log();
141
+ }