codeep 2.14.0 → 2.15.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.
Files changed (61) hide show
  1. package/README.md +35 -24
  2. package/dist/acp/commands.js +22 -1
  3. package/dist/acp/server.js +13 -2
  4. package/dist/config/index.d.ts +10 -0
  5. package/dist/config/index.js +2 -2
  6. package/dist/config/providers.js +15 -10
  7. package/dist/renderer/App.d.ts +0 -30
  8. package/dist/renderer/App.js +149 -659
  9. package/dist/renderer/agentExecution.d.ts +1 -0
  10. package/dist/renderer/agentExecution.js +3 -2
  11. package/dist/renderer/commands/helpers.d.ts +63 -0
  12. package/dist/renderer/commands/helpers.js +108 -0
  13. package/dist/renderer/commands/registry.js +5 -0
  14. package/dist/renderer/commands.d.ts +4 -0
  15. package/dist/renderer/commands.js +179 -63
  16. package/dist/renderer/components/ActionFormatting.d.ts +17 -0
  17. package/dist/renderer/components/ActionFormatting.js +67 -0
  18. package/dist/renderer/components/Autocomplete.d.ts +33 -0
  19. package/dist/renderer/components/Autocomplete.js +40 -0
  20. package/dist/renderer/components/Intro.d.ts +9 -0
  21. package/dist/renderer/components/Intro.js +5 -15
  22. package/dist/renderer/components/MessageFormatter.d.ts +96 -0
  23. package/dist/renderer/components/MessageFormatter.js +375 -0
  24. package/dist/renderer/components/Permission.d.ts +4 -0
  25. package/dist/renderer/components/Permission.js +1 -1
  26. package/dist/renderer/components/Status.d.ts +4 -0
  27. package/dist/renderer/components/Status.js +2 -3
  28. package/dist/renderer/components/WelcomeFormatter.d.ts +19 -0
  29. package/dist/renderer/components/WelcomeFormatter.js +79 -0
  30. package/dist/renderer/components/uiConstants.d.ts +8 -0
  31. package/dist/renderer/components/uiConstants.js +24 -0
  32. package/dist/renderer/inputParsing.d.ts +22 -0
  33. package/dist/renderer/inputParsing.js +28 -0
  34. package/dist/renderer/layout.d.ts +215 -0
  35. package/dist/renderer/layout.js +326 -0
  36. package/dist/renderer/main.d.ts +2 -1
  37. package/dist/renderer/main.js +45 -10
  38. package/dist/renderer/ollamaHint.d.ts +12 -0
  39. package/dist/renderer/ollamaHint.js +29 -0
  40. package/dist/utils/agentChat.js +23 -1
  41. package/dist/utils/codeepCloud.d.ts +54 -0
  42. package/dist/utils/codeepCloud.js +95 -0
  43. package/dist/utils/export.d.ts +12 -0
  44. package/dist/utils/export.js +3 -3
  45. package/dist/utils/hooks.d.ts +26 -0
  46. package/dist/utils/hooks.js +69 -1
  47. package/dist/utils/keychain.js +45 -29
  48. package/dist/utils/logger.d.ts +12 -0
  49. package/dist/utils/logger.js +1 -1
  50. package/dist/utils/mcpConfig.d.ts +26 -0
  51. package/dist/utils/mcpConfig.js +109 -4
  52. package/dist/utils/skillBundles.d.ts +14 -0
  53. package/dist/utils/skillBundles.js +3 -3
  54. package/dist/utils/skillBundlesCloud.d.ts +7 -0
  55. package/dist/utils/skillBundlesCloud.js +1 -1
  56. package/dist/utils/tokenTracker.js +12 -2
  57. package/dist/utils/toolParsing.d.ts +11 -0
  58. package/dist/utils/toolParsing.js +6 -0
  59. package/dist/version.d.ts +1 -1
  60. package/dist/version.js +1 -1
  61. package/package.json +2 -2
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Welcome-screen formatter.
3
+ *
4
+ * Pure function extracted from `App.ts` so the welcome banner's colour
5
+ * rules can be unit-tested without instantiating the full renderer.
6
+ * Called once per render for messages whose `role` is `'welcome'`.
7
+ */
8
+ import { fg, style } from '../ansi.js';
9
+ import { PRIMARY_COLOR } from './uiConstants.js';
10
+ /**
11
+ * Format the welcome message body into coloured terminal lines.
12
+ *
13
+ * The body is a small DSL of line shapes:
14
+ * - `Codeep vX.X.X · Provider · Model` — version header
15
+ * - ` Project <path>` — project label
16
+ * - ` Access <read · write>` — access label
17
+ * - ` Mode <mode>` — mode label
18
+ * - lines containing `⚠` — amber warning
19
+ * - lines containing `/help` — shortcuts hint
20
+ *
21
+ * Anything else is pushed verbatim.
22
+ */
23
+ export function formatWelcomeMessage(content) {
24
+ const lines = [];
25
+ const DIM = fg.rgb(80, 80, 80);
26
+ const LABEL = fg.rgb(100, 100, 100);
27
+ const SEP = DIM + ' · ' + style.reset;
28
+ for (const line of content.split('\n')) {
29
+ if (line.trim() === '') {
30
+ lines.push({ text: '', style: '' });
31
+ continue;
32
+ }
33
+ // Version line: "Codeep vX.X.X · Provider · Model"
34
+ if (line.startsWith('Codeep ')) {
35
+ const parts = line.split(' · ');
36
+ const colored = PRIMARY_COLOR + style.bold + (parts[0] || '') + style.reset
37
+ + SEP + fg.rgb(180, 180, 180) + (parts[1] || '') + style.reset
38
+ + SEP + fg.rgb(130, 130, 130) + (parts[2] || '') + style.reset;
39
+ lines.push({ text: colored, style: '', raw: true });
40
+ continue;
41
+ }
42
+ // Project line
43
+ if (/^\s+Project\s/.test(line)) {
44
+ const value = line.replace(/^\s+Project\s+/, '');
45
+ lines.push({ text: LABEL + ' Project ' + style.reset + fg.rgb(100, 180, 220) + value + style.reset, style: '', raw: true });
46
+ continue;
47
+ }
48
+ // Access line
49
+ if (/^\s+Access\s/.test(line)) {
50
+ const value = line.replace(/^\s+Access\s+/, '');
51
+ const parts = value.split(' · ');
52
+ const accessColored = fg.rgb(100, 200, 120) + style.bold + (parts[0] || '') + style.reset;
53
+ const rest = parts.slice(1).map(p => fg.rgb(80, 160, 100) + p + style.reset).join(SEP);
54
+ lines.push({ text: LABEL + ' Access ' + style.reset + accessColored + (rest ? SEP + rest : ''), style: '', raw: true });
55
+ continue;
56
+ }
57
+ // Mode line
58
+ if (/^\s+Mode\s/.test(line)) {
59
+ const value = line.replace(/^\s+Mode\s+/, '');
60
+ lines.push({ text: LABEL + ' Mode ' + style.reset + fg.rgb(160, 160, 160) + value + style.reset, style: '', raw: true });
61
+ continue;
62
+ }
63
+ // Agent Mode warning
64
+ if (line.includes('⚠')) {
65
+ lines.push({ text: ' ' + fg.rgb(220, 160, 40) + line.trim() + style.reset, style: '', raw: true });
66
+ continue;
67
+ }
68
+ // Shortcuts line
69
+ if (line.includes('/help')) {
70
+ const parts = line.trim().split(' · ');
71
+ const colored = parts.map(p => fg.rgb(150, 150, 150) + p.trim() + style.reset).join(DIM + ' · ' + style.reset);
72
+ lines.push({ text: ' ' + colored, style: '', raw: true });
73
+ continue;
74
+ }
75
+ lines.push({ text: line, style: '' });
76
+ }
77
+ lines.push({ text: '', style: '' });
78
+ return lines;
79
+ }
@@ -0,0 +1,8 @@
1
+ /** Brand red — used for the logo, the agent-panel title, and accents. */
2
+ export declare const PRIMARY_COLOR: string;
3
+ /** Spinner frames for the agent progress panel (8-step rotation). */
4
+ export declare const SPINNER_FRAMES: string[];
5
+ /** ASCII art logo, one string per terminal line. */
6
+ export declare const LOGO_LINES: string[];
7
+ /** Logo height in terminal lines (LOGO_LINES.length). */
8
+ export declare const LOGO_HEIGHT: number;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared UI constants for the renderer.
3
+ *
4
+ * Centralised so the colour palette, spinner animation, and ASCII logo
5
+ * have a single home — both `App.ts` and any extracted component can
6
+ * import them without re-declaring (which would let the palette drift
7
+ * between files).
8
+ */
9
+ import { fg } from '../ansi.js';
10
+ /** Brand red — used for the logo, the agent-panel title, and accents. */
11
+ export const PRIMARY_COLOR = fg.rgb(240, 42, 48);
12
+ /** Spinner frames for the agent progress panel (8-step rotation). */
13
+ export const SPINNER_FRAMES = ['▖', '▘', '▝', '▗', '▌', '▀', '▐', '▄'];
14
+ /** ASCII art logo, one string per terminal line. */
15
+ export const LOGO_LINES = [
16
+ ' ██████╗ ██████╗ ██████╗ ███████╗███████╗██████╗ ',
17
+ '██╔════╝██╔═══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗',
18
+ '██║ ██║ ██║██║ ██║█████╗ █████╗ ██████╔╝',
19
+ '██║ ██║ ██║██║ ██║██╔══╝ ██╔══╝ ██╔═══╝ ',
20
+ '╚██████╗╚██████╔╝██████╔╝███████╗███████╗██║ ',
21
+ ' ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ',
22
+ ];
23
+ /** Logo height in terminal lines (LOGO_LINES.length). */
24
+ export const LOGO_HEIGHT = LOGO_LINES.length;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Pure input-parsing helpers extracted from App.ts.
3
+ *
4
+ * The old `handleCommand` method inlined `input.slice(1).split(' ')` and a
5
+ * `.toLowerCase()` on every call, with no test coverage. Pulling the parse
6
+ * into a pure function lets us unit-test the edge cases (extra whitespace,
7
+ * empty args, uppercase, leading slash) directly.
8
+ */
9
+ export interface ParsedCommand {
10
+ /** Lower-cased command name, without the leading slash. */
11
+ command: string;
12
+ /** Remaining args, already split on spaces (empty strings removed). */
13
+ args: string[];
14
+ }
15
+ /**
16
+ * Parse a raw user input line that begins with `/` into a command name
17
+ * and arguments. Trims and collapses runs of whitespace so `/scan src`
18
+ * behaves the same as `/scan src`.
19
+ *
20
+ * Returns `null` when the input doesn’t start with `/` or is blank.
21
+ */
22
+ export declare function parseCommandInput(input: string): ParsedCommand | null;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Pure input-parsing helpers extracted from App.ts.
3
+ *
4
+ * The old `handleCommand` method inlined `input.slice(1).split(' ')` and a
5
+ * `.toLowerCase()` on every call, with no test coverage. Pulling the parse
6
+ * into a pure function lets us unit-test the edge cases (extra whitespace,
7
+ * empty args, uppercase, leading slash) directly.
8
+ */
9
+ /**
10
+ * Parse a raw user input line that begins with `/` into a command name
11
+ * and arguments. Trims and collapses runs of whitespace so `/scan src`
12
+ * behaves the same as `/scan src`.
13
+ *
14
+ * Returns `null` when the input doesn’t start with `/` or is blank.
15
+ */
16
+ export function parseCommandInput(input) {
17
+ if (!input.startsWith('/'))
18
+ return null;
19
+ // Collapse runs of whitespace so " " between args doesn’t yield
20
+ // empty-string args, and trim the leading "/" plus surrounding space.
21
+ const parts = input.slice(1).trim().split(/\s+/);
22
+ if (parts.length === 0 || parts[0] === '')
23
+ return null;
24
+ return {
25
+ command: parts[0].toLowerCase(),
26
+ args: parts.slice(1),
27
+ };
28
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Pure layout helpers extracted from App.ts.
3
+ *
4
+ * These functions take a *snapshot* of the App's UI state (which panels are
5
+ * open, how many items they hold) and return geometric values (heights,
6
+ * offsets) without touching `this`. Keeping them pure means they can be
7
+ * unit-tested directly — the layout math is the part of the renderer most
8
+ * prone to off-by-one regressions, and it was previously untestable because
9
+ * it was inlined in `renderChat` with `this.*` access on every line.
10
+ *
11
+ * Convention: every field on `LayoutSnapshot` is `readonly` so callers
12
+ * cannot mutate the App's real state through the snapshot.
13
+ */
14
+ /** Read-only snapshot of the fields `bottomPanelHeight` consults. */
15
+ export interface LayoutSnapshot {
16
+ readonly height: number;
17
+ readonly pasteInfoOpen: boolean;
18
+ readonly pasteInfoPreviewLines: number;
19
+ readonly isAgentRunning: boolean;
20
+ readonly confirmOpen: boolean;
21
+ readonly permissionOpen: boolean;
22
+ readonly sessionPickerOpen: boolean;
23
+ readonly sessionPickerItemCount: number;
24
+ readonly confirmMessageCount: number;
25
+ readonly statusOpen: boolean;
26
+ readonly helpOpen: boolean;
27
+ readonly searchOpen: boolean;
28
+ readonly searchResultCount: number;
29
+ readonly exportOpen: boolean;
30
+ readonly logoutOpen: boolean;
31
+ readonly logoutProviderCount: number;
32
+ readonly loginOpen: boolean;
33
+ readonly loginStep: 'provider' | 'apikey';
34
+ readonly loginProviderCount: number;
35
+ readonly menuOpen: boolean;
36
+ readonly menuItemCount: number;
37
+ readonly settingsOpen: boolean;
38
+ readonly settingsCount: number;
39
+ readonly showAutocomplete: boolean;
40
+ readonly autocompleteItemCount: number;
41
+ }
42
+ /**
43
+ * Compute how many terminal rows the bottom panel (paste info, agent box,
44
+ * permission/session/confirm/search/export/login/logout/menu/settings
45
+ * dialogs, autocomplete) occupies in the current frame.
46
+ *
47
+ * Mirrors the if/else chain that used to live inline in `renderChat`.
48
+ * Returns 0 when no panel is open.
49
+ */
50
+ export declare function bottomPanelHeight(s: LayoutSnapshot): number;
51
+ /**
52
+ * Split the available terminal height into the main chat area and the
53
+ * bottom panel. Returns the y-coordinates the renderer paints into.
54
+ *
55
+ * - `messagesStart` is always 0 (top of the screen).
56
+ * - `messagesEnd` is the last row the message list may use.
57
+ * - `separatorLine`, `inputLine`, `statusLine` are the three reserved
58
+ * rows at the bottom of the main area, in order.
59
+ */
60
+ export interface ChatLayout {
61
+ messagesStart: number;
62
+ messagesEnd: number;
63
+ separatorLine: number;
64
+ inputLine: number;
65
+ statusLine: number;
66
+ mainHeight: number;
67
+ }
68
+ export declare function chatLayout(height: number, panelHeight: number): ChatLayout;
69
+ /**
70
+ * Count how many terminal rows a single chat message will occupy once
71
+ * word-wrapped to `maxWidth` columns. Used by `scrollToMessage` to find
72
+ * the right scroll offset.
73
+ *
74
+ * Every message renders as: 1 header row + 1 blank row + one or more
75
+ * wrapped content rows, followed by 1 blank spacing row.
76
+ */
77
+ export declare function messageLineCount(content: string, maxWidth: number): number;
78
+ /**
79
+ * Sum `messageLineCount` across a list of messages and return both the
80
+ * running total and the line offset where `targetIndex` begins. This is
81
+ * the pure core of the old `scrollToMessage` method.
82
+ */
83
+ export declare function messageOffsets(contents: string[], maxWidth: number, targetIndex: number): {
84
+ totalLines: number;
85
+ targetStartLine: number;
86
+ };
87
+ /**
88
+ * Compute the scroll offset that places the target message roughly in
89
+ * the middle of the visible window.
90
+ */
91
+ export declare function scrollOffsetForTarget(totalLines: number, targetStartLine: number, visibleLines: number): number;
92
+ /**
93
+ * Compute the visible window of a chat transcript given the current scroll
94
+ * offset. Returns the [startIndex, endIndex) slice into the all-lines array
95
+ * and, as a side-effect contract, the clamped scroll offset the caller
96
+ * should store (the renderer overwrites `this.scrollOffset` with this).
97
+ *
98
+ * Extracted from `getVisibleMessages` so the off-by-one-prone scroll math
99
+ * has direct unit tests.
100
+ */
101
+ export declare function scrollWindow(args: {
102
+ totalLines: number;
103
+ height: number;
104
+ scrollOffset: number;
105
+ }): {
106
+ startIndex: number;
107
+ endIndex: number;
108
+ clampedScrollOffset: number;
109
+ };
110
+ /** Render a gradient progress bar of the given width for the given ratio. */
111
+ export declare function agentProgressBar(iteration: number, maxIterations: number, barWidth: number): string;
112
+ /** Truncate `text` to `maxLen` columns, appending an ellipsis if it doesn’t fit. */
113
+ export declare function truncateNotification(text: string, maxLen: number): string;
114
+ export interface PasteInfo {
115
+ chars: number;
116
+ lines: number;
117
+ preview: string;
118
+ fullText: string;
119
+ }
120
+ /** Threshold above which a paste is considered "large" and shows a dialog. */
121
+ export declare const PASTE_DIALOG_THRESHOLD: {
122
+ chars: number;
123
+ lines: number;
124
+ };
125
+ /** True when the paste is large enough to warrant the confirm dialog. */
126
+ export declare function shouldShowPasteDialog(text: string): boolean;
127
+ /** Build the PasteInfo struct for a large paste (preview truncated to 200 chars). */
128
+ export declare function buildPasteInfo(text: string): PasteInfo;
129
+ /**
130
+ * Compact a raw token count into the short string shown in the status bar
131
+ * ("123", "1.2K", "12.3K"). Returns an empty string when tokens is 0 so
132
+ * the caller can omit the segment entirely.
133
+ */
134
+ export declare function formatTokenCount(tokens: number): string;
135
+ /**
136
+ * Pick the context-sensitive hint shown at the right edge of the status
137
+ * bar. The "new messages below" badge takes priority when the user has
138
+ * scrolled up — otherwise the hint depends on whether work is in flight.
139
+ */
140
+ export declare function statusBarRightHint(args: {
141
+ scrollOffset: number;
142
+ unseenWhileScrolled: number;
143
+ isStreaming: boolean;
144
+ isLoading: boolean;
145
+ }): string;
146
+ /** The panel that currently owns keyboard focus, in priority order. */
147
+ export type ActivePanel = 'pasteInfo' | 'permission' | 'sessionPicker' | 'confirm' | 'status' | 'help' | 'settings' | 'search' | 'export' | 'logout' | 'login' | 'menu' | 'autocomplete' | 'chat';
148
+ export interface PanelState {
149
+ readonly pasteInfoOpen: boolean;
150
+ readonly permissionOpen: boolean;
151
+ readonly sessionPickerOpen: boolean;
152
+ readonly confirmOpen: boolean;
153
+ readonly statusOpen: boolean;
154
+ readonly helpOpen: boolean;
155
+ readonly settingsOpen: boolean;
156
+ readonly searchOpen: boolean;
157
+ readonly exportOpen: boolean;
158
+ readonly logoutOpen: boolean;
159
+ readonly loginOpen: boolean;
160
+ readonly menuOpen: boolean;
161
+ readonly showAutocomplete: boolean;
162
+ }
163
+ /**
164
+ * Return the highest-priority open panel. `chat` is the fallback when no
165
+ * panel is open. The order matches the if/else chain that used to live in
166
+ * `handleChatKey`.
167
+ */
168
+ export declare function activePanel(s: PanelState): ActivePanel;
169
+ export interface InputDisplayOptions {
170
+ /** Full editor value (may contain newlines). */
171
+ value: string;
172
+ /** Character offset of the cursor within `value`. */
173
+ cursorPos: number;
174
+ /** Available width (terminal columns). */
175
+ width: number;
176
+ /** Whether multi-line (❯❯) mode is active. */
177
+ isMultilineMode: boolean;
178
+ }
179
+ export interface InputDisplay {
180
+ /** Prompt symbol shown before the text ("❯ ", "❯❯ ", "[3] ❯ "). */
181
+ promptSymbol: string;
182
+ /** Visible slice of the input (already truncated / ellipsised). */
183
+ displayValue: string;
184
+ /** Column position for the cursor (absolute, 0-based from screen left). */
185
+ cursorX: number;
186
+ /** Placeholder text to show when the editor is empty. */
187
+ placeholder: string;
188
+ /** True when the editor value is empty. */
189
+ isEmpty: boolean;
190
+ }
191
+ /**
192
+ * Compute the prompt symbol for the current state. Multi-line content
193
+ * shows a `[n] ❯ ` prefix with the line count; otherwise `❯❯ ` in
194
+ * multi-line mode, or the plain `❯ `.
195
+ */
196
+ export declare function inputPromptSymbol(value: string, isMultilineMode: boolean): string;
197
+ /**
198
+ * Compute the visible slice of a long input line, plus the cursor column
199
+ * it maps to. Mirrors the inline logic that used to live in `renderInput`:
200
+ * when the line fits, show it whole; otherwise anchor the cursor at 70%
201
+ * of the available width and slide the viewport.
202
+ */
203
+ export declare function inputViewport(args: {
204
+ line: string;
205
+ cursorInLine: number;
206
+ maxInputWidth: number;
207
+ }): {
208
+ displayValue: string;
209
+ cursorOffset: number;
210
+ };
211
+ /**
212
+ * Top-level entry point used by `renderInput`. Produces the prompt symbol,
213
+ * the visible text, the absolute cursor X, and the placeholder.
214
+ */
215
+ export declare function computeInputDisplay(opts: InputDisplayOptions): InputDisplay;