byterover-cli 0.1.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/README.md +781 -0
- package/bin/dev.cmd +4 -0
- package/bin/dev.js +7 -0
- package/bin/run.cmd +4 -0
- package/bin/run.js +7 -0
- package/dist/commands/add.d.ts +60 -0
- package/dist/commands/add.js +230 -0
- package/dist/commands/clear.d.ts +13 -0
- package/dist/commands/clear.js +57 -0
- package/dist/commands/complete.d.ts +108 -0
- package/dist/commands/complete.js +340 -0
- package/dist/commands/gen-rules.d.ts +26 -0
- package/dist/commands/gen-rules.js +89 -0
- package/dist/commands/init.d.ts +24 -0
- package/dist/commands/init.js +135 -0
- package/dist/commands/login.d.ts +22 -0
- package/dist/commands/login.js +103 -0
- package/dist/commands/push.d.ts +33 -0
- package/dist/commands/push.js +150 -0
- package/dist/commands/retrieve.d.ts +26 -0
- package/dist/commands/retrieve.js +101 -0
- package/dist/commands/space/list.d.ts +22 -0
- package/dist/commands/space/list.js +105 -0
- package/dist/commands/space/switch.d.ts +20 -0
- package/dist/commands/space/switch.js +110 -0
- package/dist/commands/status.d.ts +22 -0
- package/dist/commands/status.js +116 -0
- package/dist/config/auth.config.d.ts +32 -0
- package/dist/config/auth.config.js +35 -0
- package/dist/config/environment.d.ts +35 -0
- package/dist/config/environment.js +39 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.js +12 -0
- package/dist/core/domain/entities/agent.d.ts +5 -0
- package/dist/core/domain/entities/agent.js +23 -0
- package/dist/core/domain/entities/auth-token.d.ts +43 -0
- package/dist/core/domain/entities/auth-token.js +70 -0
- package/dist/core/domain/entities/br-config.d.ts +25 -0
- package/dist/core/domain/entities/br-config.js +58 -0
- package/dist/core/domain/entities/bullet.d.ts +51 -0
- package/dist/core/domain/entities/bullet.js +94 -0
- package/dist/core/domain/entities/curator-output.d.ts +14 -0
- package/dist/core/domain/entities/curator-output.js +23 -0
- package/dist/core/domain/entities/delta-batch.d.ts +30 -0
- package/dist/core/domain/entities/delta-batch.js +52 -0
- package/dist/core/domain/entities/delta-operation.d.ts +31 -0
- package/dist/core/domain/entities/delta-operation.js +50 -0
- package/dist/core/domain/entities/event.d.ts +8 -0
- package/dist/core/domain/entities/event.js +15 -0
- package/dist/core/domain/entities/executor-output.d.ts +27 -0
- package/dist/core/domain/entities/executor-output.js +33 -0
- package/dist/core/domain/entities/memory.d.ts +55 -0
- package/dist/core/domain/entities/memory.js +90 -0
- package/dist/core/domain/entities/oauth-token-data.d.ts +13 -0
- package/dist/core/domain/entities/oauth-token-data.js +20 -0
- package/dist/core/domain/entities/playbook.d.ts +97 -0
- package/dist/core/domain/entities/playbook.js +275 -0
- package/dist/core/domain/entities/presigned-url.d.ts +9 -0
- package/dist/core/domain/entities/presigned-url.js +18 -0
- package/dist/core/domain/entities/presigned-urls-response.d.ts +10 -0
- package/dist/core/domain/entities/presigned-urls-response.js +18 -0
- package/dist/core/domain/entities/reflector-output.d.ts +38 -0
- package/dist/core/domain/entities/reflector-output.js +44 -0
- package/dist/core/domain/entities/retrieve-result.d.ts +35 -0
- package/dist/core/domain/entities/retrieve-result.js +35 -0
- package/dist/core/domain/entities/space.d.ts +24 -0
- package/dist/core/domain/entities/space.js +52 -0
- package/dist/core/domain/entities/team.d.ts +42 -0
- package/dist/core/domain/entities/team.js +89 -0
- package/dist/core/domain/entities/user.d.ts +20 -0
- package/dist/core/domain/entities/user.js +32 -0
- package/dist/core/domain/errors/ace-error.d.ts +34 -0
- package/dist/core/domain/errors/ace-error.js +53 -0
- package/dist/core/domain/errors/auth-error.d.ts +10 -0
- package/dist/core/domain/errors/auth-error.js +20 -0
- package/dist/core/domain/errors/discovery-error.d.ts +21 -0
- package/dist/core/domain/errors/discovery-error.js +33 -0
- package/dist/core/domain/errors/rule-error.d.ts +6 -0
- package/dist/core/domain/errors/rule-error.js +12 -0
- package/dist/core/interfaces/i-ace-prompt-builder.d.ts +48 -0
- package/dist/core/interfaces/i-ace-prompt-builder.js +1 -0
- package/dist/core/interfaces/i-auth-service.d.ts +35 -0
- package/dist/core/interfaces/i-auth-service.js +1 -0
- package/dist/core/interfaces/i-browser-launcher.d.ts +11 -0
- package/dist/core/interfaces/i-browser-launcher.js +1 -0
- package/dist/core/interfaces/i-bullet-content-store.d.ts +36 -0
- package/dist/core/interfaces/i-bullet-content-store.js +1 -0
- package/dist/core/interfaces/i-callback-handler.d.ts +35 -0
- package/dist/core/interfaces/i-callback-handler.js +1 -0
- package/dist/core/interfaces/i-delta-store.d.ts +15 -0
- package/dist/core/interfaces/i-delta-store.js +1 -0
- package/dist/core/interfaces/i-executor-output-store.d.ts +14 -0
- package/dist/core/interfaces/i-executor-output-store.js +1 -0
- package/dist/core/interfaces/i-file-service.d.ts +34 -0
- package/dist/core/interfaces/i-file-service.js +1 -0
- package/dist/core/interfaces/i-http-client.d.ts +33 -0
- package/dist/core/interfaces/i-http-client.js +1 -0
- package/dist/core/interfaces/i-memory-retrieval-service.d.ts +40 -0
- package/dist/core/interfaces/i-memory-retrieval-service.js +1 -0
- package/dist/core/interfaces/i-memory-storage-service.d.ts +55 -0
- package/dist/core/interfaces/i-memory-storage-service.js +1 -0
- package/dist/core/interfaces/i-oidc-discovery-service.d.ts +20 -0
- package/dist/core/interfaces/i-oidc-discovery-service.js +1 -0
- package/dist/core/interfaces/i-playbook-service.d.ts +69 -0
- package/dist/core/interfaces/i-playbook-service.js +1 -0
- package/dist/core/interfaces/i-playbook-store.d.ts +38 -0
- package/dist/core/interfaces/i-playbook-store.js +1 -0
- package/dist/core/interfaces/i-project-config-store.d.ts +26 -0
- package/dist/core/interfaces/i-project-config-store.js +1 -0
- package/dist/core/interfaces/i-reflection-store.d.ts +21 -0
- package/dist/core/interfaces/i-reflection-store.js +1 -0
- package/dist/core/interfaces/i-rule-template-service.d.ts +17 -0
- package/dist/core/interfaces/i-rule-template-service.js +4 -0
- package/dist/core/interfaces/i-rule-writer-service.d.ts +13 -0
- package/dist/core/interfaces/i-rule-writer-service.js +1 -0
- package/dist/core/interfaces/i-space-service.d.ts +28 -0
- package/dist/core/interfaces/i-space-service.js +1 -0
- package/dist/core/interfaces/i-team-service.d.ts +29 -0
- package/dist/core/interfaces/i-team-service.js +1 -0
- package/dist/core/interfaces/i-template-loader.d.ts +29 -0
- package/dist/core/interfaces/i-template-loader.js +1 -0
- package/dist/core/interfaces/i-token-store.d.ts +22 -0
- package/dist/core/interfaces/i-token-store.js +1 -0
- package/dist/core/interfaces/i-tracking-service.d.ts +21 -0
- package/dist/core/interfaces/i-tracking-service.js +1 -0
- package/dist/core/interfaces/i-user-service.d.ts +14 -0
- package/dist/core/interfaces/i-user-service.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/infra/ace/ace-file-utils.d.ts +46 -0
- package/dist/infra/ace/ace-file-utils.js +83 -0
- package/dist/infra/ace/ace-prompt-templates.d.ts +13 -0
- package/dist/infra/ace/ace-prompt-templates.js +177 -0
- package/dist/infra/ace/file-bullet-content-store.d.ts +27 -0
- package/dist/infra/ace/file-bullet-content-store.js +89 -0
- package/dist/infra/ace/file-delta-store.d.ts +9 -0
- package/dist/infra/ace/file-delta-store.js +26 -0
- package/dist/infra/ace/file-executor-output-store.d.ts +9 -0
- package/dist/infra/ace/file-executor-output-store.js +26 -0
- package/dist/infra/ace/file-playbook-store.d.ts +29 -0
- package/dist/infra/ace/file-playbook-store.js +107 -0
- package/dist/infra/ace/file-reflection-store.d.ts +10 -0
- package/dist/infra/ace/file-reflection-store.js +55 -0
- package/dist/infra/auth/oauth-service.d.ts +49 -0
- package/dist/infra/auth/oauth-service.js +126 -0
- package/dist/infra/auth/oidc-discovery-service.d.ts +51 -0
- package/dist/infra/auth/oidc-discovery-service.js +145 -0
- package/dist/infra/browser/system-browser-launcher.d.ts +10 -0
- package/dist/infra/browser/system-browser-launcher.js +18 -0
- package/dist/infra/config/file-config-store.d.ts +21 -0
- package/dist/infra/config/file-config-store.js +57 -0
- package/dist/infra/file/fs-file-service.d.ts +28 -0
- package/dist/infra/file/fs-file-service.js +57 -0
- package/dist/infra/http/authenticated-http-client.d.ts +46 -0
- package/dist/infra/http/authenticated-http-client.js +99 -0
- package/dist/infra/http/callback-handler.d.ts +13 -0
- package/dist/infra/http/callback-handler.js +24 -0
- package/dist/infra/http/callback-server.d.ts +18 -0
- package/dist/infra/http/callback-server.js +93 -0
- package/dist/infra/memory/http-memory-retrieval-service.d.ts +18 -0
- package/dist/infra/memory/http-memory-retrieval-service.js +63 -0
- package/dist/infra/memory/http-memory-storage-service.d.ts +18 -0
- package/dist/infra/memory/http-memory-storage-service.js +67 -0
- package/dist/infra/memory/memory-to-playbook-mapper.d.ts +33 -0
- package/dist/infra/memory/memory-to-playbook-mapper.js +51 -0
- package/dist/infra/playbook/file-playbook-service.d.ts +43 -0
- package/dist/infra/playbook/file-playbook-service.js +133 -0
- package/dist/infra/rule/agent-rule-config.d.ts +19 -0
- package/dist/infra/rule/agent-rule-config.js +77 -0
- package/dist/infra/rule/rule-template-service.d.ts +18 -0
- package/dist/infra/rule/rule-template-service.js +80 -0
- package/dist/infra/rule/rule-writer-service.d.ts +19 -0
- package/dist/infra/rule/rule-writer-service.js +43 -0
- package/dist/infra/space/http-space-service.d.ts +20 -0
- package/dist/infra/space/http-space-service.js +67 -0
- package/dist/infra/storage/keychain-token-store.d.ts +10 -0
- package/dist/infra/storage/keychain-token-store.js +40 -0
- package/dist/infra/team/http-team-service.d.ts +21 -0
- package/dist/infra/team/http-team-service.js +71 -0
- package/dist/infra/template/fs-template-loader.d.ts +33 -0
- package/dist/infra/template/fs-template-loader.js +62 -0
- package/dist/infra/tracking/mixpanel-tracking-service.d.ts +14 -0
- package/dist/infra/tracking/mixpanel-tracking-service.js +44 -0
- package/dist/infra/user/http-user-service.d.ts +12 -0
- package/dist/infra/user/http-user-service.js +26 -0
- package/dist/templates/README.md +103 -0
- package/dist/templates/base.md +3 -0
- package/dist/templates/sections/command-reference.md +141 -0
- package/dist/templates/sections/workflow.md +46 -0
- package/dist/utils/file-helpers.d.ts +15 -0
- package/dist/utils/file-helpers.js +45 -0
- package/oclif.manifest.json +476 -0
- package/package.json +82 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { OAuthConfig } from '../../config/auth.config.js';
|
|
2
|
+
import { OAuthTokenData } from '../../core/domain/entities/oauth-token-data.js';
|
|
3
|
+
import { AuthorizationContext, IAuthService } from '../../core/interfaces/i-auth-service.js';
|
|
4
|
+
/**
|
|
5
|
+
* OAuth service implementation for handling OAuth authentication flows.
|
|
6
|
+
*/
|
|
7
|
+
export declare class OAuthService implements IAuthService {
|
|
8
|
+
private readonly config;
|
|
9
|
+
private readonly verifierStore;
|
|
10
|
+
constructor(config: OAuthConfig);
|
|
11
|
+
/**
|
|
12
|
+
* Exchanges an authorization code for OAuth token data.
|
|
13
|
+
* @param code The authorization code received from the authorization server.
|
|
14
|
+
* @param context The authorization context from initiateAuthorization (contains state for verifier lookup).
|
|
15
|
+
* @param redirectUri The redirect URI used in the authorization request (must match for OAuth 2.0 compliance).
|
|
16
|
+
* @returns The OAuth token data with refresh token and expiration (without user information).
|
|
17
|
+
*/
|
|
18
|
+
exchangeCodeForToken(code: string, context: AuthorizationContext, redirectUri: string): Promise<OAuthTokenData>;
|
|
19
|
+
/**
|
|
20
|
+
* Initiates the authorization flow by generating PKCE parameters and building the authorization URL.
|
|
21
|
+
* The code_verifier is generated and stored internally by the service.
|
|
22
|
+
* @param redirectUri The redirect URI where authorization codes will be received.
|
|
23
|
+
* @returns Authorization context containing the URL and state for CSRF protection.
|
|
24
|
+
*/
|
|
25
|
+
initiateAuthorization(redirectUri: string): AuthorizationContext;
|
|
26
|
+
refreshToken(refreshToken: string): Promise<OAuthTokenData>;
|
|
27
|
+
/**
|
|
28
|
+
* Generates a code challenge from a code verifier using SHA-256 and base64url encoding.
|
|
29
|
+
* @param codeVerifier The code verifier.
|
|
30
|
+
* @returns The code challenge.
|
|
31
|
+
*/
|
|
32
|
+
private generateCodeChallenge;
|
|
33
|
+
/**
|
|
34
|
+
* Generates a cryptographically secure code verifier for PKCE.
|
|
35
|
+
* @returns A random code verifier string.
|
|
36
|
+
*/
|
|
37
|
+
private generateCodeVerifier;
|
|
38
|
+
/**
|
|
39
|
+
* Generates a cryptographically secure state parameter for CSRF protection.
|
|
40
|
+
* @returns A random state string.
|
|
41
|
+
*/
|
|
42
|
+
private generateState;
|
|
43
|
+
/**
|
|
44
|
+
* Parses the token response from the OAuth server.
|
|
45
|
+
* @param data The response data from the OAuth server.
|
|
46
|
+
* @returns The parsed OAuthTokenData (without user information).
|
|
47
|
+
*/
|
|
48
|
+
private parseTokenResponse;
|
|
49
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import axios, { isAxiosError } from 'axios';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { OAuthTokenData } from '../../core/domain/entities/oauth-token-data.js';
|
|
5
|
+
import { AuthenticationError } from '../../core/domain/errors/auth-error.js';
|
|
6
|
+
/**
|
|
7
|
+
* OAuth service implementation for handling OAuth authentication flows.
|
|
8
|
+
*/
|
|
9
|
+
export class OAuthService {
|
|
10
|
+
config;
|
|
11
|
+
verifierStore = new Map();
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Exchanges an authorization code for OAuth token data.
|
|
17
|
+
* @param code The authorization code received from the authorization server.
|
|
18
|
+
* @param context The authorization context from initiateAuthorization (contains state for verifier lookup).
|
|
19
|
+
* @param redirectUri The redirect URI used in the authorization request (must match for OAuth 2.0 compliance).
|
|
20
|
+
* @returns The OAuth token data with refresh token and expiration (without user information).
|
|
21
|
+
*/
|
|
22
|
+
async exchangeCodeForToken(code, context, redirectUri) {
|
|
23
|
+
// Retrieve the code_verifier using the state from the context
|
|
24
|
+
const codeVerifier = this.verifierStore.get(context.state);
|
|
25
|
+
if (!codeVerifier) {
|
|
26
|
+
throw new AuthenticationError('Invalid authorization context: code_verifier not found. Context may be from a different service instance or already used.', 'invalid_context');
|
|
27
|
+
}
|
|
28
|
+
// Remove the code_verifier from the store (single-use)
|
|
29
|
+
this.verifierStore.delete(context.state);
|
|
30
|
+
try {
|
|
31
|
+
const response = await axios.post(this.config.tokenUrl, {
|
|
32
|
+
client_id: this.config.clientId,
|
|
33
|
+
// client_secret is undefined for public clients using PKCE.
|
|
34
|
+
client_secret: this.config.clientSecret,
|
|
35
|
+
code,
|
|
36
|
+
code_verifier: codeVerifier,
|
|
37
|
+
grant_type: 'authorization_code',
|
|
38
|
+
redirect_uri: redirectUri,
|
|
39
|
+
}, {
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
return this.parseTokenResponse(response.data);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (isAxiosError(error)) {
|
|
48
|
+
throw new AuthenticationError(error.response?.data?.error_description ?? 'Failed to exchange code for token', error.response?.data?.error);
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Initiates the authorization flow by generating PKCE parameters and building the authorization URL.
|
|
55
|
+
* The code_verifier is generated and stored internally by the service.
|
|
56
|
+
* @param redirectUri The redirect URI where authorization codes will be received.
|
|
57
|
+
* @returns Authorization context containing the URL and state for CSRF protection.
|
|
58
|
+
*/
|
|
59
|
+
initiateAuthorization(redirectUri) {
|
|
60
|
+
const codeVerifier = this.generateCodeVerifier();
|
|
61
|
+
const state = this.generateState();
|
|
62
|
+
const codeChallenge = this.generateCodeChallenge(codeVerifier);
|
|
63
|
+
// Store the code_verifier mapped to the state for later retrieval
|
|
64
|
+
this.verifierStore.set(state, codeVerifier);
|
|
65
|
+
const params = new URLSearchParams({
|
|
66
|
+
client_id: this.config.clientId,
|
|
67
|
+
code_challenge: codeChallenge,
|
|
68
|
+
code_challenge_method: 'S256',
|
|
69
|
+
redirect_uri: redirectUri,
|
|
70
|
+
response_type: 'code',
|
|
71
|
+
scope: this.config.scopes.join(' '),
|
|
72
|
+
state,
|
|
73
|
+
});
|
|
74
|
+
const authUrl = `${this.config.authorizationUrl}?${params.toString()}`;
|
|
75
|
+
return { authUrl, state };
|
|
76
|
+
}
|
|
77
|
+
async refreshToken(refreshToken) {
|
|
78
|
+
try {
|
|
79
|
+
const response = await axios.post(this.config.tokenUrl, {
|
|
80
|
+
client_id: this.config.clientId,
|
|
81
|
+
// client_secret is undefined for public clients using PKCE.
|
|
82
|
+
client_secret: this.config.clientSecret,
|
|
83
|
+
grant_type: 'refresh_token',
|
|
84
|
+
refresh_token: refreshToken,
|
|
85
|
+
});
|
|
86
|
+
return this.parseTokenResponse(response.data);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (isAxiosError(error)) {
|
|
90
|
+
throw new AuthenticationError(error.response?.data?.error_description ?? 'Failed to refresh token', error.response?.data?.error);
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Generates a code challenge from a code verifier using SHA-256 and base64url encoding.
|
|
97
|
+
* @param codeVerifier The code verifier.
|
|
98
|
+
* @returns The code challenge.
|
|
99
|
+
*/
|
|
100
|
+
generateCodeChallenge(codeVerifier) {
|
|
101
|
+
return crypto.createHash('sha256').update(codeVerifier).digest('base64url');
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Generates a cryptographically secure code verifier for PKCE.
|
|
105
|
+
* @returns A random code verifier string.
|
|
106
|
+
*/
|
|
107
|
+
generateCodeVerifier() {
|
|
108
|
+
return crypto.randomBytes(32).toString('base64url');
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Generates a cryptographically secure state parameter for CSRF protection.
|
|
112
|
+
* @returns A random state string.
|
|
113
|
+
*/
|
|
114
|
+
generateState() {
|
|
115
|
+
return crypto.randomBytes(16).toString('base64url');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Parses the token response from the OAuth server.
|
|
119
|
+
* @param data The response data from the OAuth server.
|
|
120
|
+
* @returns The parsed OAuthTokenData (without user information).
|
|
121
|
+
*/
|
|
122
|
+
parseTokenResponse(data) {
|
|
123
|
+
const expiresAt = new Date(Date.now() + data.expires_in * 1000);
|
|
124
|
+
return new OAuthTokenData(data.access_token, expiresAt, data.refresh_token, data.session_key, data.token_type);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { IOidcDiscoveryService, OidcMetadata } from '../../core/interfaces/i-oidc-discovery-service.js';
|
|
2
|
+
/**
|
|
3
|
+
* OIDC discovery service implementation.
|
|
4
|
+
* Fetches OIDC configuration from the well-known endpoint with in-memory caching.
|
|
5
|
+
*/
|
|
6
|
+
export declare class OidcDiscoveryService implements IOidcDiscoveryService {
|
|
7
|
+
private readonly cache;
|
|
8
|
+
private readonly cacheTtlMs;
|
|
9
|
+
private readonly maxRetries;
|
|
10
|
+
private readonly retryDelayMs;
|
|
11
|
+
private readonly timeoutMs;
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new OIDC discovery service.
|
|
14
|
+
* @param cacheTtlMs Cache TTL in milliseconds (default: 1 hour)
|
|
15
|
+
* @param timeoutMs Request timeout in milliseconds (default: 5 seconds)
|
|
16
|
+
* @param maxRetries Maximum number of retry attempts (default: 3)
|
|
17
|
+
* @param retryDelayMs Base delay between retries in milliseconds (default: 1 second)
|
|
18
|
+
*/
|
|
19
|
+
constructor(cacheTtlMs?: number, timeoutMs?: number, maxRetries?: number, retryDelayMs?: number);
|
|
20
|
+
discover(issuerUrl: string): Promise<OidcMetadata>;
|
|
21
|
+
/**
|
|
22
|
+
* Fetches OIDC metadata from the well-known endpoint with retry logic.
|
|
23
|
+
* @param issuerUrl The base URL of the OIDC issuer.
|
|
24
|
+
* @returns The OIDC metadata.
|
|
25
|
+
*/
|
|
26
|
+
private fetchMetadata;
|
|
27
|
+
/**
|
|
28
|
+
* Fetches OIDC metadata from the well-known endpoint (single attempt).
|
|
29
|
+
* @param issuerUrl The base URL of the OIDC issuer.
|
|
30
|
+
* @param attempt Current attempt number.
|
|
31
|
+
* @returns The OIDC metadata.
|
|
32
|
+
*/
|
|
33
|
+
private fetchMetadataOnce;
|
|
34
|
+
/**
|
|
35
|
+
* Gets metadata from cache if available and not expired.
|
|
36
|
+
* @param issuerUrl The issuer URL.
|
|
37
|
+
* @returns The cached metadata or undefined.
|
|
38
|
+
*/
|
|
39
|
+
private getFromCache;
|
|
40
|
+
/**
|
|
41
|
+
* Sleep for a specified duration.
|
|
42
|
+
* @param ms Milliseconds to sleep.
|
|
43
|
+
*/
|
|
44
|
+
private sleep;
|
|
45
|
+
/**
|
|
46
|
+
* Stores metadata in cache.
|
|
47
|
+
* @param issuerUrl The issuer URL.
|
|
48
|
+
* @param metadata The metadata to cache.
|
|
49
|
+
*/
|
|
50
|
+
private storeInCache;
|
|
51
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import axios, { isAxiosError } from 'axios';
|
|
2
|
+
import { DiscoveryError, DiscoveryNetworkError, DiscoveryTimeoutError } from '../../core/domain/errors/discovery-error.js';
|
|
3
|
+
/**
|
|
4
|
+
* OIDC discovery service implementation.
|
|
5
|
+
* Fetches OIDC configuration from the well-known endpoint with in-memory caching.
|
|
6
|
+
*/
|
|
7
|
+
export class OidcDiscoveryService {
|
|
8
|
+
cache = new Map();
|
|
9
|
+
cacheTtlMs;
|
|
10
|
+
maxRetries;
|
|
11
|
+
retryDelayMs;
|
|
12
|
+
timeoutMs;
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new OIDC discovery service.
|
|
15
|
+
* @param cacheTtlMs Cache TTL in milliseconds (default: 1 hour)
|
|
16
|
+
* @param timeoutMs Request timeout in milliseconds (default: 5 seconds)
|
|
17
|
+
* @param maxRetries Maximum number of retry attempts (default: 3)
|
|
18
|
+
* @param retryDelayMs Base delay between retries in milliseconds (default: 1 second)
|
|
19
|
+
*/
|
|
20
|
+
constructor(cacheTtlMs = 3_600_000, timeoutMs = 5000, maxRetries = 3, retryDelayMs = 1000) {
|
|
21
|
+
this.cacheTtlMs = cacheTtlMs;
|
|
22
|
+
this.maxRetries = maxRetries;
|
|
23
|
+
this.retryDelayMs = retryDelayMs;
|
|
24
|
+
this.timeoutMs = timeoutMs;
|
|
25
|
+
}
|
|
26
|
+
async discover(issuerUrl) {
|
|
27
|
+
// Check cache first
|
|
28
|
+
const cached = this.getFromCache(issuerUrl);
|
|
29
|
+
if (cached) {
|
|
30
|
+
return cached;
|
|
31
|
+
}
|
|
32
|
+
// Fetch from discovery endpoint
|
|
33
|
+
const metadata = await this.fetchMetadata(issuerUrl);
|
|
34
|
+
// Store in cache
|
|
35
|
+
this.storeInCache(issuerUrl, metadata);
|
|
36
|
+
return metadata;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Fetches OIDC metadata from the well-known endpoint with retry logic.
|
|
40
|
+
* @param issuerUrl The base URL of the OIDC issuer.
|
|
41
|
+
* @returns The OIDC metadata.
|
|
42
|
+
*/
|
|
43
|
+
async fetchMetadata(issuerUrl) {
|
|
44
|
+
let lastError;
|
|
45
|
+
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
|
|
46
|
+
try {
|
|
47
|
+
// eslint-disable-next-line no-await-in-loop
|
|
48
|
+
return await this.fetchMetadataOnce(issuerUrl, attempt);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
lastError = error;
|
|
52
|
+
// Don't retry on non-retryable errors
|
|
53
|
+
if (error instanceof DiscoveryError && !(error instanceof DiscoveryNetworkError)) {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
// Don't retry if this was the last attempt
|
|
57
|
+
if (attempt >= this.maxRetries) {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
// Wait before retrying (exponential backoff)
|
|
61
|
+
const delay = this.retryDelayMs * 2 ** (attempt - 1);
|
|
62
|
+
// eslint-disable-next-line no-await-in-loop
|
|
63
|
+
await this.sleep(delay);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// All retries exhausted
|
|
67
|
+
throw new DiscoveryError(`Failed to discover OIDC configuration after ${this.maxRetries} attempts: ${lastError?.message}`, issuerUrl, this.maxRetries);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Fetches OIDC metadata from the well-known endpoint (single attempt).
|
|
71
|
+
* @param issuerUrl The base URL of the OIDC issuer.
|
|
72
|
+
* @param attempt Current attempt number.
|
|
73
|
+
* @returns The OIDC metadata.
|
|
74
|
+
*/
|
|
75
|
+
async fetchMetadataOnce(issuerUrl, attempt) {
|
|
76
|
+
try {
|
|
77
|
+
const wellKnownUrl = `${issuerUrl}/.well-known/openid-configuration`;
|
|
78
|
+
const response = await axios.get(wellKnownUrl, {
|
|
79
|
+
timeout: this.timeoutMs,
|
|
80
|
+
});
|
|
81
|
+
// Validate required fields
|
|
82
|
+
if (!response.data.authorization_endpoint || !response.data.token_endpoint) {
|
|
83
|
+
throw new DiscoveryError('Invalid OIDC discovery document: missing required endpoints', issuerUrl, attempt);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
authorizationEndpoint: response.data.authorization_endpoint,
|
|
87
|
+
issuer: response.data.issuer,
|
|
88
|
+
scopesSupported: response.data.scopes_supported,
|
|
89
|
+
tokenEndpoint: response.data.token_endpoint,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (isAxiosError(error)) {
|
|
94
|
+
// Timeout error
|
|
95
|
+
if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') {
|
|
96
|
+
throw new DiscoveryTimeoutError(issuerUrl, this.timeoutMs, attempt);
|
|
97
|
+
}
|
|
98
|
+
// Network errors (retryable)
|
|
99
|
+
if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED' || !error.response) {
|
|
100
|
+
throw new DiscoveryNetworkError(issuerUrl, error, attempt);
|
|
101
|
+
}
|
|
102
|
+
// HTTP errors (non-retryable)
|
|
103
|
+
throw new DiscoveryError(`HTTP ${error.response?.status}: ${error.response?.statusText || error.message}`, issuerUrl, attempt);
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Gets metadata from cache if available and not expired.
|
|
110
|
+
* @param issuerUrl The issuer URL.
|
|
111
|
+
* @returns The cached metadata or undefined.
|
|
112
|
+
*/
|
|
113
|
+
getFromCache(issuerUrl) {
|
|
114
|
+
const entry = this.cache.get(issuerUrl);
|
|
115
|
+
if (!entry) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
// Check if expired
|
|
119
|
+
if (Date.now() > entry.expiresAt) {
|
|
120
|
+
this.cache.delete(issuerUrl);
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
return entry.metadata;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Sleep for a specified duration.
|
|
127
|
+
* @param ms Milliseconds to sleep.
|
|
128
|
+
*/
|
|
129
|
+
async sleep(ms) {
|
|
130
|
+
return new Promise((resolve) => {
|
|
131
|
+
setTimeout(resolve, ms);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Stores metadata in cache.
|
|
136
|
+
* @param issuerUrl The issuer URL.
|
|
137
|
+
* @param metadata The metadata to cache.
|
|
138
|
+
*/
|
|
139
|
+
storeInCache(issuerUrl, metadata) {
|
|
140
|
+
this.cache.set(issuerUrl, {
|
|
141
|
+
expiresAt: Date.now() + this.cacheTtlMs,
|
|
142
|
+
metadata,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import open from 'open';
|
|
2
|
+
import type { IBrowserLauncher } from '../../core/interfaces/i-browser-launcher.js';
|
|
3
|
+
/**
|
|
4
|
+
* Browser launcher implementation that opens URLs in the system's default browser.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SystemBrowserLauncher implements IBrowserLauncher {
|
|
7
|
+
private readonly openFn;
|
|
8
|
+
constructor(openFn?: typeof open);
|
|
9
|
+
open(url: string): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import open from 'open';
|
|
2
|
+
/**
|
|
3
|
+
* Browser launcher implementation that opens URLs in the system's default browser.
|
|
4
|
+
*/
|
|
5
|
+
export class SystemBrowserLauncher {
|
|
6
|
+
openFn;
|
|
7
|
+
constructor(openFn = open) {
|
|
8
|
+
this.openFn = openFn;
|
|
9
|
+
}
|
|
10
|
+
async open(url) {
|
|
11
|
+
try {
|
|
12
|
+
await this.openFn(url);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
throw new Error(`Failed to launch browser: ${error}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IProjectConfigStore } from '../../core/interfaces/i-project-config-store.js';
|
|
2
|
+
import { BrConfig } from '../../core/domain/entities/br-config.js';
|
|
3
|
+
/**
|
|
4
|
+
* File-based implementation of IProjectConfigStore.
|
|
5
|
+
* Stores configuration in .br/config.json in the project directory.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ProjectConfigStore implements IProjectConfigStore {
|
|
8
|
+
private static readonly BR_DIR;
|
|
9
|
+
private static readonly CONFIG_FILE;
|
|
10
|
+
exists(directory?: string): Promise<boolean>;
|
|
11
|
+
read(directory?: string): Promise<BrConfig | undefined>;
|
|
12
|
+
write(config: BrConfig, directory?: string): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Gets the full path to the .br directory.
|
|
15
|
+
*/
|
|
16
|
+
private getBrDirPath;
|
|
17
|
+
/**
|
|
18
|
+
* Gets the full path to the config.json file.
|
|
19
|
+
*/
|
|
20
|
+
private getConfigPath;
|
|
21
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { BrConfig } from '../../core/domain/entities/br-config.js';
|
|
5
|
+
/**
|
|
6
|
+
* File-based implementation of IProjectConfigStore.
|
|
7
|
+
* Stores configuration in .br/config.json in the project directory.
|
|
8
|
+
*/
|
|
9
|
+
export class ProjectConfigStore {
|
|
10
|
+
static BR_DIR = '.br';
|
|
11
|
+
static CONFIG_FILE = 'config.json';
|
|
12
|
+
async exists(directory) {
|
|
13
|
+
const configPath = this.getConfigPath(directory);
|
|
14
|
+
return existsSync(configPath);
|
|
15
|
+
}
|
|
16
|
+
async read(directory) {
|
|
17
|
+
const configPath = this.getConfigPath(directory);
|
|
18
|
+
if (!existsSync(configPath)) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const content = await readFile(configPath, 'utf8');
|
|
23
|
+
const json = JSON.parse(content);
|
|
24
|
+
return BrConfig.fromJson(json);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
throw new Error(`Failed to read config from ${configPath}: ${error.message}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async write(config, directory) {
|
|
31
|
+
const brDirPath = this.getBrDirPath(directory);
|
|
32
|
+
const configPath = this.getConfigPath(directory);
|
|
33
|
+
try {
|
|
34
|
+
// Create .br directory if it doesn't exist
|
|
35
|
+
await mkdir(brDirPath, { recursive: true });
|
|
36
|
+
// Write config.json
|
|
37
|
+
const content = JSON.stringify(config.toJson(), undefined, 2);
|
|
38
|
+
await writeFile(configPath, content, 'utf8');
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw new Error(`Failed to write config to ${configPath}: ${error.message}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Gets the full path to the .br directory.
|
|
46
|
+
*/
|
|
47
|
+
getBrDirPath(directory) {
|
|
48
|
+
const baseDir = directory ?? process.cwd();
|
|
49
|
+
return join(baseDir, ProjectConfigStore.BR_DIR);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Gets the full path to the config.json file.
|
|
53
|
+
*/
|
|
54
|
+
getConfigPath(directory) {
|
|
55
|
+
return join(this.getBrDirPath(directory), ProjectConfigStore.CONFIG_FILE);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type IFileService, type WriteMode } from '../../core/interfaces/i-file-service.js';
|
|
2
|
+
/**
|
|
3
|
+
* File service implementation using Node.js fs module.
|
|
4
|
+
*/
|
|
5
|
+
export declare class FsFileService implements IFileService {
|
|
6
|
+
/**
|
|
7
|
+
* Checks if a file exists at the specified path.
|
|
8
|
+
*
|
|
9
|
+
* @param filePath The path to the file to check.
|
|
10
|
+
* @returns A promise that resolves with `true` if the file exists, `false` otherwise.
|
|
11
|
+
*/
|
|
12
|
+
exists(filePath: string): Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* Reads content from the specified file.
|
|
15
|
+
*
|
|
16
|
+
* @param filePath The path to the file to read from.
|
|
17
|
+
* @returns A promise that resolves with the content of the file.
|
|
18
|
+
*/
|
|
19
|
+
read(filePath: string): Promise<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Writes content to the specified file.
|
|
22
|
+
* @param content The content to write.
|
|
23
|
+
* @param filePath The path to the file to write to.
|
|
24
|
+
* @param mode The mode to write the content in ('append' or 'overwrite').
|
|
25
|
+
* @returns A promise that resolves when the content has been written.
|
|
26
|
+
*/
|
|
27
|
+
write(content: string, filePath: string, mode: WriteMode): Promise<void>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { access, appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* File service implementation using Node.js fs module.
|
|
5
|
+
*/
|
|
6
|
+
export class FsFileService {
|
|
7
|
+
/**
|
|
8
|
+
* Checks if a file exists at the specified path.
|
|
9
|
+
*
|
|
10
|
+
* @param filePath The path to the file to check.
|
|
11
|
+
* @returns A promise that resolves with `true` if the file exists, `false` otherwise.
|
|
12
|
+
*/
|
|
13
|
+
async exists(filePath) {
|
|
14
|
+
try {
|
|
15
|
+
await access(filePath);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Reads content from the specified file.
|
|
24
|
+
*
|
|
25
|
+
* @param filePath The path to the file to read from.
|
|
26
|
+
* @returns A promise that resolves with the content of the file.
|
|
27
|
+
*/
|
|
28
|
+
async read(filePath) {
|
|
29
|
+
try {
|
|
30
|
+
return await readFile(filePath, 'utf8');
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new Error(`Failed to read content from file '${filePath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Writes content to the specified file.
|
|
38
|
+
* @param content The content to write.
|
|
39
|
+
* @param filePath The path to the file to write to.
|
|
40
|
+
* @param mode The mode to write the content in ('append' or 'overwrite').
|
|
41
|
+
* @returns A promise that resolves when the content has been written.
|
|
42
|
+
*/
|
|
43
|
+
async write(content, filePath, mode) {
|
|
44
|
+
try {
|
|
45
|
+
// Ensure directory exists
|
|
46
|
+
const directory = dirname(filePath);
|
|
47
|
+
await mkdir(directory, { recursive: true });
|
|
48
|
+
// Write writeOperation
|
|
49
|
+
const writeOperation = mode === 'append' ? appendFile(filePath, content, 'utf8') : writeFile(filePath, content, 'utf8');
|
|
50
|
+
// Execute writeOperation
|
|
51
|
+
await writeOperation;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw new Error(`Failed to ${mode} content to file '${filePath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { HttpRequestConfig, IHttpClient } from '../../core/interfaces/i-http-client.js';
|
|
2
|
+
/**
|
|
3
|
+
* HTTP client implementation that automatically adds authentication headers to all requests.
|
|
4
|
+
*
|
|
5
|
+
* This client wraps axios and automatically includes:
|
|
6
|
+
* - Authorization: Bearer {accessToken}
|
|
7
|
+
* - x-byterover-session-id: {sessionKey}
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const client = new AuthenticatedHttpClient(accessToken, sessionKey)
|
|
12
|
+
* const data = await client.get<ResponseType>('https://api.example.com/endpoint')
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare class AuthenticatedHttpClient implements IHttpClient {
|
|
16
|
+
private readonly accessToken;
|
|
17
|
+
private readonly sessionKey;
|
|
18
|
+
constructor(accessToken: string, sessionKey: string);
|
|
19
|
+
/**
|
|
20
|
+
* Performs an HTTP GET request with authentication headers.
|
|
21
|
+
* @param url The URL to request
|
|
22
|
+
* @param config Optional request configuration (headers, timeout)
|
|
23
|
+
* @returns A promise that resolves to the response data
|
|
24
|
+
* @throws Error if the request fails
|
|
25
|
+
*/
|
|
26
|
+
get<T>(url: string, config?: HttpRequestConfig): Promise<T>;
|
|
27
|
+
/**
|
|
28
|
+
* Performs an HTTP POST request with authentication headers.
|
|
29
|
+
* @param url The URL to request
|
|
30
|
+
* @param data The data to send in the request body
|
|
31
|
+
* @param config Optional request configuration (headers, timeout)
|
|
32
|
+
* @returns A promise that resolves to the response data
|
|
33
|
+
* @throws Error if the request fails
|
|
34
|
+
*/
|
|
35
|
+
post<TResponse, TData = unknown>(url: string, data?: TData, config?: HttpRequestConfig): Promise<TResponse>;
|
|
36
|
+
/**
|
|
37
|
+
* Builds request headers by merging authentication headers with custom headers.
|
|
38
|
+
* Custom headers take precedence over default headers.
|
|
39
|
+
*/
|
|
40
|
+
private buildHeaders;
|
|
41
|
+
/**
|
|
42
|
+
* Transforms axios errors into generic Error instances.
|
|
43
|
+
* Preserves error information while abstracting axios-specific details.
|
|
44
|
+
*/
|
|
45
|
+
private handleError;
|
|
46
|
+
}
|