byterover-cli 0.4.0 → 0.4.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/dist/core/domain/entities/event.d.ts +1 -1
- package/dist/core/domain/entities/event.js +3 -1
- package/dist/hooks/init/welcome.js +9 -24
- package/dist/infra/repl/repl-startup.js +2 -0
- package/dist/infra/usecase/curate-use-case.js +11 -6
- package/dist/infra/usecase/init-use-case.js +16 -6
- package/dist/infra/usecase/login-use-case.js +9 -3
- package/dist/infra/usecase/query-use-case.js +5 -4
- package/dist/tui/contexts/use-commands.js +4 -3
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Array of all supported Events.
|
|
3
3
|
*/
|
|
4
|
-
export declare const EVENT_VALUES: readonly ["auth:
|
|
4
|
+
export declare const EVENT_VALUES: readonly ["repl", "auth:sign_in", "auth:signed_out", "space:init", "space:changed", "rule:generate", "mem:status", "mem:curate", "mem:pull", "mem:push", "mem:query", "onboarding:init_completed", "onboarding:curate_completed", "onboarding:query_completed", "onboarding:skipped", "onboarding:completed", "init"];
|
|
5
5
|
export type EventName = (typeof EVENT_VALUES)[number];
|
|
6
6
|
export interface PropertyDict {
|
|
7
7
|
[key: string]: any;
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Array of all supported Events.
|
|
3
3
|
*/
|
|
4
4
|
export const EVENT_VALUES = [
|
|
5
|
-
'
|
|
5
|
+
'repl',
|
|
6
|
+
'auth:sign_in',
|
|
6
7
|
'auth:signed_out',
|
|
7
8
|
'space:init',
|
|
8
9
|
'space:changed',
|
|
@@ -17,4 +18,5 @@ export const EVENT_VALUES = [
|
|
|
17
18
|
'onboarding:query_completed',
|
|
18
19
|
'onboarding:skipped',
|
|
19
20
|
'onboarding:completed',
|
|
21
|
+
'init'
|
|
20
22
|
];
|
|
@@ -16,11 +16,11 @@ function checkNodeVersion() {
|
|
|
16
16
|
const [, major, minor] = versionMatch;
|
|
17
17
|
const majorVersion = Number.parseInt(major, 10);
|
|
18
18
|
const minorVersion = Number.parseInt(minor, 10);
|
|
19
|
-
// Minimum supported version: Node
|
|
20
|
-
if (majorVersion <
|
|
19
|
+
// Minimum supported version: Node 20
|
|
20
|
+
if (majorVersion < 20) {
|
|
21
21
|
return {
|
|
22
22
|
message: `Node.js ${majorVersion}.${minorVersion} is not supported.\n` +
|
|
23
|
-
'ByteRover CLI requires Node.js
|
|
23
|
+
'ByteRover CLI requires Node.js 20 or higher.\n' +
|
|
24
24
|
`Current version: ${nodeVersion}\n\n` +
|
|
25
25
|
'Please upgrade Node.js:\n' +
|
|
26
26
|
' - Using nvm: nvm install 22 && nvm use 22\n' +
|
|
@@ -28,21 +28,21 @@ function checkNodeVersion() {
|
|
|
28
28
|
type: 'error',
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
-
// Recommended versions: Node
|
|
32
|
-
// Node 24+ may have compatibility issues with native modules
|
|
31
|
+
// Recommended versions: Node 20, 22
|
|
32
|
+
// Node 24+ may have compatibility issues with native modules
|
|
33
33
|
if (majorVersion >= 24) {
|
|
34
34
|
return {
|
|
35
35
|
message: `Node.js ${majorVersion}.${minorVersion} has not been fully tested with ByteRover CLI.\n` +
|
|
36
36
|
`Current version: ${nodeVersion}\n` +
|
|
37
|
-
'Recommended versions: Node.js
|
|
38
|
-
'Some native dependencies
|
|
37
|
+
'Recommended versions: Node.js 20.x or 22.x\n\n' +
|
|
38
|
+
'Some native dependencies may not work correctly.\n' +
|
|
39
39
|
'If you encounter errors, please switch to a recommended version:\n' +
|
|
40
40
|
' - Using nvm: nvm install 22 && nvm use 22',
|
|
41
41
|
type: 'warning',
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
// All checks passed
|
|
45
|
-
return
|
|
45
|
+
return undefined;
|
|
46
46
|
}
|
|
47
47
|
const hook = async function (options) {
|
|
48
48
|
// Check Node.js version compatibility first
|
|
@@ -79,23 +79,8 @@ const hook = async function (options) {
|
|
|
79
79
|
chalk.cyan(String.raw ` |____/ \__, |\__\___|_| \_\___/ \_/ \___|_| `),
|
|
80
80
|
chalk.cyan(' |___/ '),
|
|
81
81
|
].join('\n');
|
|
82
|
-
const onboardingText = [
|
|
83
|
-
'ByteRover CLI quick start:',
|
|
84
|
-
'',
|
|
85
|
-
' 1. Authenticate with ByteRover:',
|
|
86
|
-
' brv login',
|
|
87
|
-
' 2. Link your workspace:',
|
|
88
|
-
' brv init',
|
|
89
|
-
'',
|
|
90
|
-
'After setup, run `brv status` to confirm your connection.',
|
|
91
|
-
'',
|
|
92
|
-
' Explore commands:',
|
|
93
|
-
' • `brv curate` - Organize and curate your context tree',
|
|
94
|
-
' • `brv query` - Search and query your context tree',
|
|
95
|
-
' • `brv push` - Push your context tree to ByteRover',
|
|
96
|
-
].join('\n');
|
|
97
82
|
const docsLink = `For more information, run 'brv --help', 'brv [command] --help' or visit ${DOCS_URL}`;
|
|
98
|
-
this.log(`\n${logo}\n\n${
|
|
83
|
+
this.log(`\n${logo}\n\n${docsLink}\n\n`);
|
|
99
84
|
}
|
|
100
85
|
};
|
|
101
86
|
export default hook;
|
|
@@ -19,8 +19,10 @@ export async function startRepl(options) {
|
|
|
19
19
|
brvConfig = await projectConfigStore.read();
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
await trackingService.track('repl', { status: 'started' });
|
|
22
23
|
// Render the App with providers
|
|
23
24
|
const { waitUntilExit } = render(_jsx(AppProviders, { initialAuthToken: isAuthorized ? authToken : undefined, initialBrvConfig: brvConfig, onboardingPreferenceStore: onboardingPreferenceStore, projectConfigStore: projectConfigStore, tokenStore: tokenStore, trackingService: trackingService, version: version, children: _jsx(App, {}) }));
|
|
24
25
|
await waitUntilExit();
|
|
25
26
|
stopQueuePollingService();
|
|
27
|
+
await trackingService.track('repl', { status: 'finished' });
|
|
26
28
|
}
|
|
@@ -124,6 +124,7 @@ export class CurateUseCase {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
async run(options) {
|
|
127
|
+
await this.trackingService.track('mem:curate', { status: 'started' });
|
|
127
128
|
// Determine mode: autonomous if context is provided
|
|
128
129
|
return options.context ? this.runAutonomous(options.context, options) : this.runInteractive();
|
|
129
130
|
}
|
|
@@ -131,7 +132,7 @@ export class CurateUseCase {
|
|
|
131
132
|
* Handle workspace not initialized error
|
|
132
133
|
*/
|
|
133
134
|
handleWorkspaceError(_error) {
|
|
134
|
-
const message = 'Project not initialized. Please run "
|
|
135
|
+
const message = 'Project not initialized. Please run "/init" to select your team and workspace.';
|
|
135
136
|
this.terminal.log(message);
|
|
136
137
|
}
|
|
137
138
|
/**
|
|
@@ -197,14 +198,14 @@ export class CurateUseCase {
|
|
|
197
198
|
// Get authentication token
|
|
198
199
|
const token = await this.tokenStore.load();
|
|
199
200
|
if (!token) {
|
|
200
|
-
this.terminal.log('Authentication required. Please run "
|
|
201
|
+
this.terminal.log('Authentication required. Please run "/login" first.');
|
|
201
202
|
return;
|
|
202
203
|
}
|
|
203
204
|
// Load project config
|
|
204
205
|
const brvConfig = await this.projectConfigStore.read();
|
|
205
206
|
// Validate workspace is initialized
|
|
206
207
|
if (!brvConfig) {
|
|
207
|
-
throw new WorkspaceNotInitializedError('Project not initialized. Please run "
|
|
208
|
+
throw new WorkspaceNotInitializedError('Project not initialized. Please run "/init" to select your team and workspace.', '.brv');
|
|
208
209
|
}
|
|
209
210
|
// Process file references if provided (validates and creates instructions)
|
|
210
211
|
const fileReferenceInstructions = this.processFileReferences(options.files ?? []);
|
|
@@ -223,7 +224,7 @@ export class CurateUseCase {
|
|
|
223
224
|
// Simple output for agents - just confirm saved
|
|
224
225
|
this.terminal.log('✓ Context queued for processing.');
|
|
225
226
|
// Track the event
|
|
226
|
-
await this.trackingService.track('mem:curate');
|
|
227
|
+
await this.trackingService.track('mem:curate', { status: 'finished' });
|
|
227
228
|
}
|
|
228
229
|
catch (error) {
|
|
229
230
|
if (error instanceof WorkspaceNotInitializedError) {
|
|
@@ -231,7 +232,9 @@ export class CurateUseCase {
|
|
|
231
232
|
return;
|
|
232
233
|
}
|
|
233
234
|
// Display error
|
|
234
|
-
|
|
235
|
+
const errMsg = error instanceof Error ? error.message : 'Runtime error occurred';
|
|
236
|
+
await this.trackingService.track('mem:curate', { message: errMsg, status: 'error' });
|
|
237
|
+
this.terminal.error(errMsg);
|
|
235
238
|
}
|
|
236
239
|
}
|
|
237
240
|
/**
|
|
@@ -261,7 +264,9 @@ export class CurateUseCase {
|
|
|
261
264
|
await this.openFile(contextFilePath);
|
|
262
265
|
}
|
|
263
266
|
catch (error) {
|
|
264
|
-
|
|
267
|
+
const errMsg = error instanceof Error ? error.message : 'Unexpected error occurred';
|
|
268
|
+
await this.trackingService.track('mem:curate', { message: errMsg, status: 'error' });
|
|
269
|
+
this.terminal.error(errMsg);
|
|
265
270
|
}
|
|
266
271
|
}
|
|
267
272
|
validateTopicName(value, targetPath) {
|
|
@@ -58,7 +58,9 @@ export class InitUseCase {
|
|
|
58
58
|
}
|
|
59
59
|
catch (error) {
|
|
60
60
|
this.terminal.actionStop('✗');
|
|
61
|
-
|
|
61
|
+
const brvDirRemovalErr = `Failed to remove ${BRV_DIR}/: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
62
|
+
await this.trackingService.track('init', { message: brvDirRemovalErr, status: 'error' });
|
|
63
|
+
throw new Error(brvDirRemovalErr);
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
async confirmReInitialization(config) {
|
|
@@ -93,11 +95,11 @@ export class InitUseCase {
|
|
|
93
95
|
async ensureAuthenticated() {
|
|
94
96
|
const token = await this.tokenStore.load();
|
|
95
97
|
if (token === undefined) {
|
|
96
|
-
this.terminal.log('Not authenticated. Please run "
|
|
98
|
+
this.terminal.log('Not authenticated. Please run "/login" first.');
|
|
97
99
|
return undefined;
|
|
98
100
|
}
|
|
99
101
|
if (!token.isValid()) {
|
|
100
|
-
this.terminal.log('Authentication token expired. Please run "
|
|
102
|
+
this.terminal.log('Authentication token expired. Please run "/login" again.');
|
|
101
103
|
return undefined;
|
|
102
104
|
}
|
|
103
105
|
return token;
|
|
@@ -205,7 +207,9 @@ export class InitUseCase {
|
|
|
205
207
|
try {
|
|
206
208
|
const projectConfig = await this.projectConfigStore.read();
|
|
207
209
|
if (projectConfig === undefined) {
|
|
208
|
-
|
|
210
|
+
const corruptedConfigFileErr = 'Configuration file exists but cannot be read. Please check .brv/config.json';
|
|
211
|
+
this.trackingService.track('init', { message: corruptedConfigFileErr, status: 'error' });
|
|
212
|
+
throw new Error(corruptedConfigFileErr);
|
|
209
213
|
}
|
|
210
214
|
return projectConfig;
|
|
211
215
|
}
|
|
@@ -362,6 +366,7 @@ export class InitUseCase {
|
|
|
362
366
|
}
|
|
363
367
|
async run(options) {
|
|
364
368
|
try {
|
|
369
|
+
await this.trackingService.track('init', { status: 'started' });
|
|
365
370
|
const authToken = await this.ensureAuthenticated();
|
|
366
371
|
if (!authToken)
|
|
367
372
|
return;
|
|
@@ -416,11 +421,14 @@ export class InitUseCase {
|
|
|
416
421
|
await this.trackingService.track('rule:generate');
|
|
417
422
|
await this.trackingService.track('space:init');
|
|
418
423
|
this.logSuccess(selectedSpace);
|
|
424
|
+
await this.trackingService.track('init', { status: 'finished' });
|
|
419
425
|
}
|
|
420
426
|
catch (error) {
|
|
421
427
|
// Stop action if it's in progress
|
|
422
428
|
this.terminal.actionStop();
|
|
423
|
-
|
|
429
|
+
const initErr = `Initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
430
|
+
await this.trackingService.track('init', { message: initErr, status: 'error' });
|
|
431
|
+
this.terminal.error(initErr);
|
|
424
432
|
}
|
|
425
433
|
}
|
|
426
434
|
async syncFromRemoteOrInitialize(params) {
|
|
@@ -452,7 +460,9 @@ export class InitUseCase {
|
|
|
452
460
|
}
|
|
453
461
|
}
|
|
454
462
|
catch (error) {
|
|
455
|
-
|
|
463
|
+
const syncFailureErr = `Failed to sync from ByteRover: ${error instanceof Error ? error.message : 'Unknown error'}. Please try again.`;
|
|
464
|
+
await this.trackingService.track('init', { message: syncFailureErr, status: 'error' });
|
|
465
|
+
throw new Error(syncFailureErr);
|
|
456
466
|
}
|
|
457
467
|
}
|
|
458
468
|
/**
|
|
@@ -19,13 +19,16 @@ export class LoginUseCase {
|
|
|
19
19
|
}
|
|
20
20
|
async run() {
|
|
21
21
|
try {
|
|
22
|
+
await this.trackingService.track('auth:sign_in', { status: 'started' });
|
|
22
23
|
this.terminal.log('Starting authentication process...');
|
|
23
24
|
// Start callback server
|
|
24
25
|
await this.callbackHandler.start();
|
|
25
26
|
// Get port and build redirect URI
|
|
26
27
|
const port = this.callbackHandler.getPort();
|
|
27
28
|
if (!port) {
|
|
28
|
-
|
|
29
|
+
const getPortFailedErr = 'Failed to get callback server port';
|
|
30
|
+
await this.trackingService.track('auth:sign_in', { message: getPortFailedErr, status: 'error' });
|
|
31
|
+
throw new Error(getPortFailedErr);
|
|
29
32
|
}
|
|
30
33
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
31
34
|
// Initiate authorization (generates PKCE parameters and state internally)
|
|
@@ -38,6 +41,7 @@ export class LoginUseCase {
|
|
|
38
41
|
}
|
|
39
42
|
catch {
|
|
40
43
|
// Browser launch failed, will return URL to user
|
|
44
|
+
await this.trackingService.track('auth:sign_in', { message: 'browser launch failed', status: 'error' });
|
|
41
45
|
}
|
|
42
46
|
// If browser failed to open, display the URL for manual copy
|
|
43
47
|
if (!browserOpened) {
|
|
@@ -60,13 +64,13 @@ export class LoginUseCase {
|
|
|
60
64
|
userId: user.id,
|
|
61
65
|
});
|
|
62
66
|
await this.tokenStore.save(authToken);
|
|
63
|
-
|
|
64
|
-
await this.trackingService.track('auth:signed_in');
|
|
67
|
+
await this.trackingService.track('auth:sign_in', { status: 'finished' });
|
|
65
68
|
this.terminal.log(`Logged in as ${user.email}`);
|
|
66
69
|
}
|
|
67
70
|
catch (error) {
|
|
68
71
|
// Throw error to let oclif handle display
|
|
69
72
|
const errorMessage = error instanceof Error ? error.message : 'Authentication failed';
|
|
73
|
+
await this.trackingService.track('auth:sign_in', { message: errorMessage, status: 'error' });
|
|
70
74
|
this.terminal.error(errorMessage);
|
|
71
75
|
}
|
|
72
76
|
}
|
|
@@ -74,10 +78,12 @@ export class LoginUseCase {
|
|
|
74
78
|
if (error instanceof DiscoveryError) {
|
|
75
79
|
const errorMessage = `Failed to configure authentication: ${error.message}\n` +
|
|
76
80
|
'Please check your network connection and try again.';
|
|
81
|
+
await this.trackingService.track('auth:sign_in', { message: errorMessage, status: 'error' });
|
|
77
82
|
this.terminal.error(errorMessage);
|
|
78
83
|
}
|
|
79
84
|
// Throw error to let oclif handle display
|
|
80
85
|
const errorMessage = error instanceof Error ? error.message : 'Authentication failed';
|
|
86
|
+
await this.trackingService.track('auth:sign_in', { message: errorMessage, status: 'error' });
|
|
81
87
|
this.terminal.error(errorMessage);
|
|
82
88
|
}
|
|
83
89
|
finally {
|
|
@@ -32,6 +32,7 @@ export class QueryUseCase {
|
|
|
32
32
|
return randomUUID();
|
|
33
33
|
}
|
|
34
34
|
async run(options) {
|
|
35
|
+
await this.trackingService.track('mem:query', { status: 'started' });
|
|
35
36
|
// Initialize storage for tool call tracking (auto-detects .brv/blobs)
|
|
36
37
|
const storage = await getAgentStorage();
|
|
37
38
|
let executionId = null;
|
|
@@ -39,14 +40,14 @@ export class QueryUseCase {
|
|
|
39
40
|
// Get authentication token
|
|
40
41
|
const token = await this.tokenStore.load();
|
|
41
42
|
if (!token) {
|
|
42
|
-
this.terminal.log('Authentication required. Please run "
|
|
43
|
+
this.terminal.log('Authentication required. Please run "/login" first.');
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
46
|
// Load project config
|
|
46
47
|
const brvConfig = await this.projectConfigStore.read();
|
|
47
48
|
// Validate workspace is initialized
|
|
48
49
|
if (!brvConfig) {
|
|
49
|
-
throw new WorkspaceNotInitializedError('Project not initialized. Please run "
|
|
50
|
+
throw new WorkspaceNotInitializedError('Project not initialized. Please run "/init" to select your team and workspace.', '.brv');
|
|
50
51
|
}
|
|
51
52
|
// Create execution with status='running' (query runs synchronously)
|
|
52
53
|
executionId = storage.createExecution('query', options.query);
|
|
@@ -87,8 +88,7 @@ export class QueryUseCase {
|
|
|
87
88
|
storage.updateExecutionStatus(executionId, 'completed', response);
|
|
88
89
|
this.terminal.log('\nQuery Results:');
|
|
89
90
|
this.terminal.log(response);
|
|
90
|
-
|
|
91
|
-
await this.trackingService.track('mem:query');
|
|
91
|
+
await this.trackingService.track('mem:query', { status: 'finished' });
|
|
92
92
|
}
|
|
93
93
|
finally {
|
|
94
94
|
// Cleanup old executions
|
|
@@ -107,6 +107,7 @@ export class QueryUseCase {
|
|
|
107
107
|
}
|
|
108
108
|
// Display context on one line, error on separate line
|
|
109
109
|
process.stderr.write('Failed to query context tree:\n');
|
|
110
|
+
await this.trackingService.track('mem:query', { message: formatError(error), status: 'error' });
|
|
110
111
|
this.terminal.log(formatError(error));
|
|
111
112
|
}
|
|
112
113
|
}
|
|
@@ -19,9 +19,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
19
19
|
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
|
20
20
|
import { load } from '../../infra/repl/commands/index.js';
|
|
21
21
|
import { useSlashCommandProcessor } from '../hooks/index.js';
|
|
22
|
+
import { useServices } from './services-context.js';
|
|
22
23
|
const CommandsContext = createContext(undefined);
|
|
23
24
|
export function CommandsProvider({ children }) {
|
|
24
25
|
const [commands, setCommands] = useState([]);
|
|
26
|
+
const { version } = useServices();
|
|
25
27
|
useEffect(() => {
|
|
26
28
|
const abortController = new AbortController();
|
|
27
29
|
async function loadCommands() {
|
|
@@ -34,9 +36,8 @@ export function CommandsProvider({ children }) {
|
|
|
34
36
|
}, []);
|
|
35
37
|
const commandContext = useMemo(() => ({
|
|
36
38
|
slashCommands: commands,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}), [commands]);
|
|
39
|
+
version,
|
|
40
|
+
}), [commands, version]);
|
|
40
41
|
const { handleSlashCommand } = useSlashCommandProcessor(commandContext, commands);
|
|
41
42
|
const contextValue = useMemo(() => ({
|
|
42
43
|
commands,
|
package/oclif.manifest.json
CHANGED