mistagent 0.1.19 → 0.1.20

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 (194) hide show
  1. package/dist/src/components/App.d.ts +2 -0
  2. package/dist/src/components/App.js +23 -5
  3. package/dist/src/components/Header.js +74 -166
  4. package/dist/src/components/MainContent.js +3 -14
  5. package/dist/src/components/shared/MarkdownRenderer.js +2 -17
  6. package/dist/src/components/shared/TextInput.js +62 -3
  7. package/dist/src/main.js +62 -4
  8. package/dist/src/types/api.d.ts +4 -0
  9. package/dist/src/utils/config.d.ts +2 -0
  10. package/dist/src/utils/config.js +23 -0
  11. package/dist/src/utils/constants.d.ts +1 -1
  12. package/dist/src/utils/constants.js +1 -1
  13. package/dist/src/utils/markdown.d.ts +10 -0
  14. package/dist/src/utils/markdown.js +223 -0
  15. package/dist/src/utils/updateChecker.js +10 -4
  16. package/package.json +3 -2
  17. package/dist/index.d.ts.map +0 -1
  18. package/dist/index.js.map +0 -1
  19. package/dist/src/api/auth.d.ts.map +0 -1
  20. package/dist/src/api/auth.js.map +0 -1
  21. package/dist/src/api/chat.d.ts.map +0 -1
  22. package/dist/src/api/chat.js.map +0 -1
  23. package/dist/src/api/client.d.ts.map +0 -1
  24. package/dist/src/api/client.js.map +0 -1
  25. package/dist/src/api/models.d.ts.map +0 -1
  26. package/dist/src/api/models.js.map +0 -1
  27. package/dist/src/api/sessions.d.ts.map +0 -1
  28. package/dist/src/api/sessions.js.map +0 -1
  29. package/dist/src/api/skills.d.ts.map +0 -1
  30. package/dist/src/api/skills.js.map +0 -1
  31. package/dist/src/api/tools.d.ts.map +0 -1
  32. package/dist/src/api/tools.js.map +0 -1
  33. package/dist/src/api/tunnel.d.ts.map +0 -1
  34. package/dist/src/api/tunnel.js.map +0 -1
  35. package/dist/src/components/App.d.ts.map +0 -1
  36. package/dist/src/components/App.js.map +0 -1
  37. package/dist/src/components/AppLayout.d.ts.map +0 -1
  38. package/dist/src/components/AppLayout.js.map +0 -1
  39. package/dist/src/components/Composer.d.ts.map +0 -1
  40. package/dist/src/components/Composer.js.map +0 -1
  41. package/dist/src/components/Footer.d.ts.map +0 -1
  42. package/dist/src/components/Footer.js.map +0 -1
  43. package/dist/src/components/Header.d.ts.map +0 -1
  44. package/dist/src/components/Header.js.map +0 -1
  45. package/dist/src/components/HistoryItemDisplay.d.ts.map +0 -1
  46. package/dist/src/components/HistoryItemDisplay.js.map +0 -1
  47. package/dist/src/components/InputPrompt.d.ts.map +0 -1
  48. package/dist/src/components/InputPrompt.js.map +0 -1
  49. package/dist/src/components/LoadingIndicator.d.ts.map +0 -1
  50. package/dist/src/components/LoadingIndicator.js.map +0 -1
  51. package/dist/src/components/LoginPrompt.d.ts.map +0 -1
  52. package/dist/src/components/LoginPrompt.js.map +0 -1
  53. package/dist/src/components/MainContent.d.ts.map +0 -1
  54. package/dist/src/components/MainContent.js.map +0 -1
  55. package/dist/src/components/ModelPicker.d.ts.map +0 -1
  56. package/dist/src/components/ModelPicker.js.map +0 -1
  57. package/dist/src/components/SessionPicker.d.ts.map +0 -1
  58. package/dist/src/components/SessionPicker.js.map +0 -1
  59. package/dist/src/components/SuggestionsDisplay.d.ts.map +0 -1
  60. package/dist/src/components/SuggestionsDisplay.js.map +0 -1
  61. package/dist/src/components/ThemePicker.d.ts.map +0 -1
  62. package/dist/src/components/ThemePicker.js.map +0 -1
  63. package/dist/src/components/messages/AssistantMessage.d.ts.map +0 -1
  64. package/dist/src/components/messages/AssistantMessage.js.map +0 -1
  65. package/dist/src/components/messages/CommandResult.d.ts.map +0 -1
  66. package/dist/src/components/messages/CommandResult.js.map +0 -1
  67. package/dist/src/components/messages/ErrorMessage.d.ts.map +0 -1
  68. package/dist/src/components/messages/ErrorMessage.js.map +0 -1
  69. package/dist/src/components/messages/InfoMessage.d.ts.map +0 -1
  70. package/dist/src/components/messages/InfoMessage.js.map +0 -1
  71. package/dist/src/components/messages/ModelMessage.d.ts.map +0 -1
  72. package/dist/src/components/messages/ModelMessage.js.map +0 -1
  73. package/dist/src/components/messages/SessionMessage.d.ts.map +0 -1
  74. package/dist/src/components/messages/SessionMessage.js.map +0 -1
  75. package/dist/src/components/messages/ToolCallMessage.d.ts.map +0 -1
  76. package/dist/src/components/messages/ToolCallMessage.js.map +0 -1
  77. package/dist/src/components/messages/UserMessage.d.ts.map +0 -1
  78. package/dist/src/components/messages/UserMessage.js.map +0 -1
  79. package/dist/src/components/shared/HorizontalLine.d.ts.map +0 -1
  80. package/dist/src/components/shared/HorizontalLine.js.map +0 -1
  81. package/dist/src/components/shared/MarkdownRenderer.d.ts.map +0 -1
  82. package/dist/src/components/shared/MarkdownRenderer.js.map +0 -1
  83. package/dist/src/components/shared/Spinner.d.ts.map +0 -1
  84. package/dist/src/components/shared/Spinner.js.map +0 -1
  85. package/dist/src/components/shared/TextInput.d.ts.map +0 -1
  86. package/dist/src/components/shared/TextInput.js.map +0 -1
  87. package/dist/src/contexts/AppContext.d.ts.map +0 -1
  88. package/dist/src/contexts/AppContext.js.map +0 -1
  89. package/dist/src/contexts/ChatContext.d.ts.map +0 -1
  90. package/dist/src/contexts/ChatContext.js.map +0 -1
  91. package/dist/src/contexts/KeypressContext.d.ts.map +0 -1
  92. package/dist/src/contexts/KeypressContext.js.map +0 -1
  93. package/dist/src/contexts/ModelContext.d.ts.map +0 -1
  94. package/dist/src/contexts/ModelContext.js.map +0 -1
  95. package/dist/src/contexts/SessionContext.d.ts.map +0 -1
  96. package/dist/src/contexts/SessionContext.js.map +0 -1
  97. package/dist/src/contexts/UIContext.d.ts.map +0 -1
  98. package/dist/src/contexts/UIContext.js.map +0 -1
  99. package/dist/src/hooks/useChat.d.ts.map +0 -1
  100. package/dist/src/hooks/useChat.js.map +0 -1
  101. package/dist/src/hooks/useFileCompletion.d.ts.map +0 -1
  102. package/dist/src/hooks/useFileCompletion.js.map +0 -1
  103. package/dist/src/hooks/useInputHistory.d.ts.map +0 -1
  104. package/dist/src/hooks/useInputHistory.js.map +0 -1
  105. package/dist/src/hooks/useKeypress.d.ts.map +0 -1
  106. package/dist/src/hooks/useKeypress.js.map +0 -1
  107. package/dist/src/hooks/useLoadingIndicator.d.ts.map +0 -1
  108. package/dist/src/hooks/useLoadingIndicator.js.map +0 -1
  109. package/dist/src/hooks/usePasteBuffer.d.ts.map +0 -1
  110. package/dist/src/hooks/usePasteBuffer.js.map +0 -1
  111. package/dist/src/hooks/useSlashCommand.d.ts.map +0 -1
  112. package/dist/src/hooks/useSlashCommand.js.map +0 -1
  113. package/dist/src/hooks/useStdinInterceptor.d.ts.map +0 -1
  114. package/dist/src/hooks/useStdinInterceptor.js.map +0 -1
  115. package/dist/src/hooks/useSymbolCompletion.d.ts.map +0 -1
  116. package/dist/src/hooks/useSymbolCompletion.js.map +0 -1
  117. package/dist/src/hooks/useTextBuffer.d.ts.map +0 -1
  118. package/dist/src/hooks/useTextBuffer.js.map +0 -1
  119. package/dist/src/main.d.ts.map +0 -1
  120. package/dist/src/main.js.map +0 -1
  121. package/dist/src/tools/code-analyzer/config/ignore-service.d.ts.map +0 -1
  122. package/dist/src/tools/code-analyzer/config/ignore-service.js.map +0 -1
  123. package/dist/src/tools/code-analyzer/config/supported-languages.d.ts.map +0 -1
  124. package/dist/src/tools/code-analyzer/config/supported-languages.js.map +0 -1
  125. package/dist/src/tools/code-analyzer/core/graph/graph.d.ts.map +0 -1
  126. package/dist/src/tools/code-analyzer/core/graph/graph.js.map +0 -1
  127. package/dist/src/tools/code-analyzer/core/graph/types.d.ts.map +0 -1
  128. package/dist/src/tools/code-analyzer/core/graph/types.js.map +0 -1
  129. package/dist/src/tools/code-analyzer/core/ingestion/ast-cache.d.ts.map +0 -1
  130. package/dist/src/tools/code-analyzer/core/ingestion/ast-cache.js.map +0 -1
  131. package/dist/src/tools/code-analyzer/core/ingestion/call-processor.d.ts.map +0 -1
  132. package/dist/src/tools/code-analyzer/core/ingestion/call-processor.js.map +0 -1
  133. package/dist/src/tools/code-analyzer/core/ingestion/community-processor.d.ts.map +0 -1
  134. package/dist/src/tools/code-analyzer/core/ingestion/community-processor.js.map +0 -1
  135. package/dist/src/tools/code-analyzer/core/ingestion/entry-point-scoring.d.ts.map +0 -1
  136. package/dist/src/tools/code-analyzer/core/ingestion/entry-point-scoring.js.map +0 -1
  137. package/dist/src/tools/code-analyzer/core/ingestion/filesystem-walker.d.ts.map +0 -1
  138. package/dist/src/tools/code-analyzer/core/ingestion/filesystem-walker.js.map +0 -1
  139. package/dist/src/tools/code-analyzer/core/ingestion/framework-detection.d.ts.map +0 -1
  140. package/dist/src/tools/code-analyzer/core/ingestion/framework-detection.js.map +0 -1
  141. package/dist/src/tools/code-analyzer/core/ingestion/heritage-processor.d.ts.map +0 -1
  142. package/dist/src/tools/code-analyzer/core/ingestion/heritage-processor.js.map +0 -1
  143. package/dist/src/tools/code-analyzer/core/ingestion/import-processor.d.ts.map +0 -1
  144. package/dist/src/tools/code-analyzer/core/ingestion/import-processor.js.map +0 -1
  145. package/dist/src/tools/code-analyzer/core/ingestion/parsing-processor.d.ts.map +0 -1
  146. package/dist/src/tools/code-analyzer/core/ingestion/parsing-processor.js.map +0 -1
  147. package/dist/src/tools/code-analyzer/core/ingestion/pipeline.d.ts.map +0 -1
  148. package/dist/src/tools/code-analyzer/core/ingestion/pipeline.js.map +0 -1
  149. package/dist/src/tools/code-analyzer/core/ingestion/process-processor.d.ts.map +0 -1
  150. package/dist/src/tools/code-analyzer/core/ingestion/process-processor.js.map +0 -1
  151. package/dist/src/tools/code-analyzer/core/ingestion/structure-processor.d.ts.map +0 -1
  152. package/dist/src/tools/code-analyzer/core/ingestion/structure-processor.js.map +0 -1
  153. package/dist/src/tools/code-analyzer/core/ingestion/symbol-table.d.ts.map +0 -1
  154. package/dist/src/tools/code-analyzer/core/ingestion/symbol-table.js.map +0 -1
  155. package/dist/src/tools/code-analyzer/core/ingestion/tree-sitter-queries.d.ts.map +0 -1
  156. package/dist/src/tools/code-analyzer/core/ingestion/tree-sitter-queries.js.map +0 -1
  157. package/dist/src/tools/code-analyzer/core/ingestion/utils.d.ts.map +0 -1
  158. package/dist/src/tools/code-analyzer/core/ingestion/utils.js.map +0 -1
  159. package/dist/src/tools/code-analyzer/core/ingestion/workers/parse-worker.d.ts.map +0 -1
  160. package/dist/src/tools/code-analyzer/core/ingestion/workers/parse-worker.js.map +0 -1
  161. package/dist/src/tools/code-analyzer/core/ingestion/workers/worker-pool.d.ts.map +0 -1
  162. package/dist/src/tools/code-analyzer/core/ingestion/workers/worker-pool.js.map +0 -1
  163. package/dist/src/tools/code-analyzer/core/tree-sitter/parser-loader.d.ts.map +0 -1
  164. package/dist/src/tools/code-analyzer/core/tree-sitter/parser-loader.js.map +0 -1
  165. package/dist/src/tools/code-analyzer/index.d.ts.map +0 -1
  166. package/dist/src/tools/code-analyzer/index.js.map +0 -1
  167. package/dist/src/tools/code-analyzer/lib/utils.d.ts.map +0 -1
  168. package/dist/src/tools/code-analyzer/lib/utils.js.map +0 -1
  169. package/dist/src/tools/code-analyzer/types/pipeline.d.ts.map +0 -1
  170. package/dist/src/tools/code-analyzer/types/pipeline.js.map +0 -1
  171. package/dist/src/types/api.d.ts.map +0 -1
  172. package/dist/src/types/api.js.map +0 -1
  173. package/dist/src/types/history.d.ts.map +0 -1
  174. package/dist/src/types/history.js.map +0 -1
  175. package/dist/src/utils/colors.d.ts.map +0 -1
  176. package/dist/src/utils/colors.js.map +0 -1
  177. package/dist/src/utils/config.d.ts.map +0 -1
  178. package/dist/src/utils/config.js.map +0 -1
  179. package/dist/src/utils/constants.d.ts.map +0 -1
  180. package/dist/src/utils/constants.js.map +0 -1
  181. package/dist/src/utils/fileRef.d.ts.map +0 -1
  182. package/dist/src/utils/fileRef.js.map +0 -1
  183. package/dist/src/utils/fileTunnel.d.ts.map +0 -1
  184. package/dist/src/utils/fileTunnel.js.map +0 -1
  185. package/dist/src/utils/formatters.d.ts.map +0 -1
  186. package/dist/src/utils/formatters.js.map +0 -1
  187. package/dist/src/utils/pasteUtils.d.ts.map +0 -1
  188. package/dist/src/utils/pasteUtils.js.map +0 -1
  189. package/dist/src/utils/skillScanner.d.ts.map +0 -1
  190. package/dist/src/utils/skillScanner.js.map +0 -1
  191. package/dist/src/utils/textUtils.d.ts.map +0 -1
  192. package/dist/src/utils/textUtils.js.map +0 -1
  193. package/dist/src/utils/updateChecker.d.ts.map +0 -1
  194. package/dist/src/utils/updateChecker.js.map +0 -1
