@pellux/goodvibes-tui 0.18.12 → 0.18.13

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 (157) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +2 -2
  5. package/src/config/index.ts +1 -138
  6. package/src/config/subscription-providers.ts +1 -127
  7. package/src/core/conversation-rendering.ts +3 -3
  8. package/src/core/conversation.ts +176 -423
  9. package/src/core/history.ts +45 -0
  10. package/src/core/orchestrator.ts +3 -735
  11. package/src/core/system-message-router.ts +19 -58
  12. package/src/input/handler-content-actions.ts +2 -2
  13. package/src/input/handler-feed.ts +1 -1
  14. package/src/input/handler-modal-token-routes.ts +1 -1
  15. package/src/input/handler-ui-state.ts +1 -1
  16. package/src/input/handler.ts +1 -1
  17. package/src/input/search.ts +1 -1
  18. package/src/input/selection.ts +2 -2
  19. package/src/main.ts +1 -1
  20. package/src/panels/agent-inspector-panel.ts +3 -3
  21. package/src/panels/agent-logs-panel.ts +3 -3
  22. package/src/panels/approval-panel.ts +2 -2
  23. package/src/panels/automation-control-panel.ts +3 -3
  24. package/src/panels/base-panel.ts +14 -14
  25. package/src/panels/builtin/operations.ts +1 -1
  26. package/src/panels/builtin/session.ts +1 -1
  27. package/src/panels/builtin/shared.ts +3 -3
  28. package/src/panels/cockpit-panel.ts +2 -2
  29. package/src/panels/communication-panel.ts +3 -3
  30. package/src/panels/context-visualizer-panel.ts +2 -2
  31. package/src/panels/control-plane-panel.ts +3 -3
  32. package/src/panels/cost-tracker-panel.ts +3 -3
  33. package/src/panels/debug-panel.ts +2 -2
  34. package/src/panels/diff-panel.ts +2 -2
  35. package/src/panels/docs-panel.ts +1 -1
  36. package/src/panels/eval-panel.ts +2 -2
  37. package/src/panels/file-explorer-panel.ts +3 -3
  38. package/src/panels/file-preview-panel.ts +3 -3
  39. package/src/panels/forensics-panel.ts +2 -2
  40. package/src/panels/git-panel.ts +1 -1
  41. package/src/panels/hooks-panel.ts +3 -3
  42. package/src/panels/incident-review-panel.ts +1 -1
  43. package/src/panels/intelligence-panel.ts +2 -2
  44. package/src/panels/knowledge-panel.ts +1 -1
  45. package/src/panels/local-auth-panel.ts +2 -2
  46. package/src/panels/marketplace-panel.ts +1 -1
  47. package/src/panels/mcp-panel.ts +3 -3
  48. package/src/panels/memory-panel.ts +1 -1
  49. package/src/panels/ops-control-panel.ts +3 -3
  50. package/src/panels/ops-strategy-panel.ts +2 -2
  51. package/src/panels/orchestration-panel.ts +2 -2
  52. package/src/panels/panel-list-panel.ts +6 -6
  53. package/src/panels/plan-dashboard-panel.ts +1 -1
  54. package/src/panels/plugins-panel.ts +2 -2
  55. package/src/panels/policy-panel.ts +2 -2
  56. package/src/panels/polish.ts +3 -3
  57. package/src/panels/provider-accounts-panel.ts +2 -2
  58. package/src/panels/provider-health-panel.ts +2 -2
  59. package/src/panels/provider-stats-panel.ts +3 -3
  60. package/src/panels/remote-panel.ts +3 -3
  61. package/src/panels/routes-panel.ts +3 -3
  62. package/src/panels/sandbox-panel.ts +2 -2
  63. package/src/panels/schedule-panel.ts +1 -1
  64. package/src/panels/security-panel.ts +2 -2
  65. package/src/panels/services-panel.ts +2 -2
  66. package/src/panels/session-browser-panel.ts +2 -2
  67. package/src/panels/settings-sync-panel.ts +2 -2
  68. package/src/panels/skills-panel.ts +6 -6
  69. package/src/panels/subscription-panel.ts +2 -2
  70. package/src/panels/symbol-outline-panel.ts +3 -3
  71. package/src/panels/system-messages-panel.ts +4 -4
  72. package/src/panels/tasks-panel.ts +2 -2
  73. package/src/panels/thinking-panel.ts +3 -3
  74. package/src/panels/token-budget-panel.ts +1 -1
  75. package/src/panels/tool-inspector-panel.ts +3 -3
  76. package/src/panels/types.ts +5 -5
  77. package/src/panels/watchers-panel.ts +3 -3
  78. package/src/panels/welcome-panel.ts +1 -1
  79. package/src/panels/worktree-panel.ts +2 -2
  80. package/src/panels/wrfc-panel.ts +3 -3
  81. package/src/permissions/prompt.ts +3 -22
  82. package/src/plugins/loader.ts +15 -304
  83. package/src/renderer/agent-detail-modal.ts +1 -1
  84. package/src/renderer/autocomplete-overlay.ts +2 -2
  85. package/src/renderer/bookmark-modal.ts +1 -1
  86. package/src/renderer/bottom-bar.ts +2 -2
  87. package/src/renderer/buffer.ts +1 -1
  88. package/src/renderer/code-block.ts +2 -2
  89. package/src/renderer/compositor.ts +2 -2
  90. package/src/renderer/context-inspector.ts +1 -1
  91. package/src/renderer/conversation-layout.ts +2 -2
  92. package/src/renderer/conversation-overlays.ts +1 -1
  93. package/src/renderer/conversation-surface.ts +2 -2
  94. package/src/renderer/diff-view.ts +2 -2
  95. package/src/renderer/diff.ts +1 -1
  96. package/src/renderer/file-picker-overlay.ts +2 -2
  97. package/src/renderer/file-tree.ts +2 -2
  98. package/src/renderer/help-overlay.ts +1 -1
  99. package/src/renderer/history-search-overlay.ts +2 -2
  100. package/src/renderer/live-tail-modal.ts +1 -1
  101. package/src/renderer/markdown.ts +2 -2
  102. package/src/renderer/modal-factory.ts +3 -3
  103. package/src/renderer/model-picker-overlay.ts +2 -2
  104. package/src/renderer/overlay-box.ts +2 -2
  105. package/src/renderer/panel-composite.ts +1 -1
  106. package/src/renderer/panel-picker-overlay.ts +2 -2
  107. package/src/renderer/panel-tab-bar.ts +1 -1
  108. package/src/renderer/panel-workspace-bar.ts +1 -1
  109. package/src/renderer/process-indicator.ts +2 -2
  110. package/src/renderer/process-modal.ts +1 -1
  111. package/src/renderer/profile-picker-modal.ts +2 -2
  112. package/src/renderer/progress.ts +2 -2
  113. package/src/renderer/search-overlay.ts +2 -2
  114. package/src/renderer/selection-modal-overlay.ts +2 -2
  115. package/src/renderer/session-picker-modal.ts +2 -2
  116. package/src/renderer/settings-modal.ts +2 -2
  117. package/src/renderer/shell-surface.ts +1 -1
  118. package/src/renderer/system-message.ts +1 -1
  119. package/src/renderer/tab-strip.ts +2 -2
  120. package/src/renderer/text-layout.ts +1 -1
  121. package/src/renderer/thinking.ts +1 -1
  122. package/src/renderer/tool-call.ts +2 -2
  123. package/src/renderer/ui-factory.ts +2 -2
  124. package/src/runtime/bootstrap-command-context.ts +4 -5
  125. package/src/runtime/bootstrap-command-parts.ts +1 -3
  126. package/src/runtime/bootstrap-core.ts +3 -2
  127. package/src/runtime/bootstrap-hook-bridge.ts +15 -174
  128. package/src/runtime/bootstrap-shell.ts +4 -4
  129. package/src/runtime/bootstrap.ts +1 -1
  130. package/src/runtime/context.ts +4 -20
  131. package/src/runtime/diagnostics/panels/index.ts +1 -1
  132. package/src/runtime/diagnostics/panels/ops.ts +1 -1
  133. package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
  134. package/src/runtime/perf/panel-contracts.ts +32 -0
  135. package/src/runtime/perf/panel-health-monitor.ts +18 -0
  136. package/src/runtime/services.ts +4 -4
  137. package/src/runtime/store/domains/conversation.ts +1 -181
  138. package/src/runtime/store/domains/permissions.ts +1 -143
  139. package/src/runtime/store/helpers/reducers/conversation.ts +1 -228
  140. package/src/runtime/store/helpers/reducers/lifecycle.ts +1 -440
  141. package/src/runtime/store/selectors/index.ts +11 -6
  142. package/src/runtime/store/state.ts +12 -4
  143. package/src/runtime/ui-events.ts +46 -0
  144. package/src/runtime/ui-services.ts +1 -1
  145. package/src/shell/ui-openers.ts +1 -1
  146. package/src/tools/index.ts +1 -186
  147. package/src/types/grid.ts +48 -0
  148. package/src/utils/clipboard.ts +21 -0
  149. package/src/utils/splash-lines.ts +1 -1
  150. package/src/utils/terminal-width.ts +185 -0
  151. package/src/version.ts +1 -1
  152. package/src/daemon/facade-composition.ts +0 -398
  153. package/src/daemon/facade.ts +0 -638
  154. package/src/daemon/surface-policy.ts +0 -60
  155. package/src/daemon/types.ts +0 -191
  156. package/src/runtime/ui-read-models-core.ts +0 -95
  157. package/src/runtime/ui-read-models-operations.ts +0 -203
