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.
Files changed (103) hide show
  1. package/README.md +8 -4
  2. package/dist/commands/mcp.d.ts +13 -0
  3. package/dist/commands/mcp.js +61 -0
  4. package/dist/core/domain/cipher/agent-events/types.d.ts +44 -1
  5. package/dist/core/domain/entities/agent.js +72 -18
  6. package/dist/core/domain/entities/connector-type.d.ts +2 -1
  7. package/dist/core/domain/entities/connector-type.js +2 -1
  8. package/dist/core/interfaces/connectors/connector-types.d.ts +13 -0
  9. package/dist/core/interfaces/i-mcp-config-writer.d.ts +40 -0
  10. package/dist/core/interfaces/i-mcp-config-writer.js +1 -0
  11. package/dist/core/interfaces/i-rule-template-service.d.ts +4 -2
  12. package/dist/core/interfaces/transport/i-transport-client.d.ts +7 -0
  13. package/dist/infra/cipher/agent/cipher-agent.d.ts +8 -0
  14. package/dist/infra/cipher/agent/cipher-agent.js +16 -0
  15. package/dist/infra/cipher/llm/context/context-manager.d.ts +8 -0
  16. package/dist/infra/cipher/llm/context/context-manager.js +16 -0
  17. package/dist/infra/cipher/llm/internal-llm-service.d.ts +4 -0
  18. package/dist/infra/cipher/llm/internal-llm-service.js +38 -10
  19. package/dist/infra/cipher/session/chat-session.d.ts +3 -0
  20. package/dist/infra/cipher/session/chat-session.js +7 -1
  21. package/dist/infra/cipher/tools/implementations/curate-tool.d.ts +1 -8
  22. package/dist/infra/cipher/tools/implementations/curate-tool.js +360 -22
  23. package/dist/infra/connectors/connector-manager.js +2 -0
  24. package/dist/infra/connectors/mcp/index.d.ts +4 -0
  25. package/dist/infra/connectors/mcp/index.js +4 -0
  26. package/dist/infra/connectors/mcp/json-mcp-config-writer.d.ts +26 -0
  27. package/dist/infra/connectors/mcp/json-mcp-config-writer.js +71 -0
  28. package/dist/infra/connectors/mcp/mcp-connector-config.d.ts +229 -0
  29. package/dist/infra/connectors/mcp/mcp-connector-config.js +173 -0
  30. package/dist/infra/connectors/mcp/mcp-connector.d.ts +80 -0
  31. package/dist/infra/connectors/mcp/mcp-connector.js +324 -0
  32. package/dist/infra/connectors/mcp/toml-mcp-config-writer.d.ts +45 -0
  33. package/dist/infra/connectors/mcp/toml-mcp-config-writer.js +134 -0
  34. package/dist/infra/connectors/rules/rules-connector.d.ts +1 -8
  35. package/dist/infra/connectors/rules/rules-connector.js +20 -85
  36. package/dist/infra/connectors/shared/rule-file-manager.d.ts +72 -0
  37. package/dist/infra/connectors/shared/rule-file-manager.js +119 -0
  38. package/dist/infra/connectors/shared/template-service.d.ts +10 -1
  39. package/dist/infra/connectors/shared/template-service.js +53 -16
  40. package/dist/infra/mcp/index.d.ts +2 -0
  41. package/dist/infra/mcp/index.js +2 -0
  42. package/dist/infra/mcp/mcp-server.d.ts +58 -0
  43. package/dist/infra/mcp/mcp-server.js +178 -0
  44. package/dist/infra/mcp/tools/brv-curate-tool.d.ts +23 -0
  45. package/dist/infra/mcp/tools/brv-curate-tool.js +68 -0
  46. package/dist/infra/mcp/tools/brv-query-tool.d.ts +17 -0
  47. package/dist/infra/mcp/tools/brv-query-tool.js +68 -0
  48. package/dist/infra/mcp/tools/index.d.ts +3 -0
  49. package/dist/infra/mcp/tools/index.js +3 -0
  50. package/dist/infra/mcp/tools/task-result-waiter.d.ts +30 -0
  51. package/dist/infra/mcp/tools/task-result-waiter.js +56 -0
  52. package/dist/infra/process/agent-worker.js +37 -0
  53. package/dist/infra/repl/commands/curate-command.js +2 -2
  54. package/dist/infra/transport/socket-io-transport-client.d.ts +7 -0
  55. package/dist/infra/transport/socket-io-transport-client.js +25 -0
  56. package/dist/infra/transport/socket-io-transport-server.js +4 -0
  57. package/dist/infra/usecase/connectors-use-case.d.ts +4 -0
  58. package/dist/infra/usecase/connectors-use-case.js +29 -10
  59. package/dist/infra/usecase/init-use-case.js +2 -3
  60. package/dist/infra/usecase/status-use-case.d.ts +10 -0
  61. package/dist/infra/usecase/status-use-case.js +53 -0
  62. package/dist/resources/prompts/curate.yml +107 -4
  63. package/dist/templates/mcp-base.md +1 -0
  64. package/dist/templates/sections/mcp-workflow.md +13 -0
  65. package/dist/tui/app.js +4 -1
  66. package/dist/tui/components/command-details.js +1 -1
  67. package/dist/tui/components/execution/execution-changes.d.ts +2 -0
  68. package/dist/tui/components/execution/execution-changes.js +5 -1
  69. package/dist/tui/components/execution/execution-content.d.ts +2 -0
  70. package/dist/tui/components/execution/execution-content.js +8 -18
  71. package/dist/tui/components/execution/execution-input.d.ts +2 -0
  72. package/dist/tui/components/execution/execution-input.js +6 -4
  73. package/dist/tui/components/execution/execution-progress.d.ts +2 -0
  74. package/dist/tui/components/execution/execution-progress.js +6 -2
  75. package/dist/tui/components/execution/expanded-log-view.d.ts +20 -0
  76. package/dist/tui/components/execution/expanded-log-view.js +75 -0
  77. package/dist/tui/components/execution/expanded-message-view.d.ts +24 -0
  78. package/dist/tui/components/execution/expanded-message-view.js +68 -0
  79. package/dist/tui/components/execution/index.d.ts +2 -0
  80. package/dist/tui/components/execution/index.js +2 -0
  81. package/dist/tui/components/execution/log-item.d.ts +4 -0
  82. package/dist/tui/components/execution/log-item.js +2 -2
  83. package/dist/tui/components/footer.js +1 -1
  84. package/dist/tui/components/index.d.ts +2 -1
  85. package/dist/tui/components/index.js +2 -1
  86. package/dist/tui/components/init.js +2 -9
  87. package/dist/tui/components/logo.js +4 -3
  88. package/dist/tui/components/markdown.d.ts +13 -0
  89. package/dist/tui/components/markdown.js +88 -0
  90. package/dist/tui/components/message-item.js +1 -1
  91. package/dist/tui/components/onboarding/onboarding-flow.js +1 -1
  92. package/dist/tui/components/suggestions.js +3 -3
  93. package/dist/tui/contexts/mode-context.js +6 -2
  94. package/dist/tui/hooks/index.d.ts +1 -0
  95. package/dist/tui/hooks/index.js +1 -0
  96. package/dist/tui/hooks/use-is-latest-version.d.ts +6 -0
  97. package/dist/tui/hooks/use-is-latest-version.js +22 -0
  98. package/dist/tui/views/command-view.d.ts +1 -1
  99. package/dist/tui/views/command-view.js +83 -98
  100. package/dist/tui/views/logs-view.d.ts +8 -0
  101. package/dist/tui/views/logs-view.js +55 -27
  102. package/oclif.manifest.json +26 -1
  103. 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, { gap: 1, 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))) }));
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
- if (autoStart) {
250
- return null;
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: [!isInitialized && (_jsx(Init, { active: mode === 'activity' && currentStep === 'init', maxOutputLines: MIN_OUTPUT_LINES, 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: () => {
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 = 5;
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: 7, paddingX: 1, children: _jsx(Text, { color: colors.dimText, children: "No commands found" }) }));
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: 7, 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) => {
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: 'scroll logs', key: '↑↓' },
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: 'scroll', key: '↑↓' },
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';
@@ -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,6 @@
1
+ /**
2
+ * Hook to check if current version is the latest
3
+ *
4
+ * Polls update-notifier cache periodically to detect new versions.
5
+ */
6
+ export declare function useIsLatestVersion(version: string): boolean;
@@ -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 ScrollableList for message history with dynamic height calculation.
5
+ * Uses ScrollList for message history with automatic height measurement.
6
6
  */
7
7
  import React from 'react';
8
8
  /**