byterover-cli 2.1.5 → 2.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 (27) hide show
  1. package/dist/oclif/commands/locations.d.ts +14 -0
  2. package/dist/oclif/commands/locations.js +68 -0
  3. package/dist/oclif/commands/status.js +3 -3
  4. package/dist/server/infra/daemon/brv-server.js +2 -0
  5. package/dist/server/infra/process/feature-handlers.d.ts +4 -1
  6. package/dist/server/infra/process/feature-handlers.js +9 -2
  7. package/dist/server/infra/transport/handlers/index.d.ts +2 -0
  8. package/dist/server/infra/transport/handlers/index.js +1 -0
  9. package/dist/server/infra/transport/handlers/locations-handler.d.ts +25 -0
  10. package/dist/server/infra/transport/handlers/locations-handler.js +64 -0
  11. package/dist/server/templates/skill/SKILL.md +19 -1
  12. package/dist/shared/transport/events/index.d.ts +3 -0
  13. package/dist/shared/transport/events/index.js +3 -0
  14. package/dist/shared/transport/events/locations-events.d.ts +7 -0
  15. package/dist/shared/transport/events/locations-events.js +3 -0
  16. package/dist/shared/transport/types/dto.d.ts +11 -0
  17. package/dist/tui/features/commands/definitions/index.js +2 -0
  18. package/dist/tui/features/commands/definitions/locations.d.ts +2 -0
  19. package/dist/tui/features/commands/definitions/locations.js +11 -0
  20. package/dist/tui/features/locations/api/get-locations.d.ts +16 -0
  21. package/dist/tui/features/locations/api/get-locations.js +17 -0
  22. package/dist/tui/features/locations/components/locations-view.d.ts +3 -0
  23. package/dist/tui/features/locations/components/locations-view.js +25 -0
  24. package/dist/tui/features/locations/utils/format-locations.d.ts +2 -0
  25. package/dist/tui/features/locations/utils/format-locations.js +26 -0
  26. package/oclif.manifest.json +155 -116
  27. package/package.json +1 -1
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ import type { ProjectLocationDTO } from '../../shared/transport/types/dto.js';
3
+ import { type DaemonClientOptions } from '../lib/daemon-client.js';
4
+ export default class Locations extends Command {
5
+ static description: string;
6
+ static examples: string[];
7
+ static flags: {
8
+ format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ protected fetchLocations(options?: DaemonClientOptions): Promise<ProjectLocationDTO[]>;
11
+ run(): Promise<void>;
12
+ private formatLocationEntry;
13
+ private formatTextOutput;
14
+ }
@@ -0,0 +1,68 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { LocationsEvents } from '../../shared/transport/events/locations-events.js';
4
+ import { formatConnectionError, withDaemonRetry } from '../lib/daemon-client.js';
5
+ import { writeJsonResponse } from '../lib/json-response.js';
6
+ export default class Locations extends Command {
7
+ static description = 'List all registered projects and their context tree status';
8
+ static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> --format json'];
9
+ static flags = {
10
+ format: Flags.string({
11
+ char: 'f',
12
+ default: 'text',
13
+ description: 'Output format',
14
+ options: ['text', 'json'],
15
+ }),
16
+ };
17
+ async fetchLocations(options) {
18
+ return withDaemonRetry(async (client) => {
19
+ const response = await client.requestWithAck(LocationsEvents.GET);
20
+ return response.locations;
21
+ }, options);
22
+ }
23
+ async run() {
24
+ const { flags } = await this.parse(Locations);
25
+ const isJson = flags.format === 'json';
26
+ try {
27
+ const locations = await this.fetchLocations();
28
+ if (isJson) {
29
+ writeJsonResponse({ command: 'locations', data: { locations }, success: true });
30
+ }
31
+ else {
32
+ this.formatTextOutput(locations);
33
+ }
34
+ }
35
+ catch (error) {
36
+ if (isJson) {
37
+ writeJsonResponse({ command: 'locations', data: { error: formatConnectionError(error) }, success: false });
38
+ }
39
+ else {
40
+ this.log(formatConnectionError(error));
41
+ }
42
+ }
43
+ }
44
+ formatLocationEntry(loc) {
45
+ const label = loc.isCurrent ? ' ' + chalk.green('[current]') : loc.isActive ? ' ' + chalk.yellow('[active]') : '';
46
+ const path = loc.isCurrent || loc.isActive ? chalk.bold(loc.projectPath) : loc.projectPath;
47
+ this.log(` ${path}${label}`);
48
+ if (loc.isInitialized) {
49
+ this.log(chalk.dim(' └─ .brv/context-tree/'));
50
+ }
51
+ else {
52
+ this.log(chalk.dim(' └─ .brv/context-tree/ (not initialized)'));
53
+ }
54
+ }
55
+ formatTextOutput(locations) {
56
+ if (locations.length > 0) {
57
+ this.log(`Registered Projects — ${locations.length} found`);
58
+ this.log(chalk.dim('──────────────────────────────────────────'));
59
+ for (const loc of locations) {
60
+ this.formatLocationEntry(loc);
61
+ this.log('');
62
+ }
63
+ }
64
+ else {
65
+ this.log('Registered Projects — none found');
66
+ }
67
+ }
68
+ }
@@ -22,10 +22,10 @@ export default class Status extends Command {
22
22
  }
23
23
  async run() {
24
24
  const { flags } = await this.parse(Status);
25
- const format = flags.format;
25
+ const isJson = flags.format === 'json';
26
26
  try {
27
27
  const status = await this.fetchStatus();
28
- if (format === 'json') {
28
+ if (isJson) {
29
29
  writeJsonResponse({
30
30
  command: 'status',
31
31
  data: { ...status, cliVersion: this.config.version },
@@ -37,7 +37,7 @@ export default class Status extends Command {
37
37
  }
38
38
  }
39
39
  catch (error) {
40
- if (format === 'json') {
40
+ if (isJson) {
41
41
  writeJsonResponse({
42
42
  command: 'status',
43
43
  data: { error: formatConnectionError(error) },
@@ -352,7 +352,9 @@ async function main() {
352
352
  broadcastToProject(projectPath, event, data) {
353
353
  broadcastToProjectRoom(projectRegistry, projectRouter, projectPath, event, data);
354
354
  },
355
+ getActiveProjectPaths: () => clientManager.getActiveProjects(),
355
356
  log,
357
+ projectRegistry,
356
358
  providerConfigStore,
357
359
  providerKeychainStore,
358
360
  resolveProjectPath: (clientId) => clientManager.getClient(clientId)?.projectPath,
@@ -6,13 +6,16 @@
6
6
  */
7
7
  import type { IProviderConfigStore } from '../../core/interfaces/i-provider-config-store.js';
8
8
  import type { IProviderKeychainStore } from '../../core/interfaces/i-provider-keychain-store.js';
9
+ import type { IProjectRegistry } from '../../core/interfaces/project/i-project-registry.js';
9
10
  import type { IAuthStateStore } from '../../core/interfaces/state/i-auth-state-store.js';
10
11
  import type { ITransportServer } from '../../core/interfaces/transport/i-transport-server.js';
11
12
  import type { ProjectBroadcaster, ProjectPathResolver } from '../transport/handlers/handler-types.js';
12
13
  export interface FeatureHandlersOptions {
13
14
  authStateStore: IAuthStateStore;
14
15
  broadcastToProject: ProjectBroadcaster;
16
+ getActiveProjectPaths: () => string[];
15
17
  log: (msg: string) => void;
18
+ projectRegistry: IProjectRegistry;
16
19
  providerConfigStore: IProviderConfigStore;
17
20
  providerKeychainStore: IProviderKeychainStore;
18
21
  resolveProjectPath: ProjectPathResolver;
@@ -22,4 +25,4 @@ export interface FeatureHandlersOptions {
22
25
  * Setup all feature handlers on the transport server.
23
26
  * These handlers implement the TUI ↔ Server event contract (auth:*, config:*, status:*, etc.).
24
27
  */
25
- export declare function setupFeatureHandlers({ authStateStore, broadcastToProject, log, providerConfigStore, providerKeychainStore, resolveProjectPath, transport, }: FeatureHandlersOptions): Promise<void>;
28
+ export declare function setupFeatureHandlers({ authStateStore, broadcastToProject, getActiveProjectPaths, log, projectRegistry, providerConfigStore, providerKeychainStore, resolveProjectPath, transport, }: FeatureHandlersOptions): Promise<void>;
@@ -29,13 +29,13 @@ import { HttpSpaceService } from '../space/http-space-service.js';
29
29
  import { createTokenStore } from '../storage/token-store.js';
30
30
  import { HttpTeamService } from '../team/http-team-service.js';
31
31
  import { FsTemplateLoader } from '../template/fs-template-loader.js';
32
- import { AuthHandler, ConfigHandler, ConnectorsHandler, HubHandler, InitHandler, ModelHandler, ProviderHandler, PullHandler, PushHandler, ResetHandler, SpaceHandler, StatusHandler, } from '../transport/handlers/index.js';
32
+ import { AuthHandler, ConfigHandler, ConnectorsHandler, HubHandler, InitHandler, LocationsHandler, ModelHandler, ProviderHandler, PullHandler, PushHandler, ResetHandler, SpaceHandler, StatusHandler, } from '../transport/handlers/index.js';
33
33
  import { HttpUserService } from '../user/http-user-service.js';
34
34
  /**
35
35
  * Setup all feature handlers on the transport server.
36
36
  * These handlers implement the TUI ↔ Server event contract (auth:*, config:*, status:*, etc.).
37
37
  */
38
- export async function setupFeatureHandlers({ authStateStore, broadcastToProject, log, providerConfigStore, providerKeychainStore, resolveProjectPath, transport, }) {
38
+ export async function setupFeatureHandlers({ authStateStore, broadcastToProject, getActiveProjectPaths, log, projectRegistry, providerConfigStore, providerKeychainStore, resolveProjectPath, transport, }) {
39
39
  const envConfig = getCurrentConfig();
40
40
  const tokenStore = createTokenStore();
41
41
  const projectConfigStore = new ProjectConfigStore();
@@ -90,6 +90,13 @@ export async function setupFeatureHandlers({ authStateStore, broadcastToProject,
90
90
  tokenStore,
91
91
  transport,
92
92
  }).setup();
93
+ new LocationsHandler({
94
+ contextTreeService,
95
+ getActiveProjectPaths,
96
+ projectRegistry,
97
+ resolveProjectPath,
98
+ transport,
99
+ }).setup();
93
100
  new PushHandler({
94
101
  broadcastToProject,
95
102
  cogitPushService,
@@ -10,6 +10,8 @@ export { HubHandler } from './hub-handler.js';
10
10
  export type { HubHandlerDeps } from './hub-handler.js';
11
11
  export { InitHandler } from './init-handler.js';
12
12
  export type { InitHandlerDeps } from './init-handler.js';
13
+ export { LocationsHandler } from './locations-handler.js';
14
+ export type { LocationsHandlerDeps } from './locations-handler.js';
13
15
  export { ModelHandler } from './model-handler.js';
14
16
  export type { ModelHandlerDeps } from './model-handler.js';
15
17
  export { ProviderHandler } from './provider-handler.js';
@@ -4,6 +4,7 @@ export { ConnectorsHandler } from './connectors-handler.js';
4
4
  export { resolveRequiredProjectPath } from './handler-types.js';
5
5
  export { HubHandler } from './hub-handler.js';
6
6
  export { InitHandler } from './init-handler.js';
7
+ export { LocationsHandler } from './locations-handler.js';
7
8
  export { ModelHandler } from './model-handler.js';
8
9
  export { ProviderHandler } from './provider-handler.js';
9
10
  export { PullHandler } from './pull-handler.js';
@@ -0,0 +1,25 @@
1
+ import type { IContextTreeService } from '../../../core/interfaces/context-tree/i-context-tree-service.js';
2
+ import type { IProjectRegistry } from '../../../core/interfaces/project/i-project-registry.js';
3
+ import type { ITransportServer } from '../../../core/interfaces/transport/i-transport-server.js';
4
+ import { type ProjectPathResolver } from './handler-types.js';
5
+ export interface LocationsHandlerDeps {
6
+ contextTreeService: IContextTreeService;
7
+ getActiveProjectPaths: () => string[];
8
+ projectRegistry: IProjectRegistry;
9
+ resolveProjectPath: ProjectPathResolver;
10
+ transport: ITransportServer;
11
+ }
12
+ /**
13
+ * Handles locations:get event.
14
+ * Returns all registered project locations with context tree status.
15
+ */
16
+ export declare class LocationsHandler {
17
+ private readonly contextTreeService;
18
+ private readonly getActiveProjectPaths;
19
+ private readonly projectRegistry;
20
+ private readonly resolveProjectPath;
21
+ private readonly transport;
22
+ constructor(deps: LocationsHandlerDeps);
23
+ setup(): void;
24
+ private buildLocations;
25
+ }
@@ -0,0 +1,64 @@
1
+ import { join } from 'node:path';
2
+ import { LocationsEvents } from '../../../../shared/transport/events/locations-events.js';
3
+ import { BRV_DIR, CONTEXT_TREE_DIR } from '../../../constants.js';
4
+ import { resolveRequiredProjectPath } from './handler-types.js';
5
+ /**
6
+ * Handles locations:get event.
7
+ * Returns all registered project locations with context tree status.
8
+ */
9
+ export class LocationsHandler {
10
+ contextTreeService;
11
+ getActiveProjectPaths;
12
+ projectRegistry;
13
+ resolveProjectPath;
14
+ transport;
15
+ constructor(deps) {
16
+ this.contextTreeService = deps.contextTreeService;
17
+ this.getActiveProjectPaths = deps.getActiveProjectPaths;
18
+ this.projectRegistry = deps.projectRegistry;
19
+ this.resolveProjectPath = deps.resolveProjectPath;
20
+ this.transport = deps.transport;
21
+ }
22
+ setup() {
23
+ this.transport.onRequest(LocationsEvents.GET, async (_data, clientId) => {
24
+ const projectPath = resolveRequiredProjectPath(this.resolveProjectPath, clientId);
25
+ try {
26
+ const locations = await this.buildLocations(projectPath);
27
+ return { locations };
28
+ }
29
+ catch {
30
+ return { locations: [] };
31
+ }
32
+ });
33
+ }
34
+ async buildLocations(currentProjectPath) {
35
+ const all = this.projectRegistry.getAll();
36
+ const activeSet = new Set(this.getActiveProjectPaths());
37
+ const results = await Promise.all([...all.entries()].map(async ([path]) => {
38
+ let isInitialized = false;
39
+ try {
40
+ isInitialized = await this.contextTreeService.exists(path);
41
+ }
42
+ catch {
43
+ // FS error — treat as not initialized
44
+ }
45
+ return {
46
+ contextTreePath: join(path, BRV_DIR, CONTEXT_TREE_DIR),
47
+ isActive: activeSet.has(path) || path === currentProjectPath,
48
+ isCurrent: path === currentProjectPath,
49
+ isInitialized,
50
+ projectPath: path,
51
+ };
52
+ }));
53
+ // Sort: current first → active (has clients) → initialized → rest, all by registeredAt desc
54
+ return results.sort((a, b) => {
55
+ if (a.isCurrent !== b.isCurrent)
56
+ return a.isCurrent ? -1 : 1;
57
+ if (a.isActive !== b.isActive)
58
+ return a.isActive ? -1 : 1;
59
+ if (a.isInitialized !== b.isInitialized)
60
+ return a.isInitialized ? -1 : 1;
61
+ return (all.get(b.projectPath)?.registeredAt ?? 0) - (all.get(a.projectPath)?.registeredAt ?? 0);
62
+ });
63
+ }
64
+ }
@@ -93,7 +93,25 @@ brv providers list
93
93
  brv providers connect openai --api-key sk-xxx --model gpt-4.1
94
94
  ```
95
95
 
96
- ### 4. Cloud Sync (Optional)
96
+ ### 4. Project Locations
97
+ **Overview:** List registered projects and their context tree paths. Returns project metadata including initialization status and active state. Use `-f json` for machine-readable output.
98
+
99
+ **Use this when:**
100
+ - You need to find a project's context tree path
101
+ - You need to check which projects are registered
102
+ - You need to verify if a project is initialized
103
+
104
+ **Do NOT use this when:**
105
+ - You already know the project path from your current context
106
+ - You need project content rather than metadata — use `brv query` instead
107
+
108
+ ```bash
109
+ brv locations -f json
110
+ ```
111
+
112
+ JSON fields: `projectPath`, `contextTreePath`, `isCurrent`, `isActive`, `isInitialized`.
113
+
114
+ ### 5. Cloud Sync (Optional)
97
115
  **Overview:** Sync your local knowledge with a team via ByteRover's cloud service. Requires ByteRover authentication.
98
116
 
99
117
  **Setup steps:**
@@ -6,6 +6,7 @@ export * from './connector-events.js';
6
6
  export * from './hub-events.js';
7
7
  export * from './init-events.js';
8
8
  export * from './llm-events.js';
9
+ export * from './locations-events.js';
9
10
  export * from './model-events.js';
10
11
  export * from './onboarding-events.js';
11
12
  export * from './provider-events.js';
@@ -103,6 +104,8 @@ export declare const AllEventGroups: readonly [{
103
104
  readonly EXECUTE: "reset:execute";
104
105
  }, {
105
106
  readonly SWITCHED: "session:switched";
107
+ }, {
108
+ readonly GET: "locations:get";
106
109
  }, {
107
110
  readonly LIST: "space:list";
108
111
  readonly SWITCH: "space:switch";
@@ -8,6 +8,7 @@ export * from './connector-events.js';
8
8
  export * from './hub-events.js';
9
9
  export * from './init-events.js';
10
10
  export * from './llm-events.js';
11
+ export * from './locations-events.js';
11
12
  export * from './model-events.js';
12
13
  export * from './onboarding-events.js';
13
14
  export * from './provider-events.js';
@@ -26,6 +27,7 @@ import { ConnectorEvents } from './connector-events.js';
26
27
  import { HubEvents } from './hub-events.js';
27
28
  import { InitEvents } from './init-events.js';
28
29
  import { LlmEvents } from './llm-events.js';
30
+ import { LocationsEvents } from './locations-events.js';
29
31
  import { ModelEvents } from './model-events.js';
30
32
  import { OnboardingEvents } from './onboarding-events.js';
31
33
  import { ProviderEvents } from './provider-events.js';
@@ -55,6 +57,7 @@ export const AllEventGroups = [
55
57
  PushEvents,
56
58
  ResetEvents,
57
59
  SessionEvents,
60
+ LocationsEvents,
58
61
  SpaceEvents,
59
62
  StatusEvents,
60
63
  TaskEvents,
@@ -0,0 +1,7 @@
1
+ import type { ProjectLocationDTO } from '../types/dto.js';
2
+ export declare const LocationsEvents: {
3
+ readonly GET: "locations:get";
4
+ };
5
+ export interface LocationsGetResponse {
6
+ locations: ProjectLocationDTO[];
7
+ }
@@ -0,0 +1,3 @@
1
+ export const LocationsEvents = {
2
+ GET: 'locations:get',
3
+ };
@@ -97,6 +97,17 @@ export interface HubEntryDTO {
97
97
  type: 'agent-skill' | 'bundle';
98
98
  version: string;
99
99
  }
100
+ export interface ProjectLocationDTO {
101
+ /** Absolute path to the context tree directory (e.g., '/Users/foo/project/.brv/context-tree') */
102
+ contextTreePath: string;
103
+ /** True if this project has connected clients/agents or is the current project */
104
+ isActive: boolean;
105
+ /** True if this is the project the client is currently running from */
106
+ isCurrent: boolean;
107
+ /** True if .brv/context-tree exists */
108
+ isInitialized: boolean;
109
+ projectPath: string;
110
+ }
100
111
  export interface StatusDTO {
101
112
  authStatus: 'expired' | 'logged_in' | 'not_logged_in' | 'unknown';
102
113
  contextTreeChanges?: ContextTreeChanges;
@@ -2,6 +2,7 @@ import { connectorsCommand } from './connectors.js';
2
2
  import { curateCommand } from './curate.js';
3
3
  import { exitCommand } from './exit.js';
4
4
  import { hubCommand } from './hub.js';
5
+ import { locationsCommand } from './locations.js';
5
6
  import { loginCommand } from './login.js';
6
7
  import { logoutCommand } from './logout.js';
7
8
  import { modelCommand } from './model.js';
@@ -22,6 +23,7 @@ import { statusCommand } from './status.js';
22
23
  export const load = () => [
23
24
  // Core workflow - most frequently used
24
25
  statusCommand,
26
+ locationsCommand,
25
27
  curateCommand,
26
28
  queryCommand,
27
29
  // Connectors management
@@ -0,0 +1,2 @@
1
+ import type { SlashCommand } from '../../../types/commands.js';
2
+ export declare const locationsCommand: SlashCommand;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { LocationsView } from '../../locations/components/locations-view.js';
3
+ export const locationsCommand = {
4
+ action() {
5
+ return {
6
+ render: ({ onCancel, onComplete }) => React.createElement(LocationsView, { onCancel, onComplete }),
7
+ };
8
+ },
9
+ description: 'List all registered projects and their context tree status',
10
+ name: 'locations',
11
+ };
@@ -0,0 +1,16 @@
1
+ import type { QueryConfig } from '../../../lib/react-query.js';
2
+ import { type LocationsGetResponse } from '../../../../shared/transport/events/index.js';
3
+ export declare const getLocations: () => Promise<LocationsGetResponse>;
4
+ export declare const getLocationsQueryOptions: () => import("@tanstack/react-query").OmitKeyof<import("@tanstack/react-query").UseQueryOptions<LocationsGetResponse, Error, LocationsGetResponse, string[]>, "queryFn"> & {
5
+ queryFn?: import("@tanstack/react-query").QueryFunction<LocationsGetResponse, string[], never> | undefined;
6
+ } & {
7
+ queryKey: string[] & {
8
+ [dataTagSymbol]: LocationsGetResponse;
9
+ [dataTagErrorSymbol]: Error;
10
+ };
11
+ };
12
+ type UseGetLocationsOptions = {
13
+ queryConfig?: QueryConfig<typeof getLocationsQueryOptions>;
14
+ };
15
+ export declare const useGetLocations: ({ queryConfig }?: UseGetLocationsOptions) => import("@tanstack/react-query").UseQueryResult<LocationsGetResponse, Error>;
16
+ export {};
@@ -0,0 +1,17 @@
1
+ import { queryOptions, useQuery } from '@tanstack/react-query';
2
+ import { LocationsEvents } from '../../../../shared/transport/events/index.js';
3
+ import { useTransportStore } from '../../../stores/transport-store.js';
4
+ export const getLocations = () => {
5
+ const { apiClient } = useTransportStore.getState();
6
+ if (!apiClient)
7
+ return Promise.reject(new Error('Not connected'));
8
+ return apiClient.request(LocationsEvents.GET);
9
+ };
10
+ export const getLocationsQueryOptions = () => queryOptions({
11
+ queryFn: getLocations,
12
+ queryKey: ['locations'],
13
+ });
14
+ export const useGetLocations = ({ queryConfig } = {}) => useQuery({
15
+ ...getLocationsQueryOptions(),
16
+ ...queryConfig,
17
+ });
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ import type { CustomDialogCallbacks } from '../../../types/commands.js';
3
+ export declare function LocationsView({ onCancel, onComplete }: CustomDialogCallbacks): React.ReactNode;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text, useInput } from 'ink';
3
+ import Spinner from 'ink-spinner';
4
+ import { useEffect } from 'react';
5
+ import { useGetLocations } from '../api/get-locations.js';
6
+ import { formatLocations } from '../utils/format-locations.js';
7
+ export function LocationsView({ onCancel, onComplete }) {
8
+ const { data, error, isLoading } = useGetLocations();
9
+ useInput((_input, key) => {
10
+ if (key.escape)
11
+ onCancel();
12
+ });
13
+ useEffect(() => {
14
+ if (data) {
15
+ onComplete(formatLocations(data.locations));
16
+ }
17
+ if (error) {
18
+ onComplete(`Failed to get locations: ${error.message}`);
19
+ }
20
+ }, [data, error, onComplete]);
21
+ if (isLoading) {
22
+ return (_jsxs(Text, { children: [_jsx(Spinner, { type: "dots" }), " Loading locations..."] }));
23
+ }
24
+ return null;
25
+ }
@@ -0,0 +1,2 @@
1
+ import type { ProjectLocationDTO } from '../../../../shared/transport/types/dto.js';
2
+ export declare function formatLocations(locations: ProjectLocationDTO[]): string;
@@ -0,0 +1,26 @@
1
+ import chalk from 'chalk';
2
+ function formatLocationEntry(loc) {
3
+ const label = loc.isCurrent ? ' ' + chalk.green('[current]') : loc.isActive ? ' ' + chalk.yellow('[active]') : '';
4
+ const path = loc.isCurrent || loc.isActive ? chalk.bold(loc.projectPath) : loc.projectPath;
5
+ const lines = [` ${path}${label}`];
6
+ if (loc.isInitialized) {
7
+ lines.push(chalk.dim(' └─ .brv/context-tree/'));
8
+ }
9
+ else {
10
+ lines.push(chalk.dim(' └─ .brv/context-tree/ (not initialized)'));
11
+ }
12
+ return lines;
13
+ }
14
+ export function formatLocations(locations) {
15
+ const lines = [];
16
+ if (locations.length > 0) {
17
+ lines.push(`Registered Projects — ${locations.length} found`, chalk.dim('──────────────────────────────────────────'));
18
+ for (const loc of locations) {
19
+ lines.push(...formatLocationEntry(loc), '');
20
+ }
21
+ }
22
+ else {
23
+ lines.push('Registered Projects — none found');
24
+ }
25
+ return lines.join('\n');
26
+ }
@@ -75,6 +75,45 @@
75
75
  "hook-prompt-submit.js"
76
76
  ]
77
77
  },
78
+ "locations": {
79
+ "aliases": [],
80
+ "args": {},
81
+ "description": "List all registered projects and their context tree status",
82
+ "examples": [
83
+ "<%= config.bin %> <%= command.id %>",
84
+ "<%= config.bin %> <%= command.id %> --format json"
85
+ ],
86
+ "flags": {
87
+ "format": {
88
+ "char": "f",
89
+ "description": "Output format",
90
+ "name": "format",
91
+ "default": "text",
92
+ "hasDynamicHelp": false,
93
+ "multiple": false,
94
+ "options": [
95
+ "text",
96
+ "json"
97
+ ],
98
+ "type": "option"
99
+ }
100
+ },
101
+ "hasDynamicHelp": false,
102
+ "hiddenAliases": [],
103
+ "id": "locations",
104
+ "pluginAlias": "byterover-cli",
105
+ "pluginName": "byterover-cli",
106
+ "pluginType": "core",
107
+ "strict": true,
108
+ "enableJsonFlag": false,
109
+ "isESM": true,
110
+ "relativePath": [
111
+ "dist",
112
+ "oclif",
113
+ "commands",
114
+ "locations.js"
115
+ ]
116
+ },
78
117
  "login": {
79
118
  "aliases": [],
80
119
  "args": {},
@@ -897,15 +936,40 @@
897
936
  "list.js"
898
937
  ]
899
938
  },
900
- "model": {
939
+ "providers:connect": {
901
940
  "aliases": [],
902
- "args": {},
903
- "description": "Show the active model",
941
+ "args": {
942
+ "provider": {
943
+ "description": "Provider ID to connect (e.g., anthropic, openai, openrouter)",
944
+ "name": "provider",
945
+ "required": true
946
+ }
947
+ },
948
+ "description": "Connect or switch to an LLM provider",
904
949
  "examples": [
905
- "<%= config.bin %> model",
906
- "<%= config.bin %> model --format json"
950
+ "<%= config.bin %> providers connect anthropic --api-key sk-xxx",
951
+ "<%= config.bin %> providers connect openai --api-key sk-xxx --model gpt-4.1",
952
+ "<%= config.bin %> providers connect byterover",
953
+ "<%= config.bin %> providers connect openai-compatible --base-url http://localhost:11434/v1",
954
+ "<%= config.bin %> providers connect openai-compatible --base-url http://localhost:11434/v1 --api-key sk-xxx --model llama3"
907
955
  ],
908
956
  "flags": {
957
+ "api-key": {
958
+ "char": "k",
959
+ "description": "API key for the provider",
960
+ "name": "api-key",
961
+ "hasDynamicHelp": false,
962
+ "multiple": false,
963
+ "type": "option"
964
+ },
965
+ "base-url": {
966
+ "char": "b",
967
+ "description": "Base URL for OpenAI-compatible providers (e.g., http://localhost:11434/v1)",
968
+ "name": "base-url",
969
+ "hasDynamicHelp": false,
970
+ "multiple": false,
971
+ "type": "option"
972
+ },
909
973
  "format": {
910
974
  "description": "Output format (text or json)",
911
975
  "name": "format",
@@ -917,11 +981,19 @@
917
981
  "json"
918
982
  ],
919
983
  "type": "option"
984
+ },
985
+ "model": {
986
+ "char": "m",
987
+ "description": "Model to set as active after connecting",
988
+ "name": "model",
989
+ "hasDynamicHelp": false,
990
+ "multiple": false,
991
+ "type": "option"
920
992
  }
921
993
  },
922
994
  "hasDynamicHelp": false,
923
995
  "hiddenAliases": [],
924
- "id": "model",
996
+ "id": "providers:connect",
925
997
  "pluginAlias": "byterover-cli",
926
998
  "pluginName": "byterover-cli",
927
999
  "pluginType": "core",
@@ -932,17 +1004,23 @@
932
1004
  "dist",
933
1005
  "oclif",
934
1006
  "commands",
935
- "model",
936
- "index.js"
1007
+ "providers",
1008
+ "connect.js"
937
1009
  ]
938
1010
  },
939
- "model:list": {
1011
+ "providers:disconnect": {
940
1012
  "aliases": [],
941
- "args": {},
942
- "description": "List available models from all connected providers",
1013
+ "args": {
1014
+ "provider": {
1015
+ "description": "Provider ID to disconnect",
1016
+ "name": "provider",
1017
+ "required": true
1018
+ }
1019
+ },
1020
+ "description": "Disconnect an LLM provider",
943
1021
  "examples": [
944
- "<%= config.bin %> model list",
945
- "<%= config.bin %> model list --format json"
1022
+ "<%= config.bin %> providers disconnect anthropic",
1023
+ "<%= config.bin %> providers disconnect openai --format json"
946
1024
  ],
947
1025
  "flags": {
948
1026
  "format": {
@@ -956,19 +1034,11 @@
956
1034
  "json"
957
1035
  ],
958
1036
  "type": "option"
959
- },
960
- "provider": {
961
- "char": "p",
962
- "description": "Only list models for a specific provider",
963
- "name": "provider",
964
- "hasDynamicHelp": false,
965
- "multiple": false,
966
- "type": "option"
967
1037
  }
968
1038
  },
969
1039
  "hasDynamicHelp": false,
970
1040
  "hiddenAliases": [],
971
- "id": "model:list",
1041
+ "id": "providers:disconnect",
972
1042
  "pluginAlias": "byterover-cli",
973
1043
  "pluginName": "byterover-cli",
974
1044
  "pluginType": "core",
@@ -979,24 +1049,17 @@
979
1049
  "dist",
980
1050
  "oclif",
981
1051
  "commands",
982
- "model",
983
- "list.js"
1052
+ "providers",
1053
+ "disconnect.js"
984
1054
  ]
985
1055
  },
986
- "model:switch": {
1056
+ "providers": {
987
1057
  "aliases": [],
988
- "args": {
989
- "model": {
990
- "description": "Model ID to switch to (e.g., claude-sonnet-4-5, gpt-4.1)",
991
- "name": "model",
992
- "required": true
993
- }
994
- },
995
- "description": "Switch the active model",
1058
+ "args": {},
1059
+ "description": "Show active provider and model",
996
1060
  "examples": [
997
- "<%= config.bin %> model switch claude-sonnet-4-5",
998
- "<%= config.bin %> model switch gpt-4.1 --provider openai",
999
- "<%= config.bin %> model switch claude-sonnet-4-5 --format json"
1061
+ "<%= config.bin %> providers",
1062
+ "<%= config.bin %> providers --format json"
1000
1063
  ],
1001
1064
  "flags": {
1002
1065
  "format": {
@@ -1010,19 +1073,11 @@
1010
1073
  "json"
1011
1074
  ],
1012
1075
  "type": "option"
1013
- },
1014
- "provider": {
1015
- "char": "p",
1016
- "description": "Provider ID (defaults to active provider)",
1017
- "name": "provider",
1018
- "hasDynamicHelp": false,
1019
- "multiple": false,
1020
- "type": "option"
1021
1076
  }
1022
1077
  },
1023
1078
  "hasDynamicHelp": false,
1024
1079
  "hiddenAliases": [],
1025
- "id": "model:switch",
1080
+ "id": "providers",
1026
1081
  "pluginAlias": "byterover-cli",
1027
1082
  "pluginName": "byterover-cli",
1028
1083
  "pluginType": "core",
@@ -1033,44 +1088,19 @@
1033
1088
  "dist",
1034
1089
  "oclif",
1035
1090
  "commands",
1036
- "model",
1037
- "switch.js"
1091
+ "providers",
1092
+ "index.js"
1038
1093
  ]
1039
1094
  },
1040
- "providers:connect": {
1095
+ "providers:list": {
1041
1096
  "aliases": [],
1042
- "args": {
1043
- "provider": {
1044
- "description": "Provider ID to connect (e.g., anthropic, openai, openrouter)",
1045
- "name": "provider",
1046
- "required": true
1047
- }
1048
- },
1049
- "description": "Connect or switch to an LLM provider",
1097
+ "args": {},
1098
+ "description": "List all available providers and their connection status",
1050
1099
  "examples": [
1051
- "<%= config.bin %> providers connect anthropic --api-key sk-xxx",
1052
- "<%= config.bin %> providers connect openai --api-key sk-xxx --model gpt-4.1",
1053
- "<%= config.bin %> providers connect byterover",
1054
- "<%= config.bin %> providers connect openai-compatible --base-url http://localhost:11434/v1",
1055
- "<%= config.bin %> providers connect openai-compatible --base-url http://localhost:11434/v1 --api-key sk-xxx --model llama3"
1100
+ "<%= config.bin %> providers list",
1101
+ "<%= config.bin %> providers list --format json"
1056
1102
  ],
1057
1103
  "flags": {
1058
- "api-key": {
1059
- "char": "k",
1060
- "description": "API key for the provider",
1061
- "name": "api-key",
1062
- "hasDynamicHelp": false,
1063
- "multiple": false,
1064
- "type": "option"
1065
- },
1066
- "base-url": {
1067
- "char": "b",
1068
- "description": "Base URL for OpenAI-compatible providers (e.g., http://localhost:11434/v1)",
1069
- "name": "base-url",
1070
- "hasDynamicHelp": false,
1071
- "multiple": false,
1072
- "type": "option"
1073
- },
1074
1104
  "format": {
1075
1105
  "description": "Output format (text or json)",
1076
1106
  "name": "format",
@@ -1082,19 +1112,11 @@
1082
1112
  "json"
1083
1113
  ],
1084
1114
  "type": "option"
1085
- },
1086
- "model": {
1087
- "char": "m",
1088
- "description": "Model to set as active after connecting",
1089
- "name": "model",
1090
- "hasDynamicHelp": false,
1091
- "multiple": false,
1092
- "type": "option"
1093
1115
  }
1094
1116
  },
1095
1117
  "hasDynamicHelp": false,
1096
1118
  "hiddenAliases": [],
1097
- "id": "providers:connect",
1119
+ "id": "providers:list",
1098
1120
  "pluginAlias": "byterover-cli",
1099
1121
  "pluginName": "byterover-cli",
1100
1122
  "pluginType": "core",
@@ -1106,22 +1128,22 @@
1106
1128
  "oclif",
1107
1129
  "commands",
1108
1130
  "providers",
1109
- "connect.js"
1131
+ "list.js"
1110
1132
  ]
1111
1133
  },
1112
- "providers:disconnect": {
1134
+ "providers:switch": {
1113
1135
  "aliases": [],
1114
1136
  "args": {
1115
1137
  "provider": {
1116
- "description": "Provider ID to disconnect",
1138
+ "description": "Provider ID to switch to (e.g., anthropic, openai)",
1117
1139
  "name": "provider",
1118
1140
  "required": true
1119
1141
  }
1120
1142
  },
1121
- "description": "Disconnect an LLM provider",
1143
+ "description": "Switch the active provider",
1122
1144
  "examples": [
1123
- "<%= config.bin %> providers disconnect anthropic",
1124
- "<%= config.bin %> providers disconnect openai --format json"
1145
+ "<%= config.bin %> providers switch anthropic",
1146
+ "<%= config.bin %> providers switch openai --format json"
1125
1147
  ],
1126
1148
  "flags": {
1127
1149
  "format": {
@@ -1139,7 +1161,7 @@
1139
1161
  },
1140
1162
  "hasDynamicHelp": false,
1141
1163
  "hiddenAliases": [],
1142
- "id": "providers:disconnect",
1164
+ "id": "providers:switch",
1143
1165
  "pluginAlias": "byterover-cli",
1144
1166
  "pluginName": "byterover-cli",
1145
1167
  "pluginType": "core",
@@ -1151,16 +1173,16 @@
1151
1173
  "oclif",
1152
1174
  "commands",
1153
1175
  "providers",
1154
- "disconnect.js"
1176
+ "switch.js"
1155
1177
  ]
1156
1178
  },
1157
- "providers": {
1179
+ "model": {
1158
1180
  "aliases": [],
1159
1181
  "args": {},
1160
- "description": "Show active provider and model",
1182
+ "description": "Show the active model",
1161
1183
  "examples": [
1162
- "<%= config.bin %> providers",
1163
- "<%= config.bin %> providers --format json"
1184
+ "<%= config.bin %> model",
1185
+ "<%= config.bin %> model --format json"
1164
1186
  ],
1165
1187
  "flags": {
1166
1188
  "format": {
@@ -1178,7 +1200,7 @@
1178
1200
  },
1179
1201
  "hasDynamicHelp": false,
1180
1202
  "hiddenAliases": [],
1181
- "id": "providers",
1203
+ "id": "model",
1182
1204
  "pluginAlias": "byterover-cli",
1183
1205
  "pluginName": "byterover-cli",
1184
1206
  "pluginType": "core",
@@ -1189,17 +1211,17 @@
1189
1211
  "dist",
1190
1212
  "oclif",
1191
1213
  "commands",
1192
- "providers",
1214
+ "model",
1193
1215
  "index.js"
1194
1216
  ]
1195
1217
  },
1196
- "providers:list": {
1218
+ "model:list": {
1197
1219
  "aliases": [],
1198
1220
  "args": {},
1199
- "description": "List all available providers and their connection status",
1221
+ "description": "List available models from all connected providers",
1200
1222
  "examples": [
1201
- "<%= config.bin %> providers list",
1202
- "<%= config.bin %> providers list --format json"
1223
+ "<%= config.bin %> model list",
1224
+ "<%= config.bin %> model list --format json"
1203
1225
  ],
1204
1226
  "flags": {
1205
1227
  "format": {
@@ -1213,11 +1235,19 @@
1213
1235
  "json"
1214
1236
  ],
1215
1237
  "type": "option"
1238
+ },
1239
+ "provider": {
1240
+ "char": "p",
1241
+ "description": "Only list models for a specific provider",
1242
+ "name": "provider",
1243
+ "hasDynamicHelp": false,
1244
+ "multiple": false,
1245
+ "type": "option"
1216
1246
  }
1217
1247
  },
1218
1248
  "hasDynamicHelp": false,
1219
1249
  "hiddenAliases": [],
1220
- "id": "providers:list",
1250
+ "id": "model:list",
1221
1251
  "pluginAlias": "byterover-cli",
1222
1252
  "pluginName": "byterover-cli",
1223
1253
  "pluginType": "core",
@@ -1228,23 +1258,24 @@
1228
1258
  "dist",
1229
1259
  "oclif",
1230
1260
  "commands",
1231
- "providers",
1261
+ "model",
1232
1262
  "list.js"
1233
1263
  ]
1234
1264
  },
1235
- "providers:switch": {
1265
+ "model:switch": {
1236
1266
  "aliases": [],
1237
1267
  "args": {
1238
- "provider": {
1239
- "description": "Provider ID to switch to (e.g., anthropic, openai)",
1240
- "name": "provider",
1268
+ "model": {
1269
+ "description": "Model ID to switch to (e.g., claude-sonnet-4-5, gpt-4.1)",
1270
+ "name": "model",
1241
1271
  "required": true
1242
1272
  }
1243
1273
  },
1244
- "description": "Switch the active provider",
1274
+ "description": "Switch the active model",
1245
1275
  "examples": [
1246
- "<%= config.bin %> providers switch anthropic",
1247
- "<%= config.bin %> providers switch openai --format json"
1276
+ "<%= config.bin %> model switch claude-sonnet-4-5",
1277
+ "<%= config.bin %> model switch gpt-4.1 --provider openai",
1278
+ "<%= config.bin %> model switch claude-sonnet-4-5 --format json"
1248
1279
  ],
1249
1280
  "flags": {
1250
1281
  "format": {
@@ -1258,11 +1289,19 @@
1258
1289
  "json"
1259
1290
  ],
1260
1291
  "type": "option"
1292
+ },
1293
+ "provider": {
1294
+ "char": "p",
1295
+ "description": "Provider ID (defaults to active provider)",
1296
+ "name": "provider",
1297
+ "hasDynamicHelp": false,
1298
+ "multiple": false,
1299
+ "type": "option"
1261
1300
  }
1262
1301
  },
1263
1302
  "hasDynamicHelp": false,
1264
1303
  "hiddenAliases": [],
1265
- "id": "providers:switch",
1304
+ "id": "model:switch",
1266
1305
  "pluginAlias": "byterover-cli",
1267
1306
  "pluginName": "byterover-cli",
1268
1307
  "pluginType": "core",
@@ -1273,7 +1312,7 @@
1273
1312
  "dist",
1274
1313
  "oclif",
1275
1314
  "commands",
1276
- "providers",
1315
+ "model",
1277
1316
  "switch.js"
1278
1317
  ]
1279
1318
  },
@@ -1574,5 +1613,5 @@
1574
1613
  ]
1575
1614
  }
1576
1615
  },
1577
- "version": "2.1.5"
1616
+ "version": "2.2.0"
1578
1617
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "byterover-cli",
3
3
  "description": "ByteRover's CLI",
4
- "version": "2.1.5",
4
+ "version": "2.2.0",
5
5
  "author": "ByteRover",
6
6
  "bin": {
7
7
  "brv": "./bin/run.js"