@compilr-dev/cli 0.4.0 → 0.5.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.
- package/README.md +30 -12
- package/dist/agent.d.ts +74 -1
- package/dist/agent.js +259 -76
- package/dist/anchors/index.d.ts +9 -0
- package/dist/anchors/index.js +9 -0
- package/dist/anchors/project-anchors.d.ts +79 -0
- package/dist/anchors/project-anchors.js +202 -0
- package/dist/commands/handler-types.d.ts +68 -0
- package/dist/commands/handler-types.js +8 -0
- package/dist/commands/handlers/agent-commands.d.ts +13 -0
- package/dist/commands/handlers/agent-commands.js +305 -0
- package/dist/commands/handlers/design-commands.d.ts +15 -0
- package/dist/commands/handlers/design-commands.js +334 -0
- package/dist/commands/handlers/index.d.ts +20 -0
- package/dist/commands/handlers/index.js +43 -0
- package/dist/commands/handlers/overlay-commands.d.ts +21 -0
- package/dist/commands/handlers/overlay-commands.js +287 -0
- package/dist/commands/handlers/project-commands.d.ts +11 -0
- package/dist/commands/handlers/project-commands.js +167 -0
- package/dist/commands/handlers/simple-commands.d.ts +19 -0
- package/dist/commands/handlers/simple-commands.js +144 -0
- package/dist/commands/index.d.ts +2 -1
- package/dist/commands/registry.d.ts +50 -0
- package/dist/commands/registry.js +75 -0
- package/dist/commands-v2/handlers/context.d.ts +13 -0
- package/dist/commands-v2/handlers/context.js +348 -0
- package/dist/commands-v2/handlers/core.d.ts +13 -0
- package/dist/commands-v2/handlers/core.js +165 -0
- package/dist/commands-v2/handlers/debug.d.ts +11 -0
- package/dist/commands-v2/handlers/debug.js +159 -0
- package/dist/commands-v2/handlers/index.d.ts +12 -0
- package/dist/commands-v2/handlers/index.js +24 -0
- package/dist/commands-v2/handlers/project.d.ts +22 -0
- package/dist/commands-v2/handlers/project.js +814 -0
- package/dist/commands-v2/handlers/settings.d.ts +15 -0
- package/dist/commands-v2/handlers/settings.js +235 -0
- package/dist/commands-v2/index.d.ts +13 -0
- package/dist/commands-v2/index.js +15 -0
- package/dist/commands-v2/registry.d.ts +37 -0
- package/dist/commands-v2/registry.js +80 -0
- package/dist/commands-v2/types.d.ts +75 -0
- package/dist/commands-v2/types.js +7 -0
- package/dist/commands.js +110 -7
- package/dist/index.js +288 -29
- package/dist/input-handlers/index.d.ts +7 -0
- package/dist/input-handlers/index.js +7 -0
- package/dist/input-handlers/memory-handler.d.ts +26 -0
- package/dist/input-handlers/memory-handler.js +68 -0
- package/dist/repl-helpers.d.ts +63 -0
- package/dist/repl-helpers.js +318 -0
- package/dist/repl-v2.d.ts +155 -0
- package/dist/repl-v2.js +774 -0
- package/dist/repl.d.ts +32 -4
- package/dist/repl.js +250 -977
- package/dist/settings/index.d.ts +23 -0
- package/dist/settings/index.js +48 -0
- package/dist/settings/paths.d.ts +110 -0
- package/dist/settings/paths.js +264 -0
- package/dist/templates/compilr-md.js +7 -4
- package/dist/templates/index.js +3 -4
- package/dist/themes/colors.js +3 -1
- package/dist/themes/registry.d.ts +5 -36
- package/dist/themes/registry.js +11 -95
- package/dist/themes/types.d.ts +3 -38
- package/dist/themes/types.js +2 -2
- package/dist/tools/anchor-tools.d.ts +31 -0
- package/dist/tools/anchor-tools.js +255 -0
- package/dist/tools/backlog-wrappers.d.ts +54 -0
- package/dist/tools/backlog-wrappers.js +338 -0
- package/dist/tools/backlog.js +1 -1
- package/dist/tools/db-tools.d.ts +65 -0
- package/dist/tools/db-tools.js +19 -0
- package/dist/tools/document-db.d.ts +43 -0
- package/dist/tools/document-db.js +220 -0
- package/dist/tools/project-db.d.ts +102 -0
- package/dist/tools/project-db.js +370 -0
- package/dist/tools/workitem-db.d.ts +103 -0
- package/dist/tools/workitem-db.js +549 -0
- package/dist/tools.js +13 -3
- package/dist/ui/agents-overlay-v2.d.ts +43 -0
- package/dist/ui/agents-overlay-v2.js +809 -0
- package/dist/ui/agents-overlay.d.ts +5 -5
- package/dist/ui/agents-overlay.js +782 -420
- package/dist/ui/anchors-overlay.d.ts +12 -0
- package/dist/ui/anchors-overlay.js +775 -0
- package/dist/ui/arch-type-overlay.d.ts +1 -6
- package/dist/ui/arch-type-overlay.js +175 -203
- package/dist/ui/ask-user-overlay-v2.d.ts +26 -0
- package/dist/ui/ask-user-overlay-v2.js +555 -0
- package/dist/ui/ask-user-overlay.d.ts +2 -2
- package/dist/ui/ask-user-overlay.js +443 -535
- package/dist/ui/ask-user-simple-overlay-v2.d.ts +25 -0
- package/dist/ui/ask-user-simple-overlay-v2.js +215 -0
- package/dist/ui/ask-user-simple-overlay.d.ts +2 -2
- package/dist/ui/ask-user-simple-overlay.js +182 -209
- package/dist/ui/backlog-overlay.d.ts +16 -1
- package/dist/ui/backlog-overlay.js +525 -659
- package/dist/ui/base/index.d.ts +26 -0
- package/dist/ui/base/index.js +33 -0
- package/dist/ui/base/inline-overlay-utils.d.ts +217 -0
- package/dist/ui/base/inline-overlay-utils.js +320 -0
- package/dist/ui/base/inline-overlay.d.ts +159 -0
- package/dist/ui/base/inline-overlay.js +257 -0
- package/dist/ui/base/key-utils.d.ts +15 -0
- package/dist/ui/base/key-utils.js +30 -0
- package/dist/ui/base/overlay-base-v2.d.ts +193 -0
- package/dist/ui/base/overlay-base-v2.js +246 -0
- package/dist/ui/base/overlay-base.d.ts +156 -0
- package/dist/ui/base/overlay-base.js +238 -0
- package/dist/ui/base/overlay-lifecycle.d.ts +65 -0
- package/dist/ui/base/overlay-lifecycle.js +159 -0
- package/dist/ui/base/overlay-types.d.ts +185 -0
- package/dist/ui/base/overlay-types.js +7 -0
- package/dist/ui/base/render-utils.d.ts +8 -0
- package/dist/ui/base/render-utils.js +11 -0
- package/dist/ui/base/screen-stack.d.ts +148 -0
- package/dist/ui/base/screen-stack.js +184 -0
- package/dist/ui/base/tabbed-list-overlay-v2.d.ts +103 -0
- package/dist/ui/base/tabbed-list-overlay-v2.js +317 -0
- package/dist/ui/base/tabbed-list-overlay.d.ts +153 -0
- package/dist/ui/base/tabbed-list-overlay.js +369 -0
- package/dist/ui/commands-overlay-v2.d.ts +33 -0
- package/dist/ui/commands-overlay-v2.js +441 -0
- package/dist/ui/commands-overlay.d.ts +7 -2
- package/dist/ui/commands-overlay.js +384 -355
- package/dist/ui/config-overlay.d.ts +5 -4
- package/dist/ui/config-overlay.js +243 -513
- package/dist/ui/conversation.d.ts +75 -4
- package/dist/ui/conversation.js +374 -161
- package/dist/ui/docs-overlay.d.ts +17 -0
- package/dist/ui/docs-overlay.js +303 -0
- package/dist/ui/ephemeral.d.ts +1 -1
- package/dist/ui/ephemeral.js +1 -1
- package/dist/ui/features/index.d.ts +34 -0
- package/dist/ui/features/index.js +34 -0
- package/dist/ui/features/input-feature.d.ts +85 -0
- package/dist/ui/features/input-feature.js +238 -0
- package/dist/ui/features/list-feature.d.ts +155 -0
- package/dist/ui/features/list-feature.js +244 -0
- package/dist/ui/features/pagination-feature.d.ts +154 -0
- package/dist/ui/features/pagination-feature.js +238 -0
- package/dist/ui/features/search-feature.d.ts +148 -0
- package/dist/ui/features/search-feature.js +185 -0
- package/dist/ui/features/tab-feature.d.ts +194 -0
- package/dist/ui/features/tab-feature.js +307 -0
- package/dist/ui/footer-v2.d.ts +222 -0
- package/dist/ui/footer-v2.js +1349 -0
- package/dist/ui/footer.d.ts +107 -0
- package/dist/ui/footer.js +359 -67
- package/dist/ui/guardrail-overlay.d.ts +29 -0
- package/dist/ui/guardrail-overlay.js +145 -0
- package/dist/ui/help-overlay-v2.d.ts +34 -0
- package/dist/ui/help-overlay-v2.js +309 -0
- package/dist/ui/help-overlay.d.ts +16 -0
- package/dist/ui/help-overlay.js +316 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -3
- package/dist/ui/init-overlay-v2.d.ts +34 -0
- package/dist/ui/init-overlay-v2.js +600 -0
- package/dist/ui/init-overlay.d.ts +12 -2
- package/dist/ui/init-overlay.js +349 -270
- package/dist/ui/input-prompt-v2.d.ts +1 -0
- package/dist/ui/input-prompt-v2.js +14 -6
- package/dist/ui/input-prompt.d.ts +116 -33
- package/dist/ui/input-prompt.js +536 -337
- package/dist/ui/iteration-limit-overlay-v2.d.ts +21 -0
- package/dist/ui/iteration-limit-overlay-v2.js +114 -0
- package/dist/ui/iteration-limit-overlay.d.ts +2 -2
- package/dist/ui/iteration-limit-overlay.js +92 -128
- package/dist/ui/keys-overlay-v2.d.ts +41 -0
- package/dist/ui/keys-overlay-v2.js +248 -0
- package/dist/ui/keys-overlay.d.ts +1 -0
- package/dist/ui/keys-overlay.js +203 -141
- package/dist/ui/line-utils.d.ts +88 -0
- package/dist/ui/line-utils.js +150 -0
- package/dist/ui/live-region.d.ts +161 -0
- package/dist/ui/live-region.js +387 -0
- package/dist/ui/mascot/expressions.d.ts +32 -0
- package/dist/ui/mascot/expressions.js +213 -0
- package/dist/ui/mascot/index.d.ts +8 -0
- package/dist/ui/mascot/index.js +8 -0
- package/dist/ui/mascot/renderer.d.ts +19 -0
- package/dist/ui/mascot/renderer.js +97 -0
- package/dist/ui/mascot-overlay-v2.d.ts +41 -0
- package/dist/ui/mascot-overlay-v2.js +138 -0
- package/dist/ui/mascot-overlay.d.ts +21 -0
- package/dist/ui/mascot-overlay.js +146 -0
- package/dist/ui/model-overlay-v2.d.ts +49 -0
- package/dist/ui/model-overlay-v2.js +118 -0
- package/dist/ui/model-overlay.d.ts +27 -0
- package/dist/ui/model-overlay.js +221 -0
- package/dist/ui/model-warning-overlay.js +3 -5
- package/dist/ui/new-overlay.d.ts +34 -0
- package/dist/ui/new-overlay.js +604 -0
- package/dist/ui/overlay/impl/agents-overlay-v2.d.ts +45 -0
- package/dist/ui/overlay/impl/agents-overlay-v2.js +825 -0
- package/dist/ui/overlay/impl/anchors-overlay-v2.d.ts +47 -0
- package/dist/ui/overlay/impl/anchors-overlay-v2.js +783 -0
- package/dist/ui/overlay/impl/arch-type-overlay-v2.d.ts +37 -0
- package/dist/ui/overlay/impl/arch-type-overlay-v2.js +240 -0
- package/dist/ui/overlay/impl/ask-user-overlay-v2.d.ts +72 -0
- package/dist/ui/overlay/impl/ask-user-overlay-v2.js +584 -0
- package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.d.ts +46 -0
- package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.js +204 -0
- package/dist/ui/overlay/impl/backlog-overlay-v2.d.ts +49 -0
- package/dist/ui/overlay/impl/backlog-overlay-v2.js +642 -0
- package/dist/ui/overlay/impl/commands-overlay-v2.d.ts +33 -0
- package/dist/ui/overlay/impl/commands-overlay-v2.js +441 -0
- package/dist/ui/overlay/impl/config-overlay-v2.d.ts +100 -0
- package/dist/ui/overlay/impl/config-overlay-v2.js +654 -0
- package/dist/ui/overlay/impl/dashboard-overlay-v2.d.ts +55 -0
- package/dist/ui/overlay/impl/dashboard-overlay-v2.js +359 -0
- package/dist/ui/overlay/impl/docs-overlay-v2.d.ts +45 -0
- package/dist/ui/overlay/impl/docs-overlay-v2.js +114 -0
- package/dist/ui/overlay/impl/document-detail-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/document-detail-overlay-v2.js +1071 -0
- package/dist/ui/overlay/impl/guardrail-overlay-v2.d.ts +43 -0
- package/dist/ui/overlay/impl/guardrail-overlay-v2.js +114 -0
- package/dist/ui/overlay/impl/help-overlay-v2.d.ts +34 -0
- package/dist/ui/overlay/impl/help-overlay-v2.js +309 -0
- package/dist/ui/overlay/impl/init-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/init-overlay-v2.js +593 -0
- package/dist/ui/overlay/impl/init-setup-overlay-v2.d.ts +25 -0
- package/dist/ui/overlay/impl/init-setup-overlay-v2.js +97 -0
- package/dist/ui/overlay/impl/iteration-limit-overlay-v2.d.ts +35 -0
- package/dist/ui/overlay/impl/iteration-limit-overlay-v2.js +105 -0
- package/dist/ui/overlay/impl/keys-overlay-v2.d.ts +41 -0
- package/dist/ui/overlay/impl/keys-overlay-v2.js +248 -0
- package/dist/ui/overlay/impl/mascot-overlay-v2.d.ts +41 -0
- package/dist/ui/overlay/impl/mascot-overlay-v2.js +138 -0
- package/dist/ui/overlay/impl/model-overlay-v2.d.ts +49 -0
- package/dist/ui/overlay/impl/model-overlay-v2.js +118 -0
- package/dist/ui/overlay/impl/model-warning-overlay-v2.d.ts +46 -0
- package/dist/ui/overlay/impl/model-warning-overlay-v2.js +132 -0
- package/dist/ui/overlay/impl/new-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/new-overlay-v2.js +593 -0
- package/dist/ui/overlay/impl/permission-overlay-v2.d.ts +36 -0
- package/dist/ui/overlay/impl/permission-overlay-v2.js +380 -0
- package/dist/ui/overlay/impl/projects-overlay-v2.d.ts +36 -0
- package/dist/ui/overlay/impl/projects-overlay-v2.js +499 -0
- package/dist/ui/overlay/impl/theme-overlay-v2.d.ts +42 -0
- package/dist/ui/overlay/impl/theme-overlay-v2.js +135 -0
- package/dist/ui/overlay/impl/tools-overlay-v2.d.ts +47 -0
- package/dist/ui/overlay/impl/tools-overlay-v2.js +218 -0
- package/dist/ui/overlay/impl/tutorial-overlay-v2.d.ts +31 -0
- package/dist/ui/overlay/impl/tutorial-overlay-v2.js +1035 -0
- package/dist/ui/overlay/impl/workflow-overlay-v2.d.ts +80 -0
- package/dist/ui/overlay/impl/workflow-overlay-v2.js +637 -0
- package/dist/ui/overlay/index.d.ts +33 -0
- package/dist/ui/overlay/index.js +35 -0
- package/dist/ui/overlay/key-utils.d.ts +6 -0
- package/dist/ui/overlay/key-utils.js +6 -0
- package/dist/ui/overlay/overlay-types.d.ts +128 -0
- package/dist/ui/overlay/overlay-types.js +22 -0
- package/dist/ui/overlay/types.d.ts +135 -0
- package/dist/ui/overlay/types.js +22 -0
- package/dist/ui/overlays/help-overlay-v2.d.ts +28 -0
- package/dist/ui/overlays/help-overlay-v2.js +198 -0
- package/dist/ui/overlays/index.d.ts +11 -0
- package/dist/ui/overlays/index.js +11 -0
- package/dist/ui/overlays.d.ts +0 -4
- package/dist/ui/overlays.js +0 -444
- package/dist/ui/permission-overlay-v2.d.ts +36 -0
- package/dist/ui/permission-overlay-v2.js +380 -0
- package/dist/ui/permission-overlay.d.ts +1 -1
- package/dist/ui/permission-overlay.js +186 -298
- package/dist/ui/projects-overlay.d.ts +19 -0
- package/dist/ui/projects-overlay.js +484 -0
- package/dist/ui/providers/types.d.ts +178 -0
- package/dist/ui/providers/types.js +9 -0
- package/dist/ui/render-modes.d.ts +36 -0
- package/dist/ui/render-modes.js +44 -0
- package/dist/ui/startup-menu.d.ts +36 -0
- package/dist/ui/startup-menu.js +236 -0
- package/dist/ui/subagent-renderer.d.ts +117 -0
- package/dist/ui/subagent-renderer.js +334 -0
- package/dist/ui/terminal-codes.d.ts +94 -0
- package/dist/ui/terminal-codes.js +124 -0
- package/dist/ui/terminal-renderer.d.ts +221 -0
- package/dist/ui/terminal-renderer.js +751 -0
- package/dist/ui/terminal-ui.d.ts +463 -0
- package/dist/ui/terminal-ui.js +2296 -0
- package/dist/ui/terminal.d.ts +20 -0
- package/dist/ui/terminal.js +72 -0
- package/dist/ui/theme-overlay-v2.d.ts +42 -0
- package/dist/ui/theme-overlay-v2.js +135 -0
- package/dist/ui/theme-overlay.d.ts +24 -0
- package/dist/ui/theme-overlay.js +127 -0
- package/dist/ui/todo-zone.js +53 -25
- package/dist/ui/tool-formatters.d.ts +16 -0
- package/dist/ui/tool-formatters.js +516 -0
- package/dist/ui/tools-overlay-v2.d.ts +47 -0
- package/dist/ui/tools-overlay-v2.js +218 -0
- package/dist/ui/tools-overlay.d.ts +10 -2
- package/dist/ui/tools-overlay.js +172 -220
- package/dist/ui/tutorial-overlay-v2.d.ts +31 -0
- package/dist/ui/tutorial-overlay-v2.js +1035 -0
- package/dist/ui/tutorial-overlay.d.ts +1 -0
- package/dist/ui/tutorial-overlay.js +400 -302
- package/dist/ui/workflow-overlay.d.ts +22 -0
- package/dist/ui/workflow-overlay.js +636 -0
- package/dist/utils/debug-log.d.ts +28 -0
- package/dist/utils/debug-log.js +57 -0
- package/dist/utils/model-tiers.js +1 -1
- package/dist/utils/path-safety.d.ts +56 -0
- package/dist/utils/path-safety.js +239 -0
- package/dist/workflow/guided-mode-injector.d.ts +42 -0
- package/dist/workflow/guided-mode-injector.js +191 -0
- package/dist/workflow/index.d.ts +8 -0
- package/dist/workflow/index.js +8 -0
- package/dist/workflow/step-criteria.d.ts +62 -0
- package/dist/workflow/step-criteria.js +150 -0
- package/dist/workflow/step-tracker.d.ts +92 -0
- package/dist/workflow/step-tracker.js +141 -0
- package/package.json +12 -5
package/dist/ui/keys-overlay.js
CHANGED
|
@@ -3,31 +3,37 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Modal overlay for managing API keys.
|
|
5
5
|
* Shows status of all providers and allows setting/deleting keys.
|
|
6
|
+
* Refactored to use BaseOverlay + ScreenStack + InputFeature.
|
|
6
7
|
*/
|
|
7
8
|
import * as terminal from './terminal.js';
|
|
8
|
-
import {
|
|
9
|
+
import { BaseOverlay, BaseScreen, ScreenStack, stay, pushScreen, popScreen, closeOverlay, isEscape, isEnter, isCtrlC, isClose, isNavigateUp, isNavigateDown, isD, } from './base/index.js';
|
|
10
|
+
import { InputFeature } from './features/index.js';
|
|
9
11
|
import { getAllProviderStatuses, setApiKey, deleteApiKey, } from '../utils/credentials.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
import { debugLog } from '../utils/debug-log.js';
|
|
13
|
+
/**
|
|
14
|
+
* List screen - shows all providers with their key status
|
|
15
|
+
*/
|
|
16
|
+
class ListScreen extends BaseScreen {
|
|
17
|
+
state;
|
|
18
|
+
styles;
|
|
19
|
+
constructor(state, styles) {
|
|
20
|
+
super();
|
|
21
|
+
this.state = state;
|
|
22
|
+
this.styles = styles;
|
|
21
23
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
render() {
|
|
25
|
+
const s = this.styles;
|
|
26
|
+
const cols = terminal.getTerminalWidth();
|
|
27
|
+
const border = s.muted('─'.repeat(Math.max(1, cols - 1)));
|
|
28
|
+
const lines = [];
|
|
29
|
+
// Header
|
|
30
|
+
lines.push(border);
|
|
31
|
+
lines.push(' ' + s.primaryBold('API Keys'));
|
|
32
|
+
lines.push('');
|
|
33
|
+
// Provider list
|
|
34
|
+
for (let i = 0; i < this.state.providers.length; i++) {
|
|
35
|
+
const p = this.state.providers[i];
|
|
36
|
+
const isCursor = this.state.selectedIndex === i;
|
|
31
37
|
const prefix = isCursor ? ' ❯ ' : ' ';
|
|
32
38
|
// Status indicator
|
|
33
39
|
let status;
|
|
@@ -46,136 +52,192 @@ function render(state, previousLineCount = 0) {
|
|
|
46
52
|
const line = `${prefix}${name.padEnd(22)} ${status}`;
|
|
47
53
|
lines.push(line);
|
|
48
54
|
}
|
|
55
|
+
// Footer
|
|
49
56
|
lines.push('');
|
|
50
|
-
lines.push(s.muted('
|
|
57
|
+
lines.push(s.muted(' ↑↓/jk Navigate · Enter Edit · d Delete · q/Esc Close'));
|
|
58
|
+
lines.push(border);
|
|
59
|
+
return lines;
|
|
51
60
|
}
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
handleKey(data) {
|
|
62
|
+
// Close keys
|
|
63
|
+
if (isCtrlC(data) || isClose(data)) {
|
|
64
|
+
return closeOverlay({ changed: this.state.changed });
|
|
65
|
+
}
|
|
66
|
+
// Navigation up
|
|
67
|
+
if (isNavigateUp(data)) {
|
|
68
|
+
if (this.state.selectedIndex > 0) {
|
|
69
|
+
this.state.selectedIndex--;
|
|
70
|
+
return stay();
|
|
71
|
+
}
|
|
72
|
+
return stay(false);
|
|
73
|
+
}
|
|
74
|
+
// Navigation down
|
|
75
|
+
if (isNavigateDown(data)) {
|
|
76
|
+
if (this.state.selectedIndex < this.state.providers.length - 1) {
|
|
77
|
+
this.state.selectedIndex++;
|
|
78
|
+
return stay();
|
|
79
|
+
}
|
|
80
|
+
return stay(false);
|
|
81
|
+
}
|
|
82
|
+
// Enter - edit selected provider
|
|
83
|
+
if (isEnter(data)) {
|
|
84
|
+
const selected = this.state.providers[this.state.selectedIndex];
|
|
85
|
+
if (selected.provider !== 'ollama') {
|
|
86
|
+
return pushScreen(new InputScreen(this.state, this.styles, selected.provider));
|
|
87
|
+
}
|
|
88
|
+
return stay(false);
|
|
89
|
+
}
|
|
90
|
+
// Delete key
|
|
91
|
+
if (isD(data)) {
|
|
92
|
+
const selected = this.state.providers[this.state.selectedIndex];
|
|
93
|
+
if (selected.provider !== 'ollama' && selected.hasKey && !selected.fromEnv) {
|
|
94
|
+
deleteApiKey(selected.provider);
|
|
95
|
+
this.state.providers = getAllProviderStatuses();
|
|
96
|
+
this.state.changed = true;
|
|
97
|
+
return stay();
|
|
98
|
+
}
|
|
99
|
+
return stay(false);
|
|
100
|
+
}
|
|
101
|
+
return stay(false);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Input screen - text input for entering API key using InputFeature
|
|
106
|
+
*/
|
|
107
|
+
class InputScreen extends BaseScreen {
|
|
108
|
+
state;
|
|
109
|
+
styles;
|
|
110
|
+
provider;
|
|
111
|
+
input;
|
|
112
|
+
constructor(state, styles, provider) {
|
|
113
|
+
super();
|
|
114
|
+
this.state = state;
|
|
115
|
+
this.styles = styles;
|
|
116
|
+
this.provider = provider;
|
|
117
|
+
this.input = new InputFeature({ placeholder: 'Paste or type your API key...' });
|
|
118
|
+
}
|
|
119
|
+
onEnter() {
|
|
120
|
+
terminal.showCursor();
|
|
121
|
+
}
|
|
122
|
+
onExit() {
|
|
123
|
+
terminal.hideCursor();
|
|
124
|
+
}
|
|
125
|
+
render() {
|
|
126
|
+
const s = this.styles;
|
|
127
|
+
const cols = terminal.getTerminalWidth();
|
|
128
|
+
const border = s.muted('─'.repeat(Math.max(1, cols - 1)));
|
|
129
|
+
const lines = [];
|
|
130
|
+
const providerInfo = this.state.providers.find(p => p.provider === this.provider);
|
|
131
|
+
// Header
|
|
132
|
+
lines.push(border);
|
|
133
|
+
lines.push(' ' + s.primaryBold('API Keys'));
|
|
134
|
+
lines.push('');
|
|
135
|
+
// Input prompt
|
|
136
|
+
lines.push(` Set API key for ${s.primary(providerInfo?.name ?? this.provider)}:`);
|
|
137
|
+
lines.push('');
|
|
138
|
+
lines.push(' ' + this.input.render(s));
|
|
139
|
+
lines.push('');
|
|
140
|
+
// Help text
|
|
141
|
+
if (providerInfo?.keyUrl) {
|
|
142
|
+
lines.push(s.muted(` Get key: ${providerInfo.keyUrl}`));
|
|
61
143
|
lines.push('');
|
|
62
|
-
|
|
144
|
+
}
|
|
145
|
+
// Footer
|
|
146
|
+
lines.push(s.muted(' Enter Save · Esc Cancel · ←→ Move cursor'));
|
|
147
|
+
lines.push(border);
|
|
148
|
+
return lines;
|
|
149
|
+
}
|
|
150
|
+
handleKey(data) {
|
|
151
|
+
// Ctrl+C closes overlay
|
|
152
|
+
if (isCtrlC(data)) {
|
|
153
|
+
return closeOverlay({ changed: this.state.changed });
|
|
154
|
+
}
|
|
155
|
+
// Escape goes back to list
|
|
156
|
+
if (isEscape(data)) {
|
|
157
|
+
return popScreen();
|
|
158
|
+
}
|
|
159
|
+
// Enter saves the key
|
|
160
|
+
if (isEnter(data)) {
|
|
161
|
+
const value = this.input.trimmedValue;
|
|
162
|
+
if (value) {
|
|
163
|
+
setApiKey(this.provider, value);
|
|
164
|
+
this.state.providers = getAllProviderStatuses();
|
|
165
|
+
this.state.changed = true;
|
|
166
|
+
}
|
|
167
|
+
return popScreen();
|
|
168
|
+
}
|
|
169
|
+
// Let InputFeature handle the key
|
|
170
|
+
const result = this.input.handleKey(data);
|
|
171
|
+
if (result.handled) {
|
|
172
|
+
return stay(result.render);
|
|
173
|
+
}
|
|
174
|
+
return stay(false);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// =============================================================================
|
|
178
|
+
// Overlay Class
|
|
179
|
+
// =============================================================================
|
|
180
|
+
class KeysOverlay extends BaseOverlay {
|
|
181
|
+
screenStack;
|
|
182
|
+
constructor() {
|
|
183
|
+
debugLog('KeysOverlay:constructor', 'start');
|
|
184
|
+
debugLog('KeysOverlay:constructor', 'calling getAllProviderStatuses');
|
|
185
|
+
const providers = getAllProviderStatuses();
|
|
186
|
+
debugLog('KeysOverlay:constructor', 'getAllProviderStatuses done');
|
|
187
|
+
super({
|
|
188
|
+
lineCount: 0,
|
|
189
|
+
maxLineCount: 0,
|
|
190
|
+
selectedIndex: 0,
|
|
191
|
+
changed: false,
|
|
192
|
+
providers,
|
|
193
|
+
});
|
|
194
|
+
debugLog('KeysOverlay:constructor', 'super() done');
|
|
195
|
+
this.screenStack = new ScreenStack();
|
|
196
|
+
this.screenStack.push(new ListScreen(this.state, this.styles));
|
|
197
|
+
debugLog('KeysOverlay:constructor', 'end');
|
|
198
|
+
}
|
|
199
|
+
render() {
|
|
200
|
+
const screen = this.screenStack.current();
|
|
201
|
+
return screen?.render() ?? [];
|
|
202
|
+
}
|
|
203
|
+
handleKey(data) {
|
|
204
|
+
const screen = this.screenStack.current();
|
|
205
|
+
if (!screen) {
|
|
206
|
+
this.close({ changed: this.state.changed });
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const result = screen.handleKey(data);
|
|
210
|
+
switch (result.action) {
|
|
211
|
+
case 'stay':
|
|
212
|
+
if (result.render) {
|
|
213
|
+
this.update();
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
case 'push':
|
|
217
|
+
this.screenStack.push(result.screen);
|
|
218
|
+
this.update();
|
|
219
|
+
break;
|
|
220
|
+
case 'pop':
|
|
221
|
+
this.screenStack.pop();
|
|
222
|
+
this.update();
|
|
223
|
+
break;
|
|
224
|
+
case 'close':
|
|
225
|
+
this.close(result.result);
|
|
226
|
+
break;
|
|
63
227
|
}
|
|
64
228
|
}
|
|
65
|
-
// Bottom border
|
|
66
|
-
lines.push(border);
|
|
67
|
-
// Render all lines
|
|
68
|
-
terminal.write(lines.join('\n'));
|
|
69
|
-
return lines.length;
|
|
70
229
|
}
|
|
71
230
|
// =============================================================================
|
|
72
|
-
//
|
|
231
|
+
// Export Function (Backward Compatible)
|
|
73
232
|
// =============================================================================
|
|
74
233
|
/**
|
|
75
234
|
* Show the keys management overlay
|
|
76
235
|
*/
|
|
77
236
|
export async function showKeysOverlay() {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
providers: getAllProviderStatuses(),
|
|
85
|
-
};
|
|
86
|
-
let lineCount = 0;
|
|
87
|
-
let changed = false;
|
|
88
|
-
// Initial render
|
|
89
|
-
lineCount = render(state, lineCount);
|
|
90
|
-
return new Promise((resolve) => {
|
|
91
|
-
const stdin = process.stdin;
|
|
92
|
-
stdin.setRawMode(true);
|
|
93
|
-
stdin.resume();
|
|
94
|
-
const cleanup = () => {
|
|
95
|
-
stdin.removeListener('data', handleKey);
|
|
96
|
-
stdin.setRawMode(false);
|
|
97
|
-
stdin.pause();
|
|
98
|
-
// Clear the overlay
|
|
99
|
-
terminal.clearLinesAbove(lineCount);
|
|
100
|
-
};
|
|
101
|
-
const handleKey = (data) => {
|
|
102
|
-
const key = data.toString();
|
|
103
|
-
if (state.mode === 'list') {
|
|
104
|
-
// List mode key handling
|
|
105
|
-
switch (key) {
|
|
106
|
-
case '\x1B': // Escape
|
|
107
|
-
case 'q':
|
|
108
|
-
cleanup();
|
|
109
|
-
resolve({ changed });
|
|
110
|
-
return;
|
|
111
|
-
case '\x1B[A': // Up arrow
|
|
112
|
-
if (state.selectedIndex > 0) {
|
|
113
|
-
state.selectedIndex--;
|
|
114
|
-
}
|
|
115
|
-
break;
|
|
116
|
-
case '\x1B[B': // Down arrow
|
|
117
|
-
if (state.selectedIndex < state.providers.length - 1) {
|
|
118
|
-
state.selectedIndex++;
|
|
119
|
-
}
|
|
120
|
-
break;
|
|
121
|
-
case '\r': // Enter - edit selected
|
|
122
|
-
case '\n': {
|
|
123
|
-
const selected = state.providers[state.selectedIndex];
|
|
124
|
-
if (selected.provider !== 'ollama') {
|
|
125
|
-
state.mode = 'input';
|
|
126
|
-
state.editingProvider = selected.provider;
|
|
127
|
-
state.inputBuffer = '';
|
|
128
|
-
}
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
case 'd':
|
|
132
|
-
case 'D': {
|
|
133
|
-
// Delete key for selected provider
|
|
134
|
-
const selected = state.providers[state.selectedIndex];
|
|
135
|
-
if (selected.provider !== 'ollama' && selected.hasKey && !selected.fromEnv) {
|
|
136
|
-
deleteApiKey(selected.provider);
|
|
137
|
-
state.providers = getAllProviderStatuses();
|
|
138
|
-
changed = true;
|
|
139
|
-
}
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
// Input mode key handling
|
|
146
|
-
if (key === '\x1B') {
|
|
147
|
-
// Escape - cancel input
|
|
148
|
-
state.mode = 'list';
|
|
149
|
-
state.editingProvider = null;
|
|
150
|
-
state.inputBuffer = '';
|
|
151
|
-
}
|
|
152
|
-
else if (key === '\r' || key === '\n') {
|
|
153
|
-
// Enter - save key
|
|
154
|
-
if (state.inputBuffer.trim() && state.editingProvider) {
|
|
155
|
-
setApiKey(state.editingProvider, state.inputBuffer.trim());
|
|
156
|
-
state.providers = getAllProviderStatuses();
|
|
157
|
-
changed = true;
|
|
158
|
-
}
|
|
159
|
-
state.mode = 'list';
|
|
160
|
-
state.editingProvider = null;
|
|
161
|
-
state.inputBuffer = '';
|
|
162
|
-
}
|
|
163
|
-
else if (key === '\x7F' || key === '\b') {
|
|
164
|
-
// Backspace
|
|
165
|
-
state.inputBuffer = state.inputBuffer.slice(0, -1);
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
// Handle paste (multiple characters) or single printable character
|
|
169
|
-
// Filter to only printable ASCII characters
|
|
170
|
-
const printable = key.split('').filter(c => c >= ' ' && c <= '~').join('');
|
|
171
|
-
if (printable.length > 0) {
|
|
172
|
-
state.inputBuffer += printable;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
// Re-render
|
|
177
|
-
lineCount = render(state, lineCount);
|
|
178
|
-
};
|
|
179
|
-
stdin.on('data', handleKey);
|
|
180
|
-
});
|
|
237
|
+
debugLog('showKeysOverlay', 'creating overlay');
|
|
238
|
+
const overlay = new KeysOverlay();
|
|
239
|
+
debugLog('showKeysOverlay', 'calling show()');
|
|
240
|
+
const result = await overlay.show();
|
|
241
|
+
debugLog('showKeysOverlay', 'show() returned');
|
|
242
|
+
return result;
|
|
181
243
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Line Utilities
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for ANSI stripping, visible length calculation,
|
|
5
|
+
* and physical line counting. Prevents inconsistencies between components.
|
|
6
|
+
*
|
|
7
|
+
* Key insight: Different ANSI patterns were causing height miscalculations:
|
|
8
|
+
* - Footer used: /\x1b\[[0-9;]*m/g (only matches 'm' terminator)
|
|
9
|
+
* - InputPrompt used: /\x1B\[[0-9;]*[a-zA-Z]/g (matches all terminators)
|
|
10
|
+
*
|
|
11
|
+
* The correct pattern matches all SGR (Select Graphic Rendition) sequences
|
|
12
|
+
* including colors, styles, cursor movements, etc.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Unified ANSI escape sequence pattern.
|
|
16
|
+
* Matches all SGR sequences: CSI (Control Sequence Introducer) followed by
|
|
17
|
+
* parameters and a terminating letter (m for colors, but also J, K, H, etc.)
|
|
18
|
+
*/
|
|
19
|
+
export declare const ANSI_PATTERN: RegExp;
|
|
20
|
+
/**
|
|
21
|
+
* Strip all ANSI escape codes from a string.
|
|
22
|
+
* Returns the visible text only.
|
|
23
|
+
*/
|
|
24
|
+
export declare function stripAnsi(str: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Get the visible length of a string (excluding ANSI codes).
|
|
27
|
+
* This is what the terminal actually displays.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getVisibleLength(str: string): number;
|
|
30
|
+
/**
|
|
31
|
+
* Calculate how many physical terminal lines a single logical line occupies.
|
|
32
|
+
* Accounts for wrapping when content exceeds terminal width.
|
|
33
|
+
*
|
|
34
|
+
* @param line - The line content (may contain ANSI codes)
|
|
35
|
+
* @param termWidth - Terminal width in columns
|
|
36
|
+
* @returns Number of physical lines (always >= 1)
|
|
37
|
+
*
|
|
38
|
+
* Example:
|
|
39
|
+
* termWidth = 80
|
|
40
|
+
* line = "hello" (5 chars) → 1 line
|
|
41
|
+
* line = "x".repeat(100) (100 chars) → 2 lines (wraps at 80)
|
|
42
|
+
* line = "" (0 chars) → 1 line (empty still takes a line)
|
|
43
|
+
*/
|
|
44
|
+
export declare function getPhysicalLineCount(line: string, termWidth: number): number;
|
|
45
|
+
/**
|
|
46
|
+
* Calculate how many physical terminal lines a line takes when starting
|
|
47
|
+
* at a specific column offset (e.g., after a prompt prefix).
|
|
48
|
+
*
|
|
49
|
+
* @param text - The text content (may contain ANSI codes)
|
|
50
|
+
* @param startCol - Starting column (0-indexed, e.g., prompt length)
|
|
51
|
+
* @param termWidth - Terminal width in columns
|
|
52
|
+
* @returns Number of physical lines
|
|
53
|
+
*
|
|
54
|
+
* Example:
|
|
55
|
+
* termWidth = 80, startCol = 10 (after "compilr> ")
|
|
56
|
+
* text = "hello" (5 chars) → 1 line (10 + 5 = 15 < 80)
|
|
57
|
+
* text = "x".repeat(75) → 2 lines (10 + 75 = 85, wraps)
|
|
58
|
+
*/
|
|
59
|
+
export declare function getPhysicalLineCountWithOffset(text: string, startCol: number, termWidth: number): number;
|
|
60
|
+
/**
|
|
61
|
+
* Calculate total physical lines for an array of logical lines.
|
|
62
|
+
*
|
|
63
|
+
* @param lines - Array of line strings (may contain ANSI codes)
|
|
64
|
+
* @param termWidth - Terminal width in columns
|
|
65
|
+
* @returns Total number of physical lines
|
|
66
|
+
*/
|
|
67
|
+
export declare function getTotalPhysicalLines(lines: string[], termWidth: number): number;
|
|
68
|
+
/**
|
|
69
|
+
* Get detailed height information for each line.
|
|
70
|
+
* Useful for debugging and understanding exactly how lines wrap.
|
|
71
|
+
*
|
|
72
|
+
* @param lines - Array of line strings
|
|
73
|
+
* @param termWidth - Terminal width
|
|
74
|
+
* @returns Array of { visibleLength, physicalLines } for each line
|
|
75
|
+
*/
|
|
76
|
+
export declare function getLineHeightDetails(lines: string[], termWidth: number): Array<{
|
|
77
|
+
visibleLength: number;
|
|
78
|
+
physicalLines: number;
|
|
79
|
+
}>;
|
|
80
|
+
/**
|
|
81
|
+
* Truncate a string to a maximum visible length, preserving ANSI codes.
|
|
82
|
+
* Adds ellipsis if truncated.
|
|
83
|
+
*
|
|
84
|
+
* @param str - String to truncate (may contain ANSI codes)
|
|
85
|
+
* @param maxLen - Maximum visible length
|
|
86
|
+
* @returns Truncated string with ellipsis if needed
|
|
87
|
+
*/
|
|
88
|
+
export declare function truncateWithAnsi(str: string, maxLen: number): string;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Line Utilities
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for ANSI stripping, visible length calculation,
|
|
5
|
+
* and physical line counting. Prevents inconsistencies between components.
|
|
6
|
+
*
|
|
7
|
+
* Key insight: Different ANSI patterns were causing height miscalculations:
|
|
8
|
+
* - Footer used: /\x1b\[[0-9;]*m/g (only matches 'm' terminator)
|
|
9
|
+
* - InputPrompt used: /\x1B\[[0-9;]*[a-zA-Z]/g (matches all terminators)
|
|
10
|
+
*
|
|
11
|
+
* The correct pattern matches all SGR (Select Graphic Rendition) sequences
|
|
12
|
+
* including colors, styles, cursor movements, etc.
|
|
13
|
+
*/
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// ANSI Handling
|
|
16
|
+
// =============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Unified ANSI escape sequence pattern.
|
|
19
|
+
* Matches all SGR sequences: CSI (Control Sequence Introducer) followed by
|
|
20
|
+
* parameters and a terminating letter (m for colors, but also J, K, H, etc.)
|
|
21
|
+
*/
|
|
22
|
+
// eslint-disable-next-line no-control-regex
|
|
23
|
+
export const ANSI_PATTERN = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
24
|
+
/**
|
|
25
|
+
* Strip all ANSI escape codes from a string.
|
|
26
|
+
* Returns the visible text only.
|
|
27
|
+
*/
|
|
28
|
+
export function stripAnsi(str) {
|
|
29
|
+
return str.replace(ANSI_PATTERN, '');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get the visible length of a string (excluding ANSI codes).
|
|
33
|
+
* This is what the terminal actually displays.
|
|
34
|
+
*/
|
|
35
|
+
export function getVisibleLength(str) {
|
|
36
|
+
return stripAnsi(str).length;
|
|
37
|
+
}
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Physical Line Calculation
|
|
40
|
+
// =============================================================================
|
|
41
|
+
/**
|
|
42
|
+
* Calculate how many physical terminal lines a single logical line occupies.
|
|
43
|
+
* Accounts for wrapping when content exceeds terminal width.
|
|
44
|
+
*
|
|
45
|
+
* @param line - The line content (may contain ANSI codes)
|
|
46
|
+
* @param termWidth - Terminal width in columns
|
|
47
|
+
* @returns Number of physical lines (always >= 1)
|
|
48
|
+
*
|
|
49
|
+
* Example:
|
|
50
|
+
* termWidth = 80
|
|
51
|
+
* line = "hello" (5 chars) → 1 line
|
|
52
|
+
* line = "x".repeat(100) (100 chars) → 2 lines (wraps at 80)
|
|
53
|
+
* line = "" (0 chars) → 1 line (empty still takes a line)
|
|
54
|
+
*/
|
|
55
|
+
export function getPhysicalLineCount(line, termWidth) {
|
|
56
|
+
const visibleLength = getVisibleLength(line);
|
|
57
|
+
if (visibleLength === 0)
|
|
58
|
+
return 1; // Empty line still takes 1 row
|
|
59
|
+
return Math.ceil(visibleLength / termWidth);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Calculate how many physical terminal lines a line takes when starting
|
|
63
|
+
* at a specific column offset (e.g., after a prompt prefix).
|
|
64
|
+
*
|
|
65
|
+
* @param text - The text content (may contain ANSI codes)
|
|
66
|
+
* @param startCol - Starting column (0-indexed, e.g., prompt length)
|
|
67
|
+
* @param termWidth - Terminal width in columns
|
|
68
|
+
* @returns Number of physical lines
|
|
69
|
+
*
|
|
70
|
+
* Example:
|
|
71
|
+
* termWidth = 80, startCol = 10 (after "compilr> ")
|
|
72
|
+
* text = "hello" (5 chars) → 1 line (10 + 5 = 15 < 80)
|
|
73
|
+
* text = "x".repeat(75) → 2 lines (10 + 75 = 85, wraps)
|
|
74
|
+
*/
|
|
75
|
+
export function getPhysicalLineCountWithOffset(text, startCol, termWidth) {
|
|
76
|
+
const visibleLength = getVisibleLength(text);
|
|
77
|
+
if (visibleLength === 0)
|
|
78
|
+
return 1;
|
|
79
|
+
const totalLen = startCol + visibleLength;
|
|
80
|
+
return Math.ceil(totalLen / termWidth) || 1;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Calculate total physical lines for an array of logical lines.
|
|
84
|
+
*
|
|
85
|
+
* @param lines - Array of line strings (may contain ANSI codes)
|
|
86
|
+
* @param termWidth - Terminal width in columns
|
|
87
|
+
* @returns Total number of physical lines
|
|
88
|
+
*/
|
|
89
|
+
export function getTotalPhysicalLines(lines, termWidth) {
|
|
90
|
+
let total = 0;
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
total += getPhysicalLineCount(line, termWidth);
|
|
93
|
+
}
|
|
94
|
+
return total;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Get detailed height information for each line.
|
|
98
|
+
* Useful for debugging and understanding exactly how lines wrap.
|
|
99
|
+
*
|
|
100
|
+
* @param lines - Array of line strings
|
|
101
|
+
* @param termWidth - Terminal width
|
|
102
|
+
* @returns Array of { visibleLength, physicalLines } for each line
|
|
103
|
+
*/
|
|
104
|
+
export function getLineHeightDetails(lines, termWidth) {
|
|
105
|
+
return lines.map(line => {
|
|
106
|
+
const visibleLength = getVisibleLength(line);
|
|
107
|
+
const physicalLines = visibleLength === 0 ? 1 : Math.ceil(visibleLength / termWidth);
|
|
108
|
+
return { visibleLength, physicalLines };
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// =============================================================================
|
|
112
|
+
// Truncation
|
|
113
|
+
// =============================================================================
|
|
114
|
+
/**
|
|
115
|
+
* Truncate a string to a maximum visible length, preserving ANSI codes.
|
|
116
|
+
* Adds ellipsis if truncated.
|
|
117
|
+
*
|
|
118
|
+
* @param str - String to truncate (may contain ANSI codes)
|
|
119
|
+
* @param maxLen - Maximum visible length
|
|
120
|
+
* @returns Truncated string with ellipsis if needed
|
|
121
|
+
*/
|
|
122
|
+
export function truncateWithAnsi(str, maxLen) {
|
|
123
|
+
const visible = stripAnsi(str);
|
|
124
|
+
if (visible.length <= maxLen)
|
|
125
|
+
return str;
|
|
126
|
+
// We need to find the position in the original string that corresponds
|
|
127
|
+
// to maxLen-1 visible characters (leaving room for ellipsis)
|
|
128
|
+
let visibleCount = 0;
|
|
129
|
+
let i = 0;
|
|
130
|
+
while (i < str.length && visibleCount < maxLen - 1) {
|
|
131
|
+
// Check if we're at an ANSI escape sequence
|
|
132
|
+
if (str[i] === '\x1b' && str[i + 1] === '[') {
|
|
133
|
+
// Skip the entire ANSI sequence
|
|
134
|
+
// eslint-disable-next-line no-control-regex
|
|
135
|
+
const match = str.slice(i).match(/^\x1b\[[0-9;]*[A-Za-z]/);
|
|
136
|
+
if (match) {
|
|
137
|
+
i += match[0].length;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
visibleCount++;
|
|
142
|
+
i++;
|
|
143
|
+
}
|
|
144
|
+
// Include any trailing ANSI reset codes
|
|
145
|
+
const remainder = str.slice(i);
|
|
146
|
+
// eslint-disable-next-line no-control-regex
|
|
147
|
+
const resetMatch = remainder.match(/^(\x1b\[[0-9;]*m)*/);
|
|
148
|
+
const resets = resetMatch ? resetMatch[0] : '';
|
|
149
|
+
return str.slice(0, i) + '…' + resets;
|
|
150
|
+
}
|