@apholdings/jensen-code 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/core/agent-session.d.ts +1 -0
  2. package/dist/core/agent-session.d.ts.map +1 -1
  3. package/dist/core/agent-session.js +15 -0
  4. package/dist/core/agent-session.js.map +1 -1
  5. package/dist/core/extensions/loader.d.ts.map +1 -1
  6. package/dist/core/extensions/loader.js +1 -1
  7. package/dist/core/extensions/loader.js.map +1 -1
  8. package/dist/core/footer-data-provider.d.ts +4 -1
  9. package/dist/core/footer-data-provider.d.ts.map +1 -1
  10. package/dist/core/footer-data-provider.js +25 -11
  11. package/dist/core/footer-data-provider.js.map +1 -1
  12. package/dist/modes/interactive/components/assistant-message.d.ts +6 -1
  13. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  14. package/dist/modes/interactive/components/assistant-message.js +40 -10
  15. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  16. package/dist/modes/interactive/components/footer.d.ts +0 -2
  17. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  18. package/dist/modes/interactive/components/footer.js +8 -146
  19. package/dist/modes/interactive/components/footer.js.map +1 -1
  20. package/dist/modes/interactive/components/header.d.ts +9 -3
  21. package/dist/modes/interactive/components/header.d.ts.map +1 -1
  22. package/dist/modes/interactive/components/header.js +125 -196
  23. package/dist/modes/interactive/components/header.js.map +1 -1
  24. package/dist/modes/interactive/components/top-bar.d.ts.map +1 -1
  25. package/dist/modes/interactive/components/top-bar.js +1 -1
  26. package/dist/modes/interactive/components/top-bar.js.map +1 -1
  27. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  28. package/dist/modes/interactive/components/user-message.js +1 -1
  29. package/dist/modes/interactive/components/user-message.js.map +1 -1
  30. package/dist/modes/interactive/interactive-mode.d.ts +17 -1
  31. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  32. package/dist/modes/interactive/interactive-mode.js +507 -211
  33. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  34. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  35. package/dist/modes/interactive/theme/theme.js +2 -0
  36. package/dist/modes/interactive/theme/theme.js.map +1 -1
  37. package/examples/extensions/antigravity-image-gen.ts +0 -1
  38. package/examples/extensions/auto-commit-on-exit.ts +0 -1
  39. package/examples/extensions/bash-spawn-hook.ts +0 -1
  40. package/examples/extensions/bookmark.ts +0 -1
  41. package/examples/extensions/built-in-tool-renderer.ts +0 -1
  42. package/examples/extensions/claude-rules.ts +0 -1
  43. package/examples/extensions/commands.ts +0 -1
  44. package/examples/extensions/confirm-destructive.ts +0 -1
  45. package/examples/extensions/custom-compaction.ts +0 -1
  46. package/examples/extensions/custom-footer.ts +0 -1
  47. package/examples/extensions/custom-header.ts +0 -1
  48. package/examples/extensions/custom-provider-anthropic/index.ts +0 -1
  49. package/examples/extensions/custom-provider-gitlab-duo/index.ts +0 -1
  50. package/examples/extensions/custom-provider-qwen-cli/index.ts +0 -1
  51. package/examples/extensions/dirty-repo-guard.ts +0 -1
  52. package/examples/extensions/doom-overlay/index.ts +0 -1
  53. package/examples/extensions/dynamic-resources/index.ts +0 -1
  54. package/examples/extensions/dynamic-tools.ts +0 -1
  55. package/examples/extensions/event-bus.ts +0 -1
  56. package/examples/extensions/file-trigger.ts +0 -1
  57. package/examples/extensions/git-checkpoint.ts +0 -1
  58. package/examples/extensions/handoff.ts +0 -1
  59. package/examples/extensions/hello.ts +0 -1
  60. package/examples/extensions/inline-bash.ts +0 -1
  61. package/examples/extensions/input-transform.ts +0 -1
  62. package/examples/extensions/interactive-shell.ts +0 -1
  63. package/examples/extensions/mac-system-theme.ts +0 -1
  64. package/examples/extensions/message-renderer.ts +0 -1
  65. package/examples/extensions/minimal-mode.ts +0 -1
  66. package/examples/extensions/modal-editor.ts +0 -1
  67. package/examples/extensions/model-status.ts +0 -1
  68. package/examples/extensions/notify.ts +0 -1
  69. package/examples/extensions/overlay-qa-tests.ts +0 -1
  70. package/examples/extensions/overlay-test.ts +0 -1
  71. package/examples/extensions/permission-gate.ts +0 -1
  72. package/examples/extensions/pirate.ts +0 -1
  73. package/examples/extensions/plan-mode/index.ts +0 -1
  74. package/examples/extensions/preset.ts +0 -1
  75. package/examples/extensions/protected-paths.ts +0 -1
  76. package/examples/extensions/provider-payload.ts +0 -1
  77. package/examples/extensions/qna.ts +0 -1
  78. package/examples/extensions/question.ts +0 -1
  79. package/examples/extensions/questionnaire.ts +0 -1
  80. package/examples/extensions/rainbow-editor.ts +0 -1
  81. package/examples/extensions/reload-runtime.ts +0 -1
  82. package/examples/extensions/rpc-demo.ts +0 -1
  83. package/examples/extensions/sandbox/index.ts +0 -1
  84. package/examples/extensions/send-user-message.ts +0 -1
  85. package/examples/extensions/session-name.ts +0 -1
  86. package/examples/extensions/shutdown-command.ts +0 -1
  87. package/examples/extensions/snake.ts +0 -1
  88. package/examples/extensions/space-invaders.ts +0 -1
  89. package/examples/extensions/ssh.ts +0 -1
  90. package/examples/extensions/status-line.ts +0 -1
  91. package/examples/extensions/subagent/agents.ts +0 -1
  92. package/examples/extensions/subagent/index.ts +0 -1
  93. package/examples/extensions/summarize.ts +0 -1
  94. package/examples/extensions/system-prompt-header.ts +0 -1
  95. package/examples/extensions/timed-confirm.ts +0 -1
  96. package/examples/extensions/titlebar-spinner.ts +0 -1
  97. package/examples/extensions/todo.ts +0 -1
  98. package/examples/extensions/tool-override.ts +0 -1
  99. package/examples/extensions/tools.ts +0 -1
  100. package/examples/extensions/trigger-compact.ts +0 -1
  101. package/examples/extensions/truncated-tool.ts +0 -1
  102. package/examples/extensions/widget-placement.ts +0 -1
  103. package/examples/extensions/with-deps/index.ts +0 -1
  104. package/examples/sdk/01-minimal.ts +0 -1
  105. package/examples/sdk/02-custom-model.ts +0 -1
  106. package/examples/sdk/03-custom-prompt.ts +0 -1
  107. package/examples/sdk/04-skills.ts +0 -1
  108. package/examples/sdk/05-tools.ts +0 -1
  109. package/examples/sdk/06-extensions.ts +0 -1
  110. package/examples/sdk/07-context-files.ts +0 -1
  111. package/examples/sdk/08-prompt-templates.ts +0 -1
  112. package/examples/sdk/09-api-keys-and-oauth.ts +0 -1
  113. package/examples/sdk/10-settings.ts +0 -1
  114. package/examples/sdk/11-sessions.ts +0 -1
  115. package/examples/sdk/12-full-control.ts +0 -1
  116. package/package.json +4 -3
