@apholdings/jensen-code 0.0.3 → 0.0.5
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/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +6 -6
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts +6 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +32 -25
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +25 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +1 -1
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +4 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +25 -11
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +1 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +5 -0
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +0 -2
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +8 -146
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/header.d.ts +9 -3
- package/dist/modes/interactive/components/header.d.ts.map +1 -1
- package/dist/modes/interactive/components/header.js +125 -196
- package/dist/modes/interactive/components/header.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +1 -2
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +23 -4
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +657 -243
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +2 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +8 -4
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +2 -2
- package/dist/utils/tools-manager.js.map +1 -1
- package/examples/extensions/osgrep.ts +643 -0
- package/examples/extensions/subagent/agents.ts +150 -38
- package/examples/extensions/subagent/index.ts +634 -514
- package/package.json +4 -3
- package/examples/README.md +0 -25
- package/examples/extensions/README.md +0 -206
- package/examples/extensions/antigravity-image-gen.ts +0 -416
- package/examples/extensions/auto-commit-on-exit.ts +0 -50
- package/examples/extensions/bash-spawn-hook.ts +0 -31
- package/examples/extensions/bookmark.ts +0 -51
- package/examples/extensions/built-in-tool-renderer.ts +0 -247
- package/examples/extensions/claude-rules.ts +0 -87
- package/examples/extensions/commands.ts +0 -73
- package/examples/extensions/confirm-destructive.ts +0 -60
- package/examples/extensions/custom-compaction.ts +0 -115
- package/examples/extensions/custom-footer.ts +0 -65
- package/examples/extensions/custom-header.ts +0 -74
- package/examples/extensions/custom-provider-anthropic/index.ts +0 -605
- package/examples/extensions/custom-provider-anthropic/package-lock.json +0 -24
- package/examples/extensions/custom-provider-anthropic/package.json +0 -19
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +0 -350
- package/examples/extensions/custom-provider-gitlab-duo/package.json +0 -16
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +0 -82
- package/examples/extensions/custom-provider-qwen-cli/index.ts +0 -346
- package/examples/extensions/custom-provider-qwen-cli/package.json +0 -16
- package/examples/extensions/dirty-repo-guard.ts +0 -57
- package/examples/extensions/doom-overlay/README.md +0 -46
- package/examples/extensions/doom-overlay/doom/build/doom.js +0 -21
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +0 -152
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +0 -72
- package/examples/extensions/doom-overlay/doom-component.ts +0 -132
- package/examples/extensions/doom-overlay/doom-engine.ts +0 -173
- package/examples/extensions/doom-overlay/doom-keys.ts +0 -104
- package/examples/extensions/doom-overlay/index.ts +0 -75
- package/examples/extensions/doom-overlay/wad-finder.ts +0 -51
- package/examples/extensions/dynamic-resources/SKILL.md +0 -8
- package/examples/extensions/dynamic-resources/dynamic.json +0 -79
- package/examples/extensions/dynamic-resources/dynamic.md +0 -5
- package/examples/extensions/dynamic-resources/index.ts +0 -16
- package/examples/extensions/dynamic-tools.ts +0 -75
- package/examples/extensions/event-bus.ts +0 -44
- package/examples/extensions/file-trigger.ts +0 -42
- package/examples/extensions/git-checkpoint.ts +0 -54
- package/examples/extensions/handoff.ts +0 -151
- package/examples/extensions/hello.ts +0 -26
- package/examples/extensions/inline-bash.ts +0 -95
- package/examples/extensions/input-transform.ts +0 -44
- package/examples/extensions/interactive-shell.ts +0 -197
- package/examples/extensions/mac-system-theme.ts +0 -48
- package/examples/extensions/message-renderer.ts +0 -60
- package/examples/extensions/minimal-mode.ts +0 -427
- package/examples/extensions/modal-editor.ts +0 -86
- package/examples/extensions/model-status.ts +0 -32
- package/examples/extensions/notify.ts +0 -56
- package/examples/extensions/overlay-qa-tests.ts +0 -1349
- package/examples/extensions/overlay-test.ts +0 -151
- package/examples/extensions/permission-gate.ts +0 -35
- package/examples/extensions/pirate.ts +0 -48
- package/examples/extensions/plan-mode/README.md +0 -65
- package/examples/extensions/plan-mode/index.ts +0 -341
- package/examples/extensions/plan-mode/utils.ts +0 -168
- package/examples/extensions/preset.ts +0 -399
- package/examples/extensions/protected-paths.ts +0 -31
- package/examples/extensions/provider-payload.ts +0 -15
- package/examples/extensions/qna.ts +0 -120
- package/examples/extensions/question.ts +0 -265
- package/examples/extensions/questionnaire.ts +0 -428
- package/examples/extensions/rainbow-editor.ts +0 -89
- package/examples/extensions/reload-runtime.ts +0 -38
- package/examples/extensions/rpc-demo.ts +0 -125
- package/examples/extensions/sandbox/index.ts +0 -319
- package/examples/extensions/sandbox/package-lock.json +0 -92
- package/examples/extensions/sandbox/package.json +0 -19
- package/examples/extensions/send-user-message.ts +0 -98
- package/examples/extensions/session-name.ts +0 -28
- package/examples/extensions/shutdown-command.ts +0 -64
- package/examples/extensions/snake.ts +0 -344
- package/examples/extensions/space-invaders.ts +0 -561
- package/examples/extensions/ssh.ts +0 -221
- package/examples/extensions/status-line.ts +0 -41
- package/examples/extensions/subagent/README.md +0 -172
- package/examples/extensions/subagent/agents/planner.md +0 -37
- package/examples/extensions/subagent/agents/reviewer.md +0 -35
- package/examples/extensions/subagent/agents/scout.md +0 -50
- package/examples/extensions/subagent/agents/worker.md +0 -24
- package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
- package/examples/extensions/subagent/prompts/implement.md +0 -10
- package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
- package/examples/extensions/summarize.ts +0 -196
- package/examples/extensions/system-prompt-header.ts +0 -18
- package/examples/extensions/timed-confirm.ts +0 -71
- package/examples/extensions/titlebar-spinner.ts +0 -59
- package/examples/extensions/todo.ts +0 -300
- package/examples/extensions/tool-override.ts +0 -144
- package/examples/extensions/tools.ts +0 -147
- package/examples/extensions/trigger-compact.ts +0 -41
- package/examples/extensions/truncated-tool.ts +0 -193
- package/examples/extensions/widget-placement.ts +0 -18
- package/examples/extensions/with-deps/index.ts +0 -33
- package/examples/extensions/with-deps/package-lock.json +0 -31
- package/examples/extensions/with-deps/package.json +0 -22
- package/examples/rpc-extension-ui.ts +0 -632
- package/examples/sdk/01-minimal.ts +0 -23
- package/examples/sdk/02-custom-model.ts +0 -50
- package/examples/sdk/03-custom-prompt.ts +0 -56
- package/examples/sdk/04-skills.ts +0 -47
- package/examples/sdk/05-tools.ts +0 -57
- package/examples/sdk/06-extensions.ts +0 -89
- package/examples/sdk/07-context-files.ts +0 -41
- package/examples/sdk/08-prompt-templates.ts +0 -48
- package/examples/sdk/09-api-keys-and-oauth.ts +0 -49
- package/examples/sdk/10-settings.ts +0 -52
- package/examples/sdk/11-sessions.ts +0 -49
- package/examples/sdk/12-full-control.ts +0 -83
- package/examples/sdk/README.md +0 -145
|
@@ -16,6 +16,7 @@ export declare class CustomEditor extends Editor {
|
|
|
16
16
|
* Register a handler for an app action.
|
|
17
17
|
*/
|
|
18
18
|
onAction(action: AppAction, handler: () => void): void;
|
|
19
|
+
clearHistory(): void;
|
|
19
20
|
handleInput(data: string): void;
|
|
20
21
|
}
|
|
21
22
|
//# sourceMappingURL=custom-editor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAChG,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAElF;;GAEG;AACH,qBAAa,YAAa,SAAQ,MAAM;IACvC,OAAO,CAAC,WAAW,CAAqB;IACjC,cAAc,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC,CAAa;IAGvD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,2EAA2E;IACpE,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAEvD,YAAY,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,aAAa,EAGjG;IAED;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAErD;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAiD9B;CACD","sourcesContent":["import { Editor, type EditorOptions, type EditorTheme, type TUI } from \"@apholdings/jensen-tui\";\nimport type { AppAction, KeybindingsManager } from \"../../../core/keybindings.js\";\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tpublic actionHandlers: Map<AppAction, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: EditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppAction, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for paste image keybinding\n\t\tif (this.keybindings.matches(data, \"pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"interrupt\" && action !== \"exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"custom-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAChG,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAElF;;GAEG;AACH,qBAAa,YAAa,SAAQ,MAAM;IACvC,OAAO,CAAC,WAAW,CAAqB;IACjC,cAAc,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC,CAAa;IAGvD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,2EAA2E;IACpE,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAEvD,YAAY,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,aAAa,EAGjG;IAED;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAErD;IAED,YAAY,IAAI,IAAI,CAOnB;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAiD9B;CACD","sourcesContent":["import { Editor, type EditorOptions, type EditorTheme, type TUI } from \"@apholdings/jensen-tui\";\nimport type { AppAction, KeybindingsManager } from \"../../../core/keybindings.js\";\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tpublic actionHandlers: Map<AppAction, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: EditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppAction, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\tclearHistory(): void {\n\t\tconst editorState = this as unknown as {\n\t\t\thistory: string[];\n\t\t\thistoryIndex: number;\n\t\t};\n\t\teditorState.history = [];\n\t\teditorState.historyIndex = -1;\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for paste image keybinding\n\t\tif (this.keybindings.matches(data, \"pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"interrupt\" && action !== \"exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
|
|
@@ -21,6 +21,11 @@ export class CustomEditor extends Editor {
|
|
|
21
21
|
onAction(action, handler) {
|
|
22
22
|
this.actionHandlers.set(action, handler);
|
|
23
23
|
}
|
|
24
|
+
clearHistory() {
|
|
25
|
+
const editorState = this;
|
|
26
|
+
editorState.history = [];
|
|
27
|
+
editorState.historyIndex = -1;
|
|
28
|
+
}
|
|
24
29
|
handleInput(data) {
|
|
25
30
|
// Check extension-registered shortcuts first
|
|
26
31
|
if (this.onExtensionShortcut?.(data)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-editor.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAkD,MAAM,wBAAwB,CAAC;AAGhG;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,MAAM;IAC/B,WAAW,CAAqB;IACjC,cAAc,GAA+B,IAAI,GAAG,EAAE,CAAC;IAE9D,oDAAoD;IAC7C,QAAQ,CAAc;IACtB,OAAO,CAAc;IACrB,YAAY,CAAc;IACjC,2EAA2E;IACpE,mBAAmB,CAA6B;IAEvD,YAAY,GAAQ,EAAE,KAAkB,EAAE,WAA+B,EAAE,OAAuB,EAAE;QACnG,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IAAA,CAC/B;IAED;;OAEG;IACH,QAAQ,CAAC,MAAiB,EAAE,OAAmB,EAAQ;QACtD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAA,CACzC;IAED,WAAW,CAAC,IAAY,EAAQ;QAC/B,6CAA6C;QAC7C,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,OAAO;QACR,CAAC;QAED,mCAAmC;QACnC,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC;YAClD,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACtB,OAAO;QACR,CAAC;QAED,8BAA8B;QAE9B,wDAAwD;QACxD,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;gBACnC,4DAA4D;gBAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBACtE,IAAI,OAAO,EAAE,CAAC;oBACb,OAAO,EAAE,CAAC;oBACV,OAAO;gBACR,CAAC;YACF,CAAC;YACD,yDAAyD;YACzD,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YACxB,OAAO;QACR,CAAC;QAED,4CAA4C;QAC5C,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;YAC5C,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACjC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAChE,IAAI,OAAO;oBAAE,OAAO,EAAE,CAAC;gBACvB,OAAO;YACR,CAAC;YACD,yEAAyE;QAC1E,CAAC;QAED,8BAA8B;QAC9B,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACrD,IAAI,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;gBAC3F,OAAO,EAAE,CAAC;gBACV,OAAO;YACR,CAAC;QACF,CAAC;QAED,qCAAqC;QACrC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAAA,CACxB;CACD","sourcesContent":["import { Editor, type EditorOptions, type EditorTheme, type TUI } from \"@apholdings/jensen-tui\";\nimport type { AppAction, KeybindingsManager } from \"../../../core/keybindings.js\";\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tpublic actionHandlers: Map<AppAction, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: EditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppAction, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for paste image keybinding\n\t\tif (this.keybindings.matches(data, \"pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"interrupt\" && action !== \"exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"custom-editor.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAkD,MAAM,wBAAwB,CAAC;AAGhG;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,MAAM;IAC/B,WAAW,CAAqB;IACjC,cAAc,GAA+B,IAAI,GAAG,EAAE,CAAC;IAE9D,oDAAoD;IAC7C,QAAQ,CAAc;IACtB,OAAO,CAAc;IACrB,YAAY,CAAc;IACjC,2EAA2E;IACpE,mBAAmB,CAA6B;IAEvD,YAAY,GAAQ,EAAE,KAAkB,EAAE,WAA+B,EAAE,OAAuB,EAAE;QACnG,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IAAA,CAC/B;IAED;;OAEG;IACH,QAAQ,CAAC,MAAiB,EAAE,OAAmB,EAAQ;QACtD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAA,CACzC;IAED,YAAY,GAAS;QACpB,MAAM,WAAW,GAAG,IAGnB,CAAC;QACF,WAAW,CAAC,OAAO,GAAG,EAAE,CAAC;QACzB,WAAW,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC;IAAA,CAC9B;IAED,WAAW,CAAC,IAAY,EAAQ;QAC/B,6CAA6C;QAC7C,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,OAAO;QACR,CAAC;QAED,mCAAmC;QACnC,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC;YAClD,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;YACtB,OAAO;QACR,CAAC;QAED,8BAA8B;QAE9B,wDAAwD;QACxD,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,EAAE,CAAC;gBACnC,4DAA4D;gBAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBACtE,IAAI,OAAO,EAAE,CAAC;oBACb,OAAO,EAAE,CAAC;oBACV,OAAO;gBACR,CAAC;YACF,CAAC;YACD,yDAAyD;YACzD,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YACxB,OAAO;QACR,CAAC;QAED,4CAA4C;QAC5C,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;YAC5C,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACjC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAChE,IAAI,OAAO;oBAAE,OAAO,EAAE,CAAC;gBACvB,OAAO;YACR,CAAC;YACD,yEAAyE;QAC1E,CAAC;QAED,8BAA8B;QAC9B,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACrD,IAAI,MAAM,KAAK,WAAW,IAAI,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;gBAC3F,OAAO,EAAE,CAAC;gBACV,OAAO;YACR,CAAC;QACF,CAAC;QAED,qCAAqC;QACrC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAAA,CACxB;CACD","sourcesContent":["import { Editor, type EditorOptions, type EditorTheme, type TUI } from \"@apholdings/jensen-tui\";\nimport type { AppAction, KeybindingsManager } from \"../../../core/keybindings.js\";\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tpublic actionHandlers: Map<AppAction, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: EditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppAction, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\tclearHistory(): void {\n\t\tconst editorState = this as unknown as {\n\t\t\thistory: string[];\n\t\t\thistoryIndex: number;\n\t\t};\n\t\teditorState.history = [];\n\t\teditorState.historyIndex = -1;\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for paste image keybinding\n\t\tif (this.keybindings.matches(data, \"pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"interrupt\" && action !== \"exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
|
|
@@ -5,12 +5,10 @@ export declare class FooterComponent implements Component {
|
|
|
5
5
|
private session;
|
|
6
6
|
private footerData;
|
|
7
7
|
private autoCompactEnabled;
|
|
8
|
-
private gitCache;
|
|
9
8
|
constructor(session: AgentSession, footerData: ReadonlyFooterDataProvider);
|
|
10
9
|
setAutoCompactEnabled(enabled: boolean): void;
|
|
11
10
|
invalidate(): void;
|
|
12
11
|
dispose(): void;
|
|
13
|
-
private getGitInfo;
|
|
14
12
|
private getContextTokens;
|
|
15
13
|
private getContextPercentValue;
|
|
16
14
|
private getContextPercentDisplay;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,wBAAwB,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAoBxF,qBAAa,eAAgB,YAAW,SAAS;IAI/C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IAJnB,OAAO,CAAC,kBAAkB,CAAQ;IAElC,YACS,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,0BAA0B,EAC3C;IAEJ,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED,UAAU,IAAI,IAAI,CAAG;IAErB,OAAO,IAAI,IAAI,CAAG;IAElB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,sBAAsB;IAM9B,OAAO,CAAC,wBAAwB;IAMhC,OAAO,CAAC,sBAAsB;IAQ9B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA0B9B;CACD","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@apholdings/jensen-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\nfunction formatTokenCount(value: number): string {\n\tif (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;\n\tif (value >= 1_000) return `${(value / 1_000).toFixed(1)}k`;\n\treturn String(value);\n}\n\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\tinvalidate(): void {}\n\n\tdispose(): void {}\n\n\tprivate getContextTokens(): string {\n\t\tconst usage = this.session.getContextUsage();\n\t\tif (!usage || usage.tokens == null) return \"--\";\n\n\t\tconst contextWindow =\n\t\t\ttypeof usage.contextWindow === \"number\"\n\t\t\t\t? usage.contextWindow\n\t\t\t\t: (this.session.state.model?.contextWindow ?? null);\n\n\t\tif (contextWindow == null) {\n\t\t\treturn formatTokenCount(usage.tokens);\n\t\t}\n\n\t\treturn `${formatTokenCount(usage.tokens)}/${formatTokenCount(contextWindow)}`;\n\t}\n\n\tprivate getContextPercentValue(): number | null {\n\t\tconst usage = this.session.getContextUsage();\n\t\tif (!usage || usage.percent == null) return null;\n\t\treturn usage.percent;\n\t}\n\n\tprivate getContextPercentDisplay(): string {\n\t\tconst percent = this.getContextPercentValue();\n\t\tif (percent == null) return \"--\";\n\t\treturn `${percent.toFixed(1)}%${this.autoCompactEnabled ? \" (auto)\" : \"\"}`;\n\t}\n\n\tprivate getContextPercentColor(): \"success\" | \"warning\" | \"error\" {\n\t\tconst percent = this.getContextPercentValue();\n\t\tif (percent == null) return \"success\";\n\t\tif (percent > 90) return \"error\";\n\t\tif (percent > 70) return \"warning\";\n\t\treturn \"success\";\n\t}\n\n\trender(width: number): string[] {\n\t\tif (width <= 0) return [\"\"];\n\n\t\tconst separator = theme.fg(\"dim\", \" · \");\n\t\tconst ellipsis = theme.fg(\"dim\", \"...\");\n\n\t\tconst right = [\n\t\t\ttheme.fg(\"dim\", \"tok \") + theme.fg(\"success\", this.getContextTokens()),\n\t\t\ttheme.fg(\"dim\", \"ctx \") + theme.fg(this.getContextPercentColor(), this.getContextPercentDisplay()),\n\t\t].join(separator);\n\n\t\tconst gap = Math.max(0, width - visibleWidth(right) - 1);\n\t\tconst footerLine = truncateToWidth(`${\" \".repeat(gap)} ${right}`, width, ellipsis);\n\t\tconst lines = [footerLine];\n\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\tlines.push(truncateToWidth(statusLine, width, ellipsis));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
1
|
import { truncateToWidth, visibleWidth } from "@apholdings/jensen-tui";
|
|
5
2
|
import { theme } from "../theme/theme.js";
|
|
6
|
-
const GIT_CACHE_TTL_MS = 5000;
|
|
7
3
|
/**
|
|
8
4
|
* Sanitize text for display in a single-line status.
|
|
9
5
|
* Removes newlines, tabs, carriage returns, and other control characters.
|
|
@@ -21,44 +17,10 @@ function formatTokenCount(value) {
|
|
|
21
17
|
return `${(value / 1_000).toFixed(1)}k`;
|
|
22
18
|
return String(value);
|
|
23
19
|
}
|
|
24
|
-
function shortenPath(input) {
|
|
25
|
-
const home = os.homedir();
|
|
26
|
-
let p = input;
|
|
27
|
-
if (home && p.startsWith(home)) {
|
|
28
|
-
p = `~${p.slice(home.length)}`;
|
|
29
|
-
}
|
|
30
|
-
if (p === "/")
|
|
31
|
-
return "/";
|
|
32
|
-
const parts = p.split("/").filter(Boolean);
|
|
33
|
-
if (parts.length <= 4)
|
|
34
|
-
return p;
|
|
35
|
-
const tail = parts.slice(-3).join("/");
|
|
36
|
-
return p.startsWith("~") ? `~/…/${tail}` : `/…/${tail}`;
|
|
37
|
-
}
|
|
38
|
-
function shortenPathForWidth(input, maxWidth) {
|
|
39
|
-
if (maxWidth <= 0)
|
|
40
|
-
return "";
|
|
41
|
-
const base = shortenPath(input);
|
|
42
|
-
if (visibleWidth(base) <= maxWidth)
|
|
43
|
-
return base;
|
|
44
|
-
const root = base.startsWith("~") ? "~/" : "/";
|
|
45
|
-
const parts = base.replace(/^~?\//, "").split("/").filter(Boolean);
|
|
46
|
-
if (parts.length === 0)
|
|
47
|
-
return truncateToWidth(base, maxWidth, "...");
|
|
48
|
-
const compactParts = parts.map((part, i) => (i === parts.length - 1 ? part : (part[0] ?? part)));
|
|
49
|
-
const compact = `${root}${compactParts.join("/")}`;
|
|
50
|
-
if (visibleWidth(compact) <= maxWidth)
|
|
51
|
-
return compact;
|
|
52
|
-
const tailOnly = `${root}…/${parts[parts.length - 1]}`;
|
|
53
|
-
if (visibleWidth(tailOnly) <= maxWidth)
|
|
54
|
-
return tailOnly;
|
|
55
|
-
return truncateToWidth(tailOnly, maxWidth, "...");
|
|
56
|
-
}
|
|
57
20
|
export class FooterComponent {
|
|
58
21
|
session;
|
|
59
22
|
footerData;
|
|
60
23
|
autoCompactEnabled = true;
|
|
61
|
-
gitCache = null;
|
|
62
24
|
constructor(session, footerData) {
|
|
63
25
|
this.session = session;
|
|
64
26
|
this.footerData = footerData;
|
|
@@ -66,59 +28,8 @@ export class FooterComponent {
|
|
|
66
28
|
setAutoCompactEnabled(enabled) {
|
|
67
29
|
this.autoCompactEnabled = enabled;
|
|
68
30
|
}
|
|
69
|
-
invalidate() {
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
dispose() {
|
|
73
|
-
this.gitCache = null;
|
|
74
|
-
}
|
|
75
|
-
getGitInfo(cwd) {
|
|
76
|
-
const now = Date.now();
|
|
77
|
-
if (this.gitCache?.cwd === cwd && now - this.gitCache.fetchedAt < GIT_CACHE_TTL_MS) {
|
|
78
|
-
return this.gitCache.info;
|
|
79
|
-
}
|
|
80
|
-
const providerBranch = this.footerData.getGitBranch();
|
|
81
|
-
try {
|
|
82
|
-
const repoRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
83
|
-
cwd,
|
|
84
|
-
encoding: "utf8",
|
|
85
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
86
|
-
timeout: 500,
|
|
87
|
-
}).trim();
|
|
88
|
-
if (!repoRoot) {
|
|
89
|
-
const fallbackInfo = providerBranch ? { inGitRepo: true, branch: providerBranch } : { inGitRepo: false };
|
|
90
|
-
this.gitCache = { cwd, info: fallbackInfo, fetchedAt: now };
|
|
91
|
-
return fallbackInfo;
|
|
92
|
-
}
|
|
93
|
-
const branch = providerBranch ||
|
|
94
|
-
execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
95
|
-
cwd,
|
|
96
|
-
encoding: "utf8",
|
|
97
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
98
|
-
timeout: 500,
|
|
99
|
-
}).trim() ||
|
|
100
|
-
undefined;
|
|
101
|
-
const porcelain = execFileSync("git", ["status", "--porcelain", "-uno"], {
|
|
102
|
-
cwd,
|
|
103
|
-
encoding: "utf8",
|
|
104
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
105
|
-
timeout: 500,
|
|
106
|
-
}).trim();
|
|
107
|
-
const info = {
|
|
108
|
-
inGitRepo: true,
|
|
109
|
-
repoName: path.basename(repoRoot),
|
|
110
|
-
branch,
|
|
111
|
-
dirty: porcelain.length > 0,
|
|
112
|
-
};
|
|
113
|
-
this.gitCache = { cwd, info, fetchedAt: now };
|
|
114
|
-
return info;
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
const fallbackInfo = providerBranch ? { inGitRepo: true, branch: providerBranch } : { inGitRepo: false };
|
|
118
|
-
this.gitCache = { cwd, info: fallbackInfo, fetchedAt: now };
|
|
119
|
-
return fallbackInfo;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
31
|
+
invalidate() { }
|
|
32
|
+
dispose() { }
|
|
122
33
|
getContextTokens() {
|
|
123
34
|
const usage = this.session.getContextUsage();
|
|
124
35
|
if (!usage || usage.tokens == null)
|
|
@@ -156,63 +67,14 @@ export class FooterComponent {
|
|
|
156
67
|
render(width) {
|
|
157
68
|
if (width <= 0)
|
|
158
69
|
return [""];
|
|
159
|
-
const cwd = process.cwd();
|
|
160
|
-
const git = this.getGitInfo(cwd);
|
|
161
70
|
const separator = theme.fg("dim", " · ");
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
theme.fg("dim", "host ") + theme.fg("muted", `${os.userInfo().username}@${os.hostname()}`),
|
|
165
|
-
];
|
|
166
|
-
if (git.inGitRepo) {
|
|
167
|
-
if (git.repoName) {
|
|
168
|
-
leftMetaParts.push(theme.fg("dim", "repo ") + theme.fg("toolTitle", git.repoName));
|
|
169
|
-
}
|
|
170
|
-
if (git.branch) {
|
|
171
|
-
const branchDisplay = git.dirty ? `${git.branch}*` : git.branch;
|
|
172
|
-
leftMetaParts.push(theme.fg("dim", "branch ") + theme.fg(git.dirty ? "warning" : "borderAccent", branchDisplay));
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
const rightParts = [
|
|
71
|
+
const ellipsis = theme.fg("dim", "...");
|
|
72
|
+
const right = [
|
|
176
73
|
theme.fg("dim", "tok ") + theme.fg("success", this.getContextTokens()),
|
|
177
74
|
theme.fg("dim", "ctx ") + theme.fg(this.getContextPercentColor(), this.getContextPercentDisplay()),
|
|
178
|
-
];
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
let right = `${rightParts.join(separator)} `;
|
|
182
|
-
let left = ` ${theme.fg("accent", cwdLabel)}${leftMetaParts.length > 0 ? separator + leftMetaParts.join(separator) : ""}`;
|
|
183
|
-
const leftWidth = visibleWidth(left);
|
|
184
|
-
const rightWidth = visibleWidth(right);
|
|
185
|
-
let footerLine;
|
|
186
|
-
if (leftWidth + minGap + rightWidth <= width) {
|
|
187
|
-
const gap = Math.max(minGap, width - leftWidth - rightWidth);
|
|
188
|
-
footerLine = left + " ".repeat(gap) + right;
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
const maxRight = Math.max(12, Math.floor(width * 0.55));
|
|
192
|
-
right = truncateToWidth(right, Math.min(rightWidth, maxRight), ellipsis);
|
|
193
|
-
const rightFitWidth = visibleWidth(right);
|
|
194
|
-
const availableLeft = Math.max(0, width - minGap - rightFitWidth);
|
|
195
|
-
if (availableLeft > 0) {
|
|
196
|
-
const meta = leftMetaParts.length > 0 ? separator + leftMetaParts.join(separator) : "";
|
|
197
|
-
const fixedLeftWidth = visibleWidth(` ${meta}`);
|
|
198
|
-
const cwdBudget = Math.max(1, availableLeft - fixedLeftWidth);
|
|
199
|
-
cwdLabel = shortenPathForWidth(cwd, cwdBudget);
|
|
200
|
-
left = ` ${theme.fg("accent", cwdLabel)}${meta}`;
|
|
201
|
-
left = truncateToWidth(left, availableLeft, ellipsis);
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
left = "";
|
|
205
|
-
}
|
|
206
|
-
const leftFitWidth = visibleWidth(left);
|
|
207
|
-
if (leftFitWidth === 0) {
|
|
208
|
-
footerLine = truncateToWidth(right, width, ellipsis);
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
const gap = Math.max(minGap, width - leftFitWidth - rightFitWidth);
|
|
212
|
-
footerLine = left + " ".repeat(gap) + right;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
footerLine = truncateToWidth(footerLine, width, ellipsis);
|
|
75
|
+
].join(separator);
|
|
76
|
+
const gap = Math.max(0, width - visibleWidth(right) - 1);
|
|
77
|
+
const footerLine = truncateToWidth(`${" ".repeat(gap)} ${right}`, width, ellipsis);
|
|
216
78
|
const lines = [footerLine];
|
|
217
79
|
const extensionStatuses = this.footerData.getExtensionStatuses();
|
|
218
80
|
if (extensionStatuses.size > 0) {
|
|
@@ -220,7 +82,7 @@ export class FooterComponent {
|
|
|
220
82
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
221
83
|
.map(([, text]) => sanitizeStatusText(text));
|
|
222
84
|
const statusLine = sortedStatuses.join(" ");
|
|
223
|
-
lines.push(truncateToWidth(statusLine, width,
|
|
85
|
+
lines.push(truncateToWidth(statusLine, width, ellipsis));
|
|
224
86
|
}
|
|
225
87
|
return lines;
|
|
226
88
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFileSync } from \"node:child_process\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { type Component, truncateToWidth, visibleWidth } from \"@apholdings/jensen-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { theme } from \"../theme/theme.js\";\n\ntype GitInfo = {\n\tinGitRepo: boolean;\n\trepoName?: string;\n\tbranch?: string;\n\tdirty?: boolean;\n};\n\ntype GitCache = {\n\tcwd: string;\n\tinfo: GitInfo;\n\tfetchedAt: number;\n};\n\nconst GIT_CACHE_TTL_MS = 5000;\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\nfunction formatTokenCount(value: number): string {\n\tif (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;\n\tif (value >= 1_000) return `${(value / 1_000).toFixed(1)}k`;\n\treturn String(value);\n}\n\nfunction shortenPath(input: string): string {\n\tconst home = os.homedir();\n\tlet p = input;\n\n\tif (home && p.startsWith(home)) {\n\t\tp = `~${p.slice(home.length)}`;\n\t}\n\n\tif (p === \"/\") return \"/\";\n\n\tconst parts = p.split(\"/\").filter(Boolean);\n\tif (parts.length <= 4) return p;\n\n\tconst tail = parts.slice(-3).join(\"/\");\n\treturn p.startsWith(\"~\") ? `~/…/${tail}` : `/…/${tail}`;\n}\n\nfunction shortenPathForWidth(input: string, maxWidth: number): string {\n\tif (maxWidth <= 0) return \"\";\n\n\tconst base = shortenPath(input);\n\tif (visibleWidth(base) <= maxWidth) return base;\n\n\tconst root = base.startsWith(\"~\") ? \"~/\" : \"/\";\n\tconst parts = base.replace(/^~?\\//, \"\").split(\"/\").filter(Boolean);\n\tif (parts.length === 0) return truncateToWidth(base, maxWidth, \"...\");\n\n\tconst compactParts = parts.map((part, i) => (i === parts.length - 1 ? part : (part[0] ?? part)));\n\tconst compact = `${root}${compactParts.join(\"/\")}`;\n\tif (visibleWidth(compact) <= maxWidth) return compact;\n\n\tconst tailOnly = `${root}…/${parts[parts.length - 1]}`;\n\tif (visibleWidth(tailOnly) <= maxWidth) return tailOnly;\n\n\treturn truncateToWidth(tailOnly, maxWidth, \"...\");\n}\n\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate gitCache: GitCache | null = null;\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.gitCache = null;\n\t}\n\n\tdispose(): void {\n\t\tthis.gitCache = null;\n\t}\n\n\tprivate getGitInfo(cwd: string): GitInfo {\n\t\tconst now = Date.now();\n\t\tif (this.gitCache?.cwd === cwd && now - this.gitCache.fetchedAt < GIT_CACHE_TTL_MS) {\n\t\t\treturn this.gitCache.info;\n\t\t}\n\n\t\tconst providerBranch = this.footerData.getGitBranch();\n\n\t\ttry {\n\t\t\tconst repoRoot = execFileSync(\"git\", [\"rev-parse\", \"--show-toplevel\"], {\n\t\t\t\tcwd,\n\t\t\t\tencoding: \"utf8\",\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\t\ttimeout: 500,\n\t\t\t}).trim();\n\n\t\t\tif (!repoRoot) {\n\t\t\t\tconst fallbackInfo = providerBranch ? { inGitRepo: true, branch: providerBranch } : { inGitRepo: false };\n\t\t\t\tthis.gitCache = { cwd, info: fallbackInfo, fetchedAt: now };\n\t\t\t\treturn fallbackInfo;\n\t\t\t}\n\n\t\t\tconst branch =\n\t\t\t\tproviderBranch ||\n\t\t\t\texecFileSync(\"git\", [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], {\n\t\t\t\t\tcwd,\n\t\t\t\t\tencoding: \"utf8\",\n\t\t\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\t\t\ttimeout: 500,\n\t\t\t\t}).trim() ||\n\t\t\t\tundefined;\n\n\t\t\tconst porcelain = execFileSync(\"git\", [\"status\", \"--porcelain\", \"-uno\"], {\n\t\t\t\tcwd,\n\t\t\t\tencoding: \"utf8\",\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\t\ttimeout: 500,\n\t\t\t}).trim();\n\n\t\t\tconst info: GitInfo = {\n\t\t\t\tinGitRepo: true,\n\t\t\t\trepoName: path.basename(repoRoot),\n\t\t\t\tbranch,\n\t\t\t\tdirty: porcelain.length > 0,\n\t\t\t};\n\n\t\t\tthis.gitCache = { cwd, info, fetchedAt: now };\n\t\t\treturn info;\n\t\t} catch {\n\t\t\tconst fallbackInfo = providerBranch ? { inGitRepo: true, branch: providerBranch } : { inGitRepo: false };\n\t\t\tthis.gitCache = { cwd, info: fallbackInfo, fetchedAt: now };\n\t\t\treturn fallbackInfo;\n\t\t}\n\t}\n\n\tprivate getContextTokens(): string {\n\t\tconst usage = this.session.getContextUsage();\n\t\tif (!usage || usage.tokens == null) return \"--\";\n\n\t\tconst contextWindow =\n\t\t\ttypeof usage.contextWindow === \"number\"\n\t\t\t\t? usage.contextWindow\n\t\t\t\t: (this.session.state.model?.contextWindow ?? null);\n\n\t\tif (contextWindow == null) {\n\t\t\treturn formatTokenCount(usage.tokens);\n\t\t}\n\n\t\treturn `${formatTokenCount(usage.tokens)}/${formatTokenCount(contextWindow)}`;\n\t}\n\n\tprivate getContextPercentValue(): number | null {\n\t\tconst usage = this.session.getContextUsage();\n\t\tif (!usage || usage.percent == null) return null;\n\t\treturn usage.percent;\n\t}\n\n\tprivate getContextPercentDisplay(): string {\n\t\tconst percent = this.getContextPercentValue();\n\t\tif (percent == null) return \"--\";\n\t\treturn `${percent.toFixed(1)}%${this.autoCompactEnabled ? \" (auto)\" : \"\"}`;\n\t}\n\n\tprivate getContextPercentColor(): \"success\" | \"warning\" | \"error\" {\n\t\tconst percent = this.getContextPercentValue();\n\t\tif (percent == null) return \"success\";\n\t\tif (percent > 90) return \"error\";\n\t\tif (percent > 70) return \"warning\";\n\t\treturn \"success\";\n\t}\n\n\trender(width: number): string[] {\n\t\tif (width <= 0) return [\"\"];\n\n\t\tconst cwd = process.cwd();\n\t\tconst git = this.getGitInfo(cwd);\n\t\tconst separator = theme.fg(\"dim\", \" · \");\n\n\t\tlet cwdLabel = shortenPath(cwd);\n\n\t\tconst leftMetaParts: string[] = [\n\t\t\ttheme.fg(\"dim\", \"host \") + theme.fg(\"muted\", `${os.userInfo().username}@${os.hostname()}`),\n\t\t];\n\n\t\tif (git.inGitRepo) {\n\t\t\tif (git.repoName) {\n\t\t\t\tleftMetaParts.push(theme.fg(\"dim\", \"repo \") + theme.fg(\"toolTitle\", git.repoName));\n\t\t\t}\n\t\t\tif (git.branch) {\n\t\t\t\tconst branchDisplay = git.dirty ? `${git.branch}*` : git.branch;\n\t\t\t\tleftMetaParts.push(\n\t\t\t\t\ttheme.fg(\"dim\", \"branch \") + theme.fg(git.dirty ? \"warning\" : \"borderAccent\", branchDisplay),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst rightParts = [\n\t\t\ttheme.fg(\"dim\", \"tok \") + theme.fg(\"success\", this.getContextTokens()),\n\t\t\ttheme.fg(\"dim\", \"ctx \") + theme.fg(this.getContextPercentColor(), this.getContextPercentDisplay()),\n\t\t];\n\n\t\tconst ellipsis = theme.fg(\"dim\", \"...\");\n\t\tconst minGap = 1;\n\n\t\tlet right = `${rightParts.join(separator)} `;\n\t\tlet left = ` ${theme.fg(\"accent\", cwdLabel)}${\n\t\t\tleftMetaParts.length > 0 ? separator + leftMetaParts.join(separator) : \"\"\n\t\t}`;\n\n\t\tconst leftWidth = visibleWidth(left);\n\t\tconst rightWidth = visibleWidth(right);\n\t\tlet footerLine: string;\n\n\t\tif (leftWidth + minGap + rightWidth <= width) {\n\t\t\tconst gap = Math.max(minGap, width - leftWidth - rightWidth);\n\t\t\tfooterLine = left + \" \".repeat(gap) + right;\n\t\t} else {\n\t\t\tconst maxRight = Math.max(12, Math.floor(width * 0.55));\n\t\t\tright = truncateToWidth(right, Math.min(rightWidth, maxRight), ellipsis);\n\n\t\t\tconst rightFitWidth = visibleWidth(right);\n\t\t\tconst availableLeft = Math.max(0, width - minGap - rightFitWidth);\n\n\t\t\tif (availableLeft > 0) {\n\t\t\t\tconst meta = leftMetaParts.length > 0 ? separator + leftMetaParts.join(separator) : \"\";\n\t\t\t\tconst fixedLeftWidth = visibleWidth(` ${meta}`);\n\t\t\t\tconst cwdBudget = Math.max(1, availableLeft - fixedLeftWidth);\n\n\t\t\t\tcwdLabel = shortenPathForWidth(cwd, cwdBudget);\n\t\t\t\tleft = ` ${theme.fg(\"accent\", cwdLabel)}${meta}`;\n\t\t\t\tleft = truncateToWidth(left, availableLeft, ellipsis);\n\t\t\t} else {\n\t\t\t\tleft = \"\";\n\t\t\t}\n\n\t\t\tconst leftFitWidth = visibleWidth(left);\n\n\t\t\tif (leftFitWidth === 0) {\n\t\t\t\tfooterLine = truncateToWidth(right, width, ellipsis);\n\t\t\t} else {\n\t\t\t\tconst gap = Math.max(minGap, width - leftFitWidth - rightFitWidth);\n\t\t\t\tfooterLine = left + \" \".repeat(gap) + right;\n\t\t\t}\n\t\t}\n\n\t\tfooterLine = truncateToWidth(footerLine, width, ellipsis);\n\t\tconst lines = [footerLine];\n\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@apholdings/jensen-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\nfunction formatTokenCount(value: number): string {\n\tif (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;\n\tif (value >= 1_000) return `${(value / 1_000).toFixed(1)}k`;\n\treturn String(value);\n}\n\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\tinvalidate(): void {}\n\n\tdispose(): void {}\n\n\tprivate getContextTokens(): string {\n\t\tconst usage = this.session.getContextUsage();\n\t\tif (!usage || usage.tokens == null) return \"--\";\n\n\t\tconst contextWindow =\n\t\t\ttypeof usage.contextWindow === \"number\"\n\t\t\t\t? usage.contextWindow\n\t\t\t\t: (this.session.state.model?.contextWindow ?? null);\n\n\t\tif (contextWindow == null) {\n\t\t\treturn formatTokenCount(usage.tokens);\n\t\t}\n\n\t\treturn `${formatTokenCount(usage.tokens)}/${formatTokenCount(contextWindow)}`;\n\t}\n\n\tprivate getContextPercentValue(): number | null {\n\t\tconst usage = this.session.getContextUsage();\n\t\tif (!usage || usage.percent == null) return null;\n\t\treturn usage.percent;\n\t}\n\n\tprivate getContextPercentDisplay(): string {\n\t\tconst percent = this.getContextPercentValue();\n\t\tif (percent == null) return \"--\";\n\t\treturn `${percent.toFixed(1)}%${this.autoCompactEnabled ? \" (auto)\" : \"\"}`;\n\t}\n\n\tprivate getContextPercentColor(): \"success\" | \"warning\" | \"error\" {\n\t\tconst percent = this.getContextPercentValue();\n\t\tif (percent == null) return \"success\";\n\t\tif (percent > 90) return \"error\";\n\t\tif (percent > 70) return \"warning\";\n\t\treturn \"success\";\n\t}\n\n\trender(width: number): string[] {\n\t\tif (width <= 0) return [\"\"];\n\n\t\tconst separator = theme.fg(\"dim\", \" · \");\n\t\tconst ellipsis = theme.fg(\"dim\", \"...\");\n\n\t\tconst right = [\n\t\t\ttheme.fg(\"dim\", \"tok \") + theme.fg(\"success\", this.getContextTokens()),\n\t\t\ttheme.fg(\"dim\", \"ctx \") + theme.fg(this.getContextPercentColor(), this.getContextPercentDisplay()),\n\t\t].join(separator);\n\n\t\tconst gap = Math.max(0, width - visibleWidth(right) - 1);\n\t\tconst footerLine = truncateToWidth(`${\" \".repeat(gap)} ${right}`, width, ellipsis);\n\t\tconst lines = [footerLine];\n\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\tlines.push(truncateToWidth(statusLine, width, ellipsis));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type {
|
|
1
|
+
import { type Component } from "@apholdings/jensen-tui";
|
|
2
|
+
import type { AgentSession } from "../../../core/agent-session.js";
|
|
3
|
+
import type { ReadonlyFooterDataProvider } from "../../../core/footer-data-provider.js";
|
|
3
4
|
export declare class Header implements Component {
|
|
5
|
+
private readonly agentSession?;
|
|
6
|
+
private readonly footerDataProvider?;
|
|
7
|
+
constructor(agentSession?: AgentSession | undefined, footerDataProvider?: ReadonlyFooterDataProvider | undefined);
|
|
8
|
+
private getData;
|
|
9
|
+
private renderCompact;
|
|
4
10
|
render(width: number): string[];
|
|
5
11
|
invalidate(): void;
|
|
6
12
|
}
|
|
7
|
-
export default
|
|
13
|
+
export default Header;
|
|
8
14
|
//# sourceMappingURL=header.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"header.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/header.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAsWtE,qBAAa,MAAO,YAAW,SAAS;IACvC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9B;IAED,UAAU,IAAI,IAAI,CAAG;CACrB;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EAAE,EAAE,YAAY,QAIpD","sourcesContent":["import type { Component } from \"@apholdings/jensen-tui\";\nimport type { ExtensionAPI } from \"../../../core/extensions/index.js\";\n\ntype RGB = { r: number; g: number; b: number };\ntype Glyph7 = [string, string, string, string, string, string, string];\ntype Glyph5 = [string, string, string, string, string];\ntype PixelKind = \"empty\" | \"face\" | \"detail\" | \"highlight\";\n\nconst ANSI_ESCAPE_GLOBAL = /\\x1b\\[[0-9;]*m/g;\nconst ANSI_ESCAPE_AT_START = /^\\x1b\\[[0-9;]*m/;\n\nconst TITLE = \"[JENSEN]\";\nconst MINI_TITLE = \"[J]\";\n\nconst faceStops: RGB[] = [\n\t{ r: 0x1a, g: 0xf5, b: 0x8a },\n\t{ r: 0x57, g: 0xe3, b: 0xf7 },\n\t{ r: 0x8c, g: 0xb6, b: 0xff },\n\t{ r: 0xc0, g: 0x7b, b: 0xff },\n];\n\nconst detailStops: RGB[] = [\n\t{ r: 0x0e, g: 0x8a, b: 0x53 },\n\t{ r: 0x2c, g: 0x86, b: 0xa2 },\n\t{ r: 0x5a, g: 0x6d, b: 0xb8 },\n\t{ r: 0x7f, g: 0x4b, b: 0xb6 },\n];\n\nconst shadowStops: RGB[] = [\n\t{ r: 0x05, g: 0x2a, b: 0x19 },\n\t{ r: 0x0b, g: 0x22, b: 0x3a },\n\t{ r: 0x19, g: 0x12, b: 0x33 },\n];\n\nfunction g7(...rows: string[]): Glyph7 {\n\treturn rows as Glyph7;\n}\n\nfunction g5(...rows: string[]): Glyph5 {\n\treturn rows as Glyph5;\n}\n\nfunction stripAnsi(input: string): string {\n\treturn input.replace(ANSI_ESCAPE_GLOBAL, \"\");\n}\n\nfunction visibleWidth(input: string): number {\n\treturn Array.from(stripAnsi(input)).length;\n}\n\nfunction truncatePlain(input: string, maxWidth: number): string {\n\tif (maxWidth <= 0) return \"\";\n\n\tlet out = \"\";\n\tlet width = 0;\n\n\tfor (const ch of Array.from(input)) {\n\t\tif (width >= maxWidth) break;\n\t\tout += ch;\n\t\twidth += 1;\n\t}\n\n\treturn out;\n}\n\nfunction truncateToWidth(input: string, maxWidth: number, ellipsis = \"\"): string {\n\tif (maxWidth <= 0) return \"\";\n\tif (visibleWidth(input) <= maxWidth) return input;\n\n\tconst ellipsisWidth = visibleWidth(ellipsis);\n\tif (ellipsisWidth >= maxWidth) return truncatePlain(ellipsis, maxWidth);\n\n\tconst targetWidth = maxWidth - ellipsisWidth;\n\tlet i = 0;\n\tlet width = 0;\n\tlet out = \"\";\n\tlet sawAnsi = false;\n\n\twhile (i < input.length && width < targetWidth) {\n\t\tconst rest = input.slice(i);\n\t\tconst ansi = rest.match(ANSI_ESCAPE_AT_START);\n\t\tif (ansi) {\n\t\t\tout += ansi[0];\n\t\t\tsawAnsi = true;\n\t\t\ti += ansi[0].length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst cp = input.codePointAt(i);\n\t\tif (cp == null) break;\n\n\t\tconst ch = String.fromCodePoint(cp);\n\t\tif (width + 1 > targetWidth) break;\n\n\t\tout += ch;\n\t\twidth += 1;\n\t\ti += ch.length;\n\t}\n\n\tif (ellipsis) out += ellipsis;\n\tif (sawAnsi) out += \"\\x1b[0m\";\n\n\treturn out;\n}\n\nfunction color(rgb: RGB, text: string): string {\n\treturn `\\x1b[38;2;${rgb.r};${rgb.g};${rgb.b}m${text}\\x1b[0m`;\n}\n\nfunction interpolateStops(stops: RGB[], t: number): RGB {\n\tif (stops.length === 1) return stops[0];\n\n\tconst clamped = Math.max(0, Math.min(1, t));\n\tconst scaled = clamped * (stops.length - 1);\n\tconst i = Math.min(Math.floor(scaled), stops.length - 2);\n\tconst f = scaled - i;\n\n\treturn {\n\t\tr: Math.round(stops[i].r + (stops[i + 1].r - stops[i].r) * f),\n\t\tg: Math.round(stops[i].g + (stops[i + 1].g - stops[i].g) * f),\n\t\tb: Math.round(stops[i].b + (stops[i + 1].b - stops[i].b) * f),\n\t};\n}\n\nfunction brighten(rgb: RGB, amount: number): RGB {\n\treturn {\n\t\tr: Math.min(255, rgb.r + amount),\n\t\tg: Math.min(255, rgb.g + amount),\n\t\tb: Math.min(255, rgb.b + amount),\n\t};\n}\n\nfunction glyphWidth<T extends readonly string[]>(glyph: T): number {\n\treturn Math.max(...glyph.map((row) => row.length));\n}\n\nfunction classifyPixel(ch: string): PixelKind {\n\tif (ch === \" \") return \"empty\";\n\tif (ch === \"█\") return \"face\";\n\tif (ch === \"░\") return \"detail\";\n\tif (ch === \"▓\") return \"highlight\";\n\treturn \"face\";\n}\n\nconst LARGE_GLYPHS: Record<string, Glyph7> = {\n\t// UFO\n\t\"[\": g7(\n\t\t\" ▓███████▓ \",\n\t\t\" ███████████ \",\n\t\t\" ███░░░░░░░███ \",\n\t\t\"███████████████\",\n\t\t\"░░▓██▓░▓░▓██▓░░\",\n\t\t\"░░▓██▓░▓░▓██▓░░\",\n\t\t\"░▓█▓▓░▓▓▓░▓▓█▓░\",\n\t),\n\n\t// ALIEN\n\t\"]\": g7(\n\t\t\" ████████████ \",\n\t\t\" ███░▓░░░░░▓░███ \",\n\t\t\"██░░░░█░░░█░░░░██\",\n\t\t\"██░░░░░░░░░░░░░██\",\n\t\t\"██░░░░█▓█▓█░░░░██\",\n\t\t\" ███░░░░░░░░░███ \",\n\t\t\" ████████████ \",\n\t),\n\n\t\" \": g7(\" \", \" \", \" \", \" \", \" \", \" \", \" \"),\n\n\tJ: g7(\"████████\", \"░░░░██░ \", \" ░░██ \", \" ░░██ \", \"██░░██ \", \"██░░██ \", \"░█████ \"),\n\n\tE: g7(\"█████████\", \"███░░░░██\", \"███ ░█ \", \"██████ \", \"███░░█ \", \"███░░ ██\", \"█████████\"),\n\n\tN: g7(\"████░░░████\", \" ████░░░░██\", \" ██░██░░░██\", \" ██░░██░░██\", \" ██ ░░██░██\", \" ██ ░░████\", \"███ ░░███\"),\n\n\tS: g7(\" ████████ \", \"███░░░░███\", \"░███ \", \" ░██████ \", \" ░░░███ \", \"███░░░░███\", \" ████████ \"),\n};\n\nconst COMPACT_GLYPHS: Record<string, Glyph5> = {\n\t// Tiny UFO\n\t\"[\": [\" ▓█████▓ \", \" █████████ \", \"███░░░░░███\", \"░░██▓░▓██░░\", \" ░█▓▓░▓▓█░ \"],\n\n\t// ALIEN - Scaled down to 5 rows\n\t\"]\": [\" █████████ \", \"██░▓░░░▓░██\", \"█░░░█░█░░░█\", \"█░░░░░░░░░█\", \" █████████ \"],\n\n\t\" \": g5(\" \", \" \", \" \", \" \", \" \"),\n\n\tJ: [\"███████\", \" ░░██ \", \" ░░██ \", \"██░░██ \", \"░█████ \"],\n\n\tE: [\"████████\", \"███░░░█ \", \"██████ \", \"███░░ █ \", \"████████\"],\n\n\tN: [\"███░░░██\", \"██░█░░██\", \"██░░█░██\", \"██ ░░███\", \"███ ░░██\"],\n\n\tS: [\" ██████ \", \"███░░░░ \", \" ░█████ \", \" ░░░░███\", \" ██████ \"],\n};\n\nfunction buildBitmap<T extends readonly string[]>(\n\ttext: string,\n\tglyphs: Record<string, T>,\n\trows: number,\n\tgap: number,\n): { pixels: PixelKind[][]; width: number; height: number } {\n\tconst chars = Array.from(text).map((ch) => glyphs[ch] ?? glyphs[\" \"]);\n\n\tlet totalWidth = 0;\n\tchars.forEach((glyph, index) => {\n\t\ttotalWidth += glyphWidth(glyph);\n\t\tif (index < chars.length - 1) totalWidth += gap;\n\t});\n\n\tconst pixels = Array.from({ length: rows }, () => Array<PixelKind>(totalWidth).fill(\"empty\"));\n\n\tlet cursorX = 0;\n\tchars.forEach((glyph, index) => {\n\t\tconst width = glyphWidth(glyph);\n\n\t\tfor (let y = 0; y < rows; y++) {\n\t\t\tconst row = glyph[y].padEnd(width, \" \");\n\t\t\tfor (let x = 0; x < width; x++) {\n\t\t\t\tpixels[y][cursorX + x] = classifyPixel(row[x]);\n\t\t\t}\n\t\t}\n\n\t\tcursorX += width;\n\t\tif (index < chars.length - 1) cursorX += gap;\n\t});\n\n\treturn { pixels, width: totalWidth, height: rows };\n}\n\nfunction renderBitmapLogo(options: {\n\twidth: number;\n\tpixels: PixelKind[][];\n\tbitmapWidth: number;\n\tbitmapHeight: number;\n\tindent?: number;\n\tshadowOffsetX?: number;\n\tshadowOffsetY?: number;\n}): string[] {\n\tconst { width, pixels, bitmapWidth, bitmapHeight, indent = 1, shadowOffsetX = 2, shadowOffsetY = 1 } = options;\n\n\tif (width <= 0) return [\"\"];\n\n\t// Face is shifted right inside the render canvas so the cast shadow can live on the left.\n\tconst renderWidth = bitmapWidth + shadowOffsetX;\n\tconst renderHeight = bitmapHeight + shadowOffsetY;\n\tconst lines: string[] = [\"\"];\n\n\tfor (let y = 0; y < renderHeight; y++) {\n\t\tlet line = \" \".repeat(Math.max(0, indent));\n\n\t\tfor (let x = 0; x < renderWidth; x++) {\n\t\t\tconst faceSourceX = x - shadowOffsetX;\n\t\t\tconst facePixel =\n\t\t\t\ty >= 0 && y < bitmapHeight && faceSourceX >= 0 && faceSourceX < bitmapWidth\n\t\t\t\t\t? pixels[y][faceSourceX]\n\t\t\t\t\t: \"empty\";\n\n\t\t\tconst shadowSourceX = x;\n\t\t\tconst shadowSourceY = y - shadowOffsetY;\n\t\t\tconst shadowPixel =\n\t\t\t\tshadowSourceX >= 0 && shadowSourceX < bitmapWidth && shadowSourceY >= 0 && shadowSourceY < bitmapHeight\n\t\t\t\t\t? pixels[shadowSourceY][shadowSourceX]\n\t\t\t\t\t: \"empty\";\n\n\t\t\tconst shadowOn = facePixel === \"empty\" && shadowPixel !== \"empty\";\n\n\t\t\tif (facePixel === \"face\") {\n\t\t\t\tconst t = bitmapWidth > 1 ? faceSourceX / (bitmapWidth - 1) : 0;\n\t\t\t\tline += color(interpolateStops(faceStops, t), \"█\");\n\t\t\t} else if (facePixel === \"detail\") {\n\t\t\t\tconst t = bitmapWidth > 1 ? faceSourceX / (bitmapWidth - 1) : 0;\n\t\t\t\tline += color(interpolateStops(detailStops, t), \"█\");\n\t\t\t} else if (facePixel === \"highlight\") {\n\t\t\t\tconst t = bitmapWidth > 1 ? faceSourceX / (bitmapWidth - 1) : 0;\n\t\t\t\tline += color(brighten(interpolateStops(faceStops, t), 28), \"█\");\n\t\t\t} else if (shadowOn) {\n\t\t\t\tconst t = bitmapWidth > 1 ? shadowSourceX / (bitmapWidth - 1) : 0;\n\t\t\t\tline += color(interpolateStops(shadowStops, t), \"█\");\n\t\t\t} else {\n\t\t\t\tline += \" \";\n\t\t\t}\n\t\t}\n\n\t\tlines.push(truncateToWidth(line, width));\n\t}\n\n\treturn lines.map((line) => truncateToWidth(line, width));\n}\n\nfunction renderMicroFallback(width: number): string[] {\n\tif (width <= 0) return [\"\"];\n\n\tlet line = \"\";\n\tconst chars = Array.from(MINI_TITLE);\n\tconst plainWidth = chars.length;\n\n\tchars.forEach((ch, i) => {\n\t\tif (ch === \" \") {\n\t\t\tline += \" \";\n\t\t\treturn;\n\t\t}\n\n\t\tconst t = plainWidth > 1 ? i / (plainWidth - 1) : 0;\n\t\tline += color(interpolateStops(faceStops, t), ch);\n\t});\n\n\treturn [\"\", truncateToWidth(line, width, \"\")];\n}\n\nfunction renderResponsiveLogo(width: number): string[] {\n\tconst large = buildBitmap(TITLE, LARGE_GLYPHS, 7, 1);\n\tconst largeNeeded = 1 + large.width + 1;\n\n\tconst compact = buildBitmap(TITLE, COMPACT_GLYPHS, 5, 1);\n\tconst compactNeeded = 1 + compact.width + 1;\n\n\tconst mini = buildBitmap(MINI_TITLE, COMPACT_GLYPHS, 5, 1);\n\tconst miniNeeded = 1 + mini.width + 1;\n\n\tif (width >= largeNeeded) {\n\t\treturn renderBitmapLogo({\n\t\t\twidth,\n\t\t\tpixels: large.pixels,\n\t\t\tbitmapWidth: large.width,\n\t\t\tbitmapHeight: large.height,\n\t\t\tindent: 1,\n\t\t\tshadowOffsetX: 2,\n\t\t\tshadowOffsetY: 1,\n\t\t});\n\t}\n\n\tif (width >= compactNeeded) {\n\t\treturn renderBitmapLogo({\n\t\t\twidth,\n\t\t\tpixels: compact.pixels,\n\t\t\tbitmapWidth: compact.width,\n\t\t\tbitmapHeight: compact.height,\n\t\t\tindent: 1,\n\t\t\tshadowOffsetX: 1,\n\t\t\tshadowOffsetY: 1,\n\t\t});\n\t}\n\n\tif (width >= miniNeeded) {\n\t\treturn renderBitmapLogo({\n\t\t\twidth,\n\t\t\tpixels: mini.pixels,\n\t\t\tbitmapWidth: mini.width,\n\t\t\tbitmapHeight: mini.height,\n\t\t\tindent: 1,\n\t\t\tshadowOffsetX: 1,\n\t\t\tshadowOffsetY: 1,\n\t\t});\n\t}\n\n\treturn renderMicroFallback(width);\n}\n\nexport class Header implements Component {\n\trender(width: number): string[] {\n\t\treturn renderResponsiveLogo(width);\n\t}\n\n\tinvalidate(): void {}\n}\n\nexport default function jensenHeader(pi: ExtensionAPI) {\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tctx.ui.setHeader((_tui, _theme) => new Header());\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"header.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/header.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,wBAAwB,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAuKxF,qBAAa,MAAO,YAAW,SAAS;IAEtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IAFrC,YACkB,YAAY,CAAC,0BAAc,EAC3B,kBAAkB,CAAC,wCAA4B,EAC7D;IAEJ,OAAO,CAAC,OAAO;IAgBf,OAAO,CAAC,aAAa;IAarB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAmD9B;IAED,UAAU,IAAI,IAAI,CAAG;CACrB;AAED,eAAe,MAAM,CAAC","sourcesContent":["import { createRequire } from \"node:module\";\nimport os from \"node:os\";\nimport { type Component, truncateToWidth, visibleWidth } from \"@apholdings/jensen-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\n\nconst require = createRequire(import.meta.url);\nconst packageJson = require(\"../../../../package.json\") as {\n\ttitle?: string;\n\tversion?: string;\n\tdisplayTitle?: string;\n\tproductTitle?: string;\n};\n\ntype RGB = { r: number; g: number; b: number };\nconst ANSI_ESCAPE_AT_START = /^\\x1b\\[[0-9;]*m/;\n\nconst TITLE = packageJson.displayTitle ?? packageJson.productTitle ?? packageJson.title ?? \"Jensen Code\";\n\nconst VERSION = packageJson.version ? `v${packageJson.version}` : \"v0.0.0\";\n\nconst LOGO = [\" █████████ \", \"██▓░░░░░▓██\", \"█░░░█░█░░░█\", \"█░░░░░░░░░█\", \" █████████ \"];\n\nconst GRADIENT_STOPS: RGB[] = [\n\t{ r: 0x1a, g: 0xf5, b: 0x8a },\n\t{ r: 0x57, g: 0xe3, b: 0xf7 },\n\t{ r: 0x8c, g: 0xb6, b: 0xff },\n\t{ r: 0xc0, g: 0x7b, b: 0xff },\n];\n\nconst COLORS = {\n\tborder: { r: 0x72, g: 0x7c, b: 0xb0 },\n\ttitle: { r: 0xd5, g: 0xd6, b: 0xdb },\n\tmuted: { r: 0x7a, g: 0x84, b: 0xb2 },\n\tsubtle: { r: 0x56, g: 0x5f, b: 0x89 },\n\taccent: { r: 0xa1, g: 0x88, b: 0xf1 },\n};\n\nfunction truncatePlain(input: string, maxWidth: number): string {\n\tif (maxWidth <= 0) return \"\";\n\n\tlet out = \"\";\n\tlet width = 0;\n\n\tfor (const ch of Array.from(input)) {\n\t\tif (width >= maxWidth) break;\n\t\tout += ch;\n\t\twidth += 1;\n\t}\n\n\treturn out;\n}\n\nfunction truncateAnsi(input: string, maxWidth: number, ellipsis = \"\"): string {\n\tif (maxWidth <= 0) return \"\";\n\tif (visibleWidth(input) <= maxWidth) return input;\n\n\tconst ellipsisWidth = visibleWidth(ellipsis);\n\tif (ellipsisWidth >= maxWidth) return truncatePlain(ellipsis, maxWidth);\n\n\tconst targetWidth = maxWidth - ellipsisWidth;\n\tlet i = 0;\n\tlet width = 0;\n\tlet out = \"\";\n\tlet sawAnsi = false;\n\n\twhile (i < input.length && width < targetWidth) {\n\t\tconst rest = input.slice(i);\n\t\tconst ansi = rest.match(ANSI_ESCAPE_AT_START);\n\t\tif (ansi) {\n\t\t\tout += ansi[0];\n\t\t\tsawAnsi = true;\n\t\t\ti += ansi[0].length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst cp = input.codePointAt(i);\n\t\tif (cp == null) break;\n\n\t\tconst ch = String.fromCodePoint(cp);\n\t\tif (width + 1 > targetWidth) break;\n\n\t\tout += ch;\n\t\twidth += 1;\n\t\ti += ch.length;\n\t}\n\n\tif (ellipsis) out += ellipsis;\n\tif (sawAnsi) out += \"\\x1b[0m\";\n\n\treturn out;\n}\n\nfunction color(rgb: RGB, text: string): string {\n\treturn `\\x1b[38;2;${rgb.r};${rgb.g};${rgb.b}m${text}\\x1b[0m`;\n}\n\nfunction bold(text: string): string {\n\treturn `\\x1b[1m${text}\\x1b[0m`;\n}\n\nfunction dim(text: string): string {\n\treturn `\\x1b[38;2;${COLORS.muted.r};${COLORS.muted.g};${COLORS.muted.b}m${text}\\x1b[0m`;\n}\n\nfunction subtle(text: string): string {\n\treturn `\\x1b[38;2;${COLORS.subtle.r};${COLORS.subtle.g};${COLORS.subtle.b}m${text}\\x1b[0m`;\n}\n\nfunction interpolateStops(stops: RGB[], t: number): RGB {\n\tif (stops.length === 0) return { r: 0, g: 0, b: 0 };\n\tif (stops.length === 1) return stops[0];\n\n\tconst clamped = Math.max(0, Math.min(1, t));\n\tconst scaled = clamped * (stops.length - 1);\n\tconst i = Math.min(Math.floor(scaled), stops.length - 2);\n\tconst f = scaled - i;\n\n\treturn {\n\t\tr: Math.round(stops[i].r + (stops[i + 1].r - stops[i].r) * f),\n\t\tg: Math.round(stops[i].g + (stops[i + 1].g - stops[i].g) * f),\n\t\tb: Math.round(stops[i].b + (stops[i + 1].b - stops[i].b) * f),\n\t};\n}\n\nfunction renderColoredLogoLine(line: string): string {\n\tconst chars = Array.from(line);\n\tconst width = chars.length;\n\n\tlet out = \"\";\n\tchars.forEach((ch, i) => {\n\t\tif (ch === \" \") {\n\t\t\tout += \" \";\n\t\t\treturn;\n\t\t}\n\t\tconst t = width > 1 ? i / (width - 1) : 0;\n\t\tout += color(interpolateStops(GRADIENT_STOPS, t), ch);\n\t});\n\n\treturn out;\n}\n\nfunction compactPath(input: string, maxWidth = 44): string {\n\tconst normalized = input.replaceAll(\"/\", process.platform === \"win32\" ? \"\\\\\" : \"/\");\n\tif (normalized.length <= maxWidth) return normalized;\n\n\tconst separator = normalized.includes(\"\\\\\") ? \"\\\\\" : \"/\";\n\tconst parts = normalized.split(separator).filter(Boolean);\n\tif (parts.length <= 2) return normalized;\n\n\tconst first = normalized.startsWith(separator) ? separator : \"\";\n\tconst driveMatch = parts[0]?.match(/^[A-Za-z]:$/);\n\tconst head = driveMatch ? `${parts[0]}${separator}` : first;\n\tconst tail = parts.slice(-2).join(separator);\n\treturn `${head}…${separator}${tail}`;\n}\n\nfunction padAnsi(input: string, width: number): string {\n\tconst remaining = Math.max(0, width - visibleWidth(input));\n\treturn input + \" \".repeat(remaining);\n}\n\nfunction bulletJoin(parts: Array<string | undefined>): string {\n\treturn parts.filter((part) => Boolean(part && part.trim().length > 0)).join(` ${subtle(\"•\")} `);\n}\n\nfunction maybePrefix(label: string, value: string | undefined): string | undefined {\n\tif (!value) return undefined;\n\treturn `${dim(label)} ${value}`;\n}\n\nexport class Header implements Component {\n\tconstructor(\n\t\tprivate readonly agentSession?: AgentSession,\n\t\tprivate readonly footerDataProvider?: ReadonlyFooterDataProvider,\n\t) {}\n\n\tprivate getData() {\n\t\tconst host = `${os.userInfo().username}@${os.hostname()}`;\n\t\tconst cwd = process.cwd();\n\t\tconst repo = this.footerDataProvider?.getGitRepoName() ?? undefined;\n\t\tconst branch = this.footerDataProvider?.getGitBranch() ?? undefined;\n\t\tconst workspace = this.agentSession?.sessionName;\n\n\t\treturn {\n\t\t\tbranch,\n\t\t\tcwd,\n\t\t\thost,\n\t\t\trepo,\n\t\t\tworkspace,\n\t\t};\n\t}\n\n\tprivate renderCompact(width: number): string[] {\n\t\tconst { branch, cwd, host, repo, workspace } = this.getData();\n\n\t\tconst line1 = truncateAnsi(`${bold(color(COLORS.title, TITLE))} ${dim(VERSION)}`, width);\n\t\tconst line2 = subtle(truncateAnsi(bulletJoin([maybePrefix(\"repo\", repo), maybePrefix(\"branch\", branch)]), width));\n\t\tconst line3 = subtle(\n\t\t\ttruncateAnsi(bulletJoin([maybePrefix(\"workspace\", workspace), maybePrefix(\"host\", host)]), width),\n\t\t);\n\t\tconst line4 = subtle(truncateAnsi(maybePrefix(\"cwd\", compactPath(cwd, Math.max(16, width - 8))) ?? \"\", width));\n\n\t\treturn [line1, line2, line3, line4];\n\t}\n\n\trender(width: number): string[] {\n\t\tif (width <= 0) return [\"\"];\n\t\tif (width < 72) return this.renderCompact(width);\n\n\t\tconst { branch, cwd, host, repo, workspace } = this.getData();\n\t\tconst logoWidth = visibleWidth(LOGO[0]);\n\t\tconst gap = 2;\n\t\tconst sidePadding = 1;\n\t\tconst minTextWidth = 12;\n\t\tconst maxTextWidth = Math.max(minTextWidth, width - logoWidth - gap - sidePadding * 2 - 2);\n\n\t\tconst baseRows = [\n\t\t\t`${bold(color(COLORS.title, TITLE))} ${dim(VERSION)}`,\n\t\t\tcolor(COLORS.title, bulletJoin([maybePrefix(\"repo\", repo), maybePrefix(\"branch\", branch)])),\n\t\t\tcolor(COLORS.title, bulletJoin([maybePrefix(\"workspace\", workspace), maybePrefix(\"host\", host)])),\n\t\t\t\"\",\n\t\t];\n\n\t\tconst textRows = [\n\t\t\t...baseRows,\n\t\t\tcolor(COLORS.title, maybePrefix(\"cwd\", compactPath(cwd, Math.max(18, maxTextWidth - 10))) ?? \"\"),\n\t\t\t\"\",\n\t\t];\n\t\tconst textWidth = Math.max(\n\t\t\tminTextWidth,\n\t\t\tMath.min(\n\t\t\t\tmaxTextWidth,\n\t\t\t\ttextRows.reduce((max, row) => Math.max(max, visibleWidth(row)), 0),\n\t\t\t),\n\t\t);\n\t\tconst innerWidth = logoWidth + gap + textWidth + sidePadding * 2;\n\n\t\tconst topBorder =\n\t\t\tcolor(COLORS.border, \"╭\") + color(COLORS.border, \"─\".repeat(innerWidth)) + color(COLORS.border, \"╮\");\n\n\t\tconst bottomBorder =\n\t\t\tcolor(COLORS.border, \"╰\") + color(COLORS.border, \"─\".repeat(innerWidth)) + color(COLORS.border, \"╯\");\n\n\t\tconst lines: string[] = [];\n\n\t\t// lines.push(\"\");\n\t\tlines.push(topBorder);\n\t\tfor (let i = 0; i < LOGO.length; i += 1) {\n\t\t\tconst logo = renderColoredLogoLine(LOGO[i]);\n\t\t\tconst text = padAnsi(truncateAnsi(textRows[i] ?? \"\", textWidth), textWidth);\n\n\t\t\tlines.push(`${color(COLORS.border, \"│\")} ${logo}${\" \".repeat(gap)}${text} ${color(COLORS.border, \"│\")}`);\n\t\t}\n\t\tlines.push(bottomBorder);\n\t\t// lines.push(\"\");\n\t\treturn lines.map((line) => truncateToWidth(line, width));\n\t}\n\n\tinvalidate(): void {}\n}\n\nexport default Header;\n"]}
|