@@ -18,6 +18,8 @@ interface AppProps {
18
18
  loginError: string | null;
19
19
  isAuthenticated: boolean;
20
20
  initialTheme: ThemeMode;
21
+ terminalWidth: number;
22
+ terminalHeight: number;
21
23
  }
22
24
  export declare const App: React.FC<AppProps>;
23
25
  export {};
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useState, useCallback } from 'react';
2
+ import { useState, useCallback, useEffect } from 'react';
3
+ import { useStdout } from 'ink';
3
4
  import { AppStateContext, AppActionsContext, } from '../contexts/AppContext.js';
4
5
  import { ChatStateContext, ChatDispatchContext, useChatReducer, } from '../contexts/ChatContext.js';
5
6
  import { UIStateContext, UIActionsContext, } from '../contexts/UIContext.js';
@@ -30,15 +31,32 @@ const AppInner = ({ onLogin, loginError, isAuthenticated, authEnabled }) => {
30
31
  }
31
32
  return _jsx(AppLayout, { onSubmit: handleSubmit, onCancel: cancelStream, onConfirmPlan: confirmPlan, onResolveToolApproval: resolveToolApproval });
32
33
  };
33
- export const App = ({ serverUrl, token, username, authEnabled, version, healthData, availableCommands, availableTools, initialModels, initialCurrentModel, initialSessions, onLogin, onLogout, loginError, isAuthenticated, initialTheme, }) => {
34
+ export const App = ({ serverUrl, token, username, authEnabled, version, healthData, availableCommands, availableTools, initialModels, initialCurrentModel, initialSessions, onLogin, onLogout, loginError, isAuthenticated, initialTheme, terminalWidth: initialTerminalWidth, terminalHeight: initialTerminalHeight, }) => {
35
+ const { stdout } = useStdout();
34
36
  const [appToken, setAppToken] = useState(token);
35
37
  const [appUsername, setAppUsername] = useState(username);
36
38
  const [chatState, chatDispatch] = useChatReducer();
37
- // Terminal size
38
- const [terminalWidth] = useState(process.stdout.columns || 80);
39
- const [terminalHeight] = useState(process.stdout.rows || 24);
40
39
  const [activeDialog, setActiveDialog] = useState('none');
41
40
  const [theme, setThemeState] = useState(initialTheme);
41
+ // Track terminal size — updated by both:
42
+ // 1. stdout 'resize' events (primary: fired by the OS → Node TTY → Ink's stdout)
43
+ // 2. A polling fallback every 200ms (catches any missed events)
44
+ const [terminalWidth, setTerminalWidth] = useState(() => stdout.columns || process.stdout.columns || initialTerminalWidth);
45
+ const [terminalHeight, setTerminalHeight] = useState(() => stdout.rows || process.stdout.rows || initialTerminalHeight);
46
+ useEffect(() => {
47
+ const readSize = () => {
48
+ setTerminalWidth(stdout.columns || process.stdout.columns || 80);
49
+ setTerminalHeight(stdout.rows || process.stdout.rows || 24);
50
+ };
51
+ stdout.on('resize', readSize);
52
+ process.stdout.on('resize', readSize);
53
+ const poll = setInterval(readSize, 200);
54
+ return () => {
55
+ stdout.off('resize', readSize);
56
+ process.stdout.off('resize', readSize);
57
+ clearInterval(poll);
58
+ };
59
+ }, [stdout]);
42
60
  const appState = {
43
61
  serverUrl,
44
62
  token: appToken,
@@ -8,12 +8,7 @@ import { useSessionState } from '../contexts/SessionContext.js';
8
8
  import { useChatState } from '../contexts/ChatContext.js';
9
9
  import { useAppState } from '../contexts/AppContext.js';
10
10
  import { MASCOT_LINES, VERSION, TIPS, getMascotGradient } from '../utils/constants.js';
11
- // ── Catppuccin tokens (matching pencil variables) ──
12
11
  const C = {
13
- bg: '#1E1E2E',
14
- bgDark: '#181825',
15
- bgDeep: '#11111B',
16
- surface1: '#313244',
17
12
  border: '#585B70',
18
13
  dimtext: '#6C7086',
19
14
  subtext: '#A6ADC8',
@@ -24,185 +19,98 @@ const C = {
24
19
  teal: '#94E2D5',
25
20
  green: '#A6E3A1',
26
21
  red: '#F38BA8',
27
- peach: '#FAB387',
28
- rose: '#E05A4E',
29
22
  yellow: '#F9E2AF',
30
23
  };
31
- // ── Shortcuts (matching pencil: full key names) ──
32
- const SHORTCUTS = [
33
- { key: '/', desc: '命令' },
34
- { key: '@', desc: '文件' },
35
- { key: '@@', desc: '符号' },
36
- { key: 'Shift+Tab', desc: '模式' },
37
- { key: 'Shift+Enter', desc: '多行' },
38
- ];
39
- // ── Helper functions ──
40
- const pad = (s, width) => {
41
- const len = stringWidth(s);
42
- if (len >= width)
43
- return s;
44
- return s + ' '.repeat(width - len);
45
- };
46
- const centerPad = (s, width) => {
47
- const len = stringWidth(s);
48
- if (len >= width)
49
- return s;
50
- const left = Math.floor((width - len) / 2);
51
- const right = width - len - left;
52
- return ' '.repeat(left) + s + ' '.repeat(right);
53
- };
54
- const truncate = (s, maxLen) => {
55
- if (maxLen <= 0)
24
+ // Three-tier breakpoints
25
+ // col1(mascot)=35 + div=1 + col2(stats)=26 + div=1 + col3(min~10) + border+pad~6 = ~79 minimum
26
+ // Use 90 as safe threshold so the 3-column layout never gets squeezed
27
+ const CONDENSED_MAX = 40; // <40 → single line
28
+ const COMPACT_MAX = 90; // <90 → stacked compact
29
+ // ≥90 → full 3-column
30
+ function truncate(s, max) {
31
+ if (max <= 0)
56
32
  return '';
57
- if (stringWidth(s) <= maxLen)
33
+ if (stringWidth(s) <= max)
58
34
  return s;
59
- let result = '';
35
+ let out = '';
60
36
  for (const ch of s) {
61
- if (stringWidth(result + ch) > maxLen - 1)
37
+ if (stringWidth(out + ch) > max - 1)
62
38
  break;
63
- result += ch;
39
+ out += ch;
64
40
  }
65
- return result + '…';
41
+ return out + '…';
42
+ }
43
+ // ─────────────────────────────────────────────
44
+ // Tier 1 — Condensed (<40 cols)
45
+ // Single row: ❯ MIST v0.x model ●
46
+ // ─────────────────────────────────────────────
47
+ const CondensedHeader = ({ modelName, isConnected, version }) => (_jsxs(Box, { flexDirection: "row", gap: 1, marginTop: 1, children: [_jsx(Text, { color: C.blue, bold: true, children: '❯' }), _jsx(Text, { color: C.text, bold: true, children: 'MIST' }), _jsx(Text, { color: C.dimtext, children: `v${version}` }), _jsx(Text, { color: C.sapphire, children: modelName }), _jsx(Text, { color: isConnected ? C.green : C.red, children: '●' })] }));
48
+ // ─────────────────────────────────────────────
49
+ // Tier 2 — Compact (40–69 cols)
50
+ // Stacked: brand + model + status + shortcuts
51
+ // ─────────────────────────────────────────────
52
+ const CompactHeader = ({ modelName, isConnected, modeLabel, theme, version }) => {
53
+ const themeIcon = theme === 'dark' ? '◐' : '◑';
54
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: C.border, paddingX: 1, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Text, { color: C.blue, bold: true, children: `❯ MIST v${version}` }), _jsx(Text, { color: isConnected ? C.green : C.red, children: isConnected ? '● ON' : '○ OFF' })] }), _jsx(Text, { color: C.sapphire, children: `model: ${modelName}` }), _jsx(Text, { color: C.dimtext, children: `mode: ${modeLabel} ${themeIcon} ${theme}` }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: C.dimtext, children: '[/] cmd [@] file [Shift+Tab] mode' }) })] }));
55
+ };
56
+ // ─────────────────────────────────────────────
57
+ // Tier 3 — Horizontal (≥70 cols)
58
+ // Full 3-column with Ink flexbox (no manual widths)
59
+ // ─────────────────────────────────────────────
60
+ const MascotLogo = () => {
61
+ const gradient = getMascotGradient();
62
+ return (_jsxs(Box, { flexDirection: "column", alignItems: "center", children: [MASCOT_LINES.map((line, i) => (_jsx(Text, { color: gradient[i] ?? gradient[gradient.length - 1], bold: true, children: line }, i))), _jsx(Text, { color: C.dimtext, children: 'A G E N T' })] }));
66
63
  };
