@damper/cli 0.1.1 → 0.1.2

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.
@@ -1,9 +1,22 @@
1
1
  import * as fs from 'node:fs';
2
2
  import { confirm, checkbox } from '@inquirer/prompts';
3
+ import { execa } from 'execa';
3
4
  import pc from 'picocolors';
4
5
  import { getWorktrees, cleanupStaleWorktrees, removeWorktree } from '../services/state.js';
5
6
  import { createDamperApi } from '../services/damper-api.js';
6
7
  import { removeWorktreeDir } from '../services/worktree.js';
8
+ async function hasUncommittedChanges(worktreePath) {
9
+ try {
10
+ const { stdout } = await execa('git', ['status', '--porcelain'], {
11
+ cwd: worktreePath,
12
+ stdio: 'pipe',
13
+ });
14
+ return stdout.trim().length > 0;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
7
20
  export async function cleanupCommand() {
8
21
  // Clean up stale entries first
9
22
  const stale = cleanupStaleWorktrees();
@@ -37,6 +50,8 @@ export async function cleanupCommand() {
37
50
  });
38
51
  continue;
39
52
  }
53
+ // Check for uncommitted changes
54
+ const uncommitted = await hasUncommittedChanges(wt.path);
40
55
  if (api) {
41
56
  try {
42
57
  const task = await api.getTask(wt.taskId);
@@ -45,6 +60,7 @@ export async function cleanupCommand() {
45
60
  worktree: wt,
46
61
  task,
47
62
  reason: 'completed',
63
+ hasUncommittedChanges: uncommitted,
48
64
  });
49
65
  }
50
66
  else if (task.status === 'planned' && !task.lockedBy) {
@@ -53,6 +69,7 @@ export async function cleanupCommand() {
53
69
  worktree: wt,
54
70
  task,
55
71
  reason: 'abandoned',
72
+ hasUncommittedChanges: uncommitted,
56
73
  });
57
74
  }
58
75
  }
@@ -61,6 +78,7 @@ export async function cleanupCommand() {
61
78
  candidates.push({
62
79
  worktree: wt,
63
80
  reason: 'manual',
81
+ hasUncommittedChanges: uncommitted,
64
82
  });
65
83
  }
66
84
  }
@@ -69,6 +87,7 @@ export async function cleanupCommand() {
69
87
  candidates.push({
70
88
  worktree: wt,
71
89
  reason: 'manual',
90
+ hasUncommittedChanges: uncommitted,
72
91
  });
73
92
  }
74
93
  }
@@ -99,10 +118,15 @@ export async function cleanupCommand() {
99
118
  reasonBadge = pc.dim('[manual]');
100
119
  break;
101
120
  }
121
+ // Warning for uncommitted changes
122
+ const uncommittedWarning = c.hasUncommittedChanges
123
+ ? pc.red(' ⚠ uncommitted changes')
124
+ : '';
102
125
  return {
103
- name: `${description} ${reasonBadge}\n ${pc.dim(worktreeName)}`,
126
+ name: `${description} ${reasonBadge}${uncommittedWarning}\n ${pc.dim(worktreeName)}`,
104
127
  value: c,
105
- checked: c.reason === 'completed' || c.reason === 'missing',
128
+ // Don't auto-check if there are uncommitted changes
129
+ checked: (c.reason === 'completed' || c.reason === 'missing') && !c.hasUncommittedChanges,
106
130
  };
107
131
  });
108
132
  const selected = await checkbox({
@@ -114,9 +138,17 @@ export async function cleanupCommand() {
114
138
  console.log(pc.dim('\nNo worktrees selected. Nothing to clean up.\n'));
115
139
  return;
116
140
  }
141
+ // Check for uncommitted changes in selected worktrees
142
+ const withUncommitted = selected.filter(c => c.hasUncommittedChanges);
143
+ if (withUncommitted.length > 0) {
144
+ console.log(pc.yellow(`\n⚠ Warning: ${withUncommitted.length} worktree(s) have uncommitted changes that will be lost!`));
145
+ }
117
146
  // Confirm
147
+ const confirmMessage = withUncommitted.length > 0
148
+ ? `Remove ${selected.length} worktree(s)? (${withUncommitted.length} with uncommitted changes)`
149
+ : `Remove ${selected.length} worktree(s)?`;
118
150
  const confirmed = await confirm({
119
- message: `Remove ${selected.length} worktree(s)?`,
151
+ message: confirmMessage,
120
152
  default: false,
121
153
  });
122
154
  if (!confirmed) {
@@ -17,7 +17,7 @@ export async function setupCommand() {
17
17
  const apiKey = getConfiguredApiKey();
18
18
  if (mcpConfigured && apiKey) {
19
19
  console.log(pc.green('✓ Damper MCP configured'));
20
- console.log(pc.dim(` API key: ${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`));
20
+ console.log(pc.dim(` API key: ${'*'.repeat(12)} (${apiKey.length} chars)`));
21
21
  // Verify the API key works
22
22
  console.log(pc.dim('\nVerifying API key...'));
23
23
  try {
@@ -165,16 +165,19 @@ export async function createWorktree(options) {
165
165
  }
166
166
  // Copy .env files
167
167
  const envFiles = findEnvFiles(projectRoot);
168
- for (const envFile of envFiles) {
169
- const source = path.join(projectRoot, envFile);
170
- const target = path.join(worktreePath, envFile);
171
- // Ensure parent directory exists
172
- const targetDir = path.dirname(target);
173
- if (!fs.existsSync(targetDir)) {
174
- fs.mkdirSync(targetDir, { recursive: true });
168
+ if (envFiles.length > 0) {
169
+ console.log(pc.dim(`Copying ${envFiles.length} .env file(s) to worktree...`));
170
+ for (const envFile of envFiles) {
171
+ const source = path.join(projectRoot, envFile);
172
+ const target = path.join(worktreePath, envFile);
173
+ // Ensure parent directory exists
174
+ const targetDir = path.dirname(target);
175
+ if (!fs.existsSync(targetDir)) {
176
+ fs.mkdirSync(targetDir, { recursive: true });
177
+ }
178
+ console.log(pc.dim(` ${envFile}`));
179
+ await fs.promises.copyFile(source, target);
175
180
  }
176
- console.log(pc.dim(`Copying ${envFile}...`));
177
- await fs.promises.copyFile(source, target);
178
181
  }
179
182
  // Save to state
180
183
  const worktreeState = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@damper/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "CLI tool for orchestrating Damper task workflows with Claude Code",
5
5
  "author": "Damper <hello@usedamper.com>",
6
6
  "repository": {