@@ -1,186 +1 @@
1
- import { join } from 'node:path';
2
- import { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools/registry';
3
- import { FileStateCache } from '@pellux/goodvibes-sdk/platform/state/file-cache';
4
- import { ProjectIndex } from '@pellux/goodvibes-sdk/platform/state/project-index';
5
- import { ModeManager } from '@pellux/goodvibes-sdk/platform/state/mode-manager';
6
- import { HookDispatcher } from '@pellux/goodvibes-sdk/platform/hooks/dispatcher';
7
- import { FileUndoManager } from '@pellux/goodvibes-sdk/platform/state/file-undo';
8
- import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
9
- import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers/registry';
10
- import type { ToolLLM } from '@pellux/goodvibes-sdk/platform/config/tool-llm';
11
- import { ReadTool } from '@pellux/goodvibes-sdk/platform/tools/read/index';
12
- import { createWriteTool } from '@pellux/goodvibes-sdk/platform/tools/write/index';
13
- import { createEditTool } from '@pellux/goodvibes-sdk/platform/tools/edit/index';
14
- import { createFindTool } from '@pellux/goodvibes-sdk/platform/tools/find/index';
15
- import { createExecTool } from '@pellux/goodvibes-sdk/platform/tools/exec/index';
16
- import { createAnalyzeTool } from '@pellux/goodvibes-sdk/platform/tools/analyze/index';
17
- import { InspectTool } from '@pellux/goodvibes-sdk/platform/tools/inspect/index';
18
- import { createAgentTool } from '@pellux/goodvibes-sdk/platform/tools/agent/index';
19
- import { createFetchTool } from '@pellux/goodvibes-sdk/platform/tools/fetch/index';
20
- import { createStateTool } from '@pellux/goodvibes-sdk/platform/tools/state/index';
21
- import { createWorkflowServices, createWorkflowTool } from '@pellux/goodvibes-sdk/platform/tools/workflow/index';
22
- import { createRegistryTool } from '@pellux/goodvibes-sdk/platform/tools/registry-tool/index';
23
- import { KVState } from '@pellux/goodvibes-sdk/platform/state/kv-state';
24
- import { createTaskTool } from '@pellux/goodvibes-sdk/platform/tools/task/index';
25
- import { createTeamTool } from '@pellux/goodvibes-sdk/platform/tools/team/index';
26
- import { createWorklistTool } from '@pellux/goodvibes-sdk/platform/tools/worklist/index';
27
- import { createMcpTool } from '@pellux/goodvibes-sdk/platform/tools/mcp/index';
28
- import { createPacketTool } from '@pellux/goodvibes-sdk/platform/tools/packet/index';
29
- import { createQueryTool } from '@pellux/goodvibes-sdk/platform/tools/query/index';
30
- import { createRemoteTool } from '@pellux/goodvibes-sdk/platform/tools/remote-trigger/index';
31
- import { createReplTool } from '@pellux/goodvibes-sdk/platform/tools/repl/index';
32
- import { controlTool } from '@pellux/goodvibes-sdk/platform/tools/control/index';
33
- import { createChannelTool } from '@pellux/goodvibes-sdk/platform/tools/channel/index';
34
- import { createWebSearchTool } from '@pellux/goodvibes-sdk/platform/tools/web-search/index';
35
- import { ProcessManager } from '@pellux/goodvibes-sdk/platform/tools/shared/process-manager';
36
- import type { AgentManager } from '@pellux/goodvibes-sdk/platform/tools/agent/index';
37
- import { AgentMessageBus } from '@pellux/goodvibes-sdk/platform/agents/message-bus';
38
- import type { WrfcController } from '@pellux/goodvibes-sdk/platform/agents/wrfc-controller';
39
- import type { WebSearchService } from '@pellux/goodvibes-sdk/platform/web-search/index';
40
- import type { ChannelPluginRegistry } from '@pellux/goodvibes-sdk/platform/channels/index';
41
- import type { RemoteRunnerRegistry } from '@pellux/goodvibes-sdk/platform/runtime/remote/index';
42
- import { CrossSessionTaskRegistry } from '@pellux/goodvibes-sdk/platform/sessions/orchestration/index';
43
- import type { SandboxSessionRegistry } from '@pellux/goodvibes-sdk/platform/runtime/sandbox/session-registry';
44
- import type { FeatureFlagManager } from '@pellux/goodvibes-sdk/platform/runtime/feature-flags/index';
45
- import type { ServiceRegistry } from '../config/service-registry.ts';
46
- import { OverflowHandler } from '@pellux/goodvibes-sdk/platform/tools/shared/overflow';
47
- import type { SessionChangeTracker } from '@pellux/goodvibes-sdk/platform/sessions/change-tracker';
48
- import type { ArchetypeLoader } from '@pellux/goodvibes-sdk/platform/agents/archetypes';
49
-
50
- /**
51
- * Register all built-in tools into the given registry.
52
- * Creates shared FileStateCache and ProjectIndex instances so read/write/edit
53
- * tools share cache state within a session.
54
- */
55
- export function registerAllTools(
56
- registry: ToolRegistry,
57
- deps?: {
58
- fileCache?: FileStateCache;
59
- projectIndex?: ProjectIndex;
60
- fileUndoManager: FileUndoManager;
61
- modeManager: ModeManager;
62
- processManager: ProcessManager;
63
- agentManager?: AgentManager;
64
- agentMessageBus: AgentMessageBus;
65
- wrfcController?: WrfcController;
66
- webSearchService?: WebSearchService;
67
- channelRegistry?: ChannelPluginRegistry | null;
68
- remoteRunnerRegistry?: RemoteRunnerRegistry;
69
- workflowServices: ReturnType<typeof createWorkflowServices>;
70
- mcpRegistry?: import('@pellux/goodvibes-sdk/platform/mcp/registry').McpRegistry;
71
- sessionOrchestration?: CrossSessionTaskRegistry;
72
- sandboxSessionRegistry?: SandboxSessionRegistry;
73
- workingDirectory: string;
74
- archetypeLoader?: Pick<ArchetypeLoader, 'loadArchetype'>;
75
- configManager?: ConfigManager;
76
- providerRegistry?: ProviderRegistry;
77
- toolLLM?: ToolLLM;
78
- featureFlags?: Pick<FeatureFlagManager, 'isEnabled'> | null;
79
- serviceRegistry?: Pick<ServiceRegistry, 'resolveAuth'> | null;
80
- overflowHandler?: OverflowHandler;
81
- changeTracker?: SessionChangeTracker;
82
- },
83
- ): { fileCache: FileStateCache; projectIndex: ProjectIndex } {
84
- const fileCache = deps?.fileCache ?? new FileStateCache();
85
- if (!deps?.fileUndoManager || !deps?.modeManager || !deps?.processManager || !deps?.agentMessageBus || !deps?.workflowServices) {
86
- throw new Error('registerAllTools requires explicit fileUndoManager, modeManager, processManager, agentMessageBus, and workflowServices ownership.');
87
- }
88
- const fileUndoManager = deps.fileUndoManager;
89
- const modeManager = deps.modeManager;
90
- const processManager = deps.processManager;
91
- const agentManager = deps?.agentManager
92
- ?? (deps?.remoteRunnerRegistry
93
- ? (deps.remoteRunnerRegistry as unknown as { agentManager?: AgentManager | null }).agentManager ?? null
94
- : null);
95
- if (!agentManager) {
96
- throw new Error('registerAllTools requires agentManager');
97
- }
98
- const agentMessageBus = deps.agentMessageBus;
99
- const wrfcController = deps?.wrfcController;
100
- const archetypeLoader = deps?.archetypeLoader;
101
- const webSearchService = deps?.webSearchService;
102
- const channelRegistry = deps?.channelRegistry ?? null;
103
- const remoteRunnerRegistry = deps?.remoteRunnerRegistry;
104
- const workflowServices = deps.workflowServices;
105
- const mcpRegistry = deps?.mcpRegistry;
106
- if (!deps?.configManager || !deps?.providerRegistry || !deps?.toolLLM) {
107
- throw new Error('registerAllTools requires configManager, providerRegistry, and toolLLM');
108
- }
109
- if (!deps?.sandboxSessionRegistry) {
110
- throw new Error('registerAllTools requires sandboxSessionRegistry');
111
- }
112
- if (!deps?.sessionOrchestration) {
113
- throw new Error('registerAllTools requires sessionOrchestration');
114
- }
115
- const sessionOrchestration = deps.sessionOrchestration;
116
- const workingDirectory = deps?.workingDirectory;
117
- if (!workingDirectory) {
118
- throw new Error('registerAllTools requires workingDirectory');
119
- }
120
- const projectIndex = deps?.projectIndex ?? new ProjectIndex(workingDirectory);
121
-
122
- registry.register(new ReadTool(projectIndex, fileCache));
123
- registry.register(createWriteTool({
124
- projectRoot: workingDirectory,
125
- fileCache,
126
- projectIndex,
127
- fileUndoManager,
128
- configManager: deps.configManager,
129
- toolLLM: deps.toolLLM,
130
- changeTracker: deps?.changeTracker,
131
- }));
132
- registry.register(createEditTool(fileCache, {
133
- fileUndoManager,
134
- configManager: deps.configManager,
135
- toolLLM: deps.toolLLM,
136
- changeTracker: deps?.changeTracker,
137
- }));
138
- registry.register(createFindTool(workingDirectory, deps.featureFlags));
139
- registry.register(createExecTool(processManager, {
140
- featureFlags: deps.featureFlags,
141
- overflowHandler: deps.overflowHandler,
142
- }));
143
- registry.register(createAnalyzeTool(deps.toolLLM, deps.featureFlags, workingDirectory));
144
- registry.register(new InspectTool(deps.featureFlags, workingDirectory));
145
- registry.register(createAgentTool({
146
- manager: agentManager,
147
- messageBus: agentMessageBus,
148
- configManager: deps.configManager,
149
- ...(archetypeLoader ? { archetypeLoader } : {}),
150
- ...(wrfcController ? { wrfcController } : {}),
151
- }));
152
- const kvState = new KVState(undefined, workingDirectory);
153
- const hookDispatcher = new HookDispatcher();
154
- registry.register(createStateTool(kvState, projectIndex, {
155
- memoryDir: join(workingDirectory, '.goodvibes', 'memory'),
156
- hookDispatcher,
157
- modeManager,
158
- }));
159
- registry.register(createWorkflowTool(workflowServices));
160
- registry.register(createFetchTool({
161
- serviceRegistry: deps.serviceRegistry,
162
- featureFlags: deps.featureFlags,
163
- }));
164
- if (webSearchService) {
165
- registry.register(createWebSearchTool(webSearchService));
166
- }
167
- registry.register(createRegistryTool(registry, {
168
- workingDirectory,
169
- homeDirectory: deps.configManager.getHomeDirectory() ?? undefined,
170
- }));
171
- registry.register(createTaskTool(sessionOrchestration));
172
- registry.register(createTeamTool({ surfaceRoot: 'tui' }));
173
- registry.register(createWorklistTool({ surfaceRoot: 'tui' }));
174
- if (mcpRegistry) {
175
- registry.register(createMcpTool(mcpRegistry));
176
- }
177
- registry.register(createPacketTool(workingDirectory));
178
- registry.register(createQueryTool(workingDirectory));
179
- if (remoteRunnerRegistry) {
180
- registry.register(createRemoteTool(remoteRunnerRegistry));
181
- }
182
- registry.register(createReplTool(deps.configManager, deps.sandboxSessionRegistry, { surfaceRoot: 'tui' }));
183
- registry.register(controlTool);
184
- registry.register(createChannelTool(channelRegistry));
185
- return { fileCache, projectIndex };
186
- }
1
+ export { registerAllTools } from '@pellux/goodvibes-sdk/platform/tools/index';
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Cell - The atomic unit of the terminal surface grid.
3
+ */
4
+ export interface Cell {
5
+ char: string;
6
+ fg: string;
7
+ bg: string;
8
+ bold: boolean;
9
+ dim: boolean;
10
+ underline: boolean;
11
+ italic: boolean;
12
+ strikethrough: boolean;
13
+ link?: string;
14
+ }
15
+
16
+ /**
17
+ * Line - A single horizontal row of Cells.
18
+ */
19
+ export type Line = Cell[];
20
+
21
+ export const createEmptyCell = (): Cell => ({
22
+ char: ' ',
23
+ fg: '',
24
+ bg: '',
25
+ bold: false,
26
+ dim: false,
27
+ underline: false,
28
+ italic: false,
29
+ strikethrough: false,
30
+ });
31
+
32
+ export const createEmptyLine = (width: number): Line =>
33
+ Array.from({ length: width }, createEmptyCell);
34
+
35
+ /**
36
+ * createStyledCell - Create a Cell with all defaults, applying only the provided overrides.
37
+ */
38
+ export const createStyledCell = (char: string, overrides: Partial<Omit<Cell, 'char'>> = {}): Cell => ({
39
+ char,
40
+ fg: overrides.fg ?? '',
41
+ bg: overrides.bg ?? '',
42
+ bold: overrides.bold ?? false,
43
+ dim: overrides.dim ?? false,
44
+ underline: overrides.underline ?? false,
45
+ italic: overrides.italic ?? false,
46
+ strikethrough: overrides.strikethrough ?? false,
47
+ link: overrides.link,
48
+ });
@@ -0,0 +1,21 @@
1
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
2
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
3
+
4
+ /**
5
+ * copyToClipboard - Uses OSC 52 escape sequence to copy text to the terminal clipboard.
6
+ * Terminal-specific: only works in terminals that support OSC 52.
7
+ */
8
+ export function copyToClipboard(text: string) {
9
+ if (!text) return;
10
+ logger.info('Clipboard: Attempting to copy via OSC 52', { length: text.length });
11
+ try {
12
+ const base64 = Buffer.from(text).toString('base64');
13
+ const sequence = `\x1b]52;c;${base64}\x07`;
14
+ process.stdout.write(sequence);
15
+ logger.info('Clipboard: OSC 52 sequence written');
16
+ } catch (err: unknown) {
17
+ logger.error('Clipboard: OSC 52 copy failed', { error: summarizeError(err) });
18
+ }
19
+ }
20
+
21
+ export { pasteFromClipboard, pasteImageFromClipboard, MIN_IMAGE_BYTES } from '@pellux/goodvibes-sdk/platform/utils/clipboard';
@@ -1,4 +1,4 @@
1
- import { center, getDisplayWidth } from '@pellux/goodvibes-sdk/platform/utils/terminal-width';
1
+ import { center, getDisplayWidth } from './terminal-width.ts';
2
2
  import { VERSION } from '../version.ts';
3
3
 
4
4
  const ART_LINES = [
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Calculates the visual width of a string in the terminal.
3
+ * Handles CJK characters, emoji (including ZWJ sequences), and
4
+ * variation selectors correctly as double-width.
5
+ */
6
+ export function getDisplayWidth(text: string): number {
7
+ let width = 0;
8
+ let i = 0;
9
+ while (i < text.length) {
10
+ const code = text.codePointAt(i)!;
11
+ const charLen = code > 0xFFFF ? 2 : 1;
12
+
13
+ if (code < 32 || code === 127) {
14
+ i += charLen;
15
+ continue;
16
+ }
17
+
18
+ if (
19
+ code === 0x200d ||
20
+ code === 0xfe0f ||
21
+ code === 0xfe0e ||
22
+ (code >= 0x0300 && code <= 0x036f) ||
23
+ (code >= 0x1ab0 && code <= 0x1aff) ||
24
+ (code >= 0x20d0 && code <= 0x20ff) ||
25
+ (code >= 0xfe20 && code <= 0xfe2f) ||
26
+ (code >= 0xe0100 && code <= 0xe01ef)
27
+ ) {
28
+ i += charLen;
29
+ continue;
30
+ }
31
+
32
+ if (
33
+ code === 0x2713 ||
34
+ code === 0x2717 ||
35
+ code === 0x2714 ||
36
+ code === 0x2718 ||
37
+ code === 0x2022 ||
38
+ code === 0x258d ||
39
+ (code >= 0x2500 && code <= 0x257f)
40
+ ) {
41
+ width += 1;
42
+ i += charLen;
43
+ continue;
44
+ }
45
+
46
+ if (
47
+ (code >= 0x1f300 && code <= 0x1f9ff) ||
48
+ (code >= 0x1fa00 && code <= 0x1faff) ||
49
+ (code >= 0x2600 && code <= 0x27bf) ||
50
+ (code >= 0x2300 && code <= 0x23ff) ||
51
+ (code >= 0x2b50 && code <= 0x2b55) ||
52
+ (code >= 0xfe00 && code <= 0xfe0f) ||
53
+ (code >= 0x1f000 && code <= 0x1f02f) ||
54
+ (code >= 0x1f680 && code <= 0x1f6ff) ||
55
+ code === 0x200d ||
56
+ (code >= 0xe000 && code <= 0xf8ff) ||
57
+ code === 0x2764 || code === 0x2763 ||
58
+ code === 0x270a || code === 0x270b || code === 0x270c ||
59
+ code === 0x261d || code === 0x2639 || code === 0x263a
60
+ ) {
61
+ width += 2;
62
+ i += charLen;
63
+ continue;
64
+ }
65
+
66
+ if (
67
+ (code >= 0x1100 && code <= 0x115f) ||
68
+ (code >= 0x2e80 && code <= 0xa4cf && code !== 0x303f) ||
69
+ (code >= 0xac00 && code <= 0xd7a3) ||
70
+ (code >= 0xf900 && code <= 0xfaff) ||
71
+ (code >= 0xff00 && code <= 0xff60) ||
72
+ (code >= 0x20000 && code <= 0x2fffd) ||
73
+ (code >= 0x30000 && code <= 0x3fffd)
74
+ ) {
75
+ width += 2;
76
+ i += charLen;
77
+ continue;
78
+ }
79
+
80
+ width += 1;
81
+ i += charLen;
82
+ }
83
+ return width;
84
+ }
85
+
86
+ export function center(text: string, width: number): string {
87
+ const displayWidth = getDisplayWidth(text);
88
+ if (displayWidth >= width) return text;
89
+ const left = Math.floor((width - displayWidth) / 2);
90
+ return ' '.repeat(left) + text;
91
+ }
92
+
93
+ export function truncateDisplay(text: string, width: number, ellipsis = '…'): string {
94
+ if (width <= 0) return '';
95
+ if (getDisplayWidth(text) <= width) return text;
96
+ const ellipsisWidth = getDisplayWidth(ellipsis);
97
+ if (ellipsisWidth >= width) return truncateDisplay(ellipsis, width, '');
98
+
99
+ let result = '';
100
+ let currentWidth = 0;
101
+ for (const char of text) {
102
+ const charWidth = getDisplayWidth(char);
103
+ if (currentWidth + charWidth + ellipsisWidth > width) break;
104
+ result += char;
105
+ currentWidth += charWidth;
106
+ }
107
+ return result + ellipsis;
108
+ }
109
+
110
+ export function padDisplayEnd(text: string, width: number): string {
111
+ const currentWidth = getDisplayWidth(text);
112
+ if (currentWidth >= width) return text;
113
+ return text + ' '.repeat(width - currentWidth);
114
+ }
115
+
116
+ export function fitDisplay(text: string, width: number, ellipsis = '…'): string {
117
+ return padDisplayEnd(truncateDisplay(text, width, ellipsis), width);
118
+ }
119
+
120
+ export function wrapText(text: string, width: number): string[] {
121
+ if (width <= 0) return [text];
122
+ const lines: string[] = [];
123
+ const paragraphs = text.split('\n');
124
+
125
+ for (const paragraph of paragraphs) {
126
+ if (paragraph.length === 0) {
127
+ lines.push('');
128
+ continue;
129
+ }
130
+
131
+ const words = paragraph.split(' ');
132
+ let currentLine = '';
133
+
134
+ for (const word of words) {
135
+ const wordWidth = getDisplayWidth(word);
136
+ const currentLineWidth = getDisplayWidth(currentLine);
137
+
138
+ if (wordWidth > width) {
139
+ if (currentLine) lines.push(currentLine);
140
+ let remaining = word;
141
+ while (getDisplayWidth(remaining) > width) {
142
+ let splitIdx = 0;
143
+ let currentWidth = 0;
144
+ for (let i = 0; i < remaining.length; i++) {
145
+ const charWidth = getDisplayWidth(remaining[i]!);
146
+ if (currentWidth + charWidth > width) break;
147
+ currentWidth += charWidth;
148
+ splitIdx = i + 1;
149
+ }
150
+ lines.push(remaining.slice(0, splitIdx));
151
+ remaining = remaining.slice(splitIdx);
152
+ }
153
+ currentLine = remaining;
154
+ continue;
155
+ }
156
+
157
+ if ((currentLineWidth + wordWidth + (currentLine ? 1 : 0)) <= width) {
158
+ currentLine += `${currentLine ? ' ' : ''}${word}`;
159
+ } else {
160
+ lines.push(currentLine);
161
+ currentLine = word;
162
+ }
163
+ }
164
+
165
+ if (currentLine) lines.push(currentLine);
166
+ }
167
+
168
+ return lines;
169
+ }
170
+
171
+ export function interpolateColor(startHex: string, endHex: string, factor: number): string {
172
+ const parse = (hex: string) => {
173
+ const r = parseInt(hex.slice(1, 3), 16);
174
+ const g = parseInt(hex.slice(3, 5), 16);
175
+ const b = parseInt(hex.slice(5, 7), 16);
176
+ return [r, g, b];
177
+ };
178
+
179
+ const [r1, g1, b1] = parse(startHex);
180
+ const [r2, g2, b2] = parse(endHex);
181
+ const r = Math.round(r1 + factor * (r2 - r1));
182
+ const g = Math.round(g1 + factor * (g2 - g1));
183
+ const b = Math.round(b1 + factor * (b2 - b1));
184
+ return `${r};${g};${b}`;
185
+ }
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '0.18.12';
9
+ let _version = '0.18.13';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  _version = pkg.version ?? _version;