67
- // ── Border chars (dashed style) ──
68
- const B = {
69
- topLeft: '╭',
70
- topRight: '',
71
- bottomLeft: '',
72
- bottomRight: '',
73
- h: '', // dashed horizontal
74
- v: '┊', // dashed vertical
64
+ const StatsPanel = ({ modelName, sessionCount, modeLabel, theme }) => {
65
+ const themeIcon = theme === 'dark' ? '◐' : '◑';
66
+ const rows = [
67
+ { label: 'model', value: truncate(modelName, 18), color: C.sapphire },
68
+ { label: 'sessions', value: String(sessionCount), color: C.teal },
69
+ { label: 'mode', value: modeLabel, color: modeLabel === 'plan' ? C.mauve : C.green },
70
+ { label: 'theme', value: `${themeIcon} ${theme}`, color: C.mauve },
71
+ ];
72
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: C.subtext, children: '// stats' }), rows.map(r => (_jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(Box, { width: 10, children: _jsx(Text, { color: C.dimtext, children: r.label }) }), _jsx(Text, { color: r.color, bold: true, children: r.value })] }, r.label)))] }));
73
+ };
74
+ const ActivityPanel = ({ tip, sessions }) => {
75
+ const dotColors = [C.green, C.blue, C.mauve];
76
+ const recent = sessions.slice(0, 3);
77
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: C.subtext, children: '// tip' }), _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(Text, { color: C.green, bold: true, children: '$ ' }), _jsx(Text, { color: C.text, children: tip })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: C.subtext, children: `// recent (${sessions.length})` }) }), recent.length === 0 ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: C.dimtext, children: 'No recent activity' }) })) : (recent.map((s, i) => {
78
+ const d = new Date(s.last_activity);
79
+ const t = d.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
80
+ return (_jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(Text, { color: C.dimtext, children: `${t} ` }), _jsx(Text, { color: dotColors[i % dotColors.length], children: '● ' }), _jsx(Text, { color: C.subtext, children: truncate(s.title || 'New Chat', 25) })] }, i));
81
+ }))] }));
75
82
  };
