byterover-cli 2.1.5 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/infra/llm/providers/openai.d.ts +12 -0
- package/dist/agent/infra/llm/providers/openai.js +52 -1
- package/dist/oclif/commands/curate/index.js +2 -2
- package/dist/oclif/commands/locations.d.ts +14 -0
- package/dist/oclif/commands/locations.js +68 -0
- package/dist/oclif/commands/model/switch.js +14 -3
- package/dist/oclif/commands/providers/connect.d.ts +9 -0
- package/dist/oclif/commands/providers/connect.js +110 -14
- package/dist/oclif/commands/providers/list.js +3 -5
- package/dist/oclif/commands/query.js +2 -2
- package/dist/oclif/commands/status.js +3 -3
- package/dist/oclif/lib/daemon-client.d.ts +4 -0
- package/dist/oclif/lib/daemon-client.js +13 -3
- package/dist/server/core/domain/entities/provider-config.d.ts +6 -0
- package/dist/server/core/domain/entities/provider-config.js +4 -3
- package/dist/server/core/domain/entities/provider-registry.d.ts +41 -1
- package/dist/server/core/domain/entities/provider-registry.js +24 -1
- package/dist/server/core/domain/errors/task-error.d.ts +2 -0
- package/dist/server/core/domain/errors/task-error.js +6 -1
- package/dist/server/core/domain/transport/schemas.d.ts +2 -0
- package/dist/server/core/interfaces/i-provider-config-store.d.ts +2 -0
- package/dist/server/core/interfaces/i-provider-model-fetcher.d.ts +12 -3
- package/dist/server/core/interfaces/i-provider-oauth-token-store.d.ts +24 -0
- package/dist/server/core/interfaces/i-provider-oauth-token-store.js +1 -0
- package/dist/server/core/interfaces/i-token-refresh-manager.d.ts +7 -0
- package/dist/server/core/interfaces/i-token-refresh-manager.js +1 -0
- package/dist/server/infra/daemon/agent-process.js +22 -4
- package/dist/server/infra/daemon/brv-server.js +15 -2
- package/dist/server/infra/http/models-dev-client.d.ts +29 -0
- package/dist/server/infra/http/models-dev-client.js +133 -0
- package/dist/server/infra/http/provider-model-fetcher-registry.d.ts +2 -1
- package/dist/server/infra/http/provider-model-fetcher-registry.js +6 -1
- package/dist/server/infra/http/provider-model-fetchers.d.ts +33 -8
- package/dist/server/infra/http/provider-model-fetchers.js +88 -10
- package/dist/server/infra/process/feature-handlers.d.ts +6 -1
- package/dist/server/infra/process/feature-handlers.js +11 -2
- package/dist/server/infra/provider/provider-config-resolver.d.ts +4 -2
- package/dist/server/infra/provider/provider-config-resolver.js +59 -4
- package/dist/server/infra/provider-oauth/callback-server.d.ts +24 -0
- package/dist/server/infra/provider-oauth/callback-server.js +203 -0
- package/dist/server/infra/provider-oauth/errors.d.ts +39 -0
- package/dist/server/infra/provider-oauth/errors.js +76 -0
- package/dist/server/infra/provider-oauth/index.d.ts +9 -0
- package/dist/server/infra/provider-oauth/index.js +9 -0
- package/dist/server/infra/provider-oauth/jwt-utils.d.ts +17 -0
- package/dist/server/infra/provider-oauth/jwt-utils.js +51 -0
- package/dist/server/infra/provider-oauth/pkce-service.d.ts +22 -0
- package/dist/server/infra/provider-oauth/pkce-service.js +33 -0
- package/dist/server/infra/provider-oauth/provider-oauth-token-store.d.ts +48 -0
- package/dist/server/infra/provider-oauth/provider-oauth-token-store.js +155 -0
- package/dist/server/infra/provider-oauth/refresh-token-exchange.d.ts +8 -0
- package/dist/server/infra/provider-oauth/refresh-token-exchange.js +39 -0
- package/dist/server/infra/provider-oauth/token-exchange.d.ts +8 -0
- package/dist/server/infra/provider-oauth/token-exchange.js +44 -0
- package/dist/server/infra/provider-oauth/token-refresh-manager.d.ts +32 -0
- package/dist/server/infra/provider-oauth/token-refresh-manager.js +96 -0
- package/dist/server/infra/provider-oauth/types.d.ts +55 -0
- package/dist/server/infra/provider-oauth/types.js +22 -0
- package/dist/server/infra/storage/file-provider-config-store.d.ts +2 -0
- package/dist/server/infra/storage/file-provider-config-store.js +1 -3
- package/dist/server/infra/transport/handlers/index.d.ts +2 -0
- package/dist/server/infra/transport/handlers/index.js +1 -0
- package/dist/server/infra/transport/handlers/locations-handler.d.ts +25 -0
- package/dist/server/infra/transport/handlers/locations-handler.js +64 -0
- package/dist/server/infra/transport/handlers/model-handler.d.ts +3 -0
- package/dist/server/infra/transport/handlers/model-handler.js +53 -11
- package/dist/server/infra/transport/handlers/provider-handler.d.ts +26 -0
- package/dist/server/infra/transport/handlers/provider-handler.js +215 -13
- package/dist/server/templates/skill/SKILL.md +19 -1
- package/dist/shared/constants/oauth.d.ts +14 -0
- package/dist/shared/constants/oauth.js +14 -0
- package/dist/shared/transport/events/index.d.ts +8 -0
- package/dist/shared/transport/events/index.js +3 -0
- package/dist/shared/transport/events/locations-events.d.ts +7 -0
- package/dist/shared/transport/events/locations-events.js +3 -0
- package/dist/shared/transport/events/model-events.d.ts +2 -0
- package/dist/shared/transport/events/provider-events.d.ts +36 -0
- package/dist/shared/transport/events/provider-events.js +5 -0
- package/dist/shared/transport/types/dto.d.ts +15 -0
- package/dist/tui/features/commands/definitions/index.js +2 -0
- package/dist/tui/features/commands/definitions/locations.d.ts +2 -0
- package/dist/tui/features/commands/definitions/locations.js +11 -0
- package/dist/tui/features/locations/api/get-locations.d.ts +16 -0
- package/dist/tui/features/locations/api/get-locations.js +17 -0
- package/dist/tui/features/locations/components/locations-view.d.ts +3 -0
- package/dist/tui/features/locations/components/locations-view.js +25 -0
- package/dist/tui/features/locations/utils/format-locations.d.ts +2 -0
- package/dist/tui/features/locations/utils/format-locations.js +26 -0
- package/dist/tui/features/model/api/set-active-model.d.ts +1 -1
- package/dist/tui/features/model/api/set-active-model.js +12 -4
- package/dist/tui/features/provider/api/await-oauth-callback.d.ts +11 -0
- package/dist/tui/features/provider/api/await-oauth-callback.js +25 -0
- package/dist/tui/features/provider/api/cancel-oauth.d.ts +5 -0
- package/dist/tui/features/provider/api/cancel-oauth.js +10 -0
- package/dist/tui/features/provider/api/start-oauth.d.ts +11 -0
- package/dist/tui/features/provider/api/start-oauth.js +15 -0
- package/dist/tui/features/provider/components/auth-method-dialog.d.ts +9 -0
- package/dist/tui/features/provider/components/auth-method-dialog.js +20 -0
- package/dist/tui/features/provider/components/oauth-dialog.d.ts +9 -0
- package/dist/tui/features/provider/components/oauth-dialog.js +96 -0
- package/dist/tui/features/provider/components/provider-dialog.js +1 -1
- package/dist/tui/features/provider/components/provider-flow.js +54 -4
- package/dist/tui/features/provider/components/provider-subscription-initializer.d.ts +1 -0
- package/dist/tui/features/provider/components/provider-subscription-initializer.js +5 -0
- package/dist/tui/features/provider/hooks/use-provider-subscriptions.d.ts +6 -0
- package/dist/tui/features/provider/hooks/use-provider-subscriptions.js +24 -0
- package/dist/tui/providers/app-providers.js +2 -1
- package/dist/tui/utils/error-messages.js +6 -1
- package/oclif.manifest.json +56 -1
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ export type SetActiveModelDTO = {
|
|
|
5
5
|
modelId: string;
|
|
6
6
|
providerId: string;
|
|
7
7
|
};
|
|
8
|
-
export declare const setActiveModel: ({ contextLength, modelId, providerId }: SetActiveModelDTO) => Promise<ModelSetActiveResponse>;
|
|
8
|
+
export declare const setActiveModel: ({ contextLength, modelId, providerId, }: SetActiveModelDTO) => Promise<ModelSetActiveResponse>;
|
|
9
9
|
type UseSetActiveModelOptions = {
|
|
10
10
|
mutationConfig?: MutationConfig<typeof setActiveModel>;
|
|
11
11
|
};
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { useMutation } from '@tanstack/react-query';
|
|
2
|
-
import { ModelEvents } from '../../../../shared/transport/events/index.js';
|
|
2
|
+
import { ModelEvents, } from '../../../../shared/transport/events/index.js';
|
|
3
3
|
import { useTransportStore } from '../../../stores/transport-store.js';
|
|
4
|
-
export const setActiveModel = ({ contextLength, modelId, providerId }) => {
|
|
4
|
+
export const setActiveModel = async ({ contextLength, modelId, providerId, }) => {
|
|
5
5
|
const { apiClient } = useTransportStore.getState();
|
|
6
6
|
if (!apiClient)
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
throw new Error('Not connected');
|
|
8
|
+
const response = await apiClient.request(ModelEvents.SET_ACTIVE, {
|
|
9
|
+
contextLength,
|
|
10
|
+
modelId,
|
|
11
|
+
providerId,
|
|
12
|
+
});
|
|
13
|
+
if (!response.success && response.error) {
|
|
14
|
+
throw new Error(response.error);
|
|
15
|
+
}
|
|
16
|
+
return response;
|
|
9
17
|
};
|
|
10
18
|
export const useSetActiveModel = ({ mutationConfig } = {}) => useMutation({
|
|
11
19
|
...mutationConfig,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MutationConfig } from '../../../lib/react-query.js';
|
|
2
|
+
import { type ProviderAwaitOAuthCallbackResponse } from '../../../../shared/transport/events/index.js';
|
|
3
|
+
export type AwaitOAuthCallbackDTO = {
|
|
4
|
+
providerId: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const awaitOAuthCallback: ({ providerId, }: AwaitOAuthCallbackDTO) => Promise<ProviderAwaitOAuthCallbackResponse>;
|
|
7
|
+
type UseAwaitOAuthCallbackOptions = {
|
|
8
|
+
mutationConfig?: MutationConfig<typeof awaitOAuthCallback>;
|
|
9
|
+
};
|
|
10
|
+
export declare const useAwaitOAuthCallback: ({ mutationConfig }?: UseAwaitOAuthCallbackOptions) => import("@tanstack/react-query").UseMutationResult<ProviderAwaitOAuthCallbackResponse, Error, AwaitOAuthCallbackDTO, unknown>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { OAUTH_CALLBACK_TIMEOUT_MS } from '../../../../shared/constants/oauth.js';
|
|
3
|
+
import { ProviderEvents, } from '../../../../shared/transport/events/index.js';
|
|
4
|
+
import { useTransportStore } from '../../../stores/transport-store.js';
|
|
5
|
+
import { getProvidersQueryOptions } from './get-providers.js';
|
|
6
|
+
export const awaitOAuthCallback = ({ providerId, }) => {
|
|
7
|
+
const { apiClient } = useTransportStore.getState();
|
|
8
|
+
if (!apiClient)
|
|
9
|
+
return Promise.reject(new Error('Not connected'));
|
|
10
|
+
return apiClient.request(ProviderEvents.AWAIT_OAUTH_CALLBACK, { providerId }, { timeout: OAUTH_CALLBACK_TIMEOUT_MS });
|
|
11
|
+
};
|
|
12
|
+
export const useAwaitOAuthCallback = ({ mutationConfig } = {}) => {
|
|
13
|
+
const queryClient = useQueryClient();
|
|
14
|
+
const { onSuccess, ...restConfig } = mutationConfig ?? {};
|
|
15
|
+
return useMutation({
|
|
16
|
+
onSuccess(...args) {
|
|
17
|
+
queryClient.invalidateQueries({
|
|
18
|
+
queryKey: getProvidersQueryOptions().queryKey,
|
|
19
|
+
});
|
|
20
|
+
onSuccess?.(...args);
|
|
21
|
+
},
|
|
22
|
+
...restConfig,
|
|
23
|
+
mutationFn: awaitOAuthCallback,
|
|
24
|
+
});
|
|
25
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ProviderCancelOAuthResponse } from '../../../../shared/transport/events/index.js';
|
|
2
|
+
export type CancelOAuthDTO = {
|
|
3
|
+
providerId: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const cancelOAuth: ({ providerId }: CancelOAuthDTO) => Promise<ProviderCancelOAuthResponse | void>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ProviderEvents, } from '../../../../shared/transport/events/index.js';
|
|
2
|
+
import { useTransportStore } from '../../../stores/transport-store.js';
|
|
3
|
+
export const cancelOAuth = ({ providerId }) => {
|
|
4
|
+
const { apiClient } = useTransportStore.getState();
|
|
5
|
+
if (!apiClient)
|
|
6
|
+
return Promise.resolve();
|
|
7
|
+
return apiClient.request(ProviderEvents.CANCEL_OAUTH, {
|
|
8
|
+
providerId,
|
|
9
|
+
});
|
|
10
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MutationConfig } from '../../../lib/react-query.js';
|
|
2
|
+
import { type ProviderStartOAuthResponse } from '../../../../shared/transport/events/index.js';
|
|
3
|
+
export type StartOAuthDTO = {
|
|
4
|
+
providerId: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const startOAuth: ({ providerId }: StartOAuthDTO) => Promise<ProviderStartOAuthResponse>;
|
|
7
|
+
type UseStartOAuthOptions = {
|
|
8
|
+
mutationConfig?: MutationConfig<typeof startOAuth>;
|
|
9
|
+
};
|
|
10
|
+
export declare const useStartOAuth: ({ mutationConfig }?: UseStartOAuthOptions) => import("@tanstack/react-query").UseMutationResult<ProviderStartOAuthResponse, Error, StartOAuthDTO, unknown>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useMutation } from '@tanstack/react-query';
|
|
2
|
+
import { ProviderEvents, } from '../../../../shared/transport/events/index.js';
|
|
3
|
+
import { useTransportStore } from '../../../stores/transport-store.js';
|
|
4
|
+
export const startOAuth = ({ providerId }) => {
|
|
5
|
+
const { apiClient } = useTransportStore.getState();
|
|
6
|
+
if (!apiClient)
|
|
7
|
+
return Promise.reject(new Error('Not connected'));
|
|
8
|
+
return apiClient.request(ProviderEvents.START_OAUTH, {
|
|
9
|
+
providerId,
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
export const useStartOAuth = ({ mutationConfig } = {}) => useMutation({
|
|
13
|
+
...mutationConfig,
|
|
14
|
+
mutationFn: startOAuth,
|
|
15
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ProviderDTO } from '../../../../shared/transport/types/dto.js';
|
|
3
|
+
export interface AuthMethodDialogProps {
|
|
4
|
+
isActive?: boolean;
|
|
5
|
+
onCancel: () => void;
|
|
6
|
+
onSelect: (method: 'api-key' | 'oauth') => void;
|
|
7
|
+
provider: ProviderDTO;
|
|
8
|
+
}
|
|
9
|
+
export declare const AuthMethodDialog: React.FC<AuthMethodDialogProps>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { SelectableList } from '../../../components/selectable-list.js';
|
|
4
|
+
import { useTheme } from '../../../hooks/index.js';
|
|
5
|
+
export const AuthMethodDialog = ({ isActive = true, onCancel, onSelect, provider, }) => {
|
|
6
|
+
const { theme: { colors } } = useTheme();
|
|
7
|
+
const items = [
|
|
8
|
+
{
|
|
9
|
+
description: 'Authenticate in your browser',
|
|
10
|
+
id: 'oauth',
|
|
11
|
+
name: provider.oauthLabel ?? 'Sign in with OAuth',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
description: 'Enter your API key manually',
|
|
15
|
+
id: 'api-key',
|
|
16
|
+
name: 'API Key',
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
return (_jsx(SelectableList, { filterKeys: (item) => [item.id, item.name], isActive: isActive, items: items, keyExtractor: (item) => item.id, onCancel: onCancel, onSelect: (item) => onSelect(item.id), renderItem: (item, isItemActive) => (_jsxs(Box, { gap: 2, children: [_jsx(Text, { backgroundColor: isItemActive ? colors.dimText : undefined, color: colors.text, children: item.name.padEnd(25) }), _jsx(Text, { color: colors.dimText, children: item.description })] })), title: `${provider.name} — Choose authentication method` }));
|
|
20
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ProviderDTO } from '../../../../shared/transport/types/dto.js';
|
|
3
|
+
export interface OAuthDialogProps {
|
|
4
|
+
isActive?: boolean;
|
|
5
|
+
onCancel: () => void;
|
|
6
|
+
onSuccess: () => void;
|
|
7
|
+
provider: ProviderDTO;
|
|
8
|
+
}
|
|
9
|
+
export declare const OAuthDialog: React.FC<OAuthDialogProps>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { SelectableList } from '../../../components/selectable-list.js';
|
|
5
|
+
import { useTheme } from '../../../hooks/index.js';
|
|
6
|
+
import { formatTransportError } from '../../../utils/index.js';
|
|
7
|
+
import { useAwaitOAuthCallback } from '../api/await-oauth-callback.js';
|
|
8
|
+
import { cancelOAuth } from '../api/cancel-oauth.js';
|
|
9
|
+
import { useStartOAuth } from '../api/start-oauth.js';
|
|
10
|
+
export const OAuthDialog = ({ isActive = true, onCancel, onSuccess, provider, }) => {
|
|
11
|
+
const { theme: { colors } } = useTheme();
|
|
12
|
+
const [step, setStep] = useState('starting');
|
|
13
|
+
const [authUrl, setAuthUrl] = useState(null);
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const mounted = useRef(true);
|
|
16
|
+
const flowStarted = useRef(false);
|
|
17
|
+
const providerIdRef = useRef(provider.id);
|
|
18
|
+
providerIdRef.current = provider.id;
|
|
19
|
+
const startOAuthMutation = useStartOAuth();
|
|
20
|
+
const awaitCallbackMutation = useAwaitOAuthCallback();
|
|
21
|
+
const runOAuthFlow = useCallback(async () => {
|
|
22
|
+
if (!mounted.current)
|
|
23
|
+
return;
|
|
24
|
+
setStep('starting');
|
|
25
|
+
setError(null);
|
|
26
|
+
try {
|
|
27
|
+
const startResult = await startOAuthMutation.mutateAsync({ providerId: provider.id });
|
|
28
|
+
if (!mounted.current)
|
|
29
|
+
return;
|
|
30
|
+
if (!startResult.success) {
|
|
31
|
+
setError(startResult.error ?? 'Failed to start OAuth flow');
|
|
32
|
+
setStep('error');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
flowStarted.current = true;
|
|
36
|
+
setAuthUrl(startResult.authUrl);
|
|
37
|
+
setStep('waiting');
|
|
38
|
+
const callbackResult = await awaitCallbackMutation.mutateAsync({ providerId: provider.id });
|
|
39
|
+
if (!mounted.current)
|
|
40
|
+
return;
|
|
41
|
+
if (callbackResult.success) {
|
|
42
|
+
onSuccess();
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
setError(callbackResult.error ?? 'OAuth authentication failed');
|
|
46
|
+
setStep('error');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error_) {
|
|
50
|
+
if (!mounted.current)
|
|
51
|
+
return;
|
|
52
|
+
setError(formatTransportError(error_));
|
|
53
|
+
setStep('error');
|
|
54
|
+
}
|
|
55
|
+
}, [awaitCallbackMutation, onSuccess, provider.id, startOAuthMutation]);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
runOAuthFlow();
|
|
58
|
+
return () => {
|
|
59
|
+
mounted.current = false;
|
|
60
|
+
if (flowStarted.current) {
|
|
61
|
+
cancelOAuth({ providerId: providerIdRef.current }).catch(() => { });
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}, []);
|
|
65
|
+
useInput((_input, key) => {
|
|
66
|
+
if (key.escape && isActive && step === 'waiting') {
|
|
67
|
+
onCancel();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
const handleErrorAction = useCallback((action) => {
|
|
71
|
+
if (action.id === 'retry') {
|
|
72
|
+
runOAuthFlow();
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
onCancel();
|
|
76
|
+
}
|
|
77
|
+
}, [onCancel, runOAuthFlow]);
|
|
78
|
+
const errorActions = [
|
|
79
|
+
{ id: 'retry', name: 'Retry' },
|
|
80
|
+
{ id: 'cancel', name: 'Cancel' },
|
|
81
|
+
];
|
|
82
|
+
switch (step) {
|
|
83
|
+
case 'error': {
|
|
84
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.warning, children: error }) }), _jsx(SelectableList, { filterKeys: (item) => [item.id, item.name], isActive: isActive, items: errorActions, keyExtractor: (item) => item.id, onCancel: onCancel, onSelect: handleErrorAction, renderItem: (item, isItemActive) => (_jsx(Text, { backgroundColor: isItemActive ? colors.dimText : undefined, color: colors.text, children: item.name })), title: "OAuth failed" })] }));
|
|
85
|
+
}
|
|
86
|
+
case 'starting': {
|
|
87
|
+
return (_jsx(Box, { children: _jsxs(Text, { color: colors.primary, children: ["Starting OAuth flow for ", provider.name, "..."] }) }));
|
|
88
|
+
}
|
|
89
|
+
case 'waiting': {
|
|
90
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { color: colors.primary, children: "Opening browser for authentication..." }), authUrl && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: colors.dimText, children: "If the browser did not open, visit this URL:" }), _jsx(Text, { color: colors.info, children: authUrl })] })), _jsx(Text, { color: colors.dimText, children: "Waiting for authorization... (press Esc to cancel)" })] }));
|
|
91
|
+
}
|
|
92
|
+
default: {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
@@ -16,5 +16,5 @@ export const ProviderDialog = ({ hideCancelButton = false, isActive = true, onCa
|
|
|
16
16
|
const { theme: { colors } } = useTheme();
|
|
17
17
|
// Find current provider for the list
|
|
18
18
|
const currentProvider = useMemo(() => providers.find((p) => p.isCurrent), [providers]);
|
|
19
|
-
return (_jsx(SelectableList, { currentItem: currentProvider, filterKeys: (item) => [item.id, item.name, item.description], getCurrentKey: (item) => item.id, hideCancelButton: hideCancelButton, isActive: isActive, items: providers, keyExtractor: (item) => item.id, onCancel: onCancel, onSelect: (item) => onSelect(item), renderItem: (item, isActive, isCurrent) => (_jsxs(Box, { gap: 2, children: [_jsx(Text, { backgroundColor: isActive ? colors.dimText : undefined, color: isActive ? colors.text : colors.text, children: item.name.padEnd(15) }), _jsx(Text, { color: colors.dimText, children: item.description }), item.isConnected && !isCurrent && (_jsx(Text, { color: colors.primary, children: "[Connected]" })), isCurrent && (_jsx(Text, { color: colors.primary, children: "(Current)" }))] })), searchPlaceholder: "Search providers...", title: title }));
|
|
19
|
+
return (_jsx(SelectableList, { currentItem: currentProvider, filterKeys: (item) => [item.id, item.name, item.description], getCurrentKey: (item) => item.id, hideCancelButton: hideCancelButton, isActive: isActive, items: providers, keyExtractor: (item) => item.id, onCancel: onCancel, onSelect: (item) => onSelect(item), renderItem: (item, isActive, isCurrent) => (_jsxs(Box, { gap: 2, children: [_jsx(Text, { backgroundColor: isActive ? colors.dimText : undefined, color: isActive ? colors.text : colors.text, children: item.name.padEnd(15) }), _jsx(Text, { color: colors.dimText, children: item.description }), item.isConnected && !isCurrent && (_jsx(Text, { color: colors.primary, children: "[Connected]" })), isCurrent && (_jsx(Text, { color: colors.primary, children: "(Current)" })), item.isConnected && item.authMethod && (_jsx(Text, { color: colors.primary, children: item.authMethod === 'oauth' ? '[OAuth]' : '[API Key]' }))] })), searchPlaceholder: "Search providers...", title: title }));
|
|
20
20
|
};
|
|
@@ -20,8 +20,10 @@ import { useGetProviders } from '../api/get-providers.js';
|
|
|
20
20
|
import { useSetActiveProvider } from '../api/set-active-provider.js';
|
|
21
21
|
import { useValidateApiKey } from '../api/validate-api-key.js';
|
|
22
22
|
import { ApiKeyDialog } from './api-key-dialog.js';
|
|
23
|
+
import { AuthMethodDialog } from './auth-method-dialog.js';
|
|
23
24
|
import { BaseUrlDialog } from './base-url-dialog.js';
|
|
24
25
|
import { ModelSelectStep } from './model-select-step.js';
|
|
26
|
+
import { OAuthDialog } from './oauth-dialog.js';
|
|
25
27
|
import { ProviderDialog } from './provider-dialog.js';
|
|
26
28
|
export const ProviderFlow = ({ hideCancelButton = false, isActive = true, onCancel, onComplete, providerDialogTitle, }) => {
|
|
27
29
|
const { theme: { colors } } = useTheme();
|
|
@@ -53,7 +55,18 @@ export const ProviderFlow = ({ hideCancelButton = false, isActive = true, onCanc
|
|
|
53
55
|
name: 'Set as active',
|
|
54
56
|
});
|
|
55
57
|
}
|
|
56
|
-
if (selectedProvider.
|
|
58
|
+
if (selectedProvider.authMethod === 'oauth') {
|
|
59
|
+
actions.push({
|
|
60
|
+
description: 'Re-authenticate via browser',
|
|
61
|
+
id: 'reconnect_oauth',
|
|
62
|
+
name: 'Reconnect OAuth',
|
|
63
|
+
}, {
|
|
64
|
+
description: 'Remove OAuth connection',
|
|
65
|
+
id: 'disconnect',
|
|
66
|
+
name: 'Disconnect',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else if (selectedProvider.requiresApiKey) {
|
|
57
70
|
actions.push({
|
|
58
71
|
description: 'Enter a new API key',
|
|
59
72
|
id: 'replace',
|
|
@@ -113,6 +126,11 @@ export const ProviderFlow = ({ hideCancelButton = false, isActive = true, onCanc
|
|
|
113
126
|
setStep('base_url');
|
|
114
127
|
return;
|
|
115
128
|
}
|
|
129
|
+
// Supports OAuth → auth method selection
|
|
130
|
+
if (provider.supportsOAuth) {
|
|
131
|
+
setStep('auth_method');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
116
134
|
// Requires API key → api_key step
|
|
117
135
|
if (provider.requiresApiKey) {
|
|
118
136
|
setStep('api_key');
|
|
@@ -166,6 +184,10 @@ export const ProviderFlow = ({ hideCancelButton = false, isActive = true, onCanc
|
|
|
166
184
|
setStep('base_url');
|
|
167
185
|
break;
|
|
168
186
|
}
|
|
187
|
+
case 'reconnect_oauth': {
|
|
188
|
+
setStep('oauth');
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
169
191
|
case 'replace': {
|
|
170
192
|
setStep('api_key');
|
|
171
193
|
break;
|
|
@@ -200,9 +222,28 @@ export const ProviderFlow = ({ hideCancelButton = false, isActive = true, onCanc
|
|
|
200
222
|
}
|
|
201
223
|
}, [baseUrl, connectMutation, selectedProvider]);
|
|
202
224
|
const handleApiKeyCancel = useCallback(() => {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
225
|
+
if (selectedProvider?.supportsOAuth) {
|
|
226
|
+
setStep('auth_method');
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
setStep('select');
|
|
230
|
+
setSelectedProvider(null);
|
|
231
|
+
setBaseUrl(null);
|
|
232
|
+
}
|
|
233
|
+
}, [selectedProvider]);
|
|
234
|
+
const handleAuthMethodSelect = useCallback((method) => {
|
|
235
|
+
if (method === 'oauth') {
|
|
236
|
+
setStep('oauth');
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
setStep('api_key');
|
|
240
|
+
}
|
|
241
|
+
}, []);
|
|
242
|
+
const handleOAuthCancel = useCallback(() => {
|
|
243
|
+
setStep('auth_method');
|
|
244
|
+
}, []);
|
|
245
|
+
const handleOAuthSuccess = useCallback(() => {
|
|
246
|
+
setStep('model_select');
|
|
206
247
|
}, []);
|
|
207
248
|
const handleValidateApiKey = useCallback(async (apiKey) => {
|
|
208
249
|
if (!selectedProvider)
|
|
@@ -232,6 +273,12 @@ export const ProviderFlow = ({ hideCancelButton = false, isActive = true, onCanc
|
|
|
232
273
|
case 'api_key': {
|
|
233
274
|
return selectedProvider ? (_jsx(ApiKeyDialog, { isActive: isActive, isOptional: selectedProvider.id === 'openai-compatible', onCancel: handleApiKeyCancel, onSuccess: handleApiKeySuccess, provider: selectedProvider, validateApiKey: handleValidateApiKey })) : null;
|
|
234
275
|
}
|
|
276
|
+
case 'auth_method': {
|
|
277
|
+
return selectedProvider ? (_jsx(AuthMethodDialog, { isActive: isActive, onCancel: () => {
|
|
278
|
+
setStep('select');
|
|
279
|
+
setSelectedProvider(null);
|
|
280
|
+
}, onSelect: handleAuthMethodSelect, provider: selectedProvider })) : null;
|
|
281
|
+
}
|
|
235
282
|
case 'base_url': {
|
|
236
283
|
return (_jsx(BaseUrlDialog, { description: "Enter the base URL of your OpenAI-compatible endpoint (Ollama, LM Studio, etc.)", isActive: isActive, onCancel: handleApiKeyCancel, onSubmit: handleBaseUrlSubmit, title: "Connect to OpenAI Compatible" }));
|
|
237
284
|
}
|
|
@@ -241,6 +288,9 @@ export const ProviderFlow = ({ hideCancelButton = false, isActive = true, onCanc
|
|
|
241
288
|
case 'model_select': {
|
|
242
289
|
return selectedProvider ? (_jsx(ModelSelectStep, { isActive: isActive, onCancel: () => setStep('select'), onComplete: (modelName) => onComplete(`Connected to ${selectedProvider.name}, model set to ${modelName}`), providerId: selectedProvider.id, providerName: selectedProvider.name })) : null;
|
|
243
290
|
}
|
|
291
|
+
case 'oauth': {
|
|
292
|
+
return selectedProvider ? (_jsx(OAuthDialog, { isActive: isActive, onCancel: handleOAuthCancel, onSuccess: handleOAuthSuccess, provider: selectedProvider })) : null;
|
|
293
|
+
}
|
|
244
294
|
case 'provider_actions': {
|
|
245
295
|
return selectedProvider ? (_jsxs(Box, { flexDirection: "column", children: [error && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.warning, children: error }) })), _jsx(SelectableList, { filterKeys: (item) => [item.id, item.name], isActive: isActive, items: providerActions, keyExtractor: (item) => item.id, onCancel: () => {
|
|
246
296
|
setStep('select');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ProviderSubscriptionInitializer(): null;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook that subscribes to provider update broadcasts and invalidates React Query caches.
|
|
3
|
+
* Call this once from a top-level component to keep provider data fresh
|
|
4
|
+
* when changes happen via oclif commands or other clients.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useProviderSubscriptions(): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook that subscribes to provider update broadcasts and invalidates React Query caches.
|
|
3
|
+
* Call this once from a top-level component to keep provider data fresh
|
|
4
|
+
* when changes happen via oclif commands or other clients.
|
|
5
|
+
*/
|
|
6
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
7
|
+
import { useEffect } from 'react';
|
|
8
|
+
import { ProviderEvents } from '../../../../shared/transport/events/index.js';
|
|
9
|
+
import { useTransportStore } from '../../../stores/transport-store.js';
|
|
10
|
+
import { getActiveProviderConfigQueryOptions } from '../api/get-active-provider-config.js';
|
|
11
|
+
import { getProvidersQueryOptions } from '../api/get-providers.js';
|
|
12
|
+
export function useProviderSubscriptions() {
|
|
13
|
+
const client = useTransportStore((s) => s.client);
|
|
14
|
+
const queryClient = useQueryClient();
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!client)
|
|
17
|
+
return;
|
|
18
|
+
const unsub = client.on(ProviderEvents.UPDATED, () => {
|
|
19
|
+
queryClient.invalidateQueries({ queryKey: getProvidersQueryOptions().queryKey });
|
|
20
|
+
queryClient.invalidateQueries({ queryKey: getActiveProviderConfigQueryOptions().queryKey });
|
|
21
|
+
});
|
|
22
|
+
return unsub;
|
|
23
|
+
}, [client, queryClient]);
|
|
24
|
+
}
|
|
@@ -7,6 +7,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
*/
|
|
8
8
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
9
9
|
import { AuthInitializer } from '../features/auth/components/auth-initializer.js';
|
|
10
|
+
import { ProviderSubscriptionInitializer } from '../features/provider/components/provider-subscription-initializer.js';
|
|
10
11
|
import { TaskSubscriptionInitializer } from '../features/tasks/components/task-subscription-initializer.js';
|
|
11
12
|
import { TransportInitializer } from '../features/transport/components/transport-initializer.js';
|
|
12
13
|
const queryClient = new QueryClient({
|
|
@@ -23,5 +24,5 @@ const queryClient = new QueryClient({
|
|
|
23
24
|
},
|
|
24
25
|
});
|
|
25
26
|
export function AppProviders({ children }) {
|
|
26
|
-
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(TransportInitializer, { children: _jsxs(AuthInitializer, { children: [_jsx(TaskSubscriptionInitializer, {}), children] }) }) }));
|
|
27
|
+
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(TransportInitializer, { children: _jsxs(AuthInitializer, { children: [_jsx(TaskSubscriptionInitializer, {}), _jsx(ProviderSubscriptionInitializer, {}), children] }) }) }));
|
|
27
28
|
}
|
|
@@ -11,6 +11,8 @@ const USER_FRIENDLY_MESSAGES = {
|
|
|
11
11
|
ERR_CONTEXT_TREE_NOT_INIT: 'Context tree not initialized.',
|
|
12
12
|
ERR_LOCAL_CHANGES_EXIST: 'You have local changes. Run /push to save your changes before pulling.',
|
|
13
13
|
ERR_NOT_AUTHENTICATED: 'Not authenticated. This is required for cloud sync. Run /login to connect your account.',
|
|
14
|
+
ERR_OAUTH_REFRESH_FAILED: 'OAuth token refresh failed. Run /providers to reconnect your provider.',
|
|
15
|
+
ERR_OAUTH_TOKEN_EXPIRED: 'OAuth token has expired. Run /providers to reconnect your provider.',
|
|
14
16
|
ERR_PROJECT_NOT_INIT: "Project not initialized. Run 'brv restart' to reinitialize.",
|
|
15
17
|
ERR_PROVIDER_NOT_CONFIGURED: 'No provider connected. Run /providers connect byterover to use the free built-in provider, or connect another provider.',
|
|
16
18
|
ERR_SPACE_NOT_CONFIGURED: 'No space configured. Run /space switch to select a space first.',
|
|
@@ -41,5 +43,8 @@ export function formatTransportError(error) {
|
|
|
41
43
|
if (code && code in USER_FRIENDLY_MESSAGES) {
|
|
42
44
|
return USER_FRIENDLY_MESSAGES[code];
|
|
43
45
|
}
|
|
44
|
-
|
|
46
|
+
if (error.name === 'TransportRequestTimeoutError') {
|
|
47
|
+
return 'Request timed out. Please try again.';
|
|
48
|
+
}
|
|
49
|
+
return error.message.replace(/ for event '[^']+'(?: after \d+ms)?$/, '');
|
|
45
50
|
}
|
package/oclif.manifest.json
CHANGED
|
@@ -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": {},
|
|
@@ -1050,6 +1089,7 @@
|
|
|
1050
1089
|
"examples": [
|
|
1051
1090
|
"<%= config.bin %> providers connect anthropic --api-key sk-xxx",
|
|
1052
1091
|
"<%= config.bin %> providers connect openai --api-key sk-xxx --model gpt-4.1",
|
|
1092
|
+
"<%= config.bin %> providers connect openai --oauth",
|
|
1053
1093
|
"<%= config.bin %> providers connect byterover",
|
|
1054
1094
|
"<%= config.bin %> providers connect openai-compatible --base-url http://localhost:11434/v1",
|
|
1055
1095
|
"<%= config.bin %> providers connect openai-compatible --base-url http://localhost:11434/v1 --api-key sk-xxx --model llama3"
|
|
@@ -1071,6 +1111,15 @@
|
|
|
1071
1111
|
"multiple": false,
|
|
1072
1112
|
"type": "option"
|
|
1073
1113
|
},
|
|
1114
|
+
"code": {
|
|
1115
|
+
"char": "c",
|
|
1116
|
+
"description": "Authorization code for code-paste OAuth providers (e.g., Anthropic). Not applicable to browser-callback providers like OpenAI — use --oauth without --code instead.",
|
|
1117
|
+
"hidden": true,
|
|
1118
|
+
"name": "code",
|
|
1119
|
+
"hasDynamicHelp": false,
|
|
1120
|
+
"multiple": false,
|
|
1121
|
+
"type": "option"
|
|
1122
|
+
},
|
|
1074
1123
|
"format": {
|
|
1075
1124
|
"description": "Output format (text or json)",
|
|
1076
1125
|
"name": "format",
|
|
@@ -1090,6 +1139,12 @@
|
|
|
1090
1139
|
"hasDynamicHelp": false,
|
|
1091
1140
|
"multiple": false,
|
|
1092
1141
|
"type": "option"
|
|
1142
|
+
},
|
|
1143
|
+
"oauth": {
|
|
1144
|
+
"description": "Connect via OAuth (browser-based)",
|
|
1145
|
+
"name": "oauth",
|
|
1146
|
+
"allowNo": false,
|
|
1147
|
+
"type": "boolean"
|
|
1093
1148
|
}
|
|
1094
1149
|
},
|
|
1095
1150
|
"hasDynamicHelp": false,
|
|
@@ -1574,5 +1629,5 @@
|
|
|
1574
1629
|
]
|
|
1575
1630
|
}
|
|
1576
1631
|
},
|
|
1577
|
-
"version": "2.
|
|
1632
|
+
"version": "2.3.0"
|
|
1578
1633
|
}
|