@@ -1 +1 @@
1
- {"version":3,"file":"assistant-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/assistant-message.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@apholdings/jensen-ai\";\nimport { Container, Markdown, type MarkdownTheme, Spacer, Text } from \"@apholdings/jensen-tui\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\n\n/**\n * Component that renders a complete assistant message\n */\nexport class AssistantMessageComponent extends Container {\n\tprivate contentContainer: Container;\n\tprivate hideThinkingBlock: boolean;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate lastMessage?: AssistantMessage;\n\n\tconstructor(\n\t\tmessage?: AssistantMessage,\n\t\thideThinkingBlock = false,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t) {\n\t\tsuper();\n\n\t\tthis.hideThinkingBlock = hideThinkingBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\n\t\t// Container for text/thinking content\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\tif (message) {\n\t\t\tthis.updateContent(message);\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tthis.hideThinkingBlock = hide;\n\t}\n\n\tupdateContent(message: AssistantMessage): void {\n\t\tthis.lastMessage = message;\n\n\t\t// Clear content container\n\t\tthis.contentContainer.clear();\n\n\t\tconst hasVisibleContent = message.content.some(\n\t\t\t(c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()),\n\t\t);\n\n\t\tif (hasVisibleContent) {\n\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t}\n\n\t\t// Render content in order\n\t\tfor (let i = 0; i < message.content.length; i++) {\n\t\t\tconst content = message.content[i];\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\t// Assistant text messages with no background - trim the text\n\t\t\t\t// Set paddingY=0 to avoid extra spacing before tool executions\n\t\t\t\tthis.contentContainer.addChild(new Markdown(content.text.trim(), 1, 0, this.markdownTheme));\n\t\t\t} else if (content.type === \"thinking\" && content.thinking.trim()) {\n\t\t\t\t// Add spacing only when another visible assistant content block follows.\n\t\t\t\t// This avoids a superfluous blank line before separately-rendered tool execution blocks.\n\t\t\t\tconst hasVisibleContentAfter = message.content\n\t\t\t\t\t.slice(i + 1)\n\t\t\t\t\t.some((c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()));\n\n\t\t\t\tif (this.hideThinkingBlock) {\n\t\t\t\t\t// Show static \"Thinking...\" label when hidden\n\t\t\t\t\tthis.contentContainer.addChild(new Text(theme.italic(theme.fg(\"thinkingText\", \"Thinking...\")), 1, 0));\n\t\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Thinking traces in thinkingText color, italic\n\t\t\t\t\tthis.contentContainer.addChild(\n\t\t\t\t\t\tnew Markdown(content.thinking.trim(), 1, 0, this.markdownTheme, {\n\t\t\t\t\t\t\tcolor: (text: string) => theme.fg(\"thinkingText\", text),\n\t\t\t\t\t\t\titalic: true,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check if aborted - show after partial content\n\t\t// But only if there are no tool calls (tool execution components will show the error)\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\t\tif (!hasToolCalls) {\n\t\t\tif (message.stopReason === \"aborted\") {\n\t\t\t\tconst abortMessage =\n\t\t\t\t\tmessage.errorMessage && message.errorMessage !== \"Request was aborted\"\n\t\t\t\t\t\t? message.errorMessage\n\t\t\t\t\t\t: \"Operation aborted\";\n\t\t\t\tif (hasVisibleContent) {\n\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t} else {\n\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t}\n\t\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"error\", abortMessage), 1, 0));\n\t\t\t} else if (message.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = message.errorMessage || \"Unknown error\";\n\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"error\", `Error: ${errorMsg}`), 1, 0));\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"assistant-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/assistant-message.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@apholdings/jensen-ai\";\nimport { Container, Markdown, type MarkdownTheme, Spacer, Text } from \"@apholdings/jensen-tui\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\n\n/**\n * Component that renders a complete assistant message\n */\nexport class AssistantMessageComponent extends Container {\n\tprivate contentContainer: Container;\n\tprivate hideThinkingBlock: boolean;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate lastMessage?: AssistantMessage;\n\tprivate topSpacing: number;\n\tprivate bottomSpacing: number;\n\n\tconstructor(\n\t\tmessage?: AssistantMessage,\n\t\thideThinkingBlock = false,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t\ttopSpacing = 1,\n\t\tbottomSpacing = 1,\n\t) {\n\t\tsuper();\n\n\t\tthis.hideThinkingBlock = hideThinkingBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.topSpacing = Math.max(0, topSpacing);\n\t\tthis.bottomSpacing = Math.max(0, bottomSpacing);\n\n\t\t// Container for text/thinking content\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\tif (message) {\n\t\t\tthis.updateContent(message);\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tthis.hideThinkingBlock = hide;\n\t}\n\n\tsetSpacing(topSpacing: number, bottomSpacing: number): void {\n\t\tthis.topSpacing = Math.max(0, topSpacing);\n\t\tthis.bottomSpacing = Math.max(0, bottomSpacing);\n\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetTopSpacing(spacing: number): void {\n\t\tthis.topSpacing = Math.max(0, spacing);\n\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetBottomSpacing(spacing: number): void {\n\t\tthis.bottomSpacing = Math.max(0, spacing);\n\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tupdateContent(message: AssistantMessage): void {\n\t\tthis.lastMessage = message;\n\n\t\t// Clear content container\n\t\tthis.contentContainer.clear();\n\n\t\tconst hasVisibleContent = message.content.some(\n\t\t\t(c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()),\n\t\t);\n\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\n\t\tlet renderedAnyBlock = false;\n\n\t\tif (hasVisibleContent && this.topSpacing > 0) {\n\t\t\tthis.contentContainer.addChild(new Spacer(this.topSpacing));\n\t\t}\n\n\t\t// Render content in order\n\t\tfor (let i = 0; i < message.content.length; i++) {\n\t\t\tconst content = message.content[i];\n\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\t// Assistant text messages with no background - trim the text\n\t\t\t\t// Set paddingY=0 to avoid extra spacing before tool executions\n\t\t\t\tthis.contentContainer.addChild(new Markdown(content.text.trim(), 1, 0, this.markdownTheme));\n\t\t\t\trenderedAnyBlock = true;\n\t\t\t} else if (content.type === \"thinking\" && content.thinking.trim()) {\n\t\t\t\t// Add spacing only when another visible assistant content block follows.\n\t\t\t\t// This avoids a superfluous blank line before separately-rendered tool execution blocks.\n\t\t\t\tconst hasVisibleContentAfter = message.content\n\t\t\t\t\t.slice(i + 1)\n\t\t\t\t\t.some((c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()));\n\n\t\t\t\tif (this.hideThinkingBlock) {\n\t\t\t\t\t// Show static \"Thinking...\" label when hidden\n\t\t\t\t\tthis.contentContainer.addChild(new Text(theme.italic(theme.fg(\"thinkingText\", \"Thinking...\")), 1, 0));\n\t\t\t\t\trenderedAnyBlock = true;\n\n\t\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Thinking traces in thinkingText color, italic\n\t\t\t\t\tthis.contentContainer.addChild(\n\t\t\t\t\t\tnew Markdown(content.thinking.trim(), 1, 0, this.markdownTheme, {\n\t\t\t\t\t\t\tcolor: (text: string) => theme.fg(\"thinkingText\", text),\n\t\t\t\t\t\t\titalic: true,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\trenderedAnyBlock = true;\n\n\t\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check if aborted - show after partial content\n\t\t// But only if there are no tool calls (tool execution components will show the error)\n\t\tif (!hasToolCalls) {\n\t\t\tif (message.stopReason === \"aborted\") {\n\t\t\t\tconst abortMessage =\n\t\t\t\t\tmessage.errorMessage && message.errorMessage !== \"Request was aborted\"\n\t\t\t\t\t\t? message.errorMessage\n\t\t\t\t\t\t: \"Operation aborted\";\n\n\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"error\", abortMessage), 1, 0));\n\t\t\t\trenderedAnyBlock = true;\n\t\t\t} else if (message.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = message.errorMessage || \"Unknown error\";\n\n\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"error\", `Error: ${errorMsg}`), 1, 0));\n\t\t\t\trenderedAnyBlock = true;\n\t\t\t}\n\t\t}\n\n\t\t// Only add bottom spacing for standalone assistant messages.\n\t\t// Do not add it when tool calls exist, because tool execution UI is rendered separately\n\t\t// and should remain visually attached to this assistant turn.\n\t\tif (!hasToolCalls && renderedAnyBlock && this.bottomSpacing > 0) {\n\t\t\tthis.contentContainer.addChild(new Spacer(this.bottomSpacing));\n\t\t}\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":"AAGA,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;AAwExF,qBAAa,eAAgB,YAAW,SAAS;IAK/C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IALnB,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAyB;IAEzC,YACS,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,0BAA0B,EAC3C;IAEJ,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,OAAO,IAAI,IAAI,CAEd;IAED,OAAO,CAAC,UAAU;IAuDlB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,sBAAsB;IAM9B,OAAO,CAAC,wBAAwB;IAMhC,OAAO,CAAC,sBAAsB;IAQ9B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAwF9B;CACD","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.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
- this.gitCache = null;
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
- let cwdLabel = shortenPath(cwd);
163
- const leftMetaParts = [
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 ellipsis = theme.fg("dim", "...");
180
- const minGap = 1;
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, theme.fg("dim", "...")));
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 { Component } from "@apholdings/jensen-tui";
2
- import type { ExtensionAPI } from "../../../core/extensions/index.js";
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 function jensenHeader(pi: ExtensionAPI): void;
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"]}