76
- // ── Dashed divider (orange, matching outer border) ──
77
- const DashedDivider = ({ width }) => (_jsx(Text, { color: C.yellow, children: B.h.repeat(width) }));
78
- // ── Empty row (full inner width, bordered) ──
79
- const EmptyRow = ({ width }) => (_jsxs(Text, { children: [_jsx(Text, { color: C.yellow, children: B.v }), _jsx(Text, { children: ' '.repeat(width) }), _jsx(Text, { color: C.yellow, children: B.v })] }));
83
+ const SHORTCUTS = [
84
+ { key: '/', desc: '命令' },
85
+ { key: '@', desc: '文件' },
86
+ { key: '@@', desc: '符号' },
87
+ { key: 'Shift+Tab', desc: '模式' },
88
+ { key: 'Shift+↵', desc: '多行' },
89
+ ];
90
+ const HorizontalHeader = ({ modelName, isConnected, sessionCount, modeLabel, theme, tip, sessions, version }) => {
91
+ const netColor = isConnected ? C.green : C.red;
92
+ const netBadge = isConnected ? '● CONNECTED' : '○ OFFLINE';
93
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: C.border, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", paddingX: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: C.blue, bold: true, children: '❯ ' }), _jsx(Text, { color: C.text, bold: true, children: 'MIST AGENT ' }), _jsx(Text, { color: C.mauve, bold: true, children: `[v${version}]` })] }), _jsx(Text, { color: netColor, children: `[ ${netBadge} ]` })] }), _jsx(Box, { borderStyle: "classic", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: C.border }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexShrink: 0, paddingX: 2, paddingY: 1, children: _jsx(MascotLogo, {}) }), _jsx(Box, { borderStyle: "classic", borderLeft: true, borderTop: false, borderBottom: false, borderRight: false, borderColor: C.border }), _jsx(Box, { flexShrink: 0, width: 26, paddingX: 1, paddingY: 1, children: _jsx(StatsPanel, { modelName: modelName, sessionCount: sessionCount, modeLabel: modeLabel, theme: theme }) }), _jsx(Box, { borderStyle: "classic", borderLeft: true, borderTop: false, borderBottom: false, borderRight: false, borderColor: C.border }), _jsx(Box, { flexGrow: 1, flexShrink: 1, paddingX: 1, paddingY: 1, overflow: "hidden", children: _jsx(ActivityPanel, { tip: tip, sessions: sessions }) })] }), _jsx(Box, { borderStyle: "classic", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: C.border }), _jsx(Box, { flexDirection: "row", justifyContent: "center", gap: 3, paddingX: 1, paddingY: 1, children: SHORTCUTS.map(s => (_jsxs(Text, { color: C.dimtext, children: [_jsx(Text, { color: C.subtext, children: `[${s.key}]` }), ` ${s.desc}`] }, s.key))) })] }));
94
+ };
95
+ // ─────────────────────────────────────────────
96
+ // Root export — picks tier based on terminalWidth
97
+ // ─────────────────────────────────────────────
80
98
  export const Header = () => {
81
99
  const { currentModel } = useModelState();
82
100
  const { terminalWidth, theme } = useUIState();
83
101
  const { sessions } = useSessionState();
84
102
  const { forceMode } = useChatState();
85
103
  const { healthData } = useAppState();
86
- const modelName = currentModel?.model ?? 'MistAgent 0.1';
104
+ const modelName = currentModel?.model ?? 'MistAgent';
87
105
  const isConnected = !!healthData;
88
- const innerWidth = terminalWidth - 2;
89
- // ── Column widths ──
90
- const mascotWidth = Math.max(...MASCOT_LINES.map(l => stringWidth(l)));
91
- const logoColWidth = mascotWidth + 8;
92
- const statsColWidth = 28;
93
- const infoColWidth = Math.max(10, innerWidth - logoColWidth - statsColWidth - 2);
94
- // ── Random tip (stable per mount) ──
95
- const [tipText] = React.useState(() => TIPS[Math.floor(Math.random() * TIPS.length)]);
96
- const mascotGradient = getMascotGradient();
97
- // ── Stats data ──
98
106
  const modeLabel = forceMode === 'plan' ? 'plan' : 'chat';
99
- const themeIcon = theme === 'dark' ? '◐' : '◑';
100
- const statsData = [
101
- { label: 'model', value: modelName, valueColor: C.sapphire },
102
- { label: 'sessions', value: String(sessions.length), valueColor: C.teal },
103
- { label: 'mode', value: modeLabel, valueColor: forceMode === 'plan' ? C.mauve : C.green },
104
- { label: 'theme', value: `${themeIcon} ${theme}`, valueColor: C.mauve },
105
- ];
106
- // ── Recent sessions ──
107
- const recentSessions = sessions.slice(0, 3);
108
- const dotColors = [C.green, C.blue, C.mauve];
109
- const emptyLogo = () => _jsx(Text, { children: ' '.repeat(logoColWidth) });
110
- const emptyStats = () => _jsx(Text, { children: ' '.repeat(statsColWidth) });
111
- const emptyInfo = () => _jsx(Text, { children: ' '.repeat(infoColWidth) });
112
- // Col 1: Logo — top padding(2) + mascot(6) + gap + subtitle + bottom padding(2)
113
- const logoRows = [
114
- emptyLogo, // top padding 1
115
- emptyLogo, // top padding 2
116
- ...MASCOT_LINES.map((line, i) => () => {
117
- const color = mascotGradient[i] ?? mascotGradient[mascotGradient.length - 1];
118
- return _jsx(Text, { color: color, bold: true, children: centerPad(line, logoColWidth) });
119
- }),
120
- emptyLogo, // gap before subtitle
121
- () => _jsx(Text, { color: C.dimtext, children: centerPad('A G E N T', logoColWidth) }),
122
- emptyLogo, // bottom padding 1
123
- emptyLogo, // bottom padding 2
124
- ];
125
- // Col 2: Stats — top padding(2) + header + gap + 4 rows (with gaps) + bottom padding
126
- const statsRows = [
127
- emptyStats, // top padding 1
128
- emptyStats, // top padding 2
129
- () => _jsx(Text, { color: C.subtext, children: pad(' // quick stats', statsColWidth) }),
130
- emptyStats, // gap after header
131
- ...statsData.flatMap((s, i) => {
132
- const row = () => {
133
- const lbl = ` ${s.label}`;
134
- const val = `${s.value} `;
135
- const gap = Math.max(1, statsColWidth - stringWidth(lbl) - stringWidth(val));
136
- return (_jsxs(Text, { children: [_jsx(Text, { color: C.dimtext, children: lbl }), _jsx(Text, { children: ' '.repeat(gap) }), _jsx(Text, { color: s.valueColor, bold: true, children: val })] }));
137
- };
138
- // Add empty row between stat rows (not after the last one)
139
- return i < statsData.length - 1 ? [row, emptyStats] : [row];
140
- }),
141
- emptyStats, // bottom padding 1
142
- emptyStats, // bottom padding 2
143
- ];
144
- // Col 3: Info — top padding(2) + tip section + gap + recent section + bottom padding
145
- const infoRows = [
146
- emptyInfo, // top padding
147
- () => _jsx(Text, { color: C.subtext, children: pad(' // daily tip', infoColWidth) }),
148
- emptyInfo, // gap after tip label
149
- () => {
150
- const tipPrefix = ' $ ';
151
- const tipContent = truncate(tipText, infoColWidth - stringWidth(tipPrefix));
152
- return (_jsxs(Text, { children: [_jsx(Text, { color: C.green, bold: true, children: tipPrefix }), _jsx(Text, { color: C.text, children: pad(tipContent, infoColWidth - stringWidth(tipPrefix)) })] }));
153
- },
154
- emptyInfo, // gap between sections
155
- emptyInfo,
156
- () => _jsx(Text, { color: C.subtext, children: pad(' // recent activity (' + sessions.length + ')', infoColWidth) }),
157
- emptyInfo, // gap after recent label
158
- ];
159
- if (recentSessions.length > 0) {
160
- for (const [idx, s] of recentSessions.entries()) {
161
- const dotColor = dotColors[idx % dotColors.length];
162
- infoRows.push(() => {
163
- const date = new Date(s.last_activity);
164
- const timeStr = date.toLocaleString('zh-CN', {
165
- month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit',
166
- });
167
- const title = s.title || 'New Chat';
168
- const timePrefix = ` ${timeStr} `;
169
- const dotStr = '● ';
170
- const usedWidth = stringWidth(timePrefix) + stringWidth(dotStr);
171
- const titleWidth = Math.max(0, infoColWidth - usedWidth);
172
- const displayTitle = pad(truncate(title, titleWidth), titleWidth);
173
- return (_jsxs(Text, { children: [_jsx(Text, { color: C.dimtext, children: timePrefix }), _jsx(Text, { color: dotColor, children: dotStr }), _jsx(Text, { color: C.subtext, children: displayTitle })] }));
174
- });
175
- // Add empty row between recent items (not after the last one)
176
- if (idx < recentSessions.length - 1) {
177
- infoRows.push(emptyInfo);
178
- }
179
- }
107
+ const [tip] = React.useState(() => TIPS[Math.floor(Math.random() * TIPS.length)]);
108
+ if (terminalWidth < CONDENSED_MAX) {
109
+ return _jsx(CondensedHeader, { modelName: modelName, isConnected: isConnected, version: VERSION });
180
110
  }
181
- else {
182
- infoRows.push(() => _jsx(Text, { color: C.dimtext, children: pad(' No recent activity', infoColWidth) }));
111
+ if (terminalWidth < COMPACT_MAX) {
112
+ return (_jsx(CompactHeader, { modelName: modelName, isConnected: isConnected, modeLabel: modeLabel, theme: theme, version: VERSION }));
183
113
  }
184
- // Equalize row count
185
- const maxRows = Math.max(logoRows.length, statsRows.length, infoRows.length);
186
- while (logoRows.length < maxRows)
187
- logoRows.push(emptyLogo);
188
- while (statsRows.length < maxRows)
189
- statsRows.push(emptyStats);
190
- while (infoRows.length < maxRows)
191
- infoRows.push(emptyInfo);
192
- // ── Top bar layout ──
193
- const netColor = isConnected ? C.green : C.red;
194
- return (_jsxs(Box, { flexDirection: "column", width: terminalWidth, marginTop: 1, children: [_jsx(Text, { children: _jsxs(Text, { color: C.yellow, children: [B.topLeft, B.h.repeat(innerWidth), B.topRight] }) }), _jsx(EmptyRow, { width: innerWidth }), (() => {
195
- const leftPad = ' ';
196
- const prompt = '❯';
197
- const brand = ' M I S T A G E N T ';
198
- const verBadge = `[ v${VERSION} ]`;
199
- const netBadge = isConnected ? '[ ● CONNECTED ]' : '[ ○ OFFLINE ]';
200
- const leftStr = `${leftPad}${prompt}${brand}${verBadge}`;
201
- const rightStr = `${netBadge} `;
202
- const gap = Math.max(1, innerWidth - stringWidth(leftStr) - stringWidth(rightStr));
203
- return (_jsxs(Text, { children: [_jsx(Text, { color: C.yellow, children: B.v }), _jsx(Text, { children: leftPad }), _jsx(Text, { color: C.blue, bold: true, children: prompt }), _jsx(Text, { color: C.text, bold: true, children: brand }), _jsx(Text, { color: C.mauve, bold: true, children: verBadge }), _jsx(Text, { children: ' '.repeat(gap) }), _jsx(Text, { color: netColor, children: netBadge }), _jsx(Text, { children: ' ' }), _jsx(Text, { color: C.yellow, children: B.v })] }));
204
- })(), _jsx(EmptyRow, { width: innerWidth }), _jsxs(Text, { children: [_jsx(Text, { color: C.yellow, children: B.v }), _jsx(DashedDivider, { width: innerWidth }), _jsx(Text, { color: C.yellow, children: B.v })] }), Array.from({ length: maxRows }).map((_, i) => {
205
- return (_jsxs(Text, { children: [_jsx(Text, { color: C.yellow, children: B.v }), logoRows[i](), _jsx(Text, { color: C.yellow, children: "\u2502" }), statsRows[i](), _jsx(Text, { color: C.yellow, children: "\u2502" }), infoRows[i](), _jsx(Text, { color: C.yellow, children: B.v })] }, i));
206
- }), _jsxs(Text, { children: [_jsx(Text, { color: C.yellow, children: B.v }), _jsx(DashedDivider, { width: innerWidth }), _jsx(Text, { color: C.yellow, children: B.v })] }), _jsx(EmptyRow, { width: innerWidth }), _jsxs(Text, { children: [_jsx(Text, { color: C.yellow, children: B.v }), _jsx(Text, { children: centerPad(SHORTCUTS.map(s => `[${s.key}] ${s.desc}`).join(' '), innerWidth) }), _jsx(Text, { color: C.yellow, children: B.v })] }), _jsx(EmptyRow, { width: innerWidth }), _jsx(Text, { children: _jsxs(Text, { color: C.yellow, children: [B.bottomLeft, B.h.repeat(innerWidth), B.bottomRight] }) })] }));
114
+ return (_jsx(HorizontalHeader, { modelName: modelName, isConnected: isConnected, sessionCount: sessions.length, modeLabel: modeLabel, theme: theme, tip: tip, sessions: sessions, version: VERSION }));
207
115
  };
208
116
  //# sourceMappingURL=Header.js.map
@@ -1,22 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useMemo } from 'react';
3
- import { Static, Box } from 'ink';
2
+ import { Box } from 'ink';
4
3
  import { useChatState, StreamingState } from '../contexts/ChatContext.js';
5
4
  import { HistoryItemDisplay } from './HistoryItemDisplay.js';
6
5
  import { AssistantMessage } from './messages/AssistantMessage.js';
7
6
  import { Header } from './Header.js';
8
- // Stable sentinel object — always the first item so <Static> renders
9
- // the Header once, and subsequent history items are appended after it.
10
- const HEADER_SENTINEL = { __header: true, id: '__header__' };
11
7
  export const MainContent = () => {
12
- const { history, pendingContent, streamingState, sessionVersion } = useChatState();
13
- // Always keep the sentinel at index 0 so Static.length grows correctly.
14
- const staticItems = useMemo(() => [HEADER_SENTINEL, ...history], [history]);
15
- return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Static, { items: staticItems, children: (item) => {
16
- if ('__header' in item) {
17
- return (_jsx(Box, { children: _jsx(Header, {}) }, "__header__"));
18
- }
19
- return (_jsx(Box, { paddingX: 1, children: _jsx(HistoryItemDisplay, { item: item }) }, item.id));
20
- } }, sessionVersion), streamingState !== StreamingState.Idle && (_jsx(Box, { paddingX: 1, children: _jsx(AssistantMessage, { text: pendingContent, isPending: true }) }))] }));
8
+ const { history, pendingContent, streamingState } = useChatState();
9
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Header, {}), history.map((item) => (_jsx(Box, { paddingX: 1, children: _jsx(HistoryItemDisplay, { item: item }) }, item.id))), streamingState !== StreamingState.Idle && (_jsx(Box, { paddingX: 1, children: _jsx(AssistantMessage, { text: pendingContent, isPending: true }) }))] }));
21
10
  };
