@damper/cli 0.9.10 → 0.9.12
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/index.js +3 -1
- package/dist/services/claude.js +21 -1
- package/dist/ui/format.d.ts +17 -0
- package/dist/ui/format.js +56 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,7 +5,9 @@ import { statusCommand } from './commands/status.js';
|
|
|
5
5
|
import { cleanupCommand } from './commands/cleanup.js';
|
|
6
6
|
import { setupCommand } from './commands/setup.js';
|
|
7
7
|
import { releaseCommand } from './commands/release.js';
|
|
8
|
-
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
const _require = createRequire(import.meta.url);
|
|
10
|
+
const { version: VERSION } = _require('../package.json');
|
|
9
11
|
function showHelp() {
|
|
10
12
|
console.log(`
|
|
11
13
|
${pc.bold('@damper/cli')} - Agent orchestration for Damper tasks
|
package/dist/services/claude.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as os from 'node:os';
|
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { execa } from 'execa';
|
|
6
6
|
import pc from 'picocolors';
|
|
7
|
-
import { shortIdRaw } from '../ui/format.js';
|
|
7
|
+
import { shortIdRaw, setTerminalTitle, clearTerminalTitle, TerminalStatusBar } from '../ui/format.js';
|
|
8
8
|
const CLAUDE_SETTINGS_DIR = path.join(os.homedir(), '.claude');
|
|
9
9
|
const CLAUDE_SETTINGS_FILE = path.join(CLAUDE_SETTINGS_DIR, 'settings.json');
|
|
10
10
|
/**
|
|
@@ -136,6 +136,11 @@ export async function launchClaude(options) {
|
|
|
136
136
|
args = yolo ? ['--dangerously-skip-permissions', initialPrompt] : [initialPrompt];
|
|
137
137
|
console.log(pc.dim(`Launching Claude in ${cwd}...`));
|
|
138
138
|
}
|
|
139
|
+
// Set terminal title and bottom status bar
|
|
140
|
+
const taskLabel = `#${shortIdRaw(taskId)}: ${taskTitle}`;
|
|
141
|
+
setTerminalTitle(taskLabel);
|
|
142
|
+
const statusBar = new TerminalStatusBar(taskLabel);
|
|
143
|
+
statusBar.show();
|
|
139
144
|
// Launch Claude Code
|
|
140
145
|
// Use spawn with stdio: 'inherit' for proper TTY passthrough
|
|
141
146
|
// Signals (Ctrl+C, Escape) are handled naturally since child inherits the terminal
|
|
@@ -158,6 +163,9 @@ export async function launchClaude(options) {
|
|
|
158
163
|
});
|
|
159
164
|
child.on('close', () => resolve());
|
|
160
165
|
});
|
|
166
|
+
// Clean up status bar and terminal title
|
|
167
|
+
statusBar.hide();
|
|
168
|
+
clearTerminalTitle();
|
|
161
169
|
console.log(pc.dim('\n─────────────────────────────────────────\n'));
|
|
162
170
|
return { cwd, taskId, apiKey };
|
|
163
171
|
}
|
|
@@ -539,6 +547,10 @@ export async function launchClaudeForReview(options) {
|
|
|
539
547
|
'',
|
|
540
548
|
'IMPORTANT: Do NOT make any code changes. This is a review-only session.',
|
|
541
549
|
].join('\n');
|
|
550
|
+
const reviewLabel = `Review #${shortIdRaw(taskId)}`;
|
|
551
|
+
setTerminalTitle(reviewLabel);
|
|
552
|
+
const statusBar = new TerminalStatusBar(reviewLabel);
|
|
553
|
+
statusBar.show();
|
|
542
554
|
await new Promise((resolve) => {
|
|
543
555
|
const child = spawn('claude', [prompt], {
|
|
544
556
|
cwd,
|
|
@@ -548,6 +560,8 @@ export async function launchClaudeForReview(options) {
|
|
|
548
560
|
child.on('error', () => resolve());
|
|
549
561
|
child.on('close', () => resolve());
|
|
550
562
|
});
|
|
563
|
+
statusBar.hide();
|
|
564
|
+
clearTerminalTitle();
|
|
551
565
|
}
|
|
552
566
|
/**
|
|
553
567
|
* Launch Claude to resolve merge conflicts
|
|
@@ -557,6 +571,10 @@ export async function launchClaudeForReview(options) {
|
|
|
557
571
|
async function launchClaudeForMerge(options) {
|
|
558
572
|
const { cwd, apiKey } = options;
|
|
559
573
|
const prompt = 'IMPORTANT: Your ONLY job is to resolve merge conflicts. Do NOT read TASK_CONTEXT.md or work on any task. Run: git merge origin/main --no-edit. If there are conflicts, resolve them, stage, and commit. Do not use any MCP tools.';
|
|
574
|
+
const mergeLabel = 'Resolving merge conflicts';
|
|
575
|
+
setTerminalTitle(mergeLabel);
|
|
576
|
+
const statusBar = new TerminalStatusBar(mergeLabel);
|
|
577
|
+
statusBar.show();
|
|
560
578
|
await new Promise((resolve) => {
|
|
561
579
|
const child = spawn('claude', ['--allowedTools', 'Bash,Read,Write,Edit,Glob,Grep', prompt], {
|
|
562
580
|
cwd,
|
|
@@ -566,6 +584,8 @@ async function launchClaudeForMerge(options) {
|
|
|
566
584
|
child.on('error', () => resolve());
|
|
567
585
|
child.on('close', () => resolve());
|
|
568
586
|
});
|
|
587
|
+
statusBar.hide();
|
|
588
|
+
clearTerminalTitle();
|
|
569
589
|
}
|
|
570
590
|
/**
|
|
571
591
|
* Check if Claude Code CLI is installed
|
package/dist/ui/format.d.ts
CHANGED
|
@@ -35,6 +35,23 @@ export declare function formatSubtaskProgress(progress: {
|
|
|
35
35
|
export declare function relativeTime(date: string | Date | null | undefined): string;
|
|
36
36
|
/** Returns a picocolors function for the given status */
|
|
37
37
|
export declare function statusColor(status: string): (s: string) => string;
|
|
38
|
+
/** Set terminal tab/window title via ANSI escape sequence (OSC 0) */
|
|
39
|
+
export declare function setTerminalTitle(title: string): void;
|
|
40
|
+
/** Clear terminal title (restore default behavior) */
|
|
41
|
+
export declare function clearTerminalTitle(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Persistent bottom status bar using ANSI scroll region (DECSTBM).
|
|
44
|
+
* Reserves the last terminal row for a fixed label while the child process
|
|
45
|
+
* operates inside the scroll region above it.
|
|
46
|
+
*/
|
|
47
|
+
export declare class TerminalStatusBar {
|
|
48
|
+
private title;
|
|
49
|
+
private handleResize;
|
|
50
|
+
constructor(title: string);
|
|
51
|
+
show(): void;
|
|
52
|
+
hide(): void;
|
|
53
|
+
private draw;
|
|
54
|
+
}
|
|
38
55
|
/** Terminal width (min 80) */
|
|
39
56
|
export declare function getTerminalWidth(): number;
|
|
40
57
|
/** Section header spanning terminal width: `── Label ───────────` */
|
package/dist/ui/format.js
CHANGED
|
@@ -144,6 +144,62 @@ export function statusColor(status) {
|
|
|
144
144
|
default: return pc.dim;
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
+
/** Set terminal tab/window title via ANSI escape sequence (OSC 0) */
|
|
148
|
+
export function setTerminalTitle(title) {
|
|
149
|
+
if (process.stdout.isTTY) {
|
|
150
|
+
process.stdout.write(`\x1b]0;${title}\x07`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Clear terminal title (restore default behavior) */
|
|
154
|
+
export function clearTerminalTitle() {
|
|
155
|
+
if (process.stdout.isTTY) {
|
|
156
|
+
process.stdout.write(`\x1b]0;\x07`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Persistent bottom status bar using ANSI scroll region (DECSTBM).
|
|
161
|
+
* Reserves the last terminal row for a fixed label while the child process
|
|
162
|
+
* operates inside the scroll region above it.
|
|
163
|
+
*/
|
|
164
|
+
export class TerminalStatusBar {
|
|
165
|
+
title;
|
|
166
|
+
handleResize = null;
|
|
167
|
+
constructor(title) {
|
|
168
|
+
this.title = title;
|
|
169
|
+
}
|
|
170
|
+
show() {
|
|
171
|
+
if (!process.stdout.isTTY)
|
|
172
|
+
return;
|
|
173
|
+
this.draw();
|
|
174
|
+
this.handleResize = () => this.draw();
|
|
175
|
+
process.stdout.on('resize', this.handleResize);
|
|
176
|
+
}
|
|
177
|
+
hide() {
|
|
178
|
+
if (!process.stdout.isTTY)
|
|
179
|
+
return;
|
|
180
|
+
if (this.handleResize) {
|
|
181
|
+
process.stdout.off('resize', this.handleResize);
|
|
182
|
+
this.handleResize = null;
|
|
183
|
+
}
|
|
184
|
+
const rows = process.stdout.rows || 24;
|
|
185
|
+
process.stdout.write('\x1b[r' + // Reset scroll region to full terminal
|
|
186
|
+
`\x1b[${rows};1H` + // Move to the status bar row
|
|
187
|
+
'\x1b[2K' // Clear the line
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
draw() {
|
|
191
|
+
const rows = process.stdout.rows || 24;
|
|
192
|
+
const cols = process.stdout.columns || 80;
|
|
193
|
+
const bar = ` ${this.title}`.padEnd(cols).slice(0, cols);
|
|
194
|
+
process.stdout.write('\x1b7' + // Save cursor position
|
|
195
|
+
`\x1b[${rows};1H` + // Move to last row
|
|
196
|
+
'\x1b[2K' + // Clear the line
|
|
197
|
+
'\x1b[7m' + bar + '\x1b[0m' + // Draw bar in inverse video
|
|
198
|
+
`\x1b[1;${rows - 1}r` + // Set scroll region above bar
|
|
199
|
+
'\x1b8' // Restore cursor position
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
147
203
|
/** Terminal width (min 80) */
|
|
148
204
|
export function getTerminalWidth() {
|
|
149
205
|
return Math.max(80, process.stdout.columns || 80);
|