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.
@@ -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
- }
@@ -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
- });
@@ -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
- }