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.
- package/dist/src/components/App.d.ts +2 -0
- package/dist/src/components/App.js +23 -5
- package/dist/src/components/Header.js +74 -166
- package/dist/src/components/MainContent.js +3 -14
- package/dist/src/components/shared/MarkdownRenderer.js +2 -17
- package/dist/src/components/shared/TextInput.js +62 -3
- package/dist/src/main.js +62 -4
- package/dist/src/types/api.d.ts +4 -0
- package/dist/src/utils/config.d.ts +2 -0
- package/dist/src/utils/config.js +23 -0
- package/dist/src/utils/constants.d.ts +1 -1
- package/dist/src/utils/constants.js +1 -1
- package/dist/src/utils/markdown.d.ts +10 -0
- package/dist/src/utils/markdown.js +223 -0
- package/dist/src/utils/updateChecker.js +10 -4
- package/package.json +3 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/src/api/auth.d.ts.map +0 -1
- package/dist/src/api/auth.js.map +0 -1
- package/dist/src/api/chat.d.ts.map +0 -1
- package/dist/src/api/chat.js.map +0 -1
- package/dist/src/api/client.d.ts.map +0 -1
- package/dist/src/api/client.js.map +0 -1
- package/dist/src/api/models.d.ts.map +0 -1
- package/dist/src/api/models.js.map +0 -1
- package/dist/src/api/sessions.d.ts.map +0 -1
- package/dist/src/api/sessions.js.map +0 -1
- package/dist/src/api/skills.d.ts.map +0 -1
- package/dist/src/api/skills.js.map +0 -1
- package/dist/src/api/tools.d.ts.map +0 -1
- package/dist/src/api/tools.js.map +0 -1
- package/dist/src/api/tunnel.d.ts.map +0 -1
- package/dist/src/api/tunnel.js.map +0 -1
- package/dist/src/components/App.d.ts.map +0 -1
- package/dist/src/components/App.js.map +0 -1
- package/dist/src/components/AppLayout.d.ts.map +0 -1
- package/dist/src/components/AppLayout.js.map +0 -1
- package/dist/src/components/Composer.d.ts.map +0 -1
- package/dist/src/components/Composer.js.map +0 -1
- package/dist/src/components/Footer.d.ts.map +0 -1
- package/dist/src/components/Footer.js.map +0 -1
- package/dist/src/components/Header.d.ts.map +0 -1
- package/dist/src/components/Header.js.map +0 -1
- package/dist/src/components/HistoryItemDisplay.d.ts.map +0 -1
- package/dist/src/components/HistoryItemDisplay.js.map +0 -1
- package/dist/src/components/InputPrompt.d.ts.map +0 -1
- package/dist/src/components/InputPrompt.js.map +0 -1
- package/dist/src/components/LoadingIndicator.d.ts.map +0 -1
- package/dist/src/components/LoadingIndicator.js.map +0 -1
- package/dist/src/components/LoginPrompt.d.ts.map +0 -1
- package/dist/src/components/LoginPrompt.js.map +0 -1
- package/dist/src/components/MainContent.d.ts.map +0 -1
- package/dist/src/components/MainContent.js.map +0 -1
- package/dist/src/components/ModelPicker.d.ts.map +0 -1
- package/dist/src/components/ModelPicker.js.map +0 -1
- package/dist/src/components/SessionPicker.d.ts.map +0 -1
- package/dist/src/components/SessionPicker.js.map +0 -1
- package/dist/src/components/SuggestionsDisplay.d.ts.map +0 -1
- package/dist/src/components/SuggestionsDisplay.js.map +0 -1
- package/dist/src/components/ThemePicker.d.ts.map +0 -1
- package/dist/src/components/ThemePicker.js.map +0 -1
- package/dist/src/components/messages/AssistantMessage.d.ts.map +0 -1
- package/dist/src/components/messages/AssistantMessage.js.map +0 -1
- package/dist/src/components/messages/CommandResult.d.ts.map +0 -1
- package/dist/src/components/messages/CommandResult.js.map +0 -1
- package/dist/src/components/messages/ErrorMessage.d.ts.map +0 -1
- package/dist/src/components/messages/ErrorMessage.js.map +0 -1
- package/dist/src/components/messages/InfoMessage.d.ts.map +0 -1
- package/dist/src/components/messages/InfoMessage.js.map +0 -1
- package/dist/src/components/messages/ModelMessage.d.ts.map +0 -1
- package/dist/src/components/messages/ModelMessage.js.map +0 -1
- package/dist/src/components/messages/SessionMessage.d.ts.map +0 -1
- package/dist/src/components/messages/SessionMessage.js.map +0 -1
- package/dist/src/components/messages/ToolCallMessage.d.ts.map +0 -1
- package/dist/src/components/messages/ToolCallMessage.js.map +0 -1
- package/dist/src/components/messages/UserMessage.d.ts.map +0 -1
- package/dist/src/components/messages/UserMessage.js.map +0 -1
- package/dist/src/components/shared/HorizontalLine.d.ts.map +0 -1
- package/dist/src/components/shared/HorizontalLine.js.map +0 -1
- package/dist/src/components/shared/MarkdownRenderer.d.ts.map +0 -1
- package/dist/src/components/shared/MarkdownRenderer.js.map +0 -1
- package/dist/src/components/shared/Spinner.d.ts.map +0 -1
- package/dist/src/components/shared/Spinner.js.map +0 -1
- package/dist/src/components/shared/TextInput.d.ts.map +0 -1
- package/dist/src/components/shared/TextInput.js.map +0 -1
- package/dist/src/contexts/AppContext.d.ts.map +0 -1
- package/dist/src/contexts/AppContext.js.map +0 -1
- package/dist/src/contexts/ChatContext.d.ts.map +0 -1
- package/dist/src/contexts/ChatContext.js.map +0 -1
- package/dist/src/contexts/KeypressContext.d.ts.map +0 -1
- package/dist/src/contexts/KeypressContext.js.map +0 -1
- package/dist/src/contexts/ModelContext.d.ts.map +0 -1
- package/dist/src/contexts/ModelContext.js.map +0 -1
- package/dist/src/contexts/SessionContext.d.ts.map +0 -1
- package/dist/src/contexts/SessionContext.js.map +0 -1
- package/dist/src/contexts/UIContext.d.ts.map +0 -1
- package/dist/src/contexts/UIContext.js.map +0 -1
- package/dist/src/hooks/useChat.d.ts.map +0 -1
- package/dist/src/hooks/useChat.js.map +0 -1
- package/dist/src/hooks/useFileCompletion.d.ts.map +0 -1
- package/dist/src/hooks/useFileCompletion.js.map +0 -1
- package/dist/src/hooks/useInputHistory.d.ts.map +0 -1
- package/dist/src/hooks/useInputHistory.js.map +0 -1
- package/dist/src/hooks/useKeypress.d.ts.map +0 -1
- package/dist/src/hooks/useKeypress.js.map +0 -1
- package/dist/src/hooks/useLoadingIndicator.d.ts.map +0 -1
- package/dist/src/hooks/useLoadingIndicator.js.map +0 -1
- package/dist/src/hooks/usePasteBuffer.d.ts.map +0 -1
- package/dist/src/hooks/usePasteBuffer.js.map +0 -1
- package/dist/src/hooks/useSlashCommand.d.ts.map +0 -1
- package/dist/src/hooks/useSlashCommand.js.map +0 -1
- package/dist/src/hooks/useStdinInterceptor.d.ts.map +0 -1
- package/dist/src/hooks/useStdinInterceptor.js.map +0 -1
- package/dist/src/hooks/useSymbolCompletion.d.ts.map +0 -1
- package/dist/src/hooks/useSymbolCompletion.js.map +0 -1
- package/dist/src/hooks/useTextBuffer.d.ts.map +0 -1
- package/dist/src/hooks/useTextBuffer.js.map +0 -1
- package/dist/src/main.d.ts.map +0 -1
- package/dist/src/main.js.map +0 -1
- package/dist/src/tools/code-analyzer/config/ignore-service.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/config/ignore-service.js.map +0 -1
- package/dist/src/tools/code-analyzer/config/supported-languages.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/config/supported-languages.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/graph/graph.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/graph/graph.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/graph/types.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/graph/types.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/ast-cache.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/ast-cache.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/call-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/call-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/community-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/community-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/entry-point-scoring.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/entry-point-scoring.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/filesystem-walker.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/filesystem-walker.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/framework-detection.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/framework-detection.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/heritage-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/heritage-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/import-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/import-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/parsing-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/parsing-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/pipeline.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/pipeline.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/process-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/process-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/structure-processor.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/structure-processor.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/symbol-table.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/symbol-table.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/tree-sitter-queries.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/tree-sitter-queries.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/utils.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/utils.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/workers/parse-worker.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/workers/parse-worker.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/workers/worker-pool.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/ingestion/workers/worker-pool.js.map +0 -1
- package/dist/src/tools/code-analyzer/core/tree-sitter/parser-loader.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/core/tree-sitter/parser-loader.js.map +0 -1
- package/dist/src/tools/code-analyzer/index.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/index.js.map +0 -1
- package/dist/src/tools/code-analyzer/lib/utils.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/lib/utils.js.map +0 -1
- package/dist/src/tools/code-analyzer/types/pipeline.d.ts.map +0 -1
- package/dist/src/tools/code-analyzer/types/pipeline.js.map +0 -1
- package/dist/src/types/api.d.ts.map +0 -1
- package/dist/src/types/api.js.map +0 -1
- package/dist/src/types/history.d.ts.map +0 -1
- package/dist/src/types/history.js.map +0 -1
- package/dist/src/utils/colors.d.ts.map +0 -1
- package/dist/src/utils/colors.js.map +0 -1
- package/dist/src/utils/config.d.ts.map +0 -1
- package/dist/src/utils/config.js.map +0 -1
- package/dist/src/utils/constants.d.ts.map +0 -1
- package/dist/src/utils/constants.js.map +0 -1
- package/dist/src/utils/fileRef.d.ts.map +0 -1
- package/dist/src/utils/fileRef.js.map +0 -1
- package/dist/src/utils/fileTunnel.d.ts.map +0 -1
- package/dist/src/utils/fileTunnel.js.map +0 -1
- package/dist/src/utils/formatters.d.ts.map +0 -1
- package/dist/src/utils/formatters.js.map +0 -1
- package/dist/src/utils/pasteUtils.d.ts.map +0 -1
- package/dist/src/utils/pasteUtils.js.map +0 -1
- package/dist/src/utils/skillScanner.d.ts.map +0 -1
- package/dist/src/utils/skillScanner.js.map +0 -1
- package/dist/src/utils/textUtils.d.ts.map +0 -1
- package/dist/src/utils/textUtils.js.map +0 -1
- package/dist/src/utils/updateChecker.d.ts.map +0 -1
- package/dist/src/utils/updateChecker.js.map +0 -1
|
@@ -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
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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) <=
|
|
33
|
+
if (stringWidth(s) <= max)
|
|
58
34
|
return s;
|
|
59
|
-
let
|
|
35
|
+
let out = '';
|
|
60
36
|
for (const ch of s) {
|
|
61
|
-
if (stringWidth(
|
|
37
|
+
if (stringWidth(out + ch) > max - 1)
|
|
62
38
|
break;
|
|
63
|
-
|
|
39
|
+
out += ch;
|
|
64
40
|
}
|
|
65
|
-
return
|
|
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
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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
|
|
100
|
-
|
|
101
|
-
{
|
|
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
|
-
|
|
182
|
-
|
|
111
|
+
if (terminalWidth < COMPACT_MAX) {
|
|
112
|
+
return (_jsx(CompactHeader, { modelName: modelName, isConnected: isConnected, modeLabel: modeLabel, theme: theme, version: VERSION }));
|
|
183
113
|
}
|
|
184
|
-
|
|
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 {
|
|
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
|
|
13
|
-
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
package/dist/src/types/api.d.ts
CHANGED
|
@@ -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;
|