22
11
  //# sourceMappingURL=MainContent.js.map
@@ -1,28 +1,13 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React, { useMemo } from 'react';
3
3
  import { Text } from 'ink';
4
- import { Marked } from 'marked';
5
- import { markedTerminal } from 'marked-terminal';
6
- import { supportsLanguage } from 'cli-highlight';
7
- const marked = new Marked(markedTerminal({
8
- reflowText: true,
9
- width: 80,
10
- showSectionPrefix: false,
11
- tab: 2,
12
- }));
13
- // Replace ```<unsupported-lang> with ``` to avoid console.warn from highlight.js
14
- function silentParse(src) {
15
- // Strip internal heading markers (e.g. {mist_session_heading:...})
16
- let cleaned = src.replace(/\{mist_session_heading:[^}]*\}\s*/g, '');
17
- cleaned = cleaned.replace(/^```(\w+)/gm, (_match, lang) => supportsLanguage(lang) ? '```' + lang : '```');
18
- return marked.parse(cleaned);
19
- }
4
+ import { applyMarkdown } from '../../utils/markdown.js';
20
5
  export const MarkdownRenderer = React.memo(({ text, }) => {
21
6
  const rendered = useMemo(() => {
22
7
  if (!text)
23
8
  return null;
24
9
  try {
25
- return silentParse(text).trimEnd();
10
+ return applyMarkdown(text);
26
11
  }
27
12
  catch {
28
13
  return text;
@@ -1,9 +1,28 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from 'ink';
2
+ import { useRef } from 'react';
3
+ import { Box, Text, useCursor } from 'ink';
3
4
  import chalk from 'chalk';
5
+ import stringWidth from 'string-width';
4
6
  import { cpSlice, cpLen } from '../../utils/textUtils.js';
5
7
  import { PASTE_PLACEHOLDER_REGEX } from '../../utils/pasteUtils.js';
6
8
  import { palette } from '../../utils/colors.js';
9
+ /**
10
+ * Walk up the Yoga layout tree to compute absolute (x, y) of a DOM node.
11
+ * Same algorithm as Ink's internal `getAbsolutePosition` (layout.ts).
12
+ */
13
+ function getAbsolutePosition(node) {
14
+ let x = 0;
15
+ let y = 0;
16
+ let cur = node;
17
+ while (cur?.parentNode) {
18
+ if (!cur.yogaNode)
19
+ return null;
20
+ x += cur.yogaNode.getComputedLeft();
21
+ y += cur.yogaNode.getComputedTop();
22
+ cur = cur.parentNode;
23
+ }
24
+ return { x, y };
25
+ }
7
26
  /**
8
27
  * Split a line into segments, marking paste placeholders for accent rendering.
9
28
  * Returns array of { text, isPaste } segments.
@@ -93,10 +112,50 @@ export const TextInput = ({ buffer, placeholder = 'Ask anything...', isActive, }
93
112
  const { viewportVisualLines, visualCursor } = buffer;
94
113
  const [cursorRow, cursorCol] = visualCursor;
95
114
  const showPlaceholder = buffer.text.length === 0;
115
+ // ── Terminal cursor positioning for IME ──
116
+ // Park the real terminal cursor at the text caret so CJK IME candidate
117
+ // windows appear inline instead of at the end of the line.
118
+ const { setCursorPosition } = useCursor();
119
+ const boxRef = useRef(null);
120
+ if (!isActive) {
121
+ setCursorPosition(undefined);
122
+ }
123
+ else {
124
+ const node = boxRef.current;
125
+ if (node) {
126
+ const abs = getAbsolutePosition(node);
127
+ if (abs) {
128
+ const indent = cursorRow === 0 ? 0 : 2;
129
+ const lineText = viewportVisualLines[cursorRow] ?? '';
130
+ const textBeforeCursor = cpSlice(lineText, 0, cursorCol);
131
+ const displayCol = stringWidth(textBeforeCursor);
132
+ // Ink's buildCursorSuffix computes: moveUp = visibleLineCount - y.
133
+ // When output fits in terminal, the output string ends with '\n'
134
+ // and visibleLineCount = lines.length - 1 (= Yoga rootH). The
135
+ // cursor sits one line below the last visible line, so Yoga's
136
+ // absolute Y maps directly.
137
+ //
138
+ // When output overflows (rootH > termRows), Ink's incremental
139
+ // renderer omits the trailing '\n', so visibleLineCount = lines.length
140
+ // (= rootH + 1 effectively in the moveUp math). The cursor sits at
141
+ // the END of the last line, not a line below it. This shifts the
142
+ // moveUp origin by one row, requiring y + 1 to compensate.
143
+ let rootNode = node;
144
+ while (rootNode?.parentNode)
145
+ rootNode = rootNode.parentNode;
146
+ const rootH = rootNode?.yogaNode?.getComputedHeight() ?? 0;
147
+ const overflows = rootH > (process.stdout.rows ?? 24);
148
+ setCursorPosition({
149
+ x: abs.x + indent + displayCol,
150
+ y: abs.y + cursorRow + (overflows ? 1 : 0),
151
+ });
152
+ }
153
+ }
154
+ }
96
155
  if (showPlaceholder) {
97
- return (_jsx(Box, { children: isActive ? (_jsxs(Text, { children: [chalk.inverse(placeholder[0] || ' '), _jsx(Text, { color: palette.textDim, children: placeholder.slice(1) })] })) : (_jsx(Text, { color: palette.textDim, children: placeholder })) }));
156
+ return (_jsx(Box, { ref: boxRef, children: isActive ? (_jsxs(Text, { children: [chalk.inverse(placeholder[0] || ' '), _jsx(Text, { color: palette.textDim, children: placeholder.slice(1) })] })) : (_jsx(Text, { color: palette.textDim, children: placeholder })) }));
98
157
  }
99
- return (_jsx(Box, { flexDirection: "column", children: viewportVisualLines.map((lineText, idx) => {
158
+ return (_jsx(Box, { ref: boxRef, flexDirection: "column", children: viewportVisualLines.map((lineText, idx) => {
100
159
  const isCursorLine = isActive && idx === cursorRow;
101
160
  // First line has no indent (follows ❯ prompt), subsequent lines indent 2 spaces
102
161
  const indent = idx === 0 ? '' : ' ';
package/dist/src/main.js CHANGED
@@ -9,7 +9,7 @@ import { toolsApi } from './api/tools.js';
9
9
  import { skillsApi } from './api/skills.js';
10
10
  import { scanLocalSkills } from './utils/skillScanner.js';
11
11
  import { sessionsApi } from './api/sessions.js';
12
- import { getServerUrl, loadToken, saveToken, clearToken, loadTheme, loadModel } from './utils/config.js';
12
+ import { getServerUrl, loadToken, saveToken, clearToken, loadTheme, loadModel, saveRefreshToken, loadRefreshToken } from './utils/config.js';
13
13
  import chalk from 'chalk';
14
14
  import { setTheme, palette } from './utils/colors.js';
15
15
  import { VERSION } from './utils/constants.js';
@@ -64,7 +64,6 @@ export async function main() {
64
64
  const muted = chalk.hex(palette.textMuted);
65
65
  const accent = chalk.hex(palette.accent);
66
66
  const rawMsg = err instanceof Error ? err.message : String(err);
67
- // Detect specific failure reasons
68
67
  let reason;
69
68
  let hint;
70
69
  if (rawMsg.includes('ECONNREFUSED') || rawMsg.includes('fetch failed')) {
@@ -101,6 +100,41 @@ export async function main() {
101
100
  catch {
102
101
  authStatus = { auth_enabled: false };
103
102
  }
103
+ // Token refresh (Supabase tokens expire in ~1 hour)
104
+ let refreshTimer = null;
105
+ const startTokenRefresh = () => {
106
+ if (refreshTimer)
107
+ return;
108
+ const supabaseUrl = authStatus.supabase_url;
109
+ const supabaseKey = authStatus.supabase_anon_key;
110
+ if (!supabaseUrl || !supabaseKey)
111
+ return;
112
+ refreshTimer = setInterval(async () => {
113
+ const rt = loadRefreshToken();
114
+ if (!rt)
115
+ return;
116
+ try {
117
+ const res = await fetch(`${supabaseUrl}/auth/v1/token?grant_type=refresh_token`, {
118
+ method: 'POST',
119
+ headers: {
120
+ 'Content-Type': 'application/json',
121
+ 'apikey': supabaseKey,
122
+ },
123
+ body: JSON.stringify({ refresh_token: rt }),
124
+ });
125
+ if (res.ok) {
126
+ const data = await res.json();
127
+ client.setToken(data.access_token);
128
+ saveToken(data.access_token);
129
+ saveRefreshToken(data.refresh_token);
130
+ token = data.access_token;
131
+ }
132
+ }
133
+ catch {
134
+ // Silent — will retry next interval
135
+ }
136
+ }, 50 * 60 * 1000); // 50 minutes
137
+ };
104
138
  // Try to restore token
105
139
  let token = null;
106
140
  let username = null;
@@ -114,6 +148,7 @@ export async function main() {
114
148
  token = savedToken;
115
149
  username = me.username;
116
150
  isAuthenticated = true;
151
+ startTokenRefresh();
117
152
  }
118
153
  catch {
119
154
  client.setToken(null);
@@ -167,16 +202,27 @@ export async function main() {
167
202
  ];
168
203
  // Login handler
169
204
  let loginError = null;
170
- const renderApp = (overrides = {}) => (_jsx(App, { serverUrl: serverUrl, token: overrides.token ?? token, username: overrides.username ?? username, authEnabled: authStatus.auth_enabled, version: healthData?.version ?? VERSION, healthData: healthData, availableCommands: commands, availableTools: tools, initialModels: models, initialCurrentModel: currentModel, initialSessions: initialSessions, initialTheme: initialTheme, onLogin: handleLogin, onLogout: handleLogout, loginError: overrides.loginError ?? loginError, isAuthenticated: overrides.isAuthenticated ?? isAuthenticated }));
205
+ // Terminal size tracked outside React so rerender() can inject fresh values
206
+ // This mirrors the claude-code pattern: Ink's renderer owns resize detection,
207
+ // and we piggyback by re-calling render()/rerender() with updated props.
208
+ const getTerminalSize = () => ({
209
+ terminalWidth: process.stdout.columns || 80,
210
+ terminalHeight: process.stdout.rows || 24,
211
+ });
212
+ const renderApp = (overrides = {}) => (_jsx(App, { serverUrl: serverUrl, token: overrides.token ?? token, username: overrides.username ?? username, authEnabled: authStatus.auth_enabled, version: healthData?.version ?? VERSION, healthData: healthData, availableCommands: commands, availableTools: tools, initialModels: models, initialCurrentModel: currentModel, initialSessions: initialSessions, initialTheme: initialTheme, onLogin: handleLogin, onLogout: handleLogout, loginError: overrides.loginError ?? loginError, isAuthenticated: overrides.isAuthenticated ?? isAuthenticated, ...getTerminalSize() }));
171
213
  const handleLogin = async (user, pass) => {
172
214
  try {
173
215
  const res = await authApi.login(user, pass);
174
216
  client.setToken(res.access_token);
175
217
  saveToken(res.access_token);
218
+ if (res.refresh_token) {
219
+ saveRefreshToken(res.refresh_token);
220
+ }
176
221
  token = res.access_token;
177
222
  username = res.user.username;
178
223
  isAuthenticated = true;
179
224
  loginError = null;
225
+ startTokenRefresh();
180
226
  rerender(renderApp({ token, username, isAuthenticated: true, loginError: null }));
181
227
  }
182
228
  catch (err) {
@@ -185,6 +231,10 @@ export async function main() {
185
231
  }
186
232
  };
187
233
  const handleLogout = () => {
234
+ if (refreshTimer) {
235
+ clearInterval(refreshTimer);
236
+ refreshTimer = null;
237
+ }
188
238
  client.setToken(null);
189
239
  clearToken();
190
240
  token = null;
@@ -199,9 +249,17 @@ export async function main() {
199
249
  process.stderr.write(formatUpdateMessage(updateInfo) + '\n\n');
200
250
  }
201
251
  // Render the app
202
- const { rerender } = render(renderApp(), {
252
+ const { rerender, clear } = render(renderApp(), {
203
253
  exitOnCtrlC: false,
204
254
  kittyKeyboard: { mode: 'auto' },
205
255
  });
256
+ // On resize: clear the screen first to avoid stale rows left over when the
257
+ // Header shrinks (e.g. horizontal→compact reduces height by ~10 rows and
258
+ // Ink's in-place diff won't erase the leftover lines). Then rerender with
259
+ // fresh terminal dimensions.
260
+ process.stdout.on('resize', () => {
261
+ clear();
262
+ rerender(renderApp());
263
+ });
206
264
  }
207
265
  //# sourceMappingURL=main.js.map
@@ -1,6 +1,7 @@
1
1
  export interface LoginResponse {
2
2
  success: boolean;
3
3
  access_token: string;
4
+ refresh_token?: string;
4
5
  token_type: string;
5
6
  user: {
6
7
  user_id: string;
@@ -13,9 +14,12 @@ export interface UserInfo {
13
14
  user_id: string;
14
15
  username: string;
15
16
  display_name: string;
17
+ is_admin?: boolean;
16
18
  }
17
19
  export interface AuthStatus {
18
20
  auth_enabled: boolean;
21
+ supabase_url?: string;
22
+ supabase_anon_key?: string;
19
23
  }
20
24
  export interface SessionInfo {
21
25
  thread_id: string;
@@ -3,6 +3,8 @@ export declare function getServerUrl(cliArg?: string): string;
3
3
  export declare function saveToken(token: string): void;
4
4
  export declare function loadToken(): string | null;
5
5
  export declare function clearToken(): void;
6
+ export declare function saveRefreshToken(token: string): void;
7
+ export declare function loadRefreshToken(): string | null;
6
8
  export type ThemeMode = 'dark' | 'light';
7
9
  export declare function saveTheme(mode: ThemeMode): void;
8
10
  export declare function loadTheme(): ThemeMode;