cairn-work 0.6.0 → 0.7.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.
- package/bin/cairn.js +5 -6
- package/lib/commands/create.js +1 -1
- package/lib/commands/doctor.js +24 -54
- package/lib/commands/onboard.js +17 -74
- package/lib/commands/update-skill.js +13 -51
- package/lib/setup/context.js +70 -0
- package/lib/setup/workspace.js +1 -1
- package/package.json +1 -1
- package/skills/agent-planning.template.md +8 -8
- package/skills/agent-skill.template.md +105 -302
- package/lib/agents/claude-code.js +0 -107
- package/lib/agents/clawdbot.js +0 -96
- package/lib/agents/cursor.js +0 -118
- package/lib/agents/detect.js +0 -60
- package/lib/agents/detect.test.js +0 -108
- package/lib/agents/generic.js +0 -62
package/lib/agents/cursor.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync } from 'fs';
|
|
2
|
-
import { join, dirname } from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = dirname(__filename);
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Set up Cairn for Cursor
|
|
11
|
-
* Adds to .cursorrules or creates .cursor/ directory with skill
|
|
12
|
-
*/
|
|
13
|
-
export async function setupCursor(workspacePath) {
|
|
14
|
-
const cwd = process.cwd();
|
|
15
|
-
const cursorRulesPath = join(cwd, '.cursorrules');
|
|
16
|
-
const skillTemplate = join(__dirname, '../../skills/agent-skill.template.md');
|
|
17
|
-
|
|
18
|
-
// Strategy 1: Add to .cursorrules if it exists
|
|
19
|
-
if (existsSync(cursorRulesPath)) {
|
|
20
|
-
const existing = readFileSync(cursorRulesPath, 'utf8');
|
|
21
|
-
const cairnRule = `
|
|
22
|
-
|
|
23
|
-
# Cairn Project Management
|
|
24
|
-
|
|
25
|
-
This project uses Cairn for project management.
|
|
26
|
-
|
|
27
|
-
**Workspace:** ${workspacePath}
|
|
28
|
-
|
|
29
|
-
When working with project management tasks:
|
|
30
|
-
1. All files are in markdown format at ${workspacePath}
|
|
31
|
-
2. Follow project → task hierarchy
|
|
32
|
-
3. Update status before asking for blocking information
|
|
33
|
-
4. Log all work with timestamps
|
|
34
|
-
|
|
35
|
-
See .cursor/cairn-skill.md for workflow documentation and .cursor/cairn-planning.md for project planning guidance.
|
|
36
|
-
`;
|
|
37
|
-
|
|
38
|
-
if (!existing.includes('Cairn Project Management')) {
|
|
39
|
-
writeFileSync(cursorRulesPath, existing + cairnRule);
|
|
40
|
-
console.log(chalk.green('✓'), 'Cairn rules added to .cursorrules');
|
|
41
|
-
} else {
|
|
42
|
-
console.log(chalk.yellow('⚠'), '.cursorrules already contains Cairn rules');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Strategy 2: Create .cursor/ directory with skill
|
|
47
|
-
const cursorDir = join(cwd, '.cursor');
|
|
48
|
-
if (!existsSync(cursorDir)) {
|
|
49
|
-
mkdirSync(cursorDir, { recursive: true });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Read template and replace workspace placeholders
|
|
53
|
-
let skillContent = readFileSync(skillTemplate, 'utf-8');
|
|
54
|
-
const workspaceRoot = dirname(workspacePath);
|
|
55
|
-
|
|
56
|
-
skillContent = skillContent
|
|
57
|
-
.replace(/\{\{WORKSPACE_PATH\}\}/g, workspacePath)
|
|
58
|
-
.replace(/\{\{WORKSPACE_ROOT\}\}/g, workspaceRoot);
|
|
59
|
-
|
|
60
|
-
const skillDest = join(cursorDir, 'cairn-skill.md');
|
|
61
|
-
writeFileSync(skillDest, skillContent);
|
|
62
|
-
console.log(chalk.green('✓'), 'Cairn skill added to .cursor/');
|
|
63
|
-
|
|
64
|
-
// Read planning template and replace placeholders
|
|
65
|
-
const planningTemplate = join(__dirname, '../../skills/agent-planning.template.md');
|
|
66
|
-
let planningContent = readFileSync(planningTemplate, 'utf-8');
|
|
67
|
-
planningContent = planningContent
|
|
68
|
-
.replace(/\{\{WORKSPACE_PATH\}\}/g, workspacePath)
|
|
69
|
-
.replace(/\{\{WORKSPACE_ROOT\}\}/g, workspaceRoot);
|
|
70
|
-
|
|
71
|
-
const planningDest = join(cursorDir, 'cairn-planning.md');
|
|
72
|
-
writeFileSync(planningDest, planningContent);
|
|
73
|
-
console.log(chalk.green('✓'), 'Cairn planning guide added to .cursor/');
|
|
74
|
-
|
|
75
|
-
// Create config
|
|
76
|
-
const configPath = join(cursorDir, 'cairn-config.json');
|
|
77
|
-
const config = {
|
|
78
|
-
workspacePath,
|
|
79
|
-
version: '0.1.0',
|
|
80
|
-
installedAt: new Date().toISOString()
|
|
81
|
-
};
|
|
82
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
83
|
-
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Get setup instructions for Cursor
|
|
89
|
-
*/
|
|
90
|
-
export function getCursorInstructions(workspacePath) {
|
|
91
|
-
return `
|
|
92
|
-
${chalk.bold('Cursor Setup Complete!')}
|
|
93
|
-
|
|
94
|
-
Your Cairn workspace: ${chalk.cyan(workspacePath)}
|
|
95
|
-
Configuration: ${chalk.cyan('.cursor/')} and ${chalk.cyan('.cursorrules')}
|
|
96
|
-
|
|
97
|
-
${chalk.bold('Test it:')}
|
|
98
|
-
Ask Cursor:
|
|
99
|
-
${chalk.yellow('"Read .cursor/cairn-skill.md and help me create a project"')}
|
|
100
|
-
|
|
101
|
-
${chalk.dim('Note: Cursor should automatically pick up .cursorrules on next prompt.')}
|
|
102
|
-
`;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Verify Cursor setup
|
|
107
|
-
*/
|
|
108
|
-
export function verifyCursor() {
|
|
109
|
-
const cwd = process.cwd();
|
|
110
|
-
const rulesPath = join(cwd, '.cursorrules');
|
|
111
|
-
const skillPath = join(cwd, '.cursor', 'cairn-skill.md');
|
|
112
|
-
|
|
113
|
-
if (!existsSync(rulesPath) && !existsSync(skillPath)) {
|
|
114
|
-
return { success: false, message: 'Cairn not configured for Cursor' };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return { success: true, message: 'Cursor setup verified' };
|
|
118
|
-
}
|
package/lib/agents/detect.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'fs';
|
|
2
|
-
import { homedir } from 'os';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Detect which AI agent(s) are available in the environment
|
|
7
|
-
* @returns {Object} { primary: string, detected: string[] }
|
|
8
|
-
*/
|
|
9
|
-
export function detectAgents() {
|
|
10
|
-
const detected = [];
|
|
11
|
-
|
|
12
|
-
// Check for Clawdbot
|
|
13
|
-
const clawdbotPath = join(homedir(), '.clawdbot');
|
|
14
|
-
if (existsSync(clawdbotPath)) {
|
|
15
|
-
detected.push('clawdbot');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Check for Claude Code
|
|
19
|
-
// Claude Code typically sets environment variables or has workspace indicators
|
|
20
|
-
if (process.env.ANTHROPIC_API_KEY || process.env.CLAUDE_CODE_WORKSPACE) {
|
|
21
|
-
detected.push('claude-code');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Check for Cursor
|
|
25
|
-
// Cursor uses .cursorrules file or sets env variables
|
|
26
|
-
if (existsSync('.cursorrules') || process.env.CURSOR_WORKSPACE) {
|
|
27
|
-
detected.push('cursor');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Check for Windsurf
|
|
31
|
-
if (process.env.WINDSURF_WORKSPACE) {
|
|
32
|
-
detected.push('windsurf');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Determine primary agent (first detected, or generic)
|
|
36
|
-
const primary = detected[0] || 'generic';
|
|
37
|
-
|
|
38
|
-
return { primary, detected };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Get agent display name
|
|
43
|
-
*/
|
|
44
|
-
export function getAgentName(type) {
|
|
45
|
-
const names = {
|
|
46
|
-
'clawdbot': 'Clawdbot',
|
|
47
|
-
'claude-code': 'Claude Code',
|
|
48
|
-
'cursor': 'Cursor',
|
|
49
|
-
'windsurf': 'Windsurf',
|
|
50
|
-
'generic': 'Generic AI Agent'
|
|
51
|
-
};
|
|
52
|
-
return names[type] || type;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Check if an agent type is supported
|
|
57
|
-
*/
|
|
58
|
-
export function isSupportedAgent(type) {
|
|
59
|
-
return ['clawdbot', 'claude-code', 'cursor', 'windsurf', 'generic'].includes(type);
|
|
60
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
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
|
-
});
|
package/lib/agents/generic.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
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
|
-
const planningPath = join(dirname(__dirname), '../skills/agent-planning.template.md');
|
|
24
|
-
|
|
25
|
-
let output = '\n';
|
|
26
|
-
output += chalk.bold('Manual Setup Required') + '\n\n';
|
|
27
|
-
output += 'Your Cairn workspace: ' + chalk.cyan(workspacePath) + '\n\n';
|
|
28
|
-
output += chalk.bold('To configure your AI agent:') + '\n\n';
|
|
29
|
-
output += '1. Share this with your agent:\n';
|
|
30
|
-
output += ' ' + chalk.dim('─────────────────────────────────────') + '\n';
|
|
31
|
-
output += ' ' + chalk.yellow('I\'m using Cairn for project management.') + '\n';
|
|
32
|
-
output += ' ' + chalk.yellow('My workspace is at: ' + workspacePath) + '\n';
|
|
33
|
-
output += ' ' + chalk.yellow('Please read the agent documentation at:') + '\n';
|
|
34
|
-
output += ' ' + chalk.yellow(' Skill: ' + skillPath) + '\n';
|
|
35
|
-
output += ' ' + chalk.yellow(' Planning: ' + planningPath) + '\n';
|
|
36
|
-
output += ' ' + chalk.dim('─────────────────────────────────────') + '\n\n';
|
|
37
|
-
output += '2. ' + chalk.bold('Or copy both files to your agent\'s context:') + '\n';
|
|
38
|
-
output += ' - ' + chalk.cyan(skillPath) + ' (operations & workflow)\n';
|
|
39
|
-
output += ' - ' + chalk.cyan(planningPath) + ' (project planning & examples)\n\n';
|
|
40
|
-
output += '3. ' + chalk.bold('Test it:') + '\n';
|
|
41
|
-
output += ' Ask your agent:\n';
|
|
42
|
-
output += ' ' + chalk.yellow('"Help me create a project called Launch My App"') + '\n\n';
|
|
43
|
-
output += chalk.bold('Supported agents with auto-setup:') + '\n';
|
|
44
|
-
output += '- Clawdbot (detected via ~/.clawdbot/)\n';
|
|
45
|
-
output += '- Claude Code (workspace-based)\n';
|
|
46
|
-
output += '- Cursor (uses .cursorrules)\n';
|
|
47
|
-
output += '- Windsurf (workspace-based)\n\n';
|
|
48
|
-
output += chalk.dim('If you install one of these agents later, run:') + '\n';
|
|
49
|
-
output += chalk.dim(' cairn onboard --force') + '\n';
|
|
50
|
-
|
|
51
|
-
return output;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Verify generic setup (always returns guidance)
|
|
56
|
-
*/
|
|
57
|
-
export function verifyGeneric() {
|
|
58
|
-
return {
|
|
59
|
-
success: true,
|
|
60
|
-
message: 'Manual setup - verify agent can read workspace'
|
|
61
|
-
};
|
|
62
|
-
}
|