@damper/cli 0.3.4 → 0.4.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.
@@ -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.3.4';
7
+ const VERSION = '0.4.0';
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
@@ -85,13 +85,29 @@ export function configureDamperMcp() {
85
85
  export async function launchClaude(options) {
86
86
  const { cwd, taskId, taskTitle, apiKey, yolo } = options;
87
87
  console.log(pc.green(`\nStarting Claude Code for task #${taskId}: ${taskTitle}`));
88
+ console.log(pc.dim(`Directory: ${cwd}`));
89
+ console.log();
90
+ // Show workflow explanation
91
+ console.log(pc.bold('Workflow:'));
92
+ console.log(pc.dim(' 1. Claude reads TASK_CONTEXT.md and creates a plan'));
93
+ if (!yolo) {
94
+ console.log(pc.dim(' 2. You approve the plan before Claude makes changes'));
95
+ }
96
+ console.log(pc.dim(` ${yolo ? '2' : '3'}. Claude implements the task, logging progress to Damper`));
97
+ console.log(pc.dim(` ${yolo ? '3' : '4'}. Claude calls complete_task (or abandon_task) when done`));
98
+ console.log();
99
+ console.log(pc.bold('When finished:'));
100
+ console.log(pc.dim(' • Push your branch and create a PR if needed'));
101
+ console.log(pc.dim(' • Run: npx @damper/cli cleanup'));
102
+ console.log(pc.dim(' (removes worktree and branch)'));
103
+ console.log();
88
104
  if (yolo) {
89
- console.log(pc.yellow('Mode: YOLO (accepting edits without confirmation)'));
105
+ console.log(pc.yellow('Mode: YOLO (auto-accepting edits)'));
90
106
  }
91
107
  else {
92
- console.log(pc.dim('Mode: Plan (will ask for approval before making changes)'));
108
+ console.log(pc.cyan('Mode: Plan (will ask for approval)'));
93
109
  }
94
- console.log(pc.dim(`Directory: ${cwd}\n`));
110
+ console.log();
95
111
  // Build initial prompt
96
112
  const planModeNote = yolo ? '' : `
97
113
  You're in PLAN MODE. First, read TASK_CONTEXT.md and create an implementation plan.
@@ -146,7 +162,129 @@ Once approved, switch to implementation and start logging your work.
146
162
  console.log(pc.dim('Install it with: npm install -g @anthropic-ai/claude-code\n'));
147
163
  process.exit(1);
148
164
  }
149
- throw err;
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 shouldPush = await confirm({
240
+ message: 'Push commits to remote?',
241
+ default: true,
242
+ });
243
+ if (shouldPush) {
244
+ try {
245
+ const { execa } = await import('execa');
246
+ await execa('git', ['push', '-u', 'origin', currentBranch], { cwd, stdio: 'inherit' });
247
+ console.log(pc.green('✓ Pushed to remote\n'));
248
+ // Offer to create PR
249
+ const shouldCreatePR = await confirm({
250
+ message: 'Create a pull request?',
251
+ default: true,
252
+ });
253
+ if (shouldCreatePR) {
254
+ try {
255
+ await execa('gh', ['pr', 'create', '--fill'], { cwd, stdio: 'inherit' });
256
+ }
257
+ catch {
258
+ console.log(pc.yellow('Could not create PR. You can create it manually on GitHub.\n'));
259
+ }
260
+ }
261
+ }
262
+ catch {
263
+ console.log(pc.red('Failed to push. You can push manually.\n'));
264
+ }
265
+ }
266
+ }
267
+ // Offer cleanup if task is done or abandoned
268
+ if (taskStatus === 'done' || taskStatus === 'planned') {
269
+ const statusText = taskStatus === 'done' ? 'completed' : 'not started/abandoned';
270
+ const shouldCleanup = await confirm({
271
+ message: `Task is ${statusText}. Remove worktree and branch?`,
272
+ default: taskStatus === 'done',
273
+ });
274
+ if (shouldCleanup) {
275
+ try {
276
+ await removeWorktreeDir(cwd, projectRoot);
277
+ console.log(pc.green('✓ Worktree and branch removed\n'));
278
+ }
279
+ catch (err) {
280
+ const error = err;
281
+ console.log(pc.red(`Failed to remove worktree: ${error.message}\n`));
282
+ }
283
+ }
284
+ }
285
+ else if (taskStatus === 'in_progress') {
286
+ console.log(pc.dim('Task is still in progress. Run `npx @damper/cli` to resume later.'));
287
+ console.log(pc.dim('Run `npx @damper/cli cleanup` when ready to remove the worktree.\n'));
150
288
  }
151
289
  }
152
290
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@damper/cli",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "CLI tool for orchestrating Damper task workflows with Claude Code",
5
5
  "author": "Damper <hello@usedamper.com>",
6
6
  "repository": {