@damper/cli 0.1.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 @@
1
+ export declare function cleanupCommand(): Promise<void>;
@@ -0,0 +1,149 @@
1
+ import * as fs from 'node:fs';
2
+ import { confirm, checkbox } from '@inquirer/prompts';
3
+ import pc from 'picocolors';
4
+ import { getWorktrees, cleanupStaleWorktrees, removeWorktree } from '../services/state.js';
5
+ import { createDamperApi } from '../services/damper-api.js';
6
+ import { removeWorktreeDir } from '../services/worktree.js';
7
+ export async function cleanupCommand() {
8
+ // Clean up stale entries first
9
+ const stale = cleanupStaleWorktrees();
10
+ if (stale.length > 0) {
11
+ console.log(pc.dim(`Removed ${stale.length} stale worktree reference(s)`));
12
+ }
13
+ const worktrees = getWorktrees();
14
+ if (worktrees.length === 0) {
15
+ console.log(pc.yellow('\nNo tracked worktrees found.'));
16
+ return;
17
+ }
18
+ // Try to connect to Damper for task status
19
+ let api;
20
+ try {
21
+ api = createDamperApi();
22
+ }
23
+ catch {
24
+ console.log(pc.yellow('\nWarning: Could not connect to Damper API (no API key).'));
25
+ console.log(pc.dim('Will show all worktrees for manual selection.\n'));
26
+ }
27
+ // Identify cleanup candidates
28
+ const candidates = [];
29
+ console.log(pc.dim('\nAnalyzing worktrees...'));
30
+ for (const wt of worktrees) {
31
+ const exists = fs.existsSync(wt.path);
32
+ if (!exists) {
33
+ // Worktree directory is missing
34
+ candidates.push({
35
+ worktree: wt,
36
+ reason: 'missing',
37
+ });
38
+ continue;
39
+ }
40
+ if (api) {
41
+ try {
42
+ const task = await api.getTask(wt.taskId);
43
+ if (task.status === 'done') {
44
+ candidates.push({
45
+ worktree: wt,
46
+ task,
47
+ reason: 'completed',
48
+ });
49
+ }
50
+ else if (task.status === 'planned' && !task.lockedBy) {
51
+ // Task was abandoned (planned and not locked)
52
+ candidates.push({
53
+ worktree: wt,
54
+ task,
55
+ reason: 'abandoned',
56
+ });
57
+ }
58
+ }
59
+ catch {
60
+ // Could not fetch task - might be deleted
61
+ candidates.push({
62
+ worktree: wt,
63
+ reason: 'manual',
64
+ });
65
+ }
66
+ }
67
+ else {
68
+ // No API - show all for manual selection
69
+ candidates.push({
70
+ worktree: wt,
71
+ reason: 'manual',
72
+ });
73
+ }
74
+ }
75
+ if (candidates.length === 0) {
76
+ console.log(pc.green('\n✓ All worktrees are for active tasks. Nothing to clean up.\n'));
77
+ return;
78
+ }
79
+ // Show candidates and let user select
80
+ console.log(pc.bold('\nWorktrees available for cleanup:\n'));
81
+ const choices = candidates.map(c => {
82
+ const worktreeName = c.worktree.path.split('/').pop() || c.worktree.path;
83
+ let description = `#${c.worktree.taskId}`;
84
+ if (c.task) {
85
+ description += ` - ${c.task.title}`;
86
+ }
87
+ let reasonBadge = '';
88
+ switch (c.reason) {
89
+ case 'completed':
90
+ reasonBadge = pc.green('[completed]');
91
+ break;
92
+ case 'abandoned':
93
+ reasonBadge = pc.yellow('[abandoned]');
94
+ break;
95
+ case 'missing':
96
+ reasonBadge = pc.red('[missing]');
97
+ break;
98
+ case 'manual':
99
+ reasonBadge = pc.dim('[manual]');
100
+ break;
101
+ }
102
+ return {
103
+ name: `${description} ${reasonBadge}\n ${pc.dim(worktreeName)}`,
104
+ value: c,
105
+ checked: c.reason === 'completed' || c.reason === 'missing',
106
+ };
107
+ });
108
+ const selected = await checkbox({
109
+ message: 'Select worktrees to remove:',
110
+ choices,
111
+ pageSize: 10,
112
+ });
113
+ if (selected.length === 0) {
114
+ console.log(pc.dim('\nNo worktrees selected. Nothing to clean up.\n'));
115
+ return;
116
+ }
117
+ // Confirm
118
+ const confirmed = await confirm({
119
+ message: `Remove ${selected.length} worktree(s)?`,
120
+ default: false,
121
+ });
122
+ if (!confirmed) {
123
+ console.log(pc.dim('\nCancelled.\n'));
124
+ return;
125
+ }
126
+ // Remove selected worktrees
127
+ console.log();
128
+ for (const candidate of selected) {
129
+ const { worktree } = candidate;
130
+ const worktreeName = worktree.path.split('/').pop() || worktree.path;
131
+ try {
132
+ if (candidate.reason === 'missing') {
133
+ // Just remove from state
134
+ removeWorktree(worktree.taskId);
135
+ console.log(pc.green(`✓ Removed reference: ${worktreeName}`));
136
+ }
137
+ else {
138
+ // Remove actual worktree
139
+ await removeWorktreeDir(worktree.path, worktree.projectRoot);
140
+ console.log(pc.green(`✓ Removed: ${worktreeName}`));
141
+ }
142
+ }
143
+ catch (err) {
144
+ const error = err;
145
+ console.log(pc.red(`✗ Failed to remove ${worktreeName}: ${error.message}`));
146
+ }
147
+ }
148
+ console.log();
149
+ }
@@ -0,0 +1 @@
1
+ export declare function setupCommand(): Promise<void>;
@@ -0,0 +1,67 @@
1
+ import pc from 'picocolors';
2
+ import { isDamperMcpConfigured, getConfiguredApiKey, setupDamperMcp, isClaudeInstalled, } from '../services/claude.js';
3
+ import { createDamperApi } from '../services/damper-api.js';
4
+ export async function setupCommand() {
5
+ console.log(pc.bold('\n@damper/cli Setup\n'));
6
+ // Check Claude CLI
7
+ const claudeInstalled = await isClaudeInstalled();
8
+ if (!claudeInstalled) {
9
+ console.log(pc.red('✗ Claude Code CLI not found'));
10
+ console.log(pc.dim(' Install it with: npm install -g @anthropic-ai/claude-code\n'));
11
+ }
12
+ else {
13
+ console.log(pc.green('✓ Claude Code CLI installed'));
14
+ }
15
+ // Check MCP configuration
16
+ const mcpConfigured = isDamperMcpConfigured();
17
+ const apiKey = getConfiguredApiKey();
18
+ if (mcpConfigured && apiKey) {
19
+ console.log(pc.green('✓ Damper MCP configured'));
20
+ console.log(pc.dim(` API key: ${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`));
21
+ // Verify the API key works
22
+ console.log(pc.dim('\nVerifying API key...'));
23
+ try {
24
+ const api = createDamperApi(apiKey);
25
+ const { project } = await api.listTasks({ limit: 1 });
26
+ console.log(pc.green(`✓ Connected to project: ${project}`));
27
+ }
28
+ catch (err) {
29
+ const error = err;
30
+ console.log(pc.red(`✗ API key verification failed: ${error.message}`));
31
+ console.log(pc.dim(' You may need to reconfigure with a valid key.\n'));
32
+ // Offer to reconfigure
33
+ const key = await setupDamperMcp();
34
+ if (key) {
35
+ console.log(pc.green('\n✓ Setup complete!\n'));
36
+ }
37
+ return;
38
+ }
39
+ console.log(pc.green('\n✓ All checks passed! You\'re ready to use @damper/cli.\n'));
40
+ console.log(pc.dim('Run `npx @damper/cli` to start working on a task.\n'));
41
+ return;
42
+ }
43
+ // Not configured - run setup
44
+ if (!mcpConfigured) {
45
+ console.log(pc.yellow('○ Damper MCP not configured'));
46
+ }
47
+ else if (!apiKey) {
48
+ console.log(pc.yellow('○ Damper MCP configured but missing API key'));
49
+ }
50
+ const key = await setupDamperMcp();
51
+ if (key) {
52
+ // Verify the new key
53
+ console.log(pc.dim('\nVerifying API key...'));
54
+ try {
55
+ const api = createDamperApi(key);
56
+ const { project } = await api.listTasks({ limit: 1 });
57
+ console.log(pc.green(`✓ Connected to project: ${project}`));
58
+ console.log(pc.green('\n✓ Setup complete! You\'re ready to use @damper/cli.\n'));
59
+ console.log(pc.dim('Run `npx @damper/cli` to start working on a task.\n'));
60
+ }
61
+ catch (err) {
62
+ const error = err;
63
+ console.log(pc.red(`✗ API key verification failed: ${error.message}`));
64
+ console.log(pc.dim(' Please check your API key and try again.\n'));
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,6 @@
1
+ export interface StartOptions {
2
+ taskId?: string;
3
+ type?: 'bug' | 'feature' | 'improvement' | 'task';
4
+ status?: 'planned' | 'in_progress' | 'done' | 'all';
5
+ }
6
+ export declare function startCommand(options: StartOptions): Promise<void>;
@@ -0,0 +1,139 @@
1
+ import * as fs from 'node:fs';
2
+ import pc from 'picocolors';
3
+ import { createDamperApi } from '../services/damper-api.js';
4
+ import { createWorktree, getGitRoot } from '../services/worktree.js';
5
+ import { bootstrapContext, refreshContext } from '../services/context-bootstrap.js';
6
+ import { pickTask } from '../ui/task-picker.js';
7
+ import { launchClaude, isClaudeInstalled, ensureMcpConfigured } from '../services/claude.js';
8
+ import { getWorktreesForProject, cleanupStaleWorktrees } from '../services/state.js';
9
+ export async function startCommand(options) {
10
+ // Check Claude CLI first
11
+ const claudeInstalled = await isClaudeInstalled();
12
+ if (!claudeInstalled) {
13
+ console.log(pc.red('\nError: Claude Code CLI not found.'));
14
+ console.log(pc.dim('Install it with: npm install -g @anthropic-ai/claude-code\n'));
15
+ process.exit(1);
16
+ }
17
+ // Ensure MCP is configured (will prompt for setup if not)
18
+ // This also handles API key setup
19
+ const apiKey = await ensureMcpConfigured();
20
+ // Get project root (git root)
21
+ let projectRoot;
22
+ try {
23
+ projectRoot = await getGitRoot(process.cwd());
24
+ }
25
+ catch {
26
+ console.log(pc.red('\nError: Not in a git repository.'));
27
+ console.log(pc.dim('Run this command from within a git repository.\n'));
28
+ process.exit(1);
29
+ }
30
+ // Create API client with the configured key
31
+ const api = createDamperApi(apiKey);
32
+ // Clean up stale worktrees
33
+ const stale = cleanupStaleWorktrees();
34
+ if (stale.length > 0) {
35
+ console.log(pc.dim(`Cleaned up ${stale.length} stale worktree reference(s)`));
36
+ }
37
+ // Get existing worktrees for this project
38
+ const worktrees = getWorktreesForProject(projectRoot);
39
+ let taskId;
40
+ let taskTitle;
41
+ let isResume = false;
42
+ let worktreePath;
43
+ if (options.taskId) {
44
+ // Direct task selection
45
+ taskId = options.taskId;
46
+ const task = await api.getTask(taskId);
47
+ taskTitle = task.title;
48
+ // Check if we have an existing worktree for this task
49
+ const existingWorktree = worktrees.find(w => w.taskId === taskId);
50
+ if (existingWorktree && fs.existsSync(existingWorktree.path)) {
51
+ isResume = true;
52
+ worktreePath = existingWorktree.path;
53
+ console.log(pc.cyan(`\nResuming task #${taskId}: ${taskTitle}`));
54
+ }
55
+ else {
56
+ console.log(pc.cyan(`\nStarting task #${taskId}: ${taskTitle}`));
57
+ }
58
+ }
59
+ else {
60
+ // Interactive task picker
61
+ const result = await pickTask({
62
+ api,
63
+ worktrees,
64
+ typeFilter: options.type,
65
+ statusFilter: options.status,
66
+ });
67
+ if (!result) {
68
+ process.exit(0);
69
+ }
70
+ taskId = result.task.id;
71
+ taskTitle = result.task.title;
72
+ isResume = result.isResume;
73
+ if (result.worktree) {
74
+ worktreePath = result.worktree.path;
75
+ }
76
+ }
77
+ if (isResume && worktreePath) {
78
+ // Resume existing worktree
79
+ console.log(pc.green(`\n✓ Resuming: #${taskId} ${taskTitle}`));
80
+ console.log(pc.dim(` Worktree: ${worktreePath}`));
81
+ // Refresh context with latest from Damper
82
+ console.log(pc.dim('\nRefreshing context from Damper...'));
83
+ await refreshContext({
84
+ api,
85
+ taskId,
86
+ worktreePath,
87
+ });
88
+ console.log(pc.green('✓ Updated TASK_CONTEXT.md with latest notes'));
89
+ }
90
+ else {
91
+ // New task - lock it first
92
+ console.log(pc.dim('\nLocking task in Damper...'));
93
+ try {
94
+ await api.startTask(taskId);
95
+ console.log(pc.green('✓ Task locked'));
96
+ }
97
+ catch (err) {
98
+ const error = err;
99
+ if (error.lockInfo) {
100
+ console.log(pc.yellow(`\n⚠️ Task is locked by "${error.lockInfo.lockedBy}" since ${error.lockInfo.lockedAt}`));
101
+ console.log(pc.dim('Use --force to take over the lock (if the other agent abandoned it).\n'));
102
+ process.exit(1);
103
+ }
104
+ throw err;
105
+ }
106
+ // Create worktree
107
+ console.log(pc.dim('\nSetting up worktree...'));
108
+ const worktreeResult = await createWorktree({
109
+ taskId,
110
+ taskTitle,
111
+ projectRoot,
112
+ });
113
+ worktreePath = worktreeResult.path;
114
+ if (worktreeResult.isNew) {
115
+ console.log(pc.green(`✓ Created worktree: ${worktreePath}`));
116
+ console.log(pc.dim(` Branch: ${worktreeResult.branch}`));
117
+ }
118
+ else {
119
+ console.log(pc.green(`✓ Using existing worktree: ${worktreePath}`));
120
+ }
121
+ // Bootstrap context
122
+ console.log(pc.dim('\nBootstrapping context from Damper...'));
123
+ const bootstrapResult = await bootstrapContext({
124
+ api,
125
+ taskId,
126
+ worktreePath,
127
+ });
128
+ console.log(pc.green(`✓ Created ${bootstrapResult.taskContextPath}`));
129
+ if (bootstrapResult.claudeMdUpdated) {
130
+ console.log(pc.green('✓ Updated CLAUDE.md with task section'));
131
+ }
132
+ }
133
+ // Launch Claude
134
+ await launchClaude({
135
+ cwd: worktreePath,
136
+ taskId,
137
+ taskTitle,
138
+ });
139
+ }
@@ -0,0 +1 @@
1
+ export declare function statusCommand(): Promise<void>;
@@ -0,0 +1,74 @@
1
+ import * as fs from 'node:fs';
2
+ import pc from 'picocolors';
3
+ import { getWorktrees, cleanupStaleWorktrees } from '../services/state.js';
4
+ import { createDamperApi } from '../services/damper-api.js';
5
+ import { getGitRoot } from '../services/worktree.js';
6
+ export async function statusCommand() {
7
+ // Clean up stale entries first
8
+ const stale = cleanupStaleWorktrees();
9
+ if (stale.length > 0) {
10
+ console.log(pc.dim(`Cleaned up ${stale.length} stale worktree reference(s)\n`));
11
+ }
12
+ // Get current project root
13
+ let projectRoot;
14
+ try {
15
+ projectRoot = await getGitRoot(process.cwd());
16
+ }
17
+ catch {
18
+ // Not in a git repo - show all worktrees
19
+ }
20
+ const worktrees = getWorktrees();
21
+ if (worktrees.length === 0) {
22
+ console.log(pc.yellow('\nNo tracked worktrees found.'));
23
+ console.log(pc.dim('Use `npx @damper/cli` to start working on a task.\n'));
24
+ return;
25
+ }
26
+ // Try to fetch task statuses from Damper
27
+ let api;
28
+ try {
29
+ api = createDamperApi();
30
+ }
31
+ catch {
32
+ // No API key - show worktrees without status
33
+ }
34
+ console.log(pc.bold('\nTracked Worktrees:\n'));
35
+ // Group by project
36
+ const byProject = new Map();
37
+ for (const wt of worktrees) {
38
+ const project = wt.projectRoot;
39
+ if (!byProject.has(project)) {
40
+ byProject.set(project, []);
41
+ }
42
+ byProject.get(project).push(wt);
43
+ }
44
+ for (const [project, projectWorktrees] of byProject) {
45
+ const isCurrent = projectRoot && project === projectRoot;
46
+ const projectName = project.split('/').pop() || project;
47
+ console.log(pc.bold(`${projectName}${isCurrent ? pc.cyan(' (current)') : ''}`));
48
+ console.log(pc.dim(` ${project}`));
49
+ console.log();
50
+ for (const wt of projectWorktrees) {
51
+ const exists = fs.existsSync(wt.path);
52
+ const worktreeName = wt.path.split('/').pop() || wt.path;
53
+ // Try to get task status
54
+ let taskStatus = '';
55
+ if (api) {
56
+ try {
57
+ const task = await api.getTask(wt.taskId);
58
+ const statusColor = task.status === 'done' ? pc.green : task.status === 'in_progress' ? pc.yellow : pc.dim;
59
+ taskStatus = statusColor(`[${task.status}]`);
60
+ }
61
+ catch {
62
+ taskStatus = pc.dim('[unknown]');
63
+ }
64
+ }
65
+ const statusIcon = exists ? pc.green('●') : pc.red('○');
66
+ console.log(` ${statusIcon} ${pc.cyan(`#${wt.taskId}`)} ${taskStatus}`);
67
+ console.log(` ${pc.dim('Path:')} ${worktreeName}`);
68
+ console.log(` ${pc.dim('Branch:')} ${wt.branch}`);
69
+ console.log(` ${pc.dim('Created:')} ${new Date(wt.createdAt).toLocaleDateString()}`);
70
+ console.log();
71
+ }
72
+ }
73
+ console.log(pc.dim('Use `npx @damper/cli cleanup` to remove worktrees for completed tasks.\n'));
74
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ import pc from 'picocolors';
3
+ import { startCommand } from './commands/start.js';
4
+ import { statusCommand } from './commands/status.js';
5
+ import { cleanupCommand } from './commands/cleanup.js';
6
+ import { setupCommand } from './commands/setup.js';
7
+ const VERSION = '0.1.0';
8
+ function showHelp() {
9
+ console.log(`
10
+ ${pc.bold('@damper/cli')} - Agent orchestration for Damper tasks
11
+
12
+ ${pc.bold('Usage:')}
13
+ npx @damper/cli Start working on a task (interactive picker)
14
+ npx @damper/cli setup Configure Damper MCP and API key
15
+ npx @damper/cli status Show all in-progress worktrees
16
+ npx @damper/cli cleanup Remove worktrees for completed tasks
17
+
18
+ ${pc.bold('Options:')}
19
+ --task <id> Work on a specific task
20
+ --type <type> Filter by type: bug, feature, improvement, task
21
+ --status <status> Filter by status: planned, in_progress, done, all
22
+ -h, --help Show this help message
23
+ -v, --version Show version
24
+
25
+ ${pc.bold('Environment:')}
26
+ DAMPER_API_KEY API key (or configure via 'setup' command)
27
+
28
+ ${pc.bold('Examples:')}
29
+ npx @damper/cli setup # First-time setup
30
+ npx @damper/cli # Interactive task picker
31
+ npx @damper/cli --task 42 # Start specific task
32
+ npx @damper/cli --type bug # Show only bugs
33
+ npx @damper/cli status # List all worktrees
34
+ npx @damper/cli cleanup # Clean up completed tasks
35
+
36
+ ${pc.bold('Workflow:')}
37
+ 1. CLI picks task from Damper roadmap
38
+ 2. Creates isolated git worktree
39
+ 3. Bootstraps context (TASK_CONTEXT.md)
40
+ 4. Launches Claude Code
41
+ 5. Claude handles task lifecycle via MCP
42
+
43
+ ${pc.dim('More info: https://usedamper.com/docs/cli')}
44
+ `);
45
+ }
46
+ function showVersion() {
47
+ console.log(`@damper/cli v${VERSION}`);
48
+ }
49
+ function parseArgs(args) {
50
+ const result = {
51
+ options: {},
52
+ help: false,
53
+ version: false,
54
+ };
55
+ for (let i = 0; i < args.length; i++) {
56
+ const arg = args[i];
57
+ if (arg === '-h' || arg === '--help') {
58
+ result.help = true;
59
+ }
60
+ else if (arg === '-v' || arg === '--version') {
61
+ result.version = true;
62
+ }
63
+ else if (arg === '--task') {
64
+ result.options.taskId = args[++i];
65
+ }
66
+ else if (arg === '--type') {
67
+ const type = args[++i];
68
+ if (type && ['bug', 'feature', 'improvement', 'task'].includes(type)) {
69
+ result.options.type = type;
70
+ }
71
+ }
72
+ else if (arg === '--status') {
73
+ const status = args[++i];
74
+ if (status && ['planned', 'in_progress', 'done', 'all'].includes(status)) {
75
+ result.options.status = status;
76
+ }
77
+ }
78
+ else if (!arg.startsWith('-') && !result.command) {
79
+ result.command = arg;
80
+ }
81
+ }
82
+ return result;
83
+ }
84
+ async function main() {
85
+ const args = process.argv.slice(2);
86
+ const { command, options, help, version } = parseArgs(args);
87
+ if (version) {
88
+ showVersion();
89
+ return;
90
+ }
91
+ if (help) {
92
+ showHelp();
93
+ return;
94
+ }
95
+ try {
96
+ switch (command) {
97
+ case 'setup':
98
+ await setupCommand();
99
+ break;
100
+ case 'status':
101
+ await statusCommand();
102
+ break;
103
+ case 'cleanup':
104
+ await cleanupCommand();
105
+ break;
106
+ case undefined:
107
+ case 'start':
108
+ await startCommand(options);
109
+ break;
110
+ default:
111
+ console.log(pc.red(`Unknown command: ${command}`));
112
+ console.log(pc.dim('Run with --help for usage information.\n'));
113
+ process.exit(1);
114
+ }
115
+ }
116
+ catch (err) {
117
+ const error = err;
118
+ console.error(pc.red(`\nError: ${error.message}`));
119
+ if (process.env.DEBUG) {
120
+ console.error(error.stack);
121
+ }
122
+ process.exit(1);
123
+ }
124
+ }
125
+ main();
@@ -0,0 +1,44 @@
1
+ interface McpServerConfig {
2
+ command: string;
3
+ args?: string[];
4
+ env?: Record<string, string>;
5
+ }
6
+ /**
7
+ * Check if Damper MCP is configured in Claude settings
8
+ */
9
+ export declare function isDamperMcpConfigured(): boolean;
10
+ /**
11
+ * Get the current API key from MCP config or environment
12
+ */
13
+ export declare function getConfiguredApiKey(): string | undefined;
14
+ /**
15
+ * Get the recommended MCP configuration
16
+ */
17
+ export declare function getDamperMcpConfig(apiKey?: string): McpServerConfig;
18
+ /**
19
+ * Configure Damper MCP in Claude settings
20
+ */
21
+ export declare function configureDamperMcp(apiKey: string): void;
22
+ /**
23
+ * Interactive setup for Damper MCP
24
+ * Returns the API key (either existing or newly entered)
25
+ */
26
+ export declare function setupDamperMcp(): Promise<string | null>;
27
+ /**
28
+ * Ensure MCP is configured, offering to set it up if not
29
+ * Returns the API key to use
30
+ */
31
+ export declare function ensureMcpConfigured(): Promise<string>;
32
+ /**
33
+ * Launch Claude Code in a directory
34
+ */
35
+ export declare function launchClaude(options: {
36
+ cwd: string;
37
+ taskId: string;
38
+ taskTitle: string;
39
+ }): Promise<void>;
40
+ /**
41
+ * Check if Claude Code CLI is installed
42
+ */
43
+ export declare function isClaudeInstalled(): Promise<boolean>;
44
+ export {};