codeep 1.2.77 → 1.2.79

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,14 +4,29 @@
4
4
  */
5
5
  import { Screen } from './Screen.js';
6
6
  import { Input, LineEditor } from './Input.js';
7
- import { fg, style, stringWidth } from './ansi.js';
7
+ import { fg, style, stringWidth, gradientText, gradientLine } from './ansi.js';
8
8
  import { SYNTAX, highlightCode } from './highlight.js';
9
9
  import { handleInlineStatusKey, handleInlineHelpKey, handleMenuKey, handleInlinePermissionKey, handleInlineSessionPickerKey, handleInlineConfirmKey, handleLoginKey, } from './handlers.js';
10
10
  import clipboardy from 'clipboardy';
11
11
  // Primary color: #f02a30 (Codeep red)
12
12
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
13
- // Spinner frames for animation
14
- const SPINNER_FRAMES = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
13
+ // Gradient stops: deep red → codeep red → orange → amber
14
+ // Used for separator lines and accent elements
15
+ const GRADIENT_STOPS = [
16
+ [160, 20, 30], // deep red
17
+ [240, 42, 48], // Codeep red
18
+ [240, 100, 30], // orange-red
19
+ [240, 160, 20], // amber
20
+ ];
21
+ // Dimmer gradient for subtle separators (half brightness)
22
+ const GRADIENT_STOPS_DIM = [
23
+ [80, 10, 15],
24
+ [140, 25, 28],
25
+ [140, 60, 18],
26
+ [140, 90, 12],
27
+ ];
28
+ // 8-bit block spinner frames
29
+ const SPINNER_FRAMES = ['▖', '▘', '▝', '▗', '▌', '▀', '▐', '▄'];
15
30
  // ASCII Logo
16
31
  const LOGO_LINES = [
17
32
  ' ██████╗ ██████╗ ██████╗ ███████╗███████╗██████╗ ',
@@ -1295,8 +1310,8 @@ export class App {
1295
1310
  }
1296
1311
  y++;
1297
1312
  }
1298
- // Separator
1299
- this.screen.horizontalLine(separatorLine, '─', fg.gray);
1313
+ // Gradient separator
1314
+ this.screen.writeRaw(separatorLine, gradientLine(width, GRADIENT_STOPS_DIM));
1300
1315
  // Input (don't render cursor when menu/settings is open)
1301
1316
  this.renderInput(inputLine, width, this.menuOpen || this.settingsOpen);
1302
1317
  // Status bar
@@ -1453,39 +1468,41 @@ export class App {
1453
1468
  this.screen.showCursor(false);
1454
1469
  return;
1455
1470
  }
1456
- // Agent running state - show special prompt
1471
+ // Agent running state - show special prompt with gradient
1457
1472
  if (this.isAgentRunning) {
1458
1473
  const spinner = SPINNER_FRAMES[this.spinnerFrame];
1459
1474
  const stepLabel = this.agentMaxIterations > 0
1460
1475
  ? `step ${this.agentIteration}/${this.agentMaxIterations}`
1461
1476
  : `step ${this.agentIteration}`;
1462
1477
  const agentText = `${spinner} Agent working... ${stepLabel} | ${this.agentActions.length} actions (Esc to stop)`;
1463
- this.screen.writeLine(y, agentText, PRIMARY_COLOR);
1478
+ this.screen.write(0, y, gradientText(agentText, GRADIENT_STOPS) + style.bold);
1464
1479
  this.screen.showCursor(false);
1465
1480
  return;
1466
1481
  }
1467
- // Loading/streaming state with animated spinner
1482
+ // Loading/streaming state with animated spinner + gradient
1468
1483
  if (this.isLoading || this.isStreaming) {
1469
1484
  const spinner = SPINNER_FRAMES[this.spinnerFrame];
1470
- const message = this.isStreaming ? 'Writing' : 'Thinking';
1471
- this.screen.writeLine(y, `${spinner} ${message}...`, PRIMARY_COLOR);
1485
+ const message = this.isStreaming ? 'Writing...' : 'Thinking...';
1486
+ const spinnerText = `${spinner} ${message}`;
1487
+ this.screen.write(0, y, gradientText(spinnerText, GRADIENT_STOPS));
1472
1488
  this.screen.showCursor(false);
1473
1489
  return;
1474
1490
  }
1475
- // Build prompt prefix — show line count for multi-line buffers
1491
+ // Build prompt prefix
1476
1492
  const lines = inputValue.split('\n');
1477
1493
  const lineCount = lines.length;
1478
- const prompt = lineCount > 1 ? `[${lineCount}] > ` : this.isMultilineMode ? 'M> ' : '> ';
1479
- const maxInputWidth = width - prompt.length - 1;
1494
+ // for normal, ❯❯ for multiline, [n] for multi-line with count
1495
+ const promptSymbol = lineCount > 1 ? `[${lineCount}] ❯ ` : this.isMultilineMode ? '❯❯ ' : '❯ ';
1496
+ const maxInputWidth = width - promptSymbol.length - 1;
1480
1497
  // Show placeholder when input is empty
1481
1498
  if (!inputValue) {
1482
- this.screen.write(0, y, prompt, fg.green);
1499
+ this.screen.write(0, y, promptSymbol, PRIMARY_COLOR);
1483
1500
  const placeholder = this.isMultilineMode
1484
- ? 'Multi-line mode (Enter=newline, Esc=send)...'
1485
- : 'Type a message or /command...';
1486
- this.screen.write(prompt.length, y, placeholder, fg.gray);
1501
+ ? 'Multi-line mode Enter=newline · Esc=send'
1502
+ : 'Message or /command';
1503
+ this.screen.write(promptSymbol.length, y, placeholder, fg.gray + style.dim);
1487
1504
  if (!hideCursor) {
1488
- this.screen.setCursor(prompt.length, y);
1505
+ this.screen.setCursor(promptSymbol.length, y);
1489
1506
  this.screen.showCursor(true);
1490
1507
  }
1491
1508
  else {
@@ -1496,14 +1513,13 @@ export class App {
1496
1513
  // For multi-line content, show the last line being edited
1497
1514
  const lastLine = lines[lines.length - 1];
1498
1515
  const displayInput = lineCount > 1 ? lastLine : inputValue;
1499
- // Cursor position within the last line
1500
1516
  const charsBeforeLastLine = lineCount > 1 ? inputValue.lastIndexOf('\n') + 1 : 0;
1501
1517
  const cursorInLine = cursorPos - charsBeforeLastLine;
1502
1518
  let displayValue;
1503
1519
  let cursorX;
1504
1520
  if (displayInput.length <= maxInputWidth) {
1505
1521
  displayValue = displayInput;
1506
- cursorX = prompt.length + Math.max(0, cursorInLine);
1522
+ cursorX = promptSymbol.length + Math.max(0, cursorInLine);
1507
1523
  }
1508
1524
  else {
1509
1525
  const effectiveCursor = Math.max(0, cursorInLine);
@@ -1515,9 +1531,11 @@ export class App {
1515
1531
  else {
1516
1532
  displayValue = displayInput.slice(0, maxInputWidth);
1517
1533
  }
1518
- cursorX = prompt.length + (effectiveCursor - visibleStart);
1534
+ cursorX = promptSymbol.length + (effectiveCursor - visibleStart);
1519
1535
  }
1520
- this.screen.writeLine(y, prompt + displayValue, fg.green);
1536
+ // Prompt symbol in primary color, input text in white
1537
+ this.screen.write(0, y, promptSymbol, PRIMARY_COLOR);
1538
+ this.screen.write(promptSymbol.length, y, displayValue, fg.white);
1521
1539
  // Hide cursor when menu/settings is open
1522
1540
  if (hideCursor) {
1523
1541
  this.screen.showCursor(false);
@@ -1890,15 +1908,15 @@ export class App {
1890
1908
  acc.errors++;
1891
1909
  return acc;
1892
1910
  }, { reads: 0, writes: 0, edits: 0, deletes: 0, commands: 0, searches: 0, errors: 0 });
1893
- // Top border with title
1894
- const title = ` ${spinner} AGENT `;
1911
+ // Top border: gradient line with gradient title embedded
1912
+ const titleInner = ` ${spinner} AGENT `;
1895
1913
  const titlePadLeft = 2;
1896
- const titlePadRight = width - titlePadLeft - title.length - 1;
1897
- this.screen.write(0, y, '─'.repeat(titlePadLeft), PRIMARY_COLOR);
1898
- this.screen.write(titlePadLeft, y, title, PRIMARY_COLOR + style.bold);
1899
- this.screen.write(titlePadLeft + title.length, y, '─'.repeat(Math.max(0, titlePadRight)), PRIMARY_COLOR);
1914
+ const lineLeft = gradientLine(titlePadLeft, GRADIENT_STOPS);
1915
+ const titleColored = gradientText(titleInner, GRADIENT_STOPS) + style.bold;
1916
+ const lineRight = gradientLine(Math.max(0, width - titlePadLeft - titleInner.length - 1), GRADIENT_STOPS);
1917
+ this.screen.write(0, y, lineLeft + titleColored + lineRight);
1900
1918
  y++;
1901
- // Current action line (clear first to avoid stale text from longer previous paths)
1919
+ // Current action line
1902
1920
  this.screen.writeLine(y, '');
1903
1921
  if (this.agentWaitingForAI) {
1904
1922
  this.screen.write(1, y, 'Thinking...', fg.gray);
@@ -1916,7 +1934,7 @@ export class App {
1916
1934
  this.screen.write(1, y, 'Starting...', fg.gray);
1917
1935
  }
1918
1936
  y++;
1919
- // Stats line: Files and step info (clear line first to avoid stale text)
1937
+ // Stats + 8-bit progress bar line
1920
1938
  this.screen.writeLine(y, '');
1921
1939
  let x = 1;
1922
1940
  // File changes
@@ -1950,19 +1968,42 @@ export class App {
1950
1968
  this.screen.write(x, y, txt, fg.cyan);
1951
1969
  x += txt.length + 1;
1952
1970
  }
1953
- // Step info on the right
1954
- const stepText = this.agentMaxIterations > 0
1955
- ? `step ${this.agentIteration}/${this.agentMaxIterations}`
1956
- : `step ${this.agentIteration}`;
1957
- this.screen.write(width - stepText.length - 1, y, stepText, fg.gray);
1971
+ // 8-bit gradient progress bar (right side, if max iterations known)
1972
+ if (this.agentMaxIterations > 0) {
1973
+ const barWidth = 14;
1974
+ const progress = Math.min(this.agentIteration / this.agentMaxIterations, 1);
1975
+ const filled = Math.round(progress * barWidth);
1976
+ // Use block chars: █▓▒░ for gradient fill effect
1977
+ const BLOCKS = ['░', '▒', '▓', '█'];
1978
+ let bar = '';
1979
+ for (let i = 0; i < barWidth; i++) {
1980
+ if (i < filled - 1)
1981
+ bar += '█';
1982
+ else if (i === filled - 1)
1983
+ bar += '▓';
1984
+ else if (i === filled)
1985
+ bar += '▒';
1986
+ else
1987
+ bar += '░';
1988
+ }
1989
+ const barColored = gradientText(bar, GRADIENT_STOPS);
1990
+ const stepText = `${this.agentIteration}/${this.agentMaxIterations}`;
1991
+ const barX = width - barWidth - stepText.length - 3;
1992
+ this.screen.write(barX, y, barColored);
1993
+ this.screen.write(width - stepText.length - 1, y, stepText, fg.gray);
1994
+ }
1995
+ else {
1996
+ const stepText = `step ${this.agentIteration}`;
1997
+ this.screen.write(width - stepText.length - 1, y, stepText, fg.gray);
1998
+ }
1958
1999
  y++;
1959
- // Bottom border with help
2000
+ // Bottom border with help text
1960
2001
  const helpText = ' Esc to stop ';
1961
2002
  const helpPadLeft = Math.floor((width - helpText.length) / 2);
1962
- const helpPadRight = Math.ceil((width - helpText.length) / 2);
1963
- this.screen.write(0, y, '─'.repeat(helpPadLeft), fg.gray);
2003
+ const helpPadRight = Math.max(0, width - helpPadLeft - helpText.length);
2004
+ this.screen.write(0, y, gradientLine(helpPadLeft, GRADIENT_STOPS_DIM));
1964
2005
  this.screen.write(helpPadLeft, y, helpText, fg.gray);
1965
- this.screen.write(helpPadLeft + helpText.length, y, '─'.repeat(helpPadRight), fg.gray);
2006
+ this.screen.write(helpPadLeft + helpText.length, y, gradientLine(helpPadRight, GRADIENT_STOPS_DIM));
1966
2007
  }
1967
2008
  /**
1968
2009
  * Get color for action type
@@ -2016,29 +2057,35 @@ export class App {
2016
2057
  * Render status bar
2017
2058
  */
2018
2059
  renderStatusBar(y, width) {
2019
- let leftText = '';
2020
- let rightText = '';
2060
+ // Clear the line first
2061
+ this.screen.writeLine(y, '');
2021
2062
  if (this.notification) {
2022
- leftText = ` ${this.notification}`;
2023
- }
2024
- else {
2025
- const stats = this.options.getStatus().tokenStats;
2026
- const tokenInfo = stats && stats.totalTokens > 0
2027
- ? ` | ${stats.totalTokens < 1000 ? stats.totalTokens : (stats.totalTokens / 1000).toFixed(1) + 'K'} tokens`
2028
- : '';
2029
- leftText = ` ${this.messages.length} messages${tokenInfo}`;
2030
- }
2031
- if (this.isStreaming) {
2032
- rightText = 'Streaming... (Esc to cancel)';
2033
- }
2034
- else if (this.isLoading) {
2035
- rightText = 'Thinking...';
2063
+ // Notification: gradient colored, full width
2064
+ const notifText = ` ${this.notification}`;
2065
+ this.screen.write(0, y, gradientText(notifText, GRADIENT_STOPS));
2066
+ return;
2036
2067
  }
2037
- else {
2038
- rightText = 'Enter send | /help commands';
2068
+ const status = this.options.getStatus();
2069
+ const stats = status.tokenStats;
2070
+ // Left segment: msg count · token count
2071
+ const msgCount = `${this.messages.length} msg`;
2072
+ const tokenStr = stats && stats.totalTokens > 0
2073
+ ? `${stats.totalTokens < 1000 ? stats.totalTokens : (stats.totalTokens / 1000).toFixed(1) + 'K'} tok`
2074
+ : '';
2075
+ const leftParts = [msgCount, tokenStr].filter(Boolean);
2076
+ const leftText = ' ' + leftParts.join(' · ');
2077
+ this.screen.write(0, y, leftText, fg.gray + style.dim);
2078
+ // Center: model name with gradient (only if it fits)
2079
+ const modelName = status.model || '';
2080
+ if (modelName && width > 40) {
2081
+ const modelX = Math.floor((width - modelName.length) / 2);
2082
+ this.screen.write(modelX, y, gradientText(modelName, GRADIENT_STOPS));
2083
+ }
2084
+ // Right: Esc hint only when active, nothing when idle
2085
+ if (this.isStreaming || this.isLoading) {
2086
+ const rightText = 'Esc ';
2087
+ this.screen.write(width - rightText.length, y, rightText, fg.gray + style.dim);
2039
2088
  }
2040
- const padding = ' '.repeat(Math.max(0, width - leftText.length - rightText.length));
2041
- this.screen.writeLine(y, leftText + padding + rightText, fg.gray);
2042
2089
  }
2043
2090
  /**
2044
2091
  * Get visible messages (including streaming)
@@ -81,6 +81,17 @@ export declare const style: {
81
81
  * Helper to create styled text
82
82
  */
83
83
  export declare function styled(text: string, ...styles: string[]): string;
84
+ /**
85
+ * Render text with a horizontal RGB gradient across multiple color stops.
86
+ * Each character gets its own fg.rgb() code.
87
+ * Stops: array of [r,g,b] colors distributed evenly across the text.
88
+ */
89
+ export declare function gradientText(text: string, stops: Array<[number, number, number]>): string;
90
+ /**
91
+ * Render a full-width separator line with a gradient.
92
+ * Uses the given char (default '─') repeated across width.
93
+ */
94
+ export declare function gradientLine(width: number, stops: Array<[number, number, number]>, char?: string): string;
84
95
  /**
85
96
  * Get terminal display width of a single character
86
97
  * CJK, fullwidth, and emoji characters take 2 columns
@@ -101,6 +101,48 @@ export function styled(text, ...styles) {
101
101
  return text;
102
102
  return styles.join('') + text + style.reset;
103
103
  }
104
+ /**
105
+ * Interpolate between two RGB colors at position t (0..1)
106
+ */
107
+ function lerpColor(from, to, t) {
108
+ return [
109
+ Math.round(from[0] + (to[0] - from[0]) * t),
110
+ Math.round(from[1] + (to[1] - from[1]) * t),
111
+ Math.round(from[2] + (to[2] - from[2]) * t),
112
+ ];
113
+ }
114
+ /**
115
+ * Render text with a horizontal RGB gradient across multiple color stops.
116
+ * Each character gets its own fg.rgb() code.
117
+ * Stops: array of [r,g,b] colors distributed evenly across the text.
118
+ */
119
+ export function gradientText(text, stops) {
120
+ if (stops.length === 0)
121
+ return text;
122
+ if (stops.length === 1)
123
+ return fg.rgb(...stops[0]) + text + style.reset;
124
+ const chars = [...text]; // handle multi-byte / emoji
125
+ const len = chars.length;
126
+ if (len === 0)
127
+ return text;
128
+ let result = '';
129
+ for (let i = 0; i < len; i++) {
130
+ const t = len === 1 ? 0 : i / (len - 1);
131
+ // Which segment between stops?
132
+ const seg = Math.min(Math.floor(t * (stops.length - 1)), stops.length - 2);
133
+ const segT = t * (stops.length - 1) - seg;
134
+ const [r, g, b] = lerpColor(stops[seg], stops[seg + 1], segT);
135
+ result += fg.rgb(r, g, b) + chars[i];
136
+ }
137
+ return result + style.reset;
138
+ }
139
+ /**
140
+ * Render a full-width separator line with a gradient.
141
+ * Uses the given char (default '─') repeated across width.
142
+ */
143
+ export function gradientLine(width, stops, char = '─') {
144
+ return gradientText(char.repeat(width), stops);
145
+ }
104
146
  /**
105
147
  * Get terminal display width of a single character
106
148
  * CJK, fullwidth, and emoji characters take 2 columns
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "1.2.77",
3
+ "version": "1.2.79",
4
4
  "description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",