byterover-cli 1.1.0 → 1.2.0
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/README.md +8 -4
- package/dist/commands/mcp.d.ts +13 -0
- package/dist/commands/mcp.js +61 -0
- package/dist/core/domain/cipher/agent-events/types.d.ts +44 -1
- package/dist/core/domain/entities/agent.js +72 -18
- package/dist/core/domain/entities/connector-type.d.ts +2 -1
- package/dist/core/domain/entities/connector-type.js +2 -1
- package/dist/core/interfaces/connectors/connector-types.d.ts +13 -0
- package/dist/core/interfaces/i-mcp-config-writer.d.ts +40 -0
- package/dist/core/interfaces/i-mcp-config-writer.js +1 -0
- package/dist/core/interfaces/i-rule-template-service.d.ts +4 -2
- package/dist/core/interfaces/transport/i-transport-client.d.ts +7 -0
- package/dist/infra/cipher/agent/cipher-agent.d.ts +8 -0
- package/dist/infra/cipher/agent/cipher-agent.js +16 -0
- package/dist/infra/cipher/llm/context/context-manager.d.ts +8 -0
- package/dist/infra/cipher/llm/context/context-manager.js +16 -0
- package/dist/infra/cipher/llm/internal-llm-service.d.ts +4 -0
- package/dist/infra/cipher/llm/internal-llm-service.js +38 -10
- package/dist/infra/cipher/session/chat-session.d.ts +3 -0
- package/dist/infra/cipher/session/chat-session.js +7 -1
- package/dist/infra/cipher/tools/implementations/curate-tool.d.ts +1 -8
- package/dist/infra/cipher/tools/implementations/curate-tool.js +360 -22
- package/dist/infra/connectors/connector-manager.js +2 -0
- package/dist/infra/connectors/mcp/index.d.ts +4 -0
- package/dist/infra/connectors/mcp/index.js +4 -0
- package/dist/infra/connectors/mcp/json-mcp-config-writer.d.ts +26 -0
- package/dist/infra/connectors/mcp/json-mcp-config-writer.js +71 -0
- package/dist/infra/connectors/mcp/mcp-connector-config.d.ts +229 -0
- package/dist/infra/connectors/mcp/mcp-connector-config.js +173 -0
- package/dist/infra/connectors/mcp/mcp-connector.d.ts +80 -0
- package/dist/infra/connectors/mcp/mcp-connector.js +324 -0
- package/dist/infra/connectors/mcp/toml-mcp-config-writer.d.ts +45 -0
- package/dist/infra/connectors/mcp/toml-mcp-config-writer.js +134 -0
- package/dist/infra/connectors/rules/rules-connector.d.ts +1 -8
- package/dist/infra/connectors/rules/rules-connector.js +20 -85
- package/dist/infra/connectors/shared/rule-file-manager.d.ts +72 -0
- package/dist/infra/connectors/shared/rule-file-manager.js +119 -0
- package/dist/infra/connectors/shared/template-service.d.ts +10 -1
- package/dist/infra/connectors/shared/template-service.js +53 -16
- package/dist/infra/mcp/index.d.ts +2 -0
- package/dist/infra/mcp/index.js +2 -0
- package/dist/infra/mcp/mcp-server.d.ts +58 -0
- package/dist/infra/mcp/mcp-server.js +178 -0
- package/dist/infra/mcp/tools/brv-curate-tool.d.ts +23 -0
- package/dist/infra/mcp/tools/brv-curate-tool.js +68 -0
- package/dist/infra/mcp/tools/brv-query-tool.d.ts +17 -0
- package/dist/infra/mcp/tools/brv-query-tool.js +68 -0
- package/dist/infra/mcp/tools/index.d.ts +3 -0
- package/dist/infra/mcp/tools/index.js +3 -0
- package/dist/infra/mcp/tools/task-result-waiter.d.ts +30 -0
- package/dist/infra/mcp/tools/task-result-waiter.js +56 -0
- package/dist/infra/process/agent-worker.js +37 -0
- package/dist/infra/repl/commands/curate-command.js +2 -2
- package/dist/infra/transport/socket-io-transport-client.d.ts +7 -0
- package/dist/infra/transport/socket-io-transport-client.js +25 -0
- package/dist/infra/transport/socket-io-transport-server.js +4 -0
- package/dist/infra/usecase/connectors-use-case.d.ts +4 -0
- package/dist/infra/usecase/connectors-use-case.js +29 -10
- package/dist/infra/usecase/init-use-case.js +2 -3
- package/dist/infra/usecase/status-use-case.d.ts +10 -0
- package/dist/infra/usecase/status-use-case.js +53 -0
- package/dist/resources/prompts/curate.yml +107 -4
- package/dist/templates/mcp-base.md +1 -0
- package/dist/templates/sections/mcp-workflow.md +13 -0
- package/dist/tui/app.js +4 -1
- package/dist/tui/components/command-details.js +1 -1
- package/dist/tui/components/execution/execution-changes.d.ts +2 -0
- package/dist/tui/components/execution/execution-changes.js +5 -1
- package/dist/tui/components/execution/execution-content.d.ts +2 -0
- package/dist/tui/components/execution/execution-content.js +8 -18
- package/dist/tui/components/execution/execution-input.d.ts +2 -0
- package/dist/tui/components/execution/execution-input.js +6 -4
- package/dist/tui/components/execution/execution-progress.d.ts +2 -0
- package/dist/tui/components/execution/execution-progress.js +6 -2
- package/dist/tui/components/execution/expanded-log-view.d.ts +20 -0
- package/dist/tui/components/execution/expanded-log-view.js +75 -0
- package/dist/tui/components/execution/expanded-message-view.d.ts +24 -0
- package/dist/tui/components/execution/expanded-message-view.js +68 -0
- package/dist/tui/components/execution/index.d.ts +2 -0
- package/dist/tui/components/execution/index.js +2 -0
- package/dist/tui/components/execution/log-item.d.ts +4 -0
- package/dist/tui/components/execution/log-item.js +2 -2
- package/dist/tui/components/footer.js +1 -1
- package/dist/tui/components/index.d.ts +2 -1
- package/dist/tui/components/index.js +2 -1
- package/dist/tui/components/init.js +2 -9
- package/dist/tui/components/logo.js +4 -3
- package/dist/tui/components/markdown.d.ts +13 -0
- package/dist/tui/components/markdown.js +88 -0
- package/dist/tui/components/message-item.js +1 -1
- package/dist/tui/components/onboarding/onboarding-flow.js +1 -1
- package/dist/tui/components/suggestions.js +3 -3
- package/dist/tui/contexts/mode-context.js +6 -2
- package/dist/tui/hooks/index.d.ts +1 -0
- package/dist/tui/hooks/index.js +1 -0
- package/dist/tui/hooks/use-is-latest-version.d.ts +6 -0
- package/dist/tui/hooks/use-is-latest-version.js +22 -0
- package/dist/tui/views/command-view.d.ts +1 -1
- package/dist/tui/views/command-view.js +83 -98
- package/dist/tui/views/logs-view.d.ts +8 -0
- package/dist/tui/views/logs-view.js +55 -27
- package/oclif.manifest.json +26 -1
- package/package.json +9 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Expanded Log View Component
|
|
4
|
+
*
|
|
5
|
+
* Full-screen overlay displaying a single log item with scrollable content.
|
|
6
|
+
* Activated by Ctrl+O on a selected log item, dismissed with Ctrl+O or Esc.
|
|
7
|
+
*/
|
|
8
|
+
import { Box, Spacer, Text, useInput, useStdout } from 'ink';
|
|
9
|
+
import { ScrollView } from 'ink-scroll-view';
|
|
10
|
+
import { useEffect, useRef, useState } from 'react';
|
|
11
|
+
import { useTheme } from '../../hooks/index.js';
|
|
12
|
+
import { formatTime } from '../../utils/index.js';
|
|
13
|
+
import { ExecutionChanges } from './execution-changes.js';
|
|
14
|
+
import { ExecutionContent } from './execution-content.js';
|
|
15
|
+
import { ExecutionProgress } from './execution-progress.js';
|
|
16
|
+
import { ExecutionStatus } from './execution-status.js';
|
|
17
|
+
export const ExpandedLogView = ({ availableHeight, isActive, log, onClose, }) => {
|
|
18
|
+
const { theme: { colors }, } = useTheme();
|
|
19
|
+
const { stdout } = useStdout();
|
|
20
|
+
const scrollViewRef = useRef(null);
|
|
21
|
+
const [hasMoreBelow, setHasMoreBelow] = useState(false);
|
|
22
|
+
const updateScrollIndicator = () => {
|
|
23
|
+
if (!scrollViewRef.current)
|
|
24
|
+
return;
|
|
25
|
+
const currentOffset = scrollViewRef.current.getScrollOffset();
|
|
26
|
+
const maxOffset = scrollViewRef.current.getBottomOffset();
|
|
27
|
+
setHasMoreBelow(currentOffset < maxOffset);
|
|
28
|
+
};
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const timer = setTimeout(updateScrollIndicator, 50);
|
|
31
|
+
return () => clearTimeout(timer);
|
|
32
|
+
}, [log]);
|
|
33
|
+
// Terminal resize handling
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const handleResize = () => {
|
|
36
|
+
scrollViewRef.current?.remeasure();
|
|
37
|
+
updateScrollIndicator();
|
|
38
|
+
};
|
|
39
|
+
stdout?.on('resize', handleResize);
|
|
40
|
+
return () => {
|
|
41
|
+
stdout?.off('resize', handleResize);
|
|
42
|
+
};
|
|
43
|
+
}, [stdout]);
|
|
44
|
+
useInput((input, key) => {
|
|
45
|
+
if (!scrollViewRef.current)
|
|
46
|
+
return;
|
|
47
|
+
if ((key.ctrl && input === 'o') || key.escape) {
|
|
48
|
+
onClose();
|
|
49
|
+
}
|
|
50
|
+
if (key.upArrow || input === 'k') {
|
|
51
|
+
scrollViewRef.current.scrollBy(-1);
|
|
52
|
+
updateScrollIndicator();
|
|
53
|
+
}
|
|
54
|
+
if (key.downArrow || input === 'j') {
|
|
55
|
+
const currentOffset = scrollViewRef.current.getScrollOffset();
|
|
56
|
+
const maxOffset = scrollViewRef.current.getBottomOffset();
|
|
57
|
+
const newOffset = Math.min(currentOffset + 1, maxOffset);
|
|
58
|
+
scrollViewRef.current.scrollTo(newOffset);
|
|
59
|
+
updateScrollIndicator();
|
|
60
|
+
}
|
|
61
|
+
if (input === 'g') {
|
|
62
|
+
scrollViewRef.current.scrollTo(0);
|
|
63
|
+
updateScrollIndicator();
|
|
64
|
+
}
|
|
65
|
+
if (input === 'G') {
|
|
66
|
+
scrollViewRef.current.scrollTo(scrollViewRef.current.getBottomOffset());
|
|
67
|
+
updateScrollIndicator();
|
|
68
|
+
}
|
|
69
|
+
}, { isActive });
|
|
70
|
+
const displayTime = formatTime(log.timestamp);
|
|
71
|
+
return (_jsxs(Box, { flexDirection: "column", height: "100%", paddingX: 2, width: "100%", children: [_jsxs(Box, { children: [_jsxs(Text, { color: log.type === 'curate' ? colors.curateCommand : colors.queryCommand, children: ["[", log.type, "] "] }), _jsxs(Text, { color: colors.dimText, children: ["@", log.source ?? 'system'] }), _jsx(Text, { dimColor: true, children: " - [ctrl+o/esc] close | [\u2191\u2193/jk] scroll | [g/G] top/bottom" }), _jsx(Spacer, {}), _jsxs(Text, { color: colors.dimText, children: ["[", displayTime, "]"] })] }), _jsxs(Box, { borderColor: colors.border, borderStyle: "single", flexDirection: "column", height: availableHeight - 1, children: [_jsxs(ScrollView, { height: availableHeight - 2, ref: scrollViewRef, children: [_jsx(Box, { marginBottom: 1, paddingX: 1, children: _jsx(Text, { children: log.input }) }), _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [log.progress && (_jsx(ExecutionProgress, { isExpand: true, maxLines: Number.MAX_SAFE_INTEGER, progress: log.progress })), _jsx(ExecutionStatus, { status: log.status })] }), (log.status === 'failed' || log.status === 'completed') && (_jsx(Box, { paddingX: 1, children: _jsx(ExecutionContent, { bottomMargin: 0, content: log.content ?? '', isError: log.status === 'failed', isExpand: true, maxLines: Number.MAX_SAFE_INTEGER }) })), log.status === 'completed' && (_jsx(Box, { paddingX: 1, children: _jsx(ExecutionChanges, { created: log.changes.created, isExpand: true, maxChanges: {
|
|
72
|
+
created: Number.MAX_SAFE_INTEGER,
|
|
73
|
+
updated: Number.MAX_SAFE_INTEGER,
|
|
74
|
+
}, updated: log.changes.updated }) }))] }), _jsx(Box, { justifyContent: "center", children: _jsx(Text, { color: colors.dimText, children: hasMoreBelow ? '↓' : ' ' }) })] })] }));
|
|
75
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expanded Message View Component
|
|
3
|
+
*
|
|
4
|
+
* Full-screen overlay displaying a single command message with scrollable output.
|
|
5
|
+
* Activated by Ctrl+O on a selected message, dismissed with Ctrl+O or Esc.
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import type { CommandMessage } from '../../types.js';
|
|
9
|
+
interface ExpandedMessageViewProps {
|
|
10
|
+
/** Available height for the expanded view (in terminal rows) */
|
|
11
|
+
availableHeight: number;
|
|
12
|
+
/** Whether input handling is active */
|
|
13
|
+
isActive: boolean;
|
|
14
|
+
/** The message to display in expanded view */
|
|
15
|
+
message: CommandMessage;
|
|
16
|
+
/** Index of the message */
|
|
17
|
+
messageIndex: number;
|
|
18
|
+
/** Callback when the view should close */
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
/** Render function for the message item */
|
|
21
|
+
renderMessageItem: (msg: CommandMessage, index: number, isExpanded?: boolean) => React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
export declare const ExpandedMessageView: React.FC<ExpandedMessageViewProps>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Expanded Message View Component
|
|
4
|
+
*
|
|
5
|
+
* Full-screen overlay displaying a single command message with scrollable output.
|
|
6
|
+
* Activated by Ctrl+O on a selected message, dismissed with Ctrl+O or Esc.
|
|
7
|
+
*/
|
|
8
|
+
import { Box, Spacer, Text, useInput, useStdout } from 'ink';
|
|
9
|
+
import { ScrollView } from 'ink-scroll-view';
|
|
10
|
+
import { useEffect, useRef, useState } from 'react';
|
|
11
|
+
import { useTheme } from '../../hooks/index.js';
|
|
12
|
+
export const ExpandedMessageView = ({ availableHeight, isActive, message, messageIndex, onClose, renderMessageItem, }) => {
|
|
13
|
+
const { theme: { colors }, } = useTheme();
|
|
14
|
+
const { stdout } = useStdout();
|
|
15
|
+
const scrollViewRef = useRef(null);
|
|
16
|
+
const [hasMoreBelow, setHasMoreBelow] = useState(false);
|
|
17
|
+
const updateScrollIndicator = () => {
|
|
18
|
+
if (!scrollViewRef.current)
|
|
19
|
+
return;
|
|
20
|
+
const currentOffset = scrollViewRef.current.getScrollOffset();
|
|
21
|
+
const maxOffset = scrollViewRef.current.getBottomOffset();
|
|
22
|
+
setHasMoreBelow(currentOffset < maxOffset);
|
|
23
|
+
};
|
|
24
|
+
// Initial scroll position check
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
// Delay to allow ScrollView to measure content
|
|
27
|
+
const timer = setTimeout(updateScrollIndicator, 50);
|
|
28
|
+
return () => clearTimeout(timer);
|
|
29
|
+
}, [message]);
|
|
30
|
+
// Terminal resize handling
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const handleResize = () => {
|
|
33
|
+
scrollViewRef.current?.remeasure();
|
|
34
|
+
updateScrollIndicator();
|
|
35
|
+
};
|
|
36
|
+
stdout?.on('resize', handleResize);
|
|
37
|
+
return () => {
|
|
38
|
+
stdout?.off('resize', handleResize);
|
|
39
|
+
};
|
|
40
|
+
}, [stdout]);
|
|
41
|
+
useInput((input, key) => {
|
|
42
|
+
if (!scrollViewRef.current)
|
|
43
|
+
return;
|
|
44
|
+
if ((key.ctrl && input === 'o') || key.escape) {
|
|
45
|
+
onClose();
|
|
46
|
+
}
|
|
47
|
+
if (key.upArrow || input === 'k') {
|
|
48
|
+
scrollViewRef.current.scrollBy(-1);
|
|
49
|
+
updateScrollIndicator();
|
|
50
|
+
}
|
|
51
|
+
if (key.downArrow || input === 'j') {
|
|
52
|
+
const currentOffset = scrollViewRef.current.getScrollOffset();
|
|
53
|
+
const maxOffset = scrollViewRef.current.getBottomOffset();
|
|
54
|
+
const newOffset = Math.min(currentOffset + 1, maxOffset);
|
|
55
|
+
scrollViewRef.current.scrollTo(newOffset);
|
|
56
|
+
updateScrollIndicator();
|
|
57
|
+
}
|
|
58
|
+
if (input === 'g') {
|
|
59
|
+
scrollViewRef.current.scrollTo(0);
|
|
60
|
+
updateScrollIndicator();
|
|
61
|
+
}
|
|
62
|
+
if (input === 'G') {
|
|
63
|
+
scrollViewRef.current.scrollTo(scrollViewRef.current.getBottomOffset());
|
|
64
|
+
updateScrollIndicator();
|
|
65
|
+
}
|
|
66
|
+
}, { isActive });
|
|
67
|
+
return (_jsxs(Box, { flexDirection: "column", height: "100%", paddingX: 2, width: "100%", children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "[ctrl+o/esc] close | [\u2191\u2193/jk] scroll | [g/G] top/bottom" }), _jsx(Spacer, {})] }), _jsxs(Box, { borderColor: colors.border, borderStyle: "single", flexDirection: "column", flexGrow: 1, height: availableHeight - 1, children: [_jsx(ScrollView, { height: availableHeight - 2, ref: scrollViewRef, children: renderMessageItem(message, messageIndex, true) }), _jsx(Box, { justifyContent: "center", children: _jsx(Text, { color: colors.dimText, children: hasMoreBelow ? "↓" : " " }) })] })] }));
|
|
68
|
+
};
|
|
@@ -8,4 +8,6 @@ export { ExecutionContent, truncateContent } from './execution-content.js';
|
|
|
8
8
|
export { ExecutionInput } from './execution-input.js';
|
|
9
9
|
export { ExecutionProgress } from './execution-progress.js';
|
|
10
10
|
export { ExecutionStatus } from './execution-status.js';
|
|
11
|
+
export { ExpandedLogView } from './expanded-log-view.js';
|
|
12
|
+
export { ExpandedMessageView } from './expanded-message-view.js';
|
|
11
13
|
export { LogItem } from './log-item.js';
|
|
@@ -8,4 +8,6 @@ export { ExecutionContent, truncateContent } from './execution-content.js';
|
|
|
8
8
|
export { ExecutionInput } from './execution-input.js';
|
|
9
9
|
export { ExecutionProgress } from './execution-progress.js';
|
|
10
10
|
export { ExecutionStatus } from './execution-status.js';
|
|
11
|
+
export { ExpandedLogView } from './expanded-log-view.js';
|
|
12
|
+
export { ExpandedMessageView } from './expanded-message-view.js';
|
|
11
13
|
export { LogItem } from './log-item.js';
|
|
@@ -9,6 +9,10 @@ import type { ActivityLog } from '../../types.js';
|
|
|
9
9
|
interface LogItemProps {
|
|
10
10
|
/** Dynamic heights based on terminal breakpoint */
|
|
11
11
|
heights: MessageItemHeights;
|
|
12
|
+
/** Whether content should be fully expanded (no truncation) */
|
|
13
|
+
isExpand?: boolean;
|
|
14
|
+
/** Whether this log item is currently selected */
|
|
15
|
+
isSelected?: boolean;
|
|
12
16
|
/** The activity log to display */
|
|
13
17
|
log: ActivityLog;
|
|
14
18
|
}
|
|
@@ -12,8 +12,8 @@ import { ExecutionContent } from './execution-content.js';
|
|
|
12
12
|
import { ExecutionInput } from './execution-input.js';
|
|
13
13
|
import { ExecutionProgress } from './execution-progress.js';
|
|
14
14
|
import { ExecutionStatus } from './execution-status.js';
|
|
15
|
-
export const LogItem = ({ heights, log }) => {
|
|
15
|
+
export const LogItem = ({ heights, isExpand, isSelected, log }) => {
|
|
16
16
|
const { theme: { colors }, } = useTheme();
|
|
17
17
|
const displayTime = formatTime(log.timestamp);
|
|
18
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, width: "100%", children: [_jsxs(Box, { children: [_jsxs(Text, { color: log.type === 'curate' ? colors.curateCommand : colors.queryCommand, children: ["[", log.type, "] "] }), _jsxs(Text, { color: colors.dimText, children: ["@", log.source ?? 'system'] }), _jsx(Spacer, {}), _jsxs(Text, { color: colors.dimText, children: ["[", displayTime, "]"] })] }), _jsx(ExecutionInput, { input: log.input }), _jsxs(Box, { flexDirection: "column", children: [log.progress && _jsx(ExecutionProgress, { maxLines: heights.maxProgressItems, progress: log.progress }), _jsx(ExecutionStatus, { status: log.status })] }), (log.status === 'failed' || log.status === 'completed') && (_jsx(ExecutionContent, { bottomMargin: heights.contentBottomMargin, content: log.content ?? '', isError: log.status === 'failed', maxLines: heights.maxContentLines })), log.status === 'completed' && (_jsx(ExecutionChanges, { created: log.changes.created, maxChanges: heights.maxChanges, updated: log.changes.updated }))] }));
|
|
18
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, width: "100%", children: [_jsxs(Box, { children: [_jsxs(Text, { color: log.type === 'curate' ? colors.curateCommand : colors.queryCommand, children: ["[", log.type, "] "] }), _jsxs(Text, { color: colors.dimText, children: ["@", log.source ?? 'system'] }), isSelected && (_jsxs(Text, { dimColor: true, italic: true, children: [" \u2190 [ctrl+o] to ", isExpand ? 'collapse' : 'expand'] })), _jsx(Spacer, {}), _jsxs(Text, { color: colors.dimText, children: ["[", displayTime, "]"] })] }), _jsx(ExecutionInput, { input: log.input, isExpand: isExpand }), _jsxs(Box, { flexDirection: "column", children: [log.progress && (_jsx(ExecutionProgress, { isExpand: isExpand, maxLines: heights.maxProgressItems, progress: log.progress })), _jsx(ExecutionStatus, { status: log.status })] }), (log.status === 'failed' || log.status === 'completed') && (_jsx(ExecutionContent, { bottomMargin: heights.contentBottomMargin, content: log.content ?? '', isError: log.status === 'failed', isExpand: isExpand, maxLines: heights.maxContentLines })), log.status === 'completed' && (_jsx(ExecutionChanges, { created: log.changes.created, isExpand: isExpand, maxChanges: heights.maxChanges, updated: log.changes.updated }))] }));
|
|
19
19
|
};
|
|
@@ -13,5 +13,5 @@ export const Footer = () => {
|
|
|
13
13
|
const { theme: { colors }, } = useTheme();
|
|
14
14
|
// Filter out 'tab' shortcut during onboarding (tab switching is disabled)
|
|
15
15
|
const visibleShortcuts = useMemo(() => (shouldShowOnboarding ? shortcuts.filter((s) => s.key !== 'tab') : shortcuts), [shortcuts, shouldShowOnboarding]);
|
|
16
|
-
return (_jsx(Box, {
|
|
16
|
+
return (_jsx(Box, { paddingX: 1, width: "100%", children: visibleShortcuts.map((shortcut, index) => (_jsxs(Box, { children: [index > 0 && _jsx(Text, { color: colors.dimText, children: " \u2022 " }), _jsx(Text, { color: colors.text, children: shortcut.key }), _jsxs(Text, { color: colors.dimText, children: [" ", shortcut.description] })] }, shortcut.key))) }));
|
|
17
17
|
};
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { CommandDetails } from './command-details.js';
|
|
5
5
|
export { EnterPrompt } from './enter-prompt.js';
|
|
6
|
-
export { ExecutionChanges, ExecutionContent, ExecutionInput, ExecutionProgress, ExecutionStatus, LogItem, truncateContent, } from './execution/index.js';
|
|
6
|
+
export { ExecutionChanges, ExecutionContent, ExecutionInput, ExecutionProgress, ExecutionStatus, ExpandedLogView, LogItem, truncateContent, } from './execution/index.js';
|
|
7
7
|
export { Footer } from './footer.js';
|
|
8
8
|
export { Header } from './header.js';
|
|
9
9
|
export { Init } from './init.js';
|
|
10
10
|
export type { InitProps } from './init.js';
|
|
11
11
|
export { Logo } from './logo.js';
|
|
12
12
|
export type { LogoVariant } from './logo.js';
|
|
13
|
+
export { Markdown } from './markdown.js';
|
|
13
14
|
export { MessageItem } from './message-item.js';
|
|
14
15
|
export { CopyablePrompt, OnboardingFlow, OnboardingStep, WelcomeBox } from './onboarding/index.js';
|
|
15
16
|
export type { OnboardingStepType } from './onboarding/index.js';
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { CommandDetails } from './command-details.js';
|
|
5
5
|
export { EnterPrompt } from './enter-prompt.js';
|
|
6
|
-
export { ExecutionChanges, ExecutionContent, ExecutionInput, ExecutionProgress, ExecutionStatus, LogItem, truncateContent, } from './execution/index.js';
|
|
6
|
+
export { ExecutionChanges, ExecutionContent, ExecutionInput, ExecutionProgress, ExecutionStatus, ExpandedLogView, LogItem, truncateContent, } from './execution/index.js';
|
|
7
7
|
export { Footer } from './footer.js';
|
|
8
8
|
export { Header } from './header.js';
|
|
9
9
|
export { Init } from './init.js';
|
|
10
10
|
export { Logo } from './logo.js';
|
|
11
|
+
export { Markdown } from './markdown.js';
|
|
11
12
|
export { MessageItem } from './message-item.js';
|
|
12
13
|
export { CopyablePrompt, OnboardingFlow, OnboardingStep, WelcomeBox } from './onboarding/index.js';
|
|
13
14
|
export { OutputLog } from './output-log.js';
|
|
@@ -237,17 +237,10 @@ export const Init = ({ active = true, autoStart = false, idleMessage = 'Your pro
|
|
|
237
237
|
handleSelectResponse,
|
|
238
238
|
maxSearchItems,
|
|
239
239
|
]);
|
|
240
|
-
// Running state - show streaming output
|
|
241
|
-
if (isRunningInit) {
|
|
242
|
-
const { displayMessages } = getMessagesFromEnd(processedStreamingMessages, maxOutputLines);
|
|
243
|
-
return (_jsx(Box, { flexDirection: "column", width: "100%", children: _jsxs(Box, { borderColor: colors.border, borderStyle: "single", flexDirection: "column", paddingX: 1, paddingY: 0, width: "100%", children: [displayMessages.map((streamMsg) => renderStreamingMessage(streamMsg)), renderActivePrompt()] }) }));
|
|
244
|
-
}
|
|
245
240
|
// Error state - show error with retry
|
|
246
241
|
if (initError) {
|
|
247
242
|
return (_jsxs(Box, { flexDirection: "column", rowGap: 1, children: [_jsxs(Text, { color: colors.errorText, children: ["Error: ", initError] }), _jsx(EnterPrompt, { action: "try again", active: active && !isRunningInit && !activePrompt, onEnter: runInit })] }));
|
|
248
243
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
return (_jsxs(Box, { flexDirection: "column", rowGap: 1, children: [showIdleMessage && _jsx(Text, { color: colors.text, children: idleMessage }), _jsx(EnterPrompt, { action: "initialize your project", active: active && !isRunningInit && !activePrompt, onEnter: runInit })] }));
|
|
244
|
+
const { displayMessages } = getMessagesFromEnd(processedStreamingMessages, maxOutputLines);
|
|
245
|
+
return (_jsxs(Box, { flexDirection: "column", rowGap: 1, children: [showIdleMessage && _jsx(Text, { color: colors.text, children: idleMessage }), displayMessages.length > 0 && (_jsxs(Box, { borderColor: colors.border, borderStyle: "single", flexDirection: "column", paddingX: 1, paddingY: 0, width: "100%", children: [displayMessages.map((streamMsg) => renderStreamingMessage(streamMsg)), renderActivePrompt()] })), !isRunningInit && displayMessages.length === 0 && (_jsx(EnterPrompt, { action: "initialize your project", active: active && !isRunningInit && !activePrompt, onEnter: runInit }))] }));
|
|
253
246
|
};
|
|
@@ -8,7 +8,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
*/
|
|
9
9
|
import { Box, Text, useStdout } from 'ink';
|
|
10
10
|
import { useMemo } from 'react';
|
|
11
|
-
import { useTheme } from '../hooks/index.js';
|
|
11
|
+
import { useIsLatestVersion, useTheme } from '../hooks/index.js';
|
|
12
12
|
/**
|
|
13
13
|
* Full ASCII logo "ByteRover" for large terminals
|
|
14
14
|
*/
|
|
@@ -83,6 +83,7 @@ function getLogoLines(variant, terminalWidth) {
|
|
|
83
83
|
export const Logo = ({ compact, version }) => {
|
|
84
84
|
const { stdout } = useStdout();
|
|
85
85
|
const { theme: { colors }, } = useTheme();
|
|
86
|
+
const isLatestVersion = useIsLatestVersion(version ?? '');
|
|
86
87
|
const terminalWidth = stdout?.columns ?? 80;
|
|
87
88
|
const terminalHeight = stdout?.rows ?? 24;
|
|
88
89
|
const variant = useMemo(() => (compact ? 'text' : selectLogoVariant(terminalWidth, terminalHeight)), [compact, terminalWidth, terminalHeight]);
|
|
@@ -90,9 +91,9 @@ export const Logo = ({ compact, version }) => {
|
|
|
90
91
|
const headerLine = useMemo(() => (variant === 'full' && LOGO_FULL[0] ? getHeaderLine(LOGO_FULL[0], version ?? '', terminalWidth) : null), [variant, version, terminalWidth]);
|
|
91
92
|
// Text-only logo for minimal terminals
|
|
92
93
|
if (variant === 'text') {
|
|
93
|
-
const textContent = MINI_LOGO + (version ? ` v${version}` : '');
|
|
94
|
+
const textContent = MINI_LOGO + (version ? ` v${version}` : '') + (isLatestVersion ? ' (latest)' : '');
|
|
94
95
|
const padEnd = calculatePadEnd(textContent.length, terminalWidth);
|
|
95
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: colors.logoDecor, children: PAD_START }), _jsxs(Text, { children: [_jsx(Text, { bold: true, color: colors.logoBold, children: MINI_LOGO }), version && _jsxs(Text, { color: colors.logoVersion, children: [" v", version] })] }), _jsx(Text, { color: colors.logoDecor, children: padEnd })] }));
|
|
96
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: colors.logoDecor, children: PAD_START }), _jsxs(Text, { children: [_jsx(Text, { bold: true, color: colors.logoBold, children: MINI_LOGO }), version && _jsxs(Text, { color: colors.logoVersion, children: [" v", version] }), isLatestVersion && _jsx(Text, { color: colors.logoVersion, children: " (latest)" })] }), _jsx(Text, { color: colors.logoDecor, children: padEnd })] }));
|
|
96
97
|
}
|
|
97
98
|
// ASCII logo with header line and version
|
|
98
99
|
return (_jsxs(Box, { flexDirection: "column", children: [headerLine && (_jsxs(Box, { children: [_jsx(Text, { color: colors.logoDecor, children: headerLine.padStart }), _jsxs(Text, { children: [_jsx(Text, { children: headerLine.brv }), _jsx(Text, { children: headerLine.spaces }), _jsx(Text, { color: colors.logoVersion, children: headerLine.version })] }), _jsx(Text, { color: colors.logoDecor, children: headerLine.padEnd })] })), logoLines.map((line, index) => (_jsxs(Box, { children: [_jsx(Text, { color: colors.logoDecor, children: line.padStart }), _jsx(Text, { color: colors.logoBold, children: line.content }), _jsx(Text, { color: colors.logoDecor, children: line.padEnd })] }, index)))] }));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown Component
|
|
3
|
+
*
|
|
4
|
+
* Parses markdown strings using unified/remark-parse and renders them as Ink components.
|
|
5
|
+
* Supports headings, paragraphs, inline formatting (bold, italic, code), code blocks,
|
|
6
|
+
* lists (ordered/unordered), and links.
|
|
7
|
+
*/
|
|
8
|
+
import React from 'react';
|
|
9
|
+
interface MarkdownProps {
|
|
10
|
+
children: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const Markdown: ({ children }: MarkdownProps) => React.ReactElement;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import remarkParse from 'remark-parse';
|
|
4
|
+
import { unified } from 'unified';
|
|
5
|
+
import { useTheme } from '../hooks/index.js';
|
|
6
|
+
const renderPhrasingContent = (nodes, theme) => nodes.map((node, index) => {
|
|
7
|
+
switch (node.type) {
|
|
8
|
+
case 'break': {
|
|
9
|
+
return _jsx(Text, { children: '\n' }, index);
|
|
10
|
+
}
|
|
11
|
+
case 'emphasis': {
|
|
12
|
+
return (_jsx(Text, { italic: true, children: renderPhrasingContent(node.children, theme) }, index));
|
|
13
|
+
}
|
|
14
|
+
case 'inlineCode': {
|
|
15
|
+
return (_jsx(Text, { backgroundColor: theme.colors.bg2, children: node.value }, index));
|
|
16
|
+
}
|
|
17
|
+
case 'link': {
|
|
18
|
+
return (_jsx(Text, { color: theme.colors.info, underline: true, children: renderPhrasingContent(node.children, theme) }, index));
|
|
19
|
+
}
|
|
20
|
+
case 'strong': {
|
|
21
|
+
return (_jsx(Text, { bold: true, children: renderPhrasingContent(node.children, theme) }, index));
|
|
22
|
+
}
|
|
23
|
+
case 'text': {
|
|
24
|
+
return _jsx(Text, { children: node.value }, index);
|
|
25
|
+
}
|
|
26
|
+
default: {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
const renderListItem = (node, context, theme) => {
|
|
32
|
+
const bullet = context.ordered ? `${context.index + 1}. ` : '• ';
|
|
33
|
+
const hasNestedList = node.children.some((child) => child.type === 'list');
|
|
34
|
+
if (hasNestedList) {
|
|
35
|
+
return (_jsx(Box, { flexDirection: "column", children: node.children.map((child, childIndex) => {
|
|
36
|
+
if (child.type === 'paragraph') {
|
|
37
|
+
return (_jsxs(Box, { children: [_jsx(Text, { children: bullet }), _jsx(Text, { children: renderPhrasingContent(child.children, theme) })] }, childIndex));
|
|
38
|
+
}
|
|
39
|
+
if (child.type === 'list') {
|
|
40
|
+
return (_jsx(Box, { marginLeft: 2, children: renderList(child, theme) }, childIndex));
|
|
41
|
+
}
|
|
42
|
+
return renderNode(child, theme, childIndex);
|
|
43
|
+
}) }, context.index));
|
|
44
|
+
}
|
|
45
|
+
const content = node.children.map((child, childIndex) => {
|
|
46
|
+
if (child.type === 'paragraph') {
|
|
47
|
+
return _jsx(Text, { children: renderPhrasingContent(child.children, theme) }, childIndex);
|
|
48
|
+
}
|
|
49
|
+
return renderNode(child, theme, childIndex);
|
|
50
|
+
});
|
|
51
|
+
return (_jsxs(Box, { children: [_jsx(Text, { children: bullet }), content] }, context.index));
|
|
52
|
+
};
|
|
53
|
+
const renderList = (node, theme) => (_jsx(Box, { flexDirection: "column", children: node.children.map((item, index) => renderListItem(item, { index, ordered: node.ordered ?? false }, theme)) }));
|
|
54
|
+
const renderNode = (node, theme, key) => {
|
|
55
|
+
switch (node.type) {
|
|
56
|
+
case 'blockquote': {
|
|
57
|
+
const blockquote = node;
|
|
58
|
+
return (_jsx(Box, { marginBottom: 1, children: _jsx(Box, { borderBottom: false, borderLeft: true, borderLeftColor: theme.colors.dimText, borderRight: false, borderStyle: "single", borderTop: false, flexDirection: "column", children: blockquote.children.map((child, index) => renderNode(child, theme, index)) }) }, key));
|
|
59
|
+
}
|
|
60
|
+
case 'code': {
|
|
61
|
+
const code = node;
|
|
62
|
+
const langLabel = code.lang ? `[${code.lang}]` : '';
|
|
63
|
+
return (_jsxs(Box, { backgroundColor: theme.colors.bg2, flexDirection: "column", marginY: 1, paddingX: 1, children: [langLabel && _jsx(Text, { color: theme.colors.dimText, children: langLabel }), _jsx(Text, { children: code.value })] }, key));
|
|
64
|
+
}
|
|
65
|
+
case 'heading': {
|
|
66
|
+
const heading = node;
|
|
67
|
+
return (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: theme.colors.primary, children: ['#'.repeat(heading.depth), " ", renderPhrasingContent(heading.children, theme)] }) }, key));
|
|
68
|
+
}
|
|
69
|
+
case 'list': {
|
|
70
|
+
return (_jsx(Box, { marginBottom: 1, children: renderList(node, theme) }, key));
|
|
71
|
+
}
|
|
72
|
+
case 'paragraph': {
|
|
73
|
+
return (_jsx(Box, { children: _jsx(Text, { children: renderPhrasingContent(node.children, theme) }) }, key));
|
|
74
|
+
}
|
|
75
|
+
case 'thematicBreak': {
|
|
76
|
+
return (_jsx(Box, { marginY: 1, children: _jsx(Text, { color: theme.colors.dimText, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }) }, key));
|
|
77
|
+
}
|
|
78
|
+
default: {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const renderChildren = (children, theme) => children.map((child, index) => renderNode(child, theme, index));
|
|
84
|
+
export const Markdown = ({ children }) => {
|
|
85
|
+
const { theme } = useTheme();
|
|
86
|
+
const tree = unified().use(remarkParse).parse(children);
|
|
87
|
+
return _jsx(Box, { flexDirection: "column", children: renderChildren(tree.children, theme) });
|
|
88
|
+
};
|
|
@@ -8,5 +8,5 @@ import { Box, Text } from 'ink';
|
|
|
8
8
|
import { useTheme } from '../hooks/index.js';
|
|
9
9
|
export const MessageItem = ({ message }) => {
|
|
10
10
|
const { theme: { colors } } = useTheme();
|
|
11
|
-
return (_jsxs(Box, { alignItems: "flex-start", flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Box, { borderBottom: false, borderLeftColor: colors.primary, borderRight: false, borderStyle: "bold", borderTop: false }), _jsx(Text, { children: message.fromCommand })] }), _jsx(Box, { borderColor: colors.border, borderStyle: "single", width: "100%", children: _jsx(Text, { children: message.content }) })] }));
|
|
11
|
+
return (_jsxs(Box, { alignItems: "flex-start", flexDirection: "column", marginBottom: 1, width: "100%", children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Box, { borderBottom: false, borderLeftColor: colors.primary, borderRight: false, borderStyle: "bold", borderTop: false }), _jsx(Text, { children: message.fromCommand })] }), _jsx(Box, { borderColor: colors.border, borderStyle: "single", width: "100%", children: _jsx(Text, { children: message.content }) })] }));
|
|
12
12
|
};
|
|
@@ -69,7 +69,7 @@ export const OnboardingFlow = ({ availableHeight, onInitComplete }) => {
|
|
|
69
69
|
completeOnboarding(true); // Pass true to indicate skipped
|
|
70
70
|
}
|
|
71
71
|
}, { isActive: isInWaitingState });
|
|
72
|
-
const renderInitContent = () => (_jsxs(Box, { flexDirection: "column", width: "100%", children: [
|
|
72
|
+
const renderInitContent = () => (_jsxs(Box, { flexDirection: "column", gap: 1, width: "100%", children: [_jsx(Init, { active: mode === 'activity' && currentStep === 'init', maxOutputLines: 10, showIdleMessage: false }), isInitialized && !initAcknowledged && (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { children: "Project initialized successfully!" }), _jsx(EnterPrompt, { action: "continue", active: isInitialized && !initAcknowledged && mode === 'activity' && currentStep === 'init', onEnter: () => {
|
|
73
73
|
setInitAcknowledged(true);
|
|
74
74
|
onInitComplete?.();
|
|
75
75
|
} })] }))] }));
|
|
@@ -13,7 +13,7 @@ import { useMode } from '../contexts/mode-context.js';
|
|
|
13
13
|
import { useTheme } from '../contexts/theme-context.js';
|
|
14
14
|
import { useSlashCompletion } from '../hooks/index.js';
|
|
15
15
|
import { CommandDetails } from './command-details.js';
|
|
16
|
-
const MAX_VISIBLE_ITEMS =
|
|
16
|
+
const MAX_VISIBLE_ITEMS = 7;
|
|
17
17
|
export const Suggestions = ({ input, onInsert, onSelect }) => {
|
|
18
18
|
const { theme: { colors }, } = useTheme();
|
|
19
19
|
const { mode, setMode } = useMode();
|
|
@@ -140,7 +140,7 @@ export const Suggestions = ({ input, onInsert, onSelect }) => {
|
|
|
140
140
|
// Don't show when user is typing arguments for a known command
|
|
141
141
|
if (suggestions.length === 0) {
|
|
142
142
|
if (isCommandAttempt && !hasMatchedCommand && input.trim().length > 1) {
|
|
143
|
-
return (_jsx(Box, { borderColor: colors.border, borderStyle: "single", height:
|
|
143
|
+
return (_jsx(Box, { borderColor: colors.border, borderStyle: "single", height: 9, paddingX: 1, children: _jsx(Text, { color: colors.dimText, children: "No commands found" }) }));
|
|
144
144
|
}
|
|
145
145
|
return null;
|
|
146
146
|
}
|
|
@@ -152,7 +152,7 @@ export const Suggestions = ({ input, onInsert, onSelect }) => {
|
|
|
152
152
|
// Calculate if there are more items above/below
|
|
153
153
|
const hasMoreAbove = windowStart > 0;
|
|
154
154
|
const hasMoreBelow = windowStart + visibleSuggestions.length < suggestions.length;
|
|
155
|
-
return (_jsxs(Box, { borderColor: colors.border, borderStyle: "single", columnGap: 1, height:
|
|
155
|
+
return (_jsxs(Box, { borderColor: colors.border, borderStyle: "single", columnGap: 1, height: 9, overflowY: 'hidden', paddingX: 1, children: [_jsxs(Box, { flexDirection: 'column', flexShrink: 0, children: [hasMoreAbove && (_jsxs(Text, { color: colors.dimText, dimColor: true, children: ["\u2191 ", windowStart, " more"] })), visibleSuggestions.map((suggestion, index) => {
|
|
156
156
|
const actualIndex = windowStart + index;
|
|
157
157
|
const isActive = actualIndex === activeIndex;
|
|
158
158
|
return (_jsx(Box, { children: _jsxs(Text, { backgroundColor: isActive ? colors.dimText : undefined, color: colors.text, children: [isActive ? '❯ ' : ' ', suggestion.label.padEnd(labelWidth)] }) }, suggestion.value));
|
|
@@ -25,12 +25,16 @@ import { createContext, useCallback, useContext, useMemo, useState } from 'react
|
|
|
25
25
|
*/
|
|
26
26
|
const SHORTCUTS_BY_MODE = {
|
|
27
27
|
activity: [
|
|
28
|
-
{ description: '
|
|
28
|
+
{ description: 'navigate', key: '↑↓' },
|
|
29
|
+
{ description: 'go to top/bottom', key: 'g/G' },
|
|
30
|
+
{ description: 'expand/collapse item', key: 'ctrl+o' },
|
|
29
31
|
{ description: 'switch view', key: 'tab' },
|
|
30
32
|
{ description: 'quit', key: 'ctrl+c' },
|
|
31
33
|
],
|
|
32
34
|
console: [
|
|
33
|
-
{ description: '
|
|
35
|
+
{ description: 'navigate', key: '↑↓' },
|
|
36
|
+
{ description: 'go to top/bottom', key: 'g/G' },
|
|
37
|
+
{ description: 'expand/collapse item', key: 'ctrl+o' },
|
|
34
38
|
{ description: 'switch view', key: 'tab' },
|
|
35
39
|
{ description: 'quit', key: 'ctrl+c' },
|
|
36
40
|
],
|
|
@@ -8,6 +8,7 @@ export { parseExecutionContent, useActivityLogs } from './use-activity-logs.js';
|
|
|
8
8
|
export type { UseActivityLogsReturn } from './use-activity-logs.js';
|
|
9
9
|
export { useAuthPolling } from './use-auth-polling.js';
|
|
10
10
|
export type { UseAuthPollingOptions } from './use-auth-polling.js';
|
|
11
|
+
export { useIsLatestVersion } from './use-is-latest-version.js';
|
|
11
12
|
export { useOnboarding } from './use-onboarding.js';
|
|
12
13
|
export type { OnboardingStep, UseOnboardingReturn } from './use-onboarding.js';
|
|
13
14
|
export { useSlashCommandProcessor } from './use-slash-command-processor.js';
|
package/dist/tui/hooks/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { ModeProvider, useMode } from '../contexts/mode-context.js';
|
|
|
6
6
|
export { ThemeProvider, useTheme } from '../contexts/theme-context.js';
|
|
7
7
|
export { parseExecutionContent, useActivityLogs } from './use-activity-logs.js';
|
|
8
8
|
export { useAuthPolling } from './use-auth-polling.js';
|
|
9
|
+
export { useIsLatestVersion } from './use-is-latest-version.js';
|
|
9
10
|
export { useOnboarding } from './use-onboarding.js';
|
|
10
11
|
export { useSlashCommandProcessor } from './use-slash-command-processor.js';
|
|
11
12
|
export { useSlashCompletion } from './use-slash-completion.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook to check if current version is the latest
|
|
3
|
+
*
|
|
4
|
+
* Polls update-notifier cache periodically to detect new versions.
|
|
5
|
+
*/
|
|
6
|
+
import { useEffect, useState } from 'react';
|
|
7
|
+
import updateNotifier from 'update-notifier';
|
|
8
|
+
const CHECK_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
9
|
+
export function useIsLatestVersion(version) {
|
|
10
|
+
const [isLatestVersion, setIsLatestVersion] = useState(true);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const checkUpdate = () => {
|
|
13
|
+
const notifier = updateNotifier({ pkg: { name: 'byterover-cli', version } });
|
|
14
|
+
const isLatest = !notifier.update || notifier.update.latest === version;
|
|
15
|
+
setIsLatestVersion(isLatest);
|
|
16
|
+
};
|
|
17
|
+
checkUpdate();
|
|
18
|
+
const interval = setInterval(checkUpdate, CHECK_INTERVAL_MS);
|
|
19
|
+
return () => clearInterval(interval);
|
|
20
|
+
}, [version]);
|
|
21
|
+
return isLatestVersion;
|
|
22
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Command View
|
|
3
3
|
*
|
|
4
4
|
* Main view with slash command input and streaming output support.
|
|
5
|
-
* Uses
|
|
5
|
+
* Uses ScrollList for message history with automatic height measurement.
|
|
6
6
|
*/
|
|
7
7
|
import React from 'react';
|
|
8
8
|
/**
|