byterover-cli 1.1.0 → 1.2.1
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 +24 -5
- 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/cipher/i-chat-session.d.ts +3 -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 +6 -1
- package/dist/infra/cipher/session/chat-session.js +12 -4
- 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/cipher/tools/implementations/task-tool.js +3 -3
- 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 +16 -0
- package/dist/infra/transport/socket-io-transport-client.js +46 -2
- 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/command-reference.md +5 -96
- package/dist/templates/sections/mcp-workflow.md +13 -0
- package/dist/templates/sections/workflow.md +21 -16
- 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
|
@@ -46,6 +46,20 @@ export class ConnectorsUseCase {
|
|
|
46
46
|
// Step 6: Install the selected connector
|
|
47
47
|
await this.installConnector(selectedAgent, selectedType);
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Display manual setup instructions for MCP configuration.
|
|
51
|
+
*/
|
|
52
|
+
displayManualInstructions(agent, instructions) {
|
|
53
|
+
this.terminal.log(`\nManual setup required for ${agent}`);
|
|
54
|
+
this.terminal.log('');
|
|
55
|
+
this.terminal.log('Add this configuration to your MCP settings:');
|
|
56
|
+
this.terminal.log('');
|
|
57
|
+
this.terminal.log(instructions.configContent);
|
|
58
|
+
this.terminal.log('');
|
|
59
|
+
if (instructions.guide) {
|
|
60
|
+
this.terminal.log(`\nFor detailed instructions, see: ${instructions.guide}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
49
63
|
/**
|
|
50
64
|
* Gets a description for a connector type.
|
|
51
65
|
*/
|
|
@@ -54,12 +68,12 @@ export class ConnectorsUseCase {
|
|
|
54
68
|
case 'hook': {
|
|
55
69
|
return `Instructions injected on each prompt (${configPath})`;
|
|
56
70
|
}
|
|
71
|
+
case 'mcp': {
|
|
72
|
+
return `Agent connects via MCP protocol ${configPath ? `(${configPath})` : ''}`;
|
|
73
|
+
}
|
|
57
74
|
case 'rules': {
|
|
58
75
|
return `Agent reads instructions from rule file (${configPath})`;
|
|
59
76
|
}
|
|
60
|
-
default: {
|
|
61
|
-
return configPath;
|
|
62
|
-
}
|
|
63
77
|
}
|
|
64
78
|
}
|
|
65
79
|
/**
|
|
@@ -70,12 +84,12 @@ export class ConnectorsUseCase {
|
|
|
70
84
|
case 'hook': {
|
|
71
85
|
return 'Hook';
|
|
72
86
|
}
|
|
87
|
+
case 'mcp': {
|
|
88
|
+
return 'MCP';
|
|
89
|
+
}
|
|
73
90
|
case 'rules': {
|
|
74
91
|
return 'Rules';
|
|
75
92
|
}
|
|
76
|
-
default: {
|
|
77
|
-
return type;
|
|
78
|
-
}
|
|
79
93
|
}
|
|
80
94
|
}
|
|
81
95
|
/**
|
|
@@ -101,8 +115,13 @@ export class ConnectorsUseCase {
|
|
|
101
115
|
async installConnector(agent, connectorType) {
|
|
102
116
|
const result = await this.connectorManager.switchConnector(agent, connectorType);
|
|
103
117
|
if (result.success) {
|
|
118
|
+
// Handle manual setup instructions
|
|
119
|
+
if (result.installResult.requiresManualSetup && result.installResult.manualInstructions) {
|
|
120
|
+
this.displayManualInstructions(agent, result.installResult.manualInstructions);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
104
123
|
if (result.fromType && result.fromType !== result.toType) {
|
|
105
|
-
this.terminal.log(`${agent} switched from ${result.fromType} to ${result.toType}`);
|
|
124
|
+
this.terminal.log(`${agent} switched from ${this.getConnectorLabel(result.fromType)} to ${this.getConnectorLabel(result.toType)}`);
|
|
106
125
|
if (result.uninstallResult?.wasInstalled) {
|
|
107
126
|
this.terminal.log(` Uninstalled: ${result.uninstallResult.configPath}`);
|
|
108
127
|
}
|
|
@@ -117,8 +136,8 @@ export class ConnectorsUseCase {
|
|
|
117
136
|
this.terminal.log(` Installed: ${result.installResult.configPath}`);
|
|
118
137
|
}
|
|
119
138
|
// Show restart message for hook connector
|
|
120
|
-
if (result.toType === 'hook' && !result.installResult.alreadyInstalled) {
|
|
121
|
-
this.terminal.warn(`\nPlease restart ${agent} to apply the new
|
|
139
|
+
if ((result.toType === 'hook' || result.toType === 'mcp') && !result.installResult.alreadyInstalled) {
|
|
140
|
+
this.terminal.warn(`\nPlease restart ${agent} to apply the new ${result.toType}.`);
|
|
122
141
|
}
|
|
123
142
|
}
|
|
124
143
|
else {
|
|
@@ -194,7 +213,7 @@ export class ConnectorsUseCase {
|
|
|
194
213
|
async promptForSwitchConfirmation(agent, fromType, toType) {
|
|
195
214
|
const fromConnector = this.connectorManager.getConnector(fromType);
|
|
196
215
|
const fromPath = fromConnector.getConfigPath(agent);
|
|
197
|
-
this.terminal.warn(`${agent} is currently connected via ${fromType} (${fromPath})`);
|
|
216
|
+
this.terminal.warn(`${agent} is currently connected via ${this.getConnectorLabel(fromType)} ${fromPath ? `(${fromPath})` : ''}`);
|
|
198
217
|
return this.terminal.confirm({
|
|
199
218
|
default: true,
|
|
200
219
|
message: `Switch to ${toType}? This will uninstall the current connector.`,
|
|
@@ -178,8 +178,8 @@ export class InitUseCase {
|
|
|
178
178
|
this.terminal.log(`${selectedAgent} connected via ${defaultType}`);
|
|
179
179
|
this.terminal.log(` Installed: ${result.configPath}`);
|
|
180
180
|
// Show restart message for hook connector
|
|
181
|
-
if (defaultType === 'hook') {
|
|
182
|
-
this.terminal.warn(`\n⚠️ Please restart ${selectedAgent} to apply the new
|
|
181
|
+
if (defaultType === 'hook' || defaultType === 'mcp') {
|
|
182
|
+
this.terminal.warn(`\n⚠️ Please restart ${selectedAgent} to apply the new ${defaultType}.`);
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
}
|
|
@@ -351,7 +351,6 @@ export class InitUseCase {
|
|
|
351
351
|
this.terminal.log('✓ Context tree initialized');
|
|
352
352
|
}
|
|
353
353
|
else {
|
|
354
|
-
// Remote has real data - sync it to local
|
|
355
354
|
await this.contextTreeWriterService.sync({ files: [...coGitSnapshot.files] });
|
|
356
355
|
await this.contextTreeSnapshotService.saveSnapshot();
|
|
357
356
|
this.terminal.log(`✓ Synced ${coGitSnapshot.files.length} context files from remote`);
|
|
@@ -4,10 +4,12 @@ import type { IProjectConfigStore } from '../../core/interfaces/i-project-config
|
|
|
4
4
|
import type { ITerminal } from '../../core/interfaces/i-terminal.js';
|
|
5
5
|
import type { ITokenStore } from '../../core/interfaces/i-token-store.js';
|
|
6
6
|
import type { ITrackingService } from '../../core/interfaces/i-tracking-service.js';
|
|
7
|
+
import type { IInstanceDiscovery } from '../../core/interfaces/instance/i-instance-discovery.js';
|
|
7
8
|
import type { IStatusUseCase } from '../../core/interfaces/usecase/i-status-use-case.js';
|
|
8
9
|
export interface StatusUseCaseOptions {
|
|
9
10
|
contextTreeService: IContextTreeService;
|
|
10
11
|
contextTreeSnapshotService: IContextTreeSnapshotService;
|
|
12
|
+
instanceDiscovery?: IInstanceDiscovery;
|
|
11
13
|
projectConfigStore: IProjectConfigStore;
|
|
12
14
|
terminal: ITerminal;
|
|
13
15
|
tokenStore: ITokenStore;
|
|
@@ -16,6 +18,7 @@ export interface StatusUseCaseOptions {
|
|
|
16
18
|
export declare class StatusUseCase implements IStatusUseCase {
|
|
17
19
|
private readonly contextTreeService;
|
|
18
20
|
private readonly contextTreeSnapshotService;
|
|
21
|
+
private readonly instanceDiscovery;
|
|
19
22
|
private readonly projectConfigStore;
|
|
20
23
|
private readonly terminal;
|
|
21
24
|
private readonly tokenStore;
|
|
@@ -24,4 +27,11 @@ export declare class StatusUseCase implements IStatusUseCase {
|
|
|
24
27
|
run(options: {
|
|
25
28
|
cliVersion: string;
|
|
26
29
|
}): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Checks the MCP connection status by:
|
|
32
|
+
* 1. Discovering running brv instance
|
|
33
|
+
* 2. Connecting to it via Socket.IO
|
|
34
|
+
* 3. Verifying bidirectional communication with ping
|
|
35
|
+
*/
|
|
36
|
+
private checkMcpStatus;
|
|
27
37
|
}
|
|
@@ -2,9 +2,12 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { BRV_DIR, CONTEXT_TREE_DIR } from '../../constants.js';
|
|
4
4
|
import { getErrorMessage } from '../../utils/error-helpers.js';
|
|
5
|
+
import { FileInstanceDiscovery } from '../instance/file-instance-discovery.js';
|
|
6
|
+
import { SocketIOTransportClient } from '../transport/socket-io-transport-client.js';
|
|
5
7
|
export class StatusUseCase {
|
|
6
8
|
contextTreeService;
|
|
7
9
|
contextTreeSnapshotService;
|
|
10
|
+
instanceDiscovery;
|
|
8
11
|
projectConfigStore;
|
|
9
12
|
terminal;
|
|
10
13
|
tokenStore;
|
|
@@ -12,6 +15,7 @@ export class StatusUseCase {
|
|
|
12
15
|
constructor(options) {
|
|
13
16
|
this.contextTreeService = options.contextTreeService;
|
|
14
17
|
this.contextTreeSnapshotService = options.contextTreeSnapshotService;
|
|
18
|
+
this.instanceDiscovery = options.instanceDiscovery ?? new FileInstanceDiscovery();
|
|
15
19
|
this.projectConfigStore = options.projectConfigStore;
|
|
16
20
|
this.terminal = options.terminal;
|
|
17
21
|
this.tokenStore = options.tokenStore;
|
|
@@ -56,6 +60,8 @@ export class StatusUseCase {
|
|
|
56
60
|
this.terminal.log('Project Status: Unable to read project configuration');
|
|
57
61
|
this.terminal.warn(`Warning: ${getErrorMessage(error)}`);
|
|
58
62
|
}
|
|
63
|
+
// MCP connection status
|
|
64
|
+
await this.checkMcpStatus();
|
|
59
65
|
// Context tree status
|
|
60
66
|
try {
|
|
61
67
|
const contextTreeExists = await this.contextTreeService.exists();
|
|
@@ -94,4 +100,51 @@ export class StatusUseCase {
|
|
|
94
100
|
this.terminal.warn(`Warning: ${error instanceof Error ? error.message : 'Context Tree unable to check status'}`);
|
|
95
101
|
}
|
|
96
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Checks the MCP connection status by:
|
|
105
|
+
* 1. Discovering running brv instance
|
|
106
|
+
* 2. Connecting to it via Socket.IO
|
|
107
|
+
* 3. Verifying bidirectional communication with ping
|
|
108
|
+
*/
|
|
109
|
+
async checkMcpStatus() {
|
|
110
|
+
try {
|
|
111
|
+
// Step 1: Discover running instance
|
|
112
|
+
const discoveryResult = await this.instanceDiscovery.discover(process.cwd());
|
|
113
|
+
if (!discoveryResult.found) {
|
|
114
|
+
if (discoveryResult.reason === 'instance_crashed') {
|
|
115
|
+
this.terminal.log(`MCP Status: ${chalk.red('Instance crashed')} (stale instance file found)`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
this.terminal.log(`MCP Status: ${chalk.yellow('No instance running')}`);
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const { instance, projectRoot } = discoveryResult;
|
|
123
|
+
this.terminal.log(`MCP Status: Instance found (PID: ${instance.pid}, Port: ${instance.port})`);
|
|
124
|
+
// Step 2: Connect to instance
|
|
125
|
+
const client = new SocketIOTransportClient();
|
|
126
|
+
const url = instance.getTransportUrl();
|
|
127
|
+
try {
|
|
128
|
+
await client.connect(url);
|
|
129
|
+
}
|
|
130
|
+
catch (connectError) {
|
|
131
|
+
this.terminal.log(`MCP Status: ${chalk.red('Connection failed')} - ${getErrorMessage(connectError)}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Step 3: Verify bidirectional communication with ping
|
|
135
|
+
const isResponsive = await client.isConnected(2000);
|
|
136
|
+
if (isResponsive) {
|
|
137
|
+
this.terminal.log(`MCP Status: ${chalk.green('Connected and responsive')} (${projectRoot})`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.terminal.log(`MCP Status: ${chalk.yellow('Connected but not responsive')} (ping timeout)`);
|
|
141
|
+
}
|
|
142
|
+
// Clean up
|
|
143
|
+
await client.disconnect();
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
this.terminal.log(`MCP Status: ${chalk.red('Error checking status')}`);
|
|
147
|
+
this.terminal.warn(`Warning: ${getErrorMessage(error)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
97
150
|
}
|
|
@@ -51,7 +51,110 @@ prompt: |
|
|
|
51
51
|
- **Before creating a new domain**: Check if existing domains could accommodate the content
|
|
52
52
|
- **Consolidate related concepts**: Group similar topics under the same domain for better organization
|
|
53
53
|
|
|
54
|
-
5. **
|
|
54
|
+
5. **Domain Context (REQUIRED for new domains)**: When creating content in a NEW domain (one that doesn't exist yet), you MUST provide the `domainContext` field:
|
|
55
|
+
|
|
56
|
+
**domainContext** - Describes the domain's purpose and scope:
|
|
57
|
+
- `purpose` (required): What this domain represents and why it exists
|
|
58
|
+
- `scope.included` (required): Array of what belongs in this domain
|
|
59
|
+
- `scope.excluded` (optional): Array of what does NOT belong in this domain
|
|
60
|
+
- `ownership` (optional): Which team/system owns this domain
|
|
61
|
+
- `usage` (optional): How this domain should be used
|
|
62
|
+
|
|
63
|
+
**Example with domainContext for a new domain:**
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"type": "ADD",
|
|
67
|
+
"path": "authentication/jwt",
|
|
68
|
+
"title": "Token Handling",
|
|
69
|
+
"content": { ... },
|
|
70
|
+
"domainContext": {
|
|
71
|
+
"purpose": "Contains all knowledge related to user and service authentication mechanisms used across the platform. Documents how identities are verified, credentials issued/validated, and authentication flows implemented.",
|
|
72
|
+
"scope": {
|
|
73
|
+
"included": [
|
|
74
|
+
"Login and signup authentication flows",
|
|
75
|
+
"Token-based authentication (JWT, refresh tokens)",
|
|
76
|
+
"Session handling",
|
|
77
|
+
"OAuth and third-party identity providers",
|
|
78
|
+
"Service-to-service authentication"
|
|
79
|
+
],
|
|
80
|
+
"excluded": [
|
|
81
|
+
"Authorization and permission models (belongs in authorization)",
|
|
82
|
+
"User profile management",
|
|
83
|
+
"Account lifecycle management"
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"ownership": "Platform Security Team",
|
|
87
|
+
"usage": "Use this domain for documenting authentication flows, token handling, identity verification. Backend engineers implementing login/token validation, security engineers auditing auth flows."
|
|
88
|
+
},
|
|
89
|
+
"reason": "Documenting JWT token handling in new authentication domain"
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**When to provide domainContext:**
|
|
94
|
+
- ALWAYS when the domain doesn't exist yet (check with `list_directory` or `glob_files` first)
|
|
95
|
+
- NOT needed when adding to an existing domain (context.md already exists)
|
|
96
|
+
|
|
97
|
+
6. **Topic Context (REQUIRED for new topics)**: When creating content in a NEW topic (one that doesn't exist yet), you MUST provide the `topicContext` field:
|
|
98
|
+
|
|
99
|
+
**topicContext** - Describes what the topic covers:
|
|
100
|
+
- `overview` (required): What this topic covers and its main focus
|
|
101
|
+
- `keyConcepts` (optional): Array of key concepts covered in this topic
|
|
102
|
+
- `relatedTopics` (optional): Array of related topics and how they connect
|
|
103
|
+
|
|
104
|
+
**Example with topicContext for a new topic:**
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"type": "ADD",
|
|
108
|
+
"path": "authentication/jwt",
|
|
109
|
+
"title": "Token Handling",
|
|
110
|
+
"content": { ... },
|
|
111
|
+
"topicContext": {
|
|
112
|
+
"overview": "Covers all aspects of JWT-based authentication including token generation, validation, and refresh mechanisms used across the platform.",
|
|
113
|
+
"keyConcepts": [
|
|
114
|
+
"JWT tokens and their structure",
|
|
115
|
+
"Refresh token rotation",
|
|
116
|
+
"Token blacklisting for revocation",
|
|
117
|
+
"Token validation middleware"
|
|
118
|
+
],
|
|
119
|
+
"relatedTopics": [
|
|
120
|
+
"authentication/session - for session-based alternatives",
|
|
121
|
+
"security/encryption - for token signing mechanisms"
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
"reason": "Documenting JWT token handling in new jwt topic"
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**When to provide topicContext:**
|
|
129
|
+
- ALWAYS when the topic doesn't exist yet (check with `list_directory` or `glob_files` first)
|
|
130
|
+
- NOT needed when adding to an existing topic (topic/context.md already exists)
|
|
131
|
+
|
|
132
|
+
7. **Subtopic Context (REQUIRED for new subtopics)**: When creating content in a NEW subtopic (one that doesn't exist yet), you MUST provide the `subtopicContext` field:
|
|
133
|
+
|
|
134
|
+
**subtopicContext** - Describes the subtopic's specific focus:
|
|
135
|
+
- `focus` (required): The specific focus of this subtopic
|
|
136
|
+
- `parentRelation` (optional): How this subtopic relates to its parent topic
|
|
137
|
+
|
|
138
|
+
**Example with subtopicContext for a new subtopic:**
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"type": "ADD",
|
|
142
|
+
"path": "authentication/jwt/refresh_tokens",
|
|
143
|
+
"title": "Rotation Strategy",
|
|
144
|
+
"content": { ... },
|
|
145
|
+
"subtopicContext": {
|
|
146
|
+
"focus": "Focuses on refresh token rotation strategy and invalidation mechanisms to prevent token reuse attacks.",
|
|
147
|
+
"parentRelation": "Handles the token refresh aspect of JWT authentication, specifically how old tokens are invalidated when new ones are issued."
|
|
148
|
+
},
|
|
149
|
+
"reason": "Documenting refresh token rotation in new subtopic"
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**When to provide subtopicContext:**
|
|
154
|
+
- ALWAYS when the subtopic doesn't exist yet (check with `list_directory` or `glob_files` first)
|
|
155
|
+
- NOT needed when adding to an existing subtopic (subtopic/context.md already exists)
|
|
156
|
+
|
|
157
|
+
8. **Two-Part Context Model (REQUIRED)**: When creating context using the `curate` tool, you MUST use the structured format with `rawConcept` and `narrative`:
|
|
55
158
|
|
|
56
159
|
**rawConcept** - Captures essential metadata and technical footprint:
|
|
57
160
|
- `task`: What is the task/feature being documented (required - always include this)
|
|
@@ -98,7 +201,7 @@ prompt: |
|
|
|
98
201
|
}
|
|
99
202
|
```
|
|
100
203
|
|
|
101
|
-
|
|
204
|
+
9. **Context Quality Requirements**: Each context MUST:
|
|
102
205
|
- Include a clear `task` in rawConcept describing what the concept is about
|
|
103
206
|
- Provide at least one of: `changes`, `files`, or `flow` in rawConcept
|
|
104
207
|
- Include at least one narrative field (`structure`, `dependencies`, or `features`)
|
|
@@ -123,11 +226,11 @@ prompt: |
|
|
|
123
226
|
code, message, context object, and optional cause. Use `ErrorHandler.wrap(fn)` for consistent error boundaries across
|
|
124
227
|
async operations."
|
|
125
228
|
|
|
126
|
-
|
|
229
|
+
10. **Tool Execution Efficiency**:
|
|
127
230
|
- When multiple tools don't depend on each other's results, execute them in parallel with `batch`
|
|
128
231
|
- Example: `glob_files`, `list_directory`, `grep_content` and `read_file` operations for different files can run together
|
|
129
232
|
|
|
130
|
-
|
|
233
|
+
11. **Response Format**:
|
|
131
234
|
- Your final response must be a brief summary (1-2 sentences) describing what knowledge was curated
|
|
132
235
|
- Always mention the topic name, and include the subtopic name if a subtopic was created
|
|
133
236
|
- Do NOT include any file paths, directory paths, or specific location details in your response
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{{mcp_workflow}}
|
|
@@ -1,100 +1,9 @@
|
|
|
1
1
|
# ByteRover CLI Command Reference
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Available Commands
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- `brv curate` - Curate context to the context tree
|
|
6
|
+
- `brv query` - Query and retrieve information from the context tree
|
|
7
|
+
- `brv status` - Show CLI status and project information
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
**Arguments:**
|
|
10
|
-
|
|
11
|
-
- `CONTEXT`: Knowledge context: patterns, decisions, errors, or insights (triggers autonomous mode, optional)
|
|
12
|
-
|
|
13
|
-
**Flags:**
|
|
14
|
-
|
|
15
|
-
- `--files`, `-f`: Include file paths for critical context (max 5 files). Only text/code files from the current project directory are allowed. **CONTEXT argument must come BEFORE this flag.**
|
|
16
|
-
|
|
17
|
-
**Good examples of context:**
|
|
18
|
-
|
|
19
|
-
- "Auth uses JWT with 24h expiry. Tokens stored in httpOnly cookies via authMiddleware.ts"
|
|
20
|
-
- "API rate limit is 100 req/min per user. Implemented using Redis with sliding window in rateLimiter.ts"
|
|
21
|
-
|
|
22
|
-
**Bad examples:**
|
|
23
|
-
|
|
24
|
-
- "Authentication" or "JWT tokens" (too vague, lacks context)
|
|
25
|
-
- "Rate limiting" (no implementation details or file references)
|
|
26
|
-
|
|
27
|
-
**Examples:**
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
# Interactive mode (manually choose domain/topic)
|
|
31
|
-
brv curate
|
|
32
|
-
|
|
33
|
-
# Autonomous mode - LLM auto-categorizes your context
|
|
34
|
-
brv curate "Auth uses JWT with 24h expiry. Tokens stored in httpOnly cookies via authMiddleware.ts"
|
|
35
|
-
|
|
36
|
-
# Include files (CONTEXT must come before --files)
|
|
37
|
-
# Single file
|
|
38
|
-
brv curate "Authentication middleware validates JWT tokens" -f src/middleware/auth.ts
|
|
39
|
-
|
|
40
|
-
# Multiple files - repeat --files flag for each file
|
|
41
|
-
brv curate "JWT authentication implementation with refresh token rotation" --files src/auth/jwt.ts --files docs/auth.md
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
**Behavior:**
|
|
45
|
-
|
|
46
|
-
- Interactive mode: Navigate context tree, create topic folder, edit context.md
|
|
47
|
-
- Autonomous mode: LLM automatically categorizes and places context in appropriate location
|
|
48
|
-
- When `--files` is provided, agent reads files in parallel before creating knowledge topics
|
|
49
|
-
|
|
50
|
-
**Requirements:** Project must be initialized (`brv init`) and authenticated (`brv login`)
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
### `brv query`
|
|
55
|
-
|
|
56
|
-
**Description:** Query and retrieve information from the context tree
|
|
57
|
-
|
|
58
|
-
**Arguments:**
|
|
59
|
-
|
|
60
|
-
- `QUERY`: Natural language question about your codebase or project knowledge (required)
|
|
61
|
-
|
|
62
|
-
**Good examples of queries:**
|
|
63
|
-
|
|
64
|
-
- "How is user authentication implemented?"
|
|
65
|
-
- "What are the API rate limits and where are they enforced?"
|
|
66
|
-
|
|
67
|
-
**Bad examples:**
|
|
68
|
-
|
|
69
|
-
- "auth" or "authentication" (too vague, not a question)
|
|
70
|
-
- "show me code" (not specific about what information is needed)
|
|
71
|
-
|
|
72
|
-
**Examples:**
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
# Ask questions about patterns, decisions, or implementation details
|
|
76
|
-
brv query What are the coding standards?
|
|
77
|
-
brv query How is authentication implemented?
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
**Behavior:**
|
|
81
|
-
|
|
82
|
-
- Uses AI agent to search and answer questions about the context tree
|
|
83
|
-
- Accepts natural language questions (not just keywords)
|
|
84
|
-
- Displays tool execution progress in real-time
|
|
85
|
-
|
|
86
|
-
**Requirements:** Project must be initialized (`brv init`) and authenticated (`brv login`)
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## Best Practices
|
|
91
|
-
|
|
92
|
-
### Efficient Workflow
|
|
93
|
-
|
|
94
|
-
1. **Read only what's needed:** Check context tree with `brv status` to see changes before reading full content with `brv query`
|
|
95
|
-
2. **Update precisely:** Use `brv curate` to add/update specific context in context tree
|
|
96
|
-
3. **Push when appropriate:** Prompt user to run `brv push` after completing significant work
|
|
97
|
-
|
|
98
|
-
### Context tree Management
|
|
99
|
-
|
|
100
|
-
- Use `brv curate` to directly add/update context in the context tree
|
|
9
|
+
Run `brv query --help` for query instruction and `brv curate --help` for curation instruction.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Workflow Instruction
|
|
2
|
+
|
|
3
|
+
You are a coding agent integrated with ByteRover via MCP (Model Context Protocol).
|
|
4
|
+
|
|
5
|
+
## Core Rules
|
|
6
|
+
|
|
7
|
+
1. **Query First**: Automatically call the mcp tool `brv-query` when you need to query the context for the task and you do not have the context.
|
|
8
|
+
2. **Curate Later**: After finishing the task, call `brv-curate` to store back the knowledge if it is very important.
|
|
9
|
+
|
|
10
|
+
## Tool Usage
|
|
11
|
+
|
|
12
|
+
- `brv-query`: Query the context tree.
|
|
13
|
+
- `brv-curate`: Store context to the context tree.
|
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
# Workflow Instruction
|
|
2
2
|
|
|
3
3
|
You are a coding agent focused on one codebase. Use the brv CLI to manage working context.
|
|
4
|
-
Core Rules:
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
- Keep a local context tree. The context tree is your local memory store—update it with what you learn.
|
|
5
|
+
## Core Rules
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- Be specific ("Use React Query for data fetching in web modules").
|
|
12
|
-
- Be actionable (clear instruction a future agent/dev can apply).
|
|
13
|
-
- Be contextual (mention module/service, constraints, links to source).
|
|
14
|
-
- Include source (file + lines or commit) when possible.
|
|
7
|
+
- **Start from memory.** First retrieve relevant context with `brv query`, then read only the code that's still necessary.
|
|
8
|
+
- **Keep a local context tree.** The context tree is your local memory store—update it with `brv curate` when you learn something valuable.
|
|
15
9
|
|
|
16
|
-
##
|
|
10
|
+
## When to Query
|
|
17
11
|
|
|
18
|
-
|
|
12
|
+
Use `brv query` **before** starting any code task that requires understanding the codebase:
|
|
13
|
+
- Writing, editing, or modifying code
|
|
14
|
+
- Understanding how something works
|
|
15
|
+
- Debugging or troubleshooting issues
|
|
16
|
+
- Making architectural decisions
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
## When to Curate
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
-
|
|
20
|
+
Use `brv curate` **after** you learn or create something valuable:
|
|
21
|
+
- Wrote or modified code
|
|
22
|
+
- Discovered how something works
|
|
23
|
+
- Made architectural/design decisions
|
|
24
|
+
- Found a bug root cause or fix pattern
|
|
24
25
|
|
|
25
|
-
##
|
|
26
|
+
## Context Tree Guideline
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
Good context is:
|
|
29
|
+
- **Specific** ("Use React Query for data fetching in web modules")
|
|
30
|
+
- **Actionable** (clear instruction a future agent/dev can apply)
|
|
31
|
+
- **Contextual** (mention module/service, constraints, links to source)
|
|
32
|
+
- **Sourced** (include file + lines or commit when possible)
|
package/dist/tui/app.js
CHANGED
|
@@ -7,6 +7,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
7
7
|
* - Authorized: Show main app with tabs
|
|
8
8
|
*/
|
|
9
9
|
import { Box } from 'ink';
|
|
10
|
+
import { useState } from 'react';
|
|
10
11
|
import { Footer, Header, TabBar } from './components/index.js';
|
|
11
12
|
import { useAuth, useTasks } from './contexts/index.js';
|
|
12
13
|
import { useTabNavigation, useTerminalBreakpoint, useUIHeights } from './hooks/index.js';
|
|
@@ -17,6 +18,8 @@ export const App = () => {
|
|
|
17
18
|
const { isAuthorized } = useAuth();
|
|
18
19
|
const { activeTab, tabs } = useTabNavigation();
|
|
19
20
|
const { stats: taskStats } = useTasks();
|
|
21
|
+
const [selectedLogIndex, setSelectedLogIndex] = useState(0);
|
|
22
|
+
const [expandedViewLogId, setExpandedViewLogId] = useState(null);
|
|
20
23
|
const contentHeight = Math.max(1, terminalHeight - header - tab - footer);
|
|
21
|
-
return (_jsxs(Box, { flexDirection: "column", height: terminalHeight, paddingBottom: appBottomPadding, width: terminalWidth, children: [_jsx(Box, { flexShrink: 0, children: _jsx(Header, { compact: isAuthorized, showStatusLine: isAuthorized, taskStats: taskStats }) }), isAuthorized ? (_jsxs(_Fragment, { children: [_jsx(Box, { flexShrink: 0, children: _jsx(TabBar, { activeTab: activeTab, tabs: tabs }) }), _jsxs(Box, { flexGrow: 1, paddingX: 1, children: [
|
|
24
|
+
return (_jsxs(Box, { flexDirection: "column", height: terminalHeight, paddingBottom: appBottomPadding, width: terminalWidth, children: [_jsx(Box, { flexShrink: 0, children: _jsx(Header, { compact: isAuthorized, showStatusLine: isAuthorized, taskStats: taskStats }) }), isAuthorized ? (_jsxs(_Fragment, { children: [_jsx(Box, { flexShrink: 0, children: _jsx(TabBar, { activeTab: activeTab, tabs: tabs }) }), _jsxs(Box, { flexGrow: 1, paddingX: 1, children: [activeTab === 'activity' && (_jsx(LogsView, { availableHeight: contentHeight, expandedViewLogId: expandedViewLogId, selectedLogIndex: selectedLogIndex, setExpandedViewLogId: setExpandedViewLogId, setSelectedLogIndex: setSelectedLogIndex })), _jsx(Box, { display: activeTab === 'console' ? 'flex' : 'none', height: "100%", width: "100%", children: _jsx(CommandView, { availableHeight: contentHeight }) })] }), _jsx(Box, { flexShrink: 0, children: _jsx(Footer, {}) })] })) : (_jsx(Box, { flexGrow: 1, paddingX: 1, children: _jsx(LoginView, {}) }))] }));
|
|
22
25
|
};
|
|
@@ -20,7 +20,7 @@ function formatUsage(label, args, flags, subCommands) {
|
|
|
20
20
|
usage += ` ${argsStr}`;
|
|
21
21
|
}
|
|
22
22
|
if (flags?.length) {
|
|
23
|
-
const flagsStr = flags.map((f) => `[--${f.name}]`).join(' ');
|
|
23
|
+
const flagsStr = flags.map((f) => (f.type === 'file' ? `[${f.char}${f.name}]` : `[--${f.name}]`)).join(' ');
|
|
24
24
|
usage += ` ${flagsStr}`;
|
|
25
25
|
}
|
|
26
26
|
return usage;
|
|
@@ -7,6 +7,8 @@ import React from 'react';
|
|
|
7
7
|
interface ExecutionChangesProps {
|
|
8
8
|
/** List of created file paths */
|
|
9
9
|
created: string[];
|
|
10
|
+
/** Whether content should be fully expanded (no truncation) */
|
|
11
|
+
isExpand?: boolean;
|
|
10
12
|
/** Maximum changes configuration */
|
|
11
13
|
maxChanges?: {
|
|
12
14
|
created: number;
|
|
@@ -6,7 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
*/
|
|
7
7
|
import { Box, Text } from 'ink';
|
|
8
8
|
import { useTheme } from '../../hooks/index.js';
|
|
9
|
-
export const ExecutionChanges = ({ created, updated, maxChanges = { created:
|
|
9
|
+
export const ExecutionChanges = ({ created, isExpand = false, updated, maxChanges = { created: Number.MAX_SAFE_INTEGER, updated: Number.MAX_SAFE_INTEGER }, }) => {
|
|
10
10
|
const { theme: { colors }, } = useTheme();
|
|
11
11
|
const totalChanges = created.length + updated.length;
|
|
12
12
|
if (totalChanges === 0) {
|
|
@@ -14,6 +14,10 @@ export const ExecutionChanges = ({ created, updated, maxChanges = { created: Inf
|
|
|
14
14
|
}
|
|
15
15
|
const hasCreated = created.length > 0;
|
|
16
16
|
const hasUpdated = updated.length > 0;
|
|
17
|
+
// In expand mode, show all changes without truncation
|
|
18
|
+
if (isExpand) {
|
|
19
|
+
return (_jsxs(Box, { flexDirection: "column", children: [hasCreated && (_jsxs(Box, { columnGap: 1, children: [_jsx(Text, { color: colors.secondary, children: "created at:" }), _jsx(Box, { flexDirection: "column", children: created.map((path) => (_jsx(Text, { children: path }, path))) })] })), hasUpdated && (_jsxs(Box, { columnGap: 1, children: [_jsx(Text, { color: colors.secondary, children: "updated at:" }), _jsx(Box, { flexDirection: "column", children: updated.map((path) => (_jsx(Text, { children: path }, path))) })] }))] }));
|
|
20
|
+
}
|
|
17
21
|
// Calculate overflow for each section
|
|
18
22
|
// maxChanges represents total lines (items + indicator if overflow)
|
|
19
23
|
const createdOverflow = created.length > maxChanges.created;
|
|
@@ -20,6 +20,8 @@ interface ExecutionContentProps {
|
|
|
20
20
|
content: string;
|
|
21
21
|
/** Whether this is error content */
|
|
22
22
|
isError?: boolean;
|
|
23
|
+
/** Whether content should be fully expanded (no truncation) */
|
|
24
|
+
isExpand?: boolean;
|
|
23
25
|
/** Maximum number of lines (rows) this component can use, including the "more lines" indicator */
|
|
24
26
|
maxLines?: number;
|
|
25
27
|
}
|
|
@@ -7,6 +7,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
import { Box, Text, useStdout } from 'ink';
|
|
8
8
|
import { useTheme } from '../../hooks/index.js';
|
|
9
9
|
import { getVisualLineCount } from '../../utils/line.js';
|
|
10
|
+
import { Markdown } from '../markdown.js';
|
|
10
11
|
const DEFAULT_MAX_LINES = 5;
|
|
11
12
|
/**
|
|
12
13
|
* Truncate content string to maxLines, returning truncated content and remaining line count.
|
|
@@ -42,39 +43,28 @@ export function truncateContent(content, maxLines, maxCharsPerLine) {
|
|
|
42
43
|
break;
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
|
-
// Replace last 3 characters of last line with "..." if truncated
|
|
46
|
-
let finalContent = truncatedLines.join('\n');
|
|
47
|
-
if (truncatedLines.length > 0 && totalVisualLines > visualLineCount) {
|
|
48
|
-
const lastLineIndex = truncatedLines.length - 1;
|
|
49
|
-
const lastLine = truncatedLines[lastLineIndex];
|
|
50
|
-
if (lastLine.length >= 3) {
|
|
51
|
-
truncatedLines[lastLineIndex] = lastLine.slice(0, -3) + '...';
|
|
52
|
-
finalContent = truncatedLines.join('\n');
|
|
53
|
-
}
|
|
54
|
-
else if (lastLine.length > 0) {
|
|
55
|
-
// If last line is shorter than 3 chars, just replace entirely with "..."
|
|
56
|
-
truncatedLines[lastLineIndex] = '...';
|
|
57
|
-
finalContent = truncatedLines.join('\n');
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
46
|
return {
|
|
61
47
|
remainingLines: totalVisualLines - visualLineCount,
|
|
62
48
|
totalLines: totalVisualLines,
|
|
63
|
-
truncatedContent:
|
|
49
|
+
truncatedContent: truncatedLines.join('\n'),
|
|
64
50
|
};
|
|
65
51
|
}
|
|
66
|
-
export const ExecutionContent = ({ bottomMargin = 1, content, isError = false, maxLines = DEFAULT_MAX_LINES, }) => {
|
|
52
|
+
export const ExecutionContent = ({ bottomMargin = 1, content, isError = false, isExpand = false, maxLines = DEFAULT_MAX_LINES, }) => {
|
|
67
53
|
const { theme: { colors }, } = useTheme();
|
|
68
54
|
const { stdout } = useStdout();
|
|
69
55
|
const contentWidth = (stdout?.columns ?? 80) - 4; // 4 is for padding
|
|
70
56
|
if (!content) {
|
|
71
57
|
return null;
|
|
72
58
|
}
|
|
59
|
+
// In expand mode, render full content without truncation
|
|
60
|
+
if (isExpand) {
|
|
61
|
+
return (_jsx(Box, { flexDirection: "column", marginY: 1, children: isError ? (_jsx(Text, { color: colors.errorText, children: content })) : (_jsx(Markdown, { children: content })) }));
|
|
62
|
+
}
|
|
73
63
|
// First check if content would overflow
|
|
74
64
|
const { totalLines } = truncateContent(content, maxLines, contentWidth);
|
|
75
65
|
const hasOverflow = totalLines > maxLines;
|
|
76
66
|
// If overflow, reserve 1 line for indicator, show (maxLines - 1) lines of content
|
|
77
67
|
const effectiveMaxLines = hasOverflow ? maxLines - 1 : maxLines;
|
|
78
68
|
const { remainingLines, truncatedContent } = truncateContent(content, effectiveMaxLines, contentWidth);
|
|
79
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: bottomMargin, children: [_jsx(Text, { color:
|
|
69
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: bottomMargin, children: [isError ? (_jsx(Text, { color: colors.errorText, children: truncatedContent })) : (_jsx(Markdown, { children: truncatedContent })), remainingLines > 0 && _jsxs(Text, { color: colors.dimText, children: [remainingLines, " more lines"] })] }));
|
|
80
70
|
};
|