@damper/cli 0.1.4 → 0.2.1
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/README.md +36 -39
- package/dist/commands/setup.js +99 -65
- package/dist/commands/start.js +24 -6
- package/dist/index.js +1 -1
- package/dist/services/claude.d.ts +7 -13
- package/dist/services/claude.js +16 -103
- package/dist/services/config.d.ts +33 -0
- package/dist/services/config.js +101 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,8 @@ damper
|
|
|
33
33
|
## Quick Start
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
# First time? Run setup
|
|
36
|
+
# First time? Run setup in your project directory
|
|
37
|
+
cd your-project
|
|
37
38
|
npx @damper/cli setup
|
|
38
39
|
|
|
39
40
|
# Start working on a task
|
|
@@ -57,17 +58,19 @@ npx @damper/cli --status planned # Filter by status
|
|
|
57
58
|
|
|
58
59
|
### `npx @damper/cli setup`
|
|
59
60
|
|
|
60
|
-
Configure Damper
|
|
61
|
+
Configure Damper for the current project. Stores your API key in `.damper/config.json`.
|
|
61
62
|
|
|
62
63
|
- Checks if Claude CLI is installed
|
|
63
|
-
-
|
|
64
|
-
-
|
|
64
|
+
- Prompts for your project's API key
|
|
65
|
+
- Configures Damper MCP globally (if not already set up)
|
|
65
66
|
|
|
66
67
|
```bash
|
|
67
|
-
npx @damper/cli setup #
|
|
68
|
+
npx @damper/cli setup # Configure current project
|
|
68
69
|
npx @damper/cli setup --reconfigure # Change API key
|
|
69
70
|
```
|
|
70
71
|
|
|
72
|
+
**Note:** Add `.damper/` to your `.gitignore` to keep your API key private.
|
|
73
|
+
|
|
71
74
|
### `npx @damper/cli status`
|
|
72
75
|
|
|
73
76
|
Show all tracked worktrees and their task status.
|
|
@@ -91,6 +94,34 @@ Remove worktrees for completed or abandoned tasks. Interactive selection with au
|
|
|
91
94
|
- Abandoned tasks (unlocked in Damper)
|
|
92
95
|
- Missing worktree directories
|
|
93
96
|
|
|
97
|
+
## Configuration
|
|
98
|
+
|
|
99
|
+
### Per-Project Setup (Default)
|
|
100
|
+
|
|
101
|
+
Each project has its own API key stored in `.damper/config.json`:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
cd project-a
|
|
105
|
+
npx @damper/cli setup # Enter API key for project-a
|
|
106
|
+
|
|
107
|
+
cd ../project-b
|
|
108
|
+
npx @damper/cli setup # Enter API key for project-b
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### API Key Resolution
|
|
112
|
+
|
|
113
|
+
The CLI looks for API keys in this order:
|
|
114
|
+
1. `DAMPER_API_KEY` environment variable (highest priority)
|
|
115
|
+
2. `.damper/config.json` in the project root
|
|
116
|
+
3. Global MCP config in `~/.claude/settings.json` (legacy)
|
|
117
|
+
|
|
118
|
+
### Environment Variables
|
|
119
|
+
|
|
120
|
+
| Variable | Description |
|
|
121
|
+
|----------|-------------|
|
|
122
|
+
| `DAMPER_API_KEY` | Override API key (useful for CI/scripts) |
|
|
123
|
+
| `DAMPER_API_URL` | API URL override (default: `https://api.usedamper.com`) |
|
|
124
|
+
|
|
94
125
|
## Workflow
|
|
95
126
|
|
|
96
127
|
### Starting a New Task
|
|
@@ -150,40 +181,6 @@ Once Claude is running, it handles:
|
|
|
150
181
|
- `complete_task` - Mark done with summary
|
|
151
182
|
- `abandon_task` - Hand off with notes for next agent
|
|
152
183
|
|
|
153
|
-
## Environment Variables
|
|
154
|
-
|
|
155
|
-
| Variable | Description |
|
|
156
|
-
|----------|-------------|
|
|
157
|
-
| `DAMPER_API_KEY` | Your Damper API key (or configure via `setup`) |
|
|
158
|
-
| `DAMPER_API_URL` | API URL override (default: `https://api.usedamper.com`) |
|
|
159
|
-
|
|
160
|
-
### Per-Project API Keys
|
|
161
|
-
|
|
162
|
-
Damper API keys are project-scoped. If you work with multiple Damper projects, you can use different keys:
|
|
163
|
-
|
|
164
|
-
**Option 1: Environment variable (recommended)**
|
|
165
|
-
```bash
|
|
166
|
-
# Set in your shell or project's .env file
|
|
167
|
-
export DAMPER_API_KEY=your-project-specific-key
|
|
168
|
-
npx @damper/cli
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
**Option 2: Use direnv for automatic switching**
|
|
172
|
-
```bash
|
|
173
|
-
# In each project directory, create .envrc:
|
|
174
|
-
echo 'export DAMPER_API_KEY=your-key-here' > .envrc
|
|
175
|
-
direnv allow
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
**Option 3: Reconfigure when switching projects**
|
|
179
|
-
```bash
|
|
180
|
-
npx @damper/cli setup --reconfigure
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
The CLI checks for keys in this order:
|
|
184
|
-
1. `DAMPER_API_KEY` environment variable
|
|
185
|
-
2. `~/.claude/settings.json` (configured via `setup`)
|
|
186
|
-
|
|
187
184
|
## How it Works
|
|
188
185
|
|
|
189
186
|
### Worktree Isolation
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { confirm } from '@inquirer/prompts';
|
|
1
|
+
import { confirm, password } from '@inquirer/prompts';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
|
-
import { isDamperMcpConfigured,
|
|
3
|
+
import { isDamperMcpConfigured, configureDamperMcp, isClaudeInstalled, } from '../services/claude.js';
|
|
4
|
+
import { getApiKey, writeProjectConfig, isProjectConfigured, tryGetProjectRoot, getProjectConfigPath, } from '../services/config.js';
|
|
4
5
|
import { createDamperApi } from '../services/damper-api.js';
|
|
5
|
-
async function
|
|
6
|
-
const { password } = await import('@inquirer/prompts');
|
|
6
|
+
async function promptForApiKey() {
|
|
7
7
|
console.log(pc.dim('\nGet your API key from: https://usedamper.com → Settings → API Keys\n'));
|
|
8
8
|
const apiKey = await password({
|
|
9
|
-
message: 'Enter your
|
|
9
|
+
message: 'Enter your Damper API key:',
|
|
10
10
|
mask: '*',
|
|
11
11
|
validate: (value) => {
|
|
12
12
|
if (!value || value.trim().length === 0) {
|
|
@@ -20,6 +20,17 @@ async function promptForNewApiKey() {
|
|
|
20
20
|
});
|
|
21
21
|
return apiKey.trim();
|
|
22
22
|
}
|
|
23
|
+
async function verifyApiKey(apiKey) {
|
|
24
|
+
try {
|
|
25
|
+
const api = createDamperApi(apiKey);
|
|
26
|
+
const { project } = await api.listTasks({ limit: 1 });
|
|
27
|
+
return { valid: true, project };
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
const error = err;
|
|
31
|
+
return { valid: false, error: error.message };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
23
34
|
export async function setupCommand(options = {}) {
|
|
24
35
|
console.log(pc.bold('\n@damper/cli Setup\n'));
|
|
25
36
|
// Check Claude CLI
|
|
@@ -31,88 +42,111 @@ export async function setupCommand(options = {}) {
|
|
|
31
42
|
else {
|
|
32
43
|
console.log(pc.green('✓ Claude Code CLI installed'));
|
|
33
44
|
}
|
|
34
|
-
// Check MCP configuration
|
|
45
|
+
// Check MCP configuration (global)
|
|
35
46
|
const mcpConfigured = isDamperMcpConfigured();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
if (mcpConfigured) {
|
|
48
|
+
console.log(pc.green('✓ Damper MCP configured globally'));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log(pc.yellow('○ Damper MCP not configured'));
|
|
52
|
+
}
|
|
53
|
+
// Get project root
|
|
54
|
+
const projectRoot = await tryGetProjectRoot();
|
|
55
|
+
if (!projectRoot) {
|
|
56
|
+
console.log(pc.yellow('\n⚠️ Not in a git repository'));
|
|
57
|
+
console.log(pc.dim(' Run this command from within a project to configure it.\n'));
|
|
58
|
+
// Still offer to set up global MCP if not configured
|
|
59
|
+
if (!mcpConfigured) {
|
|
60
|
+
const shouldSetupMcp = await confirm({
|
|
61
|
+
message: 'Set up Damper MCP globally (without API key)?',
|
|
62
|
+
default: true,
|
|
63
|
+
});
|
|
64
|
+
if (shouldSetupMcp) {
|
|
65
|
+
configureDamperMcp();
|
|
66
|
+
console.log(pc.green('\n✓ Damper MCP configured globally'));
|
|
67
|
+
console.log(pc.dim(' Run setup again from a project directory to add an API key.\n'));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Check project configuration
|
|
73
|
+
const projectConfigured = isProjectConfigured(projectRoot);
|
|
74
|
+
const existingKey = getApiKey(projectRoot);
|
|
75
|
+
if (projectConfigured && existingKey) {
|
|
76
|
+
console.log(pc.green('✓ Project configured'));
|
|
77
|
+
console.log(pc.dim(` Config: ${getProjectConfigPath(projectRoot)}`));
|
|
78
|
+
// Reconfigure flow
|
|
41
79
|
if (options.reconfigure) {
|
|
42
80
|
console.log(pc.yellow('\nReconfiguring API key...'));
|
|
43
|
-
const newKey = await
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
console.log(pc.green(`✓ Connected to project: ${project}`));
|
|
51
|
-
// Save the new key
|
|
52
|
-
configureDamperMcp(newKey);
|
|
53
|
-
process.env.DAMPER_API_KEY = newKey;
|
|
54
|
-
console.log(pc.green('\n✓ API key updated successfully!\n'));
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
const error = err;
|
|
58
|
-
console.log(pc.red(`✗ API key verification failed: ${error.message}`));
|
|
59
|
-
console.log(pc.dim(' API key was NOT updated.\n'));
|
|
60
|
-
}
|
|
81
|
+
const newKey = await promptForApiKey();
|
|
82
|
+
console.log(pc.dim('\nVerifying API key...'));
|
|
83
|
+
const { valid, project, error } = await verifyApiKey(newKey);
|
|
84
|
+
if (!valid) {
|
|
85
|
+
console.log(pc.red(`✗ API key verification failed: ${error}`));
|
|
86
|
+
console.log(pc.dim(' API key was NOT updated.\n'));
|
|
87
|
+
return;
|
|
61
88
|
}
|
|
89
|
+
console.log(pc.green(`✓ Connected to project: ${project}`));
|
|
90
|
+
// Save to project config
|
|
91
|
+
writeProjectConfig(projectRoot, { apiKey: newKey });
|
|
92
|
+
process.env.DAMPER_API_KEY = newKey;
|
|
93
|
+
console.log(pc.green('\n✓ API key updated successfully!\n'));
|
|
62
94
|
return;
|
|
63
95
|
}
|
|
64
|
-
// Verify
|
|
96
|
+
// Verify existing key works
|
|
65
97
|
console.log(pc.dim('\nVerifying API key...'));
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
console.log(pc.green(`✓ Connected to project: ${project}`));
|
|
70
|
-
}
|
|
71
|
-
catch (err) {
|
|
72
|
-
const error = err;
|
|
73
|
-
console.log(pc.red(`✗ API key verification failed: ${error.message}`));
|
|
74
|
-
// Offer to reconfigure
|
|
98
|
+
const { valid, project, error } = await verifyApiKey(existingKey);
|
|
99
|
+
if (!valid) {
|
|
100
|
+
console.log(pc.red(`✗ API key verification failed: ${error}`));
|
|
75
101
|
const shouldReconfigure = await confirm({
|
|
76
102
|
message: 'Would you like to enter a new API key?',
|
|
77
103
|
default: true,
|
|
78
104
|
});
|
|
79
105
|
if (shouldReconfigure) {
|
|
80
|
-
const newKey = await
|
|
81
|
-
|
|
82
|
-
|
|
106
|
+
const newKey = await promptForApiKey();
|
|
107
|
+
const result = await verifyApiKey(newKey);
|
|
108
|
+
if (result.valid) {
|
|
109
|
+
writeProjectConfig(projectRoot, { apiKey: newKey });
|
|
83
110
|
process.env.DAMPER_API_KEY = newKey;
|
|
84
|
-
console.log(pc.green(
|
|
111
|
+
console.log(pc.green(`\n✓ Connected to project: ${result.project}`));
|
|
112
|
+
console.log(pc.green('✓ API key updated!\n'));
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
console.log(pc.red(`✗ API key verification failed: ${result.error}\n`));
|
|
85
116
|
}
|
|
86
117
|
}
|
|
87
118
|
return;
|
|
88
119
|
}
|
|
120
|
+
console.log(pc.green(`✓ Connected to project: ${project}`));
|
|
89
121
|
console.log(pc.green('\n✓ All checks passed! You\'re ready to use @damper/cli.\n'));
|
|
90
122
|
console.log(pc.dim('Run `npx @damper/cli` to start working on a task.'));
|
|
91
123
|
console.log(pc.dim('Run `npx @damper/cli setup --reconfigure` to change API key.\n'));
|
|
92
124
|
return;
|
|
93
125
|
}
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
126
|
+
// New project setup
|
|
127
|
+
console.log(pc.yellow('\n○ Project not configured'));
|
|
128
|
+
console.log(pc.dim(` Will save config to: ${getProjectConfigPath(projectRoot)}\n`));
|
|
129
|
+
// Prompt for API key
|
|
130
|
+
const apiKey = await promptForApiKey();
|
|
131
|
+
// Verify
|
|
132
|
+
console.log(pc.dim('\nVerifying API key...'));
|
|
133
|
+
const { valid, project, error } = await verifyApiKey(apiKey);
|
|
134
|
+
if (!valid) {
|
|
135
|
+
console.log(pc.red(`✗ API key verification failed: ${error}`));
|
|
136
|
+
console.log(pc.dim(' Please check your API key and try again.\n'));
|
|
137
|
+
return;
|
|
100
138
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
console.log(pc.green('\n✓ Setup complete! You\'re ready to use @damper/cli.\n'));
|
|
110
|
-
console.log(pc.dim('Run `npx @damper/cli` to start working on a task.\n'));
|
|
111
|
-
}
|
|
112
|
-
catch (err) {
|
|
113
|
-
const error = err;
|
|
114
|
-
console.log(pc.red(`✗ API key verification failed: ${error.message}`));
|
|
115
|
-
console.log(pc.dim(' Please check your API key and try again.\n'));
|
|
116
|
-
}
|
|
139
|
+
console.log(pc.green(`✓ Connected to project: ${project}`));
|
|
140
|
+
// Save to project config
|
|
141
|
+
writeProjectConfig(projectRoot, { apiKey });
|
|
142
|
+
console.log(pc.green(`✓ Saved API key to ${getProjectConfigPath(projectRoot)}`));
|
|
143
|
+
// Set up global MCP if not configured
|
|
144
|
+
if (!mcpConfigured) {
|
|
145
|
+
configureDamperMcp();
|
|
146
|
+
console.log(pc.green('✓ Configured Damper MCP globally'));
|
|
117
147
|
}
|
|
148
|
+
// Remind about .gitignore
|
|
149
|
+
console.log(pc.yellow('\n⚠️ Add .damper/ to your .gitignore to keep your API key private'));
|
|
150
|
+
console.log(pc.green('\n✓ Setup complete! You\'re ready to use @damper/cli.\n'));
|
|
151
|
+
console.log(pc.dim('Run `npx @damper/cli` to start working on a task.\n'));
|
|
118
152
|
}
|
package/dist/commands/start.js
CHANGED
|
@@ -4,8 +4,9 @@ import { createDamperApi } from '../services/damper-api.js';
|
|
|
4
4
|
import { createWorktree, getGitRoot } from '../services/worktree.js';
|
|
5
5
|
import { bootstrapContext, refreshContext } from '../services/context-bootstrap.js';
|
|
6
6
|
import { pickTask } from '../ui/task-picker.js';
|
|
7
|
-
import { launchClaude, isClaudeInstalled,
|
|
7
|
+
import { launchClaude, isClaudeInstalled, isDamperMcpConfigured, configureDamperMcp } from '../services/claude.js';
|
|
8
8
|
import { getWorktreesForProject, cleanupStaleWorktrees } from '../services/state.js';
|
|
9
|
+
import { getApiKey, isProjectConfigured, getProjectConfigPath } from '../services/config.js';
|
|
9
10
|
export async function startCommand(options) {
|
|
10
11
|
// Check Claude CLI first
|
|
11
12
|
const claudeInstalled = await isClaudeInstalled();
|
|
@@ -14,9 +15,6 @@ export async function startCommand(options) {
|
|
|
14
15
|
console.log(pc.dim('Install it with: npm install -g @anthropic-ai/claude-code\n'));
|
|
15
16
|
process.exit(1);
|
|
16
17
|
}
|
|
17
|
-
// Ensure MCP is configured (will prompt for setup if not)
|
|
18
|
-
// This also handles API key setup
|
|
19
|
-
const apiKey = await ensureMcpConfigured();
|
|
20
18
|
// Get project root (git root)
|
|
21
19
|
let projectRoot;
|
|
22
20
|
try {
|
|
@@ -27,7 +25,26 @@ export async function startCommand(options) {
|
|
|
27
25
|
console.log(pc.dim('Run this command from within a git repository.\n'));
|
|
28
26
|
process.exit(1);
|
|
29
27
|
}
|
|
30
|
-
//
|
|
28
|
+
// Check project configuration
|
|
29
|
+
const apiKey = getApiKey(projectRoot);
|
|
30
|
+
if (!apiKey) {
|
|
31
|
+
if (!isProjectConfigured(projectRoot)) {
|
|
32
|
+
console.log(pc.yellow('\nProject not configured.'));
|
|
33
|
+
console.log(pc.dim(`Run ${pc.cyan('npx @damper/cli setup')} to configure this project.\n`));
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
console.log(pc.red('\nNo API key found.'));
|
|
37
|
+
console.log(pc.dim(`Check ${getProjectConfigPath(projectRoot)} or set DAMPER_API_KEY env var.\n`));
|
|
38
|
+
}
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
// Ensure MCP is configured globally (without key - key comes from env)
|
|
42
|
+
if (!isDamperMcpConfigured()) {
|
|
43
|
+
console.log(pc.dim('Configuring Damper MCP...'));
|
|
44
|
+
configureDamperMcp();
|
|
45
|
+
console.log(pc.green('✓ Damper MCP configured'));
|
|
46
|
+
}
|
|
47
|
+
// Create API client with the project's key
|
|
31
48
|
const api = createDamperApi(apiKey);
|
|
32
49
|
// Clean up stale worktrees
|
|
33
50
|
const stale = cleanupStaleWorktrees();
|
|
@@ -130,10 +147,11 @@ export async function startCommand(options) {
|
|
|
130
147
|
console.log(pc.green('✓ Updated CLAUDE.md with task section'));
|
|
131
148
|
}
|
|
132
149
|
}
|
|
133
|
-
// Launch Claude
|
|
150
|
+
// Launch Claude with project's API key
|
|
134
151
|
await launchClaude({
|
|
135
152
|
cwd: worktreePath,
|
|
136
153
|
taskId,
|
|
137
154
|
taskTitle,
|
|
155
|
+
apiKey,
|
|
138
156
|
});
|
|
139
157
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { startCommand } from './commands/start.js';
|
|
|
4
4
|
import { statusCommand } from './commands/status.js';
|
|
5
5
|
import { cleanupCommand } from './commands/cleanup.js';
|
|
6
6
|
import { setupCommand } from './commands/setup.js';
|
|
7
|
-
const VERSION = '0.1
|
|
7
|
+
const VERSION = '0.2.1';
|
|
8
8
|
function showHelp() {
|
|
9
9
|
console.log(`
|
|
10
10
|
${pc.bold('@damper/cli')} - Agent orchestration for Damper tasks
|
|
@@ -9,26 +9,19 @@ interface McpServerConfig {
|
|
|
9
9
|
export declare function isDamperMcpConfigured(): boolean;
|
|
10
10
|
/**
|
|
11
11
|
* Get the current API key from MCP config or environment
|
|
12
|
+
* @deprecated Use getApiKey from config.ts instead for project-aware key resolution
|
|
12
13
|
*/
|
|
13
14
|
export declare function getConfiguredApiKey(): string | undefined;
|
|
14
15
|
/**
|
|
15
16
|
* Get the recommended MCP configuration
|
|
17
|
+
* Note: API key is passed via environment when launching Claude, not stored in config
|
|
16
18
|
*/
|
|
17
|
-
export declare function getDamperMcpConfig(
|
|
19
|
+
export declare function getDamperMcpConfig(): McpServerConfig;
|
|
18
20
|
/**
|
|
19
|
-
* Configure Damper MCP in Claude settings
|
|
21
|
+
* Configure Damper MCP in Claude settings (global)
|
|
22
|
+
* Note: API key is NOT stored here - it's passed via environment when launching Claude
|
|
20
23
|
*/
|
|
21
|
-
export declare function configureDamperMcp(
|
|
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>;
|
|
24
|
+
export declare function configureDamperMcp(): void;
|
|
32
25
|
/**
|
|
33
26
|
* Launch Claude Code in a directory
|
|
34
27
|
*/
|
|
@@ -36,6 +29,7 @@ export declare function launchClaude(options: {
|
|
|
36
29
|
cwd: string;
|
|
37
30
|
taskId: string;
|
|
38
31
|
taskTitle: string;
|
|
32
|
+
apiKey: string;
|
|
39
33
|
}): Promise<void>;
|
|
40
34
|
/**
|
|
41
35
|
* Check if Claude Code CLI is installed
|
package/dist/services/claude.js
CHANGED
|
@@ -2,7 +2,6 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import { execa } from 'execa';
|
|
5
|
-
import { confirm, password } from '@inquirer/prompts';
|
|
6
5
|
import pc from 'picocolors';
|
|
7
6
|
const CLAUDE_SETTINGS_DIR = path.join(os.homedir(), '.claude');
|
|
8
7
|
const CLAUDE_SETTINGS_FILE = path.join(CLAUDE_SETTINGS_DIR, 'settings.json');
|
|
@@ -23,13 +22,14 @@ export function isDamperMcpConfigured() {
|
|
|
23
22
|
}
|
|
24
23
|
/**
|
|
25
24
|
* Get the current API key from MCP config or environment
|
|
25
|
+
* @deprecated Use getApiKey from config.ts instead for project-aware key resolution
|
|
26
26
|
*/
|
|
27
27
|
export function getConfiguredApiKey() {
|
|
28
28
|
// First check environment
|
|
29
29
|
if (process.env.DAMPER_API_KEY) {
|
|
30
30
|
return process.env.DAMPER_API_KEY;
|
|
31
31
|
}
|
|
32
|
-
// Then check Claude settings
|
|
32
|
+
// Then check Claude settings (legacy global key)
|
|
33
33
|
if (!fs.existsSync(CLAUDE_SETTINGS_FILE)) {
|
|
34
34
|
return undefined;
|
|
35
35
|
}
|
|
@@ -43,21 +43,19 @@ export function getConfiguredApiKey() {
|
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
45
|
* Get the recommended MCP configuration
|
|
46
|
+
* Note: API key is passed via environment when launching Claude, not stored in config
|
|
46
47
|
*/
|
|
47
|
-
export function getDamperMcpConfig(
|
|
48
|
-
|
|
48
|
+
export function getDamperMcpConfig() {
|
|
49
|
+
return {
|
|
49
50
|
command: 'npx',
|
|
50
51
|
args: ['-y', '@damper/mcp'],
|
|
51
52
|
};
|
|
52
|
-
if (apiKey) {
|
|
53
|
-
config.env = { DAMPER_API_KEY: apiKey };
|
|
54
|
-
}
|
|
55
|
-
return config;
|
|
56
53
|
}
|
|
57
54
|
/**
|
|
58
|
-
* Configure Damper MCP in Claude settings
|
|
55
|
+
* Configure Damper MCP in Claude settings (global)
|
|
56
|
+
* Note: API key is NOT stored here - it's passed via environment when launching Claude
|
|
59
57
|
*/
|
|
60
|
-
export function configureDamperMcp(
|
|
58
|
+
export function configureDamperMcp() {
|
|
61
59
|
// Ensure directory exists
|
|
62
60
|
if (!fs.existsSync(CLAUDE_SETTINGS_DIR)) {
|
|
63
61
|
fs.mkdirSync(CLAUDE_SETTINGS_DIR, { recursive: true });
|
|
@@ -73,106 +71,19 @@ export function configureDamperMcp(apiKey) {
|
|
|
73
71
|
settings = {};
|
|
74
72
|
}
|
|
75
73
|
}
|
|
76
|
-
// Add/update MCP config
|
|
74
|
+
// Add/update MCP config (without API key - it comes from env)
|
|
77
75
|
if (!settings.mcpServers) {
|
|
78
76
|
settings.mcpServers = {};
|
|
79
77
|
}
|
|
80
|
-
settings.mcpServers.damper = getDamperMcpConfig(
|
|
78
|
+
settings.mcpServers.damper = getDamperMcpConfig();
|
|
81
79
|
// Write back
|
|
82
80
|
fs.writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2));
|
|
83
81
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Interactive setup for Damper MCP
|
|
86
|
-
* Returns the API key (either existing or newly entered)
|
|
87
|
-
*/
|
|
88
|
-
export async function setupDamperMcp() {
|
|
89
|
-
console.log(pc.yellow('\nDamper MCP is not configured in Claude Code.'));
|
|
90
|
-
console.log(pc.dim('The MCP server allows Claude to interact with your Damper tasks.\n'));
|
|
91
|
-
const shouldSetup = await confirm({
|
|
92
|
-
message: 'Would you like to set it up now?',
|
|
93
|
-
default: true,
|
|
94
|
-
});
|
|
95
|
-
if (!shouldSetup) {
|
|
96
|
-
console.log(pc.dim('\nYou can set it up later by running:'));
|
|
97
|
-
console.log(pc.cyan(' claude mcp add damper npx @damper/mcp\n'));
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
// Check if we already have an API key in the environment
|
|
101
|
-
const existingKey = process.env.DAMPER_API_KEY;
|
|
102
|
-
let apiKey;
|
|
103
|
-
if (existingKey) {
|
|
104
|
-
console.log(pc.dim('\nFound DAMPER_API_KEY in environment.'));
|
|
105
|
-
const useExisting = await confirm({
|
|
106
|
-
message: 'Use the API key from your environment?',
|
|
107
|
-
default: true,
|
|
108
|
-
});
|
|
109
|
-
if (useExisting) {
|
|
110
|
-
apiKey = existingKey;
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
apiKey = await promptForApiKey();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
apiKey = await promptForApiKey();
|
|
118
|
-
}
|
|
119
|
-
// Configure MCP
|
|
120
|
-
console.log(pc.dim('\nConfiguring Damper MCP...'));
|
|
121
|
-
configureDamperMcp(apiKey);
|
|
122
|
-
console.log(pc.green('✓ Damper MCP configured in ~/.claude/settings.json'));
|
|
123
|
-
// Set in environment for current session
|
|
124
|
-
process.env.DAMPER_API_KEY = apiKey;
|
|
125
|
-
return apiKey;
|
|
126
|
-
}
|
|
127
|
-
async function promptForApiKey() {
|
|
128
|
-
console.log(pc.dim('\nGet your API key from: https://usedamper.com → Settings → API Keys\n'));
|
|
129
|
-
const apiKey = await password({
|
|
130
|
-
message: 'Enter your Damper API key:',
|
|
131
|
-
mask: '*',
|
|
132
|
-
validate: (value) => {
|
|
133
|
-
if (!value || value.trim().length === 0) {
|
|
134
|
-
return 'API key is required';
|
|
135
|
-
}
|
|
136
|
-
if (value.length < 10) {
|
|
137
|
-
return 'API key seems too short';
|
|
138
|
-
}
|
|
139
|
-
return true;
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
return apiKey.trim();
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Ensure MCP is configured, offering to set it up if not
|
|
146
|
-
* Returns the API key to use
|
|
147
|
-
*/
|
|
148
|
-
export async function ensureMcpConfigured() {
|
|
149
|
-
// Check if already configured
|
|
150
|
-
if (isDamperMcpConfigured()) {
|
|
151
|
-
const apiKey = getConfiguredApiKey();
|
|
152
|
-
if (apiKey) {
|
|
153
|
-
return apiKey;
|
|
154
|
-
}
|
|
155
|
-
// MCP configured but no API key - need to add it
|
|
156
|
-
console.log(pc.yellow('\nDamper MCP is configured but no API key found.'));
|
|
157
|
-
const key = await promptForApiKey();
|
|
158
|
-
configureDamperMcp(key);
|
|
159
|
-
process.env.DAMPER_API_KEY = key;
|
|
160
|
-
return key;
|
|
161
|
-
}
|
|
162
|
-
// Not configured - run setup
|
|
163
|
-
const apiKey = await setupDamperMcp();
|
|
164
|
-
if (!apiKey) {
|
|
165
|
-
// User declined setup
|
|
166
|
-
console.log(pc.red('\nCannot continue without Damper MCP configuration.'));
|
|
167
|
-
process.exit(1);
|
|
168
|
-
}
|
|
169
|
-
return apiKey;
|
|
170
|
-
}
|
|
171
82
|
/**
|
|
172
83
|
* Launch Claude Code in a directory
|
|
173
84
|
*/
|
|
174
85
|
export async function launchClaude(options) {
|
|
175
|
-
const { cwd, taskId, taskTitle } = options;
|
|
86
|
+
const { cwd, taskId, taskTitle, apiKey } = options;
|
|
176
87
|
console.log(pc.green(`\nStarting Claude Code for task #${taskId}: ${taskTitle}`));
|
|
177
88
|
console.log(pc.dim(`Directory: ${cwd}\n`));
|
|
178
89
|
// Build initial prompt
|
|
@@ -186,14 +97,16 @@ export async function launchClaude(options) {
|
|
|
186
97
|
'2. Use `add_note` for important decisions',
|
|
187
98
|
'3. Call `complete_task` when done or `abandon_task` if stopping early',
|
|
188
99
|
].join('\n');
|
|
189
|
-
// Launch Claude Code
|
|
190
|
-
//
|
|
100
|
+
// Launch Claude Code in interactive mode
|
|
101
|
+
// Pass the prompt as an argument (not --print which is non-interactive)
|
|
102
|
+
// Pass API key so MCP server gets it
|
|
191
103
|
try {
|
|
192
|
-
await execa('claude', [
|
|
104
|
+
await execa('claude', [initialPrompt], {
|
|
193
105
|
cwd,
|
|
194
106
|
stdio: 'inherit',
|
|
195
107
|
env: {
|
|
196
108
|
...process.env,
|
|
109
|
+
DAMPER_API_KEY: apiKey,
|
|
197
110
|
},
|
|
198
111
|
});
|
|
199
112
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
interface ProjectConfig {
|
|
2
|
+
apiKey?: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Get the project config directory path
|
|
6
|
+
*/
|
|
7
|
+
export declare function getProjectConfigDir(projectRoot: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Get the project config file path
|
|
10
|
+
*/
|
|
11
|
+
export declare function getProjectConfigPath(projectRoot: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Read project config
|
|
14
|
+
*/
|
|
15
|
+
export declare function readProjectConfig(projectRoot: string): ProjectConfig;
|
|
16
|
+
/**
|
|
17
|
+
* Write project config
|
|
18
|
+
*/
|
|
19
|
+
export declare function writeProjectConfig(projectRoot: string, config: ProjectConfig): void;
|
|
20
|
+
/**
|
|
21
|
+
* Get API key for a project
|
|
22
|
+
* Priority: env var > project config > global MCP config
|
|
23
|
+
*/
|
|
24
|
+
export declare function getApiKey(projectRoot?: string): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Check if project has Damper configured
|
|
27
|
+
*/
|
|
28
|
+
export declare function isProjectConfigured(projectRoot: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Try to get project root, returns undefined if not in a git repo
|
|
31
|
+
*/
|
|
32
|
+
export declare function tryGetProjectRoot(): Promise<string | undefined>;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import { getGitRoot } from './worktree.js';
|
|
5
|
+
const CONFIG_DIR = '.damper';
|
|
6
|
+
const CONFIG_FILE = 'config.json';
|
|
7
|
+
/**
|
|
8
|
+
* Get the project config directory path
|
|
9
|
+
*/
|
|
10
|
+
export function getProjectConfigDir(projectRoot) {
|
|
11
|
+
return path.join(projectRoot, CONFIG_DIR);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get the project config file path
|
|
15
|
+
*/
|
|
16
|
+
export function getProjectConfigPath(projectRoot) {
|
|
17
|
+
return path.join(getProjectConfigDir(projectRoot), CONFIG_FILE);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Read project config
|
|
21
|
+
*/
|
|
22
|
+
export function readProjectConfig(projectRoot) {
|
|
23
|
+
const configPath = getProjectConfigPath(projectRoot);
|
|
24
|
+
if (!fs.existsSync(configPath)) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Write project config
|
|
36
|
+
*/
|
|
37
|
+
export function writeProjectConfig(projectRoot, config) {
|
|
38
|
+
const configDir = getProjectConfigDir(projectRoot);
|
|
39
|
+
const configPath = getProjectConfigPath(projectRoot);
|
|
40
|
+
// Ensure directory exists
|
|
41
|
+
if (!fs.existsSync(configDir)) {
|
|
42
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
// Merge with existing config
|
|
45
|
+
const existing = readProjectConfig(projectRoot);
|
|
46
|
+
const merged = { ...existing, ...config };
|
|
47
|
+
fs.writeFileSync(configPath, JSON.stringify(merged, null, 2) + '\n');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get API key for a project
|
|
51
|
+
* Priority: env var > project config > global MCP config
|
|
52
|
+
*/
|
|
53
|
+
export function getApiKey(projectRoot) {
|
|
54
|
+
// 1. Environment variable (highest priority)
|
|
55
|
+
if (process.env.DAMPER_API_KEY) {
|
|
56
|
+
return process.env.DAMPER_API_KEY;
|
|
57
|
+
}
|
|
58
|
+
// 2. Project config
|
|
59
|
+
if (projectRoot) {
|
|
60
|
+
const projectConfig = readProjectConfig(projectRoot);
|
|
61
|
+
if (projectConfig.apiKey) {
|
|
62
|
+
return projectConfig.apiKey;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// 3. Global MCP config (legacy/fallback)
|
|
66
|
+
return getGlobalApiKey();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get API key from global MCP settings (legacy)
|
|
70
|
+
*/
|
|
71
|
+
function getGlobalApiKey() {
|
|
72
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
73
|
+
if (!fs.existsSync(settingsPath)) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
78
|
+
return settings.mcpServers?.damper?.env?.DAMPER_API_KEY;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if project has Damper configured
|
|
86
|
+
*/
|
|
87
|
+
export function isProjectConfigured(projectRoot) {
|
|
88
|
+
const config = readProjectConfig(projectRoot);
|
|
89
|
+
return !!config.apiKey;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Try to get project root, returns undefined if not in a git repo
|
|
93
|
+
*/
|
|
94
|
+
export async function tryGetProjectRoot() {
|
|
95
|
+
try {
|
|
96
|
+
return await getGitRoot(process.cwd());
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|