@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 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
- const VERSION = '0.6.7';
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
@@ -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
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@damper/cli",
3
- "version": "0.9.10",
3
+ "version": "0.9.12",
4
4
  "description": "CLI tool for orchestrating Damper task workflows with Claude Code",
5
5
  "author": "Damper <hello@usedamper.com>",
6
6
  "repository": {