@damper/cli 0.3.5 → 0.4.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/dist/commands/start.js +9 -2
- package/dist/index.js +1 -1
- package/dist/services/claude.d.ts +13 -0
- package/dist/services/claude.js +148 -1
- package/package.json +1 -1
package/dist/commands/start.js
CHANGED
|
@@ -4,7 +4,7 @@ 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, isDamperMcpConfigured, configureDamperMcp } from '../services/claude.js';
|
|
7
|
+
import { launchClaude, postTaskFlow, isClaudeInstalled, isDamperMcpConfigured, configureDamperMcp } from '../services/claude.js';
|
|
8
8
|
import { getWorktreesForProject, cleanupStaleWorktrees } from '../services/state.js';
|
|
9
9
|
import { getApiKey, isProjectConfigured, getProjectConfigPath } from '../services/config.js';
|
|
10
10
|
export async function startCommand(options) {
|
|
@@ -148,11 +148,18 @@ export async function startCommand(options) {
|
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
// Launch Claude with project's API key
|
|
151
|
-
await launchClaude({
|
|
151
|
+
const result = await launchClaude({
|
|
152
152
|
cwd: worktreePath,
|
|
153
153
|
taskId,
|
|
154
154
|
taskTitle,
|
|
155
155
|
apiKey,
|
|
156
156
|
yolo: options.yolo,
|
|
157
157
|
});
|
|
158
|
+
// Post-task flow: push, PR, cleanup
|
|
159
|
+
await postTaskFlow({
|
|
160
|
+
cwd: result.cwd,
|
|
161
|
+
taskId: result.taskId,
|
|
162
|
+
apiKey: result.apiKey,
|
|
163
|
+
projectRoot,
|
|
164
|
+
});
|
|
158
165
|
}
|
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.
|
|
7
|
+
const VERSION = '0.4.1';
|
|
8
8
|
function showHelp() {
|
|
9
9
|
console.log(`
|
|
10
10
|
${pc.bold('@damper/cli')} - Agent orchestration for Damper tasks
|
|
@@ -31,6 +31,19 @@ export declare function launchClaude(options: {
|
|
|
31
31
|
taskTitle: string;
|
|
32
32
|
apiKey: string;
|
|
33
33
|
yolo?: boolean;
|
|
34
|
+
}): Promise<{
|
|
35
|
+
cwd: string;
|
|
36
|
+
taskId: string;
|
|
37
|
+
apiKey: string;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Post-task actions after Claude exits
|
|
41
|
+
*/
|
|
42
|
+
export declare function postTaskFlow(options: {
|
|
43
|
+
cwd: string;
|
|
44
|
+
taskId: string;
|
|
45
|
+
apiKey: string;
|
|
46
|
+
projectRoot: string;
|
|
34
47
|
}): Promise<void>;
|
|
35
48
|
/**
|
|
36
49
|
* Check if Claude Code CLI is installed
|
package/dist/services/claude.js
CHANGED
|
@@ -162,7 +162,154 @@ Once approved, switch to implementation and start logging your work.
|
|
|
162
162
|
console.log(pc.dim('Install it with: npm install -g @anthropic-ai/claude-code\n'));
|
|
163
163
|
process.exit(1);
|
|
164
164
|
}
|
|
165
|
-
|
|
165
|
+
// Claude exited with error - still continue to post-task flow
|
|
166
|
+
}
|
|
167
|
+
// Post-task flow
|
|
168
|
+
console.log(pc.dim('\n─────────────────────────────────────────'));
|
|
169
|
+
console.log(pc.bold('\nClaude session ended.\n'));
|
|
170
|
+
return { cwd, taskId, apiKey };
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Post-task actions after Claude exits
|
|
174
|
+
*/
|
|
175
|
+
export async function postTaskFlow(options) {
|
|
176
|
+
const { cwd, taskId, apiKey, projectRoot } = options;
|
|
177
|
+
const { confirm, select } = await import('@inquirer/prompts');
|
|
178
|
+
const { createDamperApi } = await import('./damper-api.js');
|
|
179
|
+
const { removeWorktreeDir } = await import('./worktree.js');
|
|
180
|
+
const { removeWorktree } = await import('./state.js');
|
|
181
|
+
// Check task status from Damper
|
|
182
|
+
let taskStatus;
|
|
183
|
+
let taskTitle;
|
|
184
|
+
try {
|
|
185
|
+
const api = createDamperApi(apiKey);
|
|
186
|
+
const task = await api.getTask(taskId);
|
|
187
|
+
taskStatus = task.status;
|
|
188
|
+
taskTitle = task.title;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
console.log(pc.yellow('Could not fetch task status from Damper.'));
|
|
192
|
+
}
|
|
193
|
+
// Check git status
|
|
194
|
+
let hasUnpushedCommits = false;
|
|
195
|
+
let hasUncommittedChanges = false;
|
|
196
|
+
let currentBranch = '';
|
|
197
|
+
try {
|
|
198
|
+
const { execa } = await import('execa');
|
|
199
|
+
// Get current branch
|
|
200
|
+
const { stdout: branch } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, stdio: 'pipe' });
|
|
201
|
+
currentBranch = branch.trim();
|
|
202
|
+
// Check for uncommitted changes
|
|
203
|
+
const { stdout: status } = await execa('git', ['status', '--porcelain'], { cwd, stdio: 'pipe' });
|
|
204
|
+
hasUncommittedChanges = status.trim().length > 0;
|
|
205
|
+
// Check for unpushed commits
|
|
206
|
+
try {
|
|
207
|
+
const { stdout: unpushed } = await execa('git', ['log', '@{u}..HEAD', '--oneline'], { cwd, stdio: 'pipe' });
|
|
208
|
+
hasUnpushedCommits = unpushed.trim().length > 0;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// No upstream branch - assume all commits are unpushed
|
|
212
|
+
const { stdout: commits } = await execa('git', ['log', 'main..HEAD', '--oneline'], { cwd, stdio: 'pipe' });
|
|
213
|
+
hasUnpushedCommits = commits.trim().length > 0;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Ignore git errors
|
|
218
|
+
}
|
|
219
|
+
// Show status
|
|
220
|
+
if (taskStatus) {
|
|
221
|
+
const statusColor = taskStatus === 'done' ? pc.green : taskStatus === 'in_progress' ? pc.yellow : pc.dim;
|
|
222
|
+
console.log(`Task status: ${statusColor(taskStatus)}`);
|
|
223
|
+
}
|
|
224
|
+
if (currentBranch) {
|
|
225
|
+
console.log(`Branch: ${pc.cyan(currentBranch)}`);
|
|
226
|
+
}
|
|
227
|
+
if (hasUncommittedChanges) {
|
|
228
|
+
console.log(pc.yellow('⚠ Uncommitted changes detected'));
|
|
229
|
+
}
|
|
230
|
+
if (hasUnpushedCommits) {
|
|
231
|
+
console.log(pc.yellow('⚠ Unpushed commits detected'));
|
|
232
|
+
}
|
|
233
|
+
console.log();
|
|
234
|
+
// Offer actions based on state
|
|
235
|
+
if (hasUncommittedChanges) {
|
|
236
|
+
console.log(pc.dim('You have uncommitted changes. Commit them before pushing.\n'));
|
|
237
|
+
}
|
|
238
|
+
if (hasUnpushedCommits && !hasUncommittedChanges) {
|
|
239
|
+
const { select } = await import('@inquirer/prompts');
|
|
240
|
+
const mergeAction = await select({
|
|
241
|
+
message: 'How do you want to merge your changes?',
|
|
242
|
+
choices: [
|
|
243
|
+
{ name: 'Create a pull request', value: 'pr' },
|
|
244
|
+
{ name: 'Merge directly to main', value: 'merge' },
|
|
245
|
+
{ name: 'Just push (decide later)', value: 'push' },
|
|
246
|
+
{ name: 'Skip (keep local)', value: 'skip' },
|
|
247
|
+
],
|
|
248
|
+
});
|
|
249
|
+
if (mergeAction === 'skip') {
|
|
250
|
+
console.log(pc.dim('Keeping changes local.\n'));
|
|
251
|
+
}
|
|
252
|
+
else if (mergeAction === 'merge') {
|
|
253
|
+
// Merge directly to main
|
|
254
|
+
try {
|
|
255
|
+
const { execa } = await import('execa');
|
|
256
|
+
console.log(pc.dim('Switching to main and merging...'));
|
|
257
|
+
// Fetch latest main
|
|
258
|
+
await execa('git', ['fetch', 'origin', 'main'], { cwd, stdio: 'pipe' });
|
|
259
|
+
// Checkout main in the main project root (not worktree)
|
|
260
|
+
await execa('git', ['checkout', 'main'], { cwd: projectRoot, stdio: 'pipe' });
|
|
261
|
+
// Merge the feature branch
|
|
262
|
+
await execa('git', ['merge', currentBranch, '--no-edit'], { cwd: projectRoot, stdio: 'inherit' });
|
|
263
|
+
// Push main
|
|
264
|
+
await execa('git', ['push', 'origin', 'main'], { cwd: projectRoot, stdio: 'inherit' });
|
|
265
|
+
console.log(pc.green('✓ Merged to main and pushed\n'));
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
console.log(pc.red('Failed to merge. You may need to resolve conflicts manually.\n'));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
// Push first (needed for both 'pr' and 'push')
|
|
273
|
+
try {
|
|
274
|
+
const { execa } = await import('execa');
|
|
275
|
+
await execa('git', ['push', '-u', 'origin', currentBranch], { cwd, stdio: 'inherit' });
|
|
276
|
+
console.log(pc.green('✓ Pushed to remote\n'));
|
|
277
|
+
// Create PR if requested
|
|
278
|
+
if (mergeAction === 'pr') {
|
|
279
|
+
try {
|
|
280
|
+
await execa('gh', ['pr', 'create', '--fill'], { cwd, stdio: 'inherit' });
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
console.log(pc.yellow('Could not create PR. You can create it manually on GitHub.\n'));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
console.log(pc.red('Failed to push. You can push manually.\n'));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Offer cleanup if task is done or abandoned
|
|
293
|
+
if (taskStatus === 'done' || taskStatus === 'planned') {
|
|
294
|
+
const statusText = taskStatus === 'done' ? 'completed' : 'not started/abandoned';
|
|
295
|
+
const shouldCleanup = await confirm({
|
|
296
|
+
message: `Task is ${statusText}. Remove worktree and branch?`,
|
|
297
|
+
default: taskStatus === 'done',
|
|
298
|
+
});
|
|
299
|
+
if (shouldCleanup) {
|
|
300
|
+
try {
|
|
301
|
+
await removeWorktreeDir(cwd, projectRoot);
|
|
302
|
+
console.log(pc.green('✓ Worktree and branch removed\n'));
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
const error = err;
|
|
306
|
+
console.log(pc.red(`Failed to remove worktree: ${error.message}\n`));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
else if (taskStatus === 'in_progress') {
|
|
311
|
+
console.log(pc.dim('Task is still in progress. Run `npx @damper/cli` to resume later.'));
|
|
312
|
+
console.log(pc.dim('Run `npx @damper/cli cleanup` when ready to remove the worktree.\n'));
|
|
166
313
|
}
|
|
167
314
|
}
|
|
168
315
|
/**
|