@damper/cli 0.1.4 → 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.
package/README.md CHANGED
@@ -33,7 +33,8 @@ damper
33
33
  ## Quick Start
34
34
 
35
35
  ```bash
36
- # First time? Run setup to configure MCP and API key
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 MCP in Claude Code. Run this first if you haven't set up MCP yet.
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
- - Configures `~/.claude/settings.json` with Damper MCP
64
- - Prompts for API key if not in environment
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 # First-time 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
@@ -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, getConfiguredApiKey, setupDamperMcp, configureDamperMcp, isClaudeInstalled, } from '../services/claude.js';
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 promptForNewApiKey() {
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 new Damper API key:',
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
- const apiKey = getConfiguredApiKey();
37
- if (mcpConfigured && apiKey) {
38
- console.log(pc.green('✓ Damper MCP configured'));
39
- console.log(pc.dim(` API key: ${'*'.repeat(12)} (${apiKey.length} chars)`));
40
- // If reconfigure flag is set, prompt for new key
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 promptForNewApiKey();
44
- if (newKey) {
45
- // Verify before saving
46
- console.log(pc.dim('\nVerifying new API key...'));
47
- try {
48
- const api = createDamperApi(newKey);
49
- const { project } = await api.listTasks({ limit: 1 });
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 the API key works
96
+ // Verify existing key works
65
97
  console.log(pc.dim('\nVerifying API key...'));
66
- try {
67
- const api = createDamperApi(apiKey);
68
- const { project } = await api.listTasks({ limit: 1 });
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 promptForNewApiKey();
81
- if (newKey) {
82
- configureDamperMcp(newKey);
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('\n✓ API key updated!\n'));
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
- // Not configured - run setup
95
- if (!mcpConfigured) {
96
- console.log(pc.yellow('○ Damper MCP not configured'));
97
- }
98
- else if (!apiKey) {
99
- console.log(pc.yellow('○ Damper MCP configured but missing API key'));
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
- const key = await setupDamperMcp();
102
- if (key) {
103
- // Verify the new key
104
- console.log(pc.dim('\nVerifying API key...'));
105
- try {
106
- const api = createDamperApi(key);
107
- const { project } = await api.listTasks({ limit: 1 });
108
- console.log(pc.green(`✓ Connected to project: ${project}`));
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
  }
@@ -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, ensureMcpConfigured } from '../services/claude.js';
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
- // Create API client with the configured key
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.4';
7
+ const VERSION = '0.2.0';
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(apiKey?: string): McpServerConfig;
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(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>;
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
@@ -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(apiKey) {
48
- const config = {
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(apiKey) {
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(apiKey);
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
@@ -188,12 +99,14 @@ export async function launchClaude(options) {
188
99
  ].join('\n');
189
100
  // Launch Claude Code
190
101
  // Using subprocess with inherited stdio so user can interact
102
+ // Pass API key so MCP server gets it
191
103
  try {
192
104
  await execa('claude', ['--print', 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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@damper/cli",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "CLI tool for orchestrating Damper task workflows with Claude Code",
5
5
  "author": "Damper <hello@usedamper.com>",
6
6
  "repository": {