@braingrid/cli 0.0.2
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 +164 -0
- package/dist/.build-info.json +9 -0
- package/dist/build-config.d.ts +25 -0
- package/dist/build-config.d.ts.map +1 -0
- package/dist/build-config.js +27 -0
- package/dist/build-config.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +246 -0
- package/dist/cli.js.map +1 -0
- package/dist/handlers/auth.handlers.d.ts +13 -0
- package/dist/handlers/auth.handlers.d.ts.map +1 -0
- package/dist/handlers/auth.handlers.js +99 -0
- package/dist/handlers/auth.handlers.js.map +1 -0
- package/dist/handlers/index.d.ts +11 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +11 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/project.handlers.d.ts +29 -0
- package/dist/handlers/project.handlers.d.ts.map +1 -0
- package/dist/handlers/project.handlers.js +200 -0
- package/dist/handlers/project.handlers.js.map +1 -0
- package/dist/handlers/requirement.handlers.d.ts +32 -0
- package/dist/handlers/requirement.handlers.d.ts.map +1 -0
- package/dist/handlers/requirement.handlers.js +234 -0
- package/dist/handlers/requirement.handlers.js.map +1 -0
- package/dist/handlers/status.handlers.d.ts +12 -0
- package/dist/handlers/status.handlers.d.ts.map +1 -0
- package/dist/handlers/status.handlers.js +209 -0
- package/dist/handlers/status.handlers.js.map +1 -0
- package/dist/handlers/task.handlers.d.ts +29 -0
- package/dist/handlers/task.handlers.d.ts.map +1 -0
- package/dist/handlers/task.handlers.js +272 -0
- package/dist/handlers/task.handlers.js.map +1 -0
- package/dist/handlers/types.d.ts +10 -0
- package/dist/handlers/types.d.ts.map +1 -0
- package/dist/handlers/types.js +5 -0
- package/dist/handlers/types.js.map +1 -0
- package/dist/services/auth.d.ts +41 -0
- package/dist/services/auth.d.ts.map +1 -0
- package/dist/services/auth.js +498 -0
- package/dist/services/auth.js.map +1 -0
- package/dist/services/claude.d.ts +18 -0
- package/dist/services/claude.d.ts.map +1 -0
- package/dist/services/claude.js +164 -0
- package/dist/services/claude.js.map +1 -0
- package/dist/services/context-manager.d.ts +170 -0
- package/dist/services/context-manager.d.ts.map +1 -0
- package/dist/services/context-manager.js +261 -0
- package/dist/services/context-manager.js.map +1 -0
- package/dist/services/credential-store.d.ts +47 -0
- package/dist/services/credential-store.d.ts.map +1 -0
- package/dist/services/credential-store.js +88 -0
- package/dist/services/credential-store.js.map +1 -0
- package/dist/services/oauth2-auth.d.ts +60 -0
- package/dist/services/oauth2-auth.d.ts.map +1 -0
- package/dist/services/oauth2-auth.js +377 -0
- package/dist/services/oauth2-auth.js.map +1 -0
- package/dist/services/project-service.d.ts +22 -0
- package/dist/services/project-service.d.ts.map +1 -0
- package/dist/services/project-service.js +52 -0
- package/dist/services/project-service.js.map +1 -0
- package/dist/services/requirement-service.d.ts +32 -0
- package/dist/services/requirement-service.d.ts.map +1 -0
- package/dist/services/requirement-service.js +91 -0
- package/dist/services/requirement-service.js.map +1 -0
- package/dist/services/task-service.d.ts +22 -0
- package/dist/services/task-service.d.ts.map +1 -0
- package/dist/services/task-service.js +52 -0
- package/dist/services/task-service.js.map +1 -0
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/test/setup.js +40 -0
- package/dist/test/setup.js.map +1 -0
- package/dist/types/api.d.ts +16 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +5 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/auth.d.ts +68 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/auth.js +2 -0
- package/dist/types/auth.js.map +1 -0
- package/dist/types/claude.d.ts +22 -0
- package/dist/types/claude.d.ts.map +1 -0
- package/dist/types/claude.js +2 -0
- package/dist/types/claude.js.map +1 -0
- package/dist/types/project.d.ts +32 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/project.js +5 -0
- package/dist/types/project.js.map +1 -0
- package/dist/types/requirement.d.ts +69 -0
- package/dist/types/requirement.d.ts.map +1 -0
- package/dist/types/requirement.js +5 -0
- package/dist/types/requirement.js.map +1 -0
- package/dist/types/task.d.ts +44 -0
- package/dist/types/task.d.ts.map +1 -0
- package/dist/types/task.js +5 -0
- package/dist/types/task.js.map +1 -0
- package/dist/utils/axios-retry.d.ts +25 -0
- package/dist/utils/axios-retry.d.ts.map +1 -0
- package/dist/utils/axios-retry.js +174 -0
- package/dist/utils/axios-retry.js.map +1 -0
- package/dist/utils/axios-with-auth.d.ts +10 -0
- package/dist/utils/axios-with-auth.d.ts.map +1 -0
- package/dist/utils/axios-with-auth.js +118 -0
- package/dist/utils/axios-with-auth.js.map +1 -0
- package/dist/utils/cli-tools.d.ts +30 -0
- package/dist/utils/cli-tools.d.ts.map +1 -0
- package/dist/utils/cli-tools.js +131 -0
- package/dist/utils/cli-tools.js.map +1 -0
- package/dist/utils/command-execution.d.ts +30 -0
- package/dist/utils/command-execution.d.ts.map +1 -0
- package/dist/utils/command-execution.js +264 -0
- package/dist/utils/command-execution.js.map +1 -0
- package/dist/utils/command-parser.d.ts +85 -0
- package/dist/utils/command-parser.d.ts.map +1 -0
- package/dist/utils/command-parser.js +287 -0
- package/dist/utils/command-parser.js.map +1 -0
- package/dist/utils/config.d.ts +9 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +59 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/error-formatter.d.ts +17 -0
- package/dist/utils/error-formatter.d.ts.map +1 -0
- package/dist/utils/error-formatter.js +115 -0
- package/dist/utils/error-formatter.js.map +1 -0
- package/dist/utils/formatting.d.ts +10 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/formatting.js +122 -0
- package/dist/utils/formatting.js.map +1 -0
- package/dist/utils/git.d.ts +55 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +131 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/jwt.d.ts +45 -0
- package/dist/utils/jwt.d.ts.map +1 -0
- package/dist/utils/jwt.js +64 -0
- package/dist/utils/jwt.js.map +1 -0
- package/dist/utils/logger.d.ts +36 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +176 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/requirements.d.ts +28 -0
- package/dist/utils/requirements.d.ts.map +1 -0
- package/dist/utils/requirements.js +54 -0
- package/dist/utils/requirements.js.map +1 -0
- package/dist/utils/status-parser.d.ts +95 -0
- package/dist/utils/status-parser.d.ts.map +1 -0
- package/dist/utils/status-parser.js +189 -0
- package/dist/utils/status-parser.js.map +1 -0
- package/dist/utils/tasks.d.ts +9 -0
- package/dist/utils/tasks.d.ts.map +1 -0
- package/dist/utils/tasks.js +38 -0
- package/dist/utils/tasks.js.map +1 -0
- package/package.json +101 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential Store - Encrypted credential storage using conf
|
|
3
|
+
*
|
|
4
|
+
* Provides a keytar-compatible API for storing credentials securely
|
|
5
|
+
* using file-based encrypted storage.
|
|
6
|
+
*
|
|
7
|
+
* Storage locations:
|
|
8
|
+
* - macOS: ~/Library/Preferences/BrainGrid/config.json
|
|
9
|
+
* - Linux: ~/.config/BrainGrid/config.json
|
|
10
|
+
* - Windows: %APPDATA%\BrainGrid\config.json
|
|
11
|
+
*
|
|
12
|
+
* Security:
|
|
13
|
+
* - Encrypted using AES-256-CBC
|
|
14
|
+
* - Encryption key derived from machine ID + app salt
|
|
15
|
+
* - File permissions automatically set to 0600 on Unix systems
|
|
16
|
+
*/
|
|
17
|
+
import Conf from 'conf';
|
|
18
|
+
import machineId from 'node-machine-id';
|
|
19
|
+
import crypto from 'crypto';
|
|
20
|
+
// App-specific salt for key derivation
|
|
21
|
+
const APP_SALT = 'braingrid-cli-v1-credentials';
|
|
22
|
+
// Derive encryption key from machine ID + salt
|
|
23
|
+
// This makes the encryption key machine-specific
|
|
24
|
+
function deriveEncryptionKey() {
|
|
25
|
+
try {
|
|
26
|
+
const id = machineId.machineIdSync();
|
|
27
|
+
return crypto
|
|
28
|
+
.createHash('sha256')
|
|
29
|
+
.update(id + APP_SALT)
|
|
30
|
+
.digest('hex')
|
|
31
|
+
.substring(0, 32); // 32 bytes for AES-256
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Fallback if machine ID fails (unlikely)
|
|
35
|
+
// This is still better than plain text
|
|
36
|
+
console.warn('⚠️ Could not derive machine-specific encryption key, using fallback');
|
|
37
|
+
return crypto.createHash('sha256').update(APP_SALT).digest('hex').substring(0, 32);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Initialize encrypted configuration store
|
|
41
|
+
const store = new Conf({
|
|
42
|
+
projectName: 'BrainGrid',
|
|
43
|
+
encryptionKey: deriveEncryptionKey(),
|
|
44
|
+
clearInvalidConfig: true, // Clear config if decryption fails
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* Credential store with keytar-compatible API
|
|
48
|
+
*/
|
|
49
|
+
export const credentialStore = {
|
|
50
|
+
/**
|
|
51
|
+
* Get a stored password/credential
|
|
52
|
+
* @param service Service name (e.g., 'braingrid-cli')
|
|
53
|
+
* @param account Account name (e.g., 'session', 'refresh-token')
|
|
54
|
+
* @returns The stored credential or null if not found
|
|
55
|
+
*/
|
|
56
|
+
async getPassword(service, account) {
|
|
57
|
+
const key = `${service}.${account}`;
|
|
58
|
+
const value = store.get(key);
|
|
59
|
+
return value !== undefined ? String(value) : null;
|
|
60
|
+
},
|
|
61
|
+
/**
|
|
62
|
+
* Store a password/credential
|
|
63
|
+
* @param service Service name
|
|
64
|
+
* @param account Account name
|
|
65
|
+
* @param password The credential to store
|
|
66
|
+
*/
|
|
67
|
+
async setPassword(service, account, password) {
|
|
68
|
+
const key = `${service}.${account}`;
|
|
69
|
+
store.set(key, password);
|
|
70
|
+
},
|
|
71
|
+
/**
|
|
72
|
+
* Delete a stored password/credential
|
|
73
|
+
* @param service Service name
|
|
74
|
+
* @param account Account name
|
|
75
|
+
*/
|
|
76
|
+
async deletePassword(service, account) {
|
|
77
|
+
const key = `${service}.${account}`;
|
|
78
|
+
store.delete(key);
|
|
79
|
+
},
|
|
80
|
+
/**
|
|
81
|
+
* Get the path to the encrypted config file
|
|
82
|
+
* Useful for informing users where credentials are stored
|
|
83
|
+
*/
|
|
84
|
+
getStoragePath() {
|
|
85
|
+
return store.path;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=credential-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-store.js","sourceRoot":"","sources":["../../src/services/credential-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,SAAS,MAAM,iBAAiB,CAAC;AACxC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,uCAAuC;AACvC,MAAM,QAAQ,GAAG,8BAA8B,CAAC;AAEhD,+CAA+C;AAC/C,iDAAiD;AACjD,SAAS,mBAAmB;IAC3B,IAAI,CAAC;QACJ,MAAM,EAAE,GAAG,SAAS,CAAC,aAAa,EAAE,CAAC;QACrC,OAAO,MAAM;aACX,UAAU,CAAC,QAAQ,CAAC;aACpB,MAAM,CAAC,EAAE,GAAG,QAAQ,CAAC;aACrB,MAAM,CAAC,KAAK,CAAC;aACb,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,uBAAuB;IAC5C,CAAC;IAAC,MAAM,CAAC;QACR,0CAA0C;QAC1C,uCAAuC;QACvC,OAAO,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;QACrF,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACpF,CAAC;AACF,CAAC;AAED,2CAA2C;AAC3C,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC;IACtB,WAAW,EAAE,WAAW;IACxB,aAAa,EAAE,mBAAmB,EAAE;IACpC,kBAAkB,EAAE,IAAI,EAAE,mCAAmC;CAC7D,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC9B;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CAAC,OAAe,EAAE,OAAe;QACjD,MAAM,GAAG,GAAG,GAAG,OAAO,IAAI,OAAO,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnD,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CAAC,OAAe,EAAE,OAAe,EAAE,QAAgB;QACnE,MAAM,GAAG,GAAG,GAAG,OAAO,IAAI,OAAO,EAAE,CAAC;QACpC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,cAAc,CAAC,OAAe,EAAE,OAAe;QACpD,MAAM,GAAG,GAAG,GAAG,OAAO,IAAI,OAAO,EAAE,CAAC;QACpC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IAED;;;OAGG;IACH,cAAc;QACb,OAAO,KAAK,CAAC,IAAI,CAAC;IACnB,CAAC;CACD,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface OAuth2AuthResult {
|
|
2
|
+
accessToken: string;
|
|
3
|
+
refreshToken?: string;
|
|
4
|
+
idToken?: string;
|
|
5
|
+
expiresIn?: number;
|
|
6
|
+
tokenType?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class OAuth2Handler {
|
|
9
|
+
private abortController?;
|
|
10
|
+
private onAuthCodeReceived?;
|
|
11
|
+
private server?;
|
|
12
|
+
private authorizationCode?;
|
|
13
|
+
private authError?;
|
|
14
|
+
private state?;
|
|
15
|
+
/**
|
|
16
|
+
* Set callback for when auth URL is ready
|
|
17
|
+
*/
|
|
18
|
+
setAuthUrlCallback(callback: (authUrl: string) => void): void;
|
|
19
|
+
/**
|
|
20
|
+
* Generate PKCE challenge for OAuth2 flow
|
|
21
|
+
*/
|
|
22
|
+
private generatePKCEChallenge;
|
|
23
|
+
/**
|
|
24
|
+
* Generate a cryptographically secure state parameter
|
|
25
|
+
*/
|
|
26
|
+
private generateState;
|
|
27
|
+
/**
|
|
28
|
+
* Get the fixed port for the callback server
|
|
29
|
+
*/
|
|
30
|
+
private getCallbackPort;
|
|
31
|
+
/**
|
|
32
|
+
* Start local HTTP server to handle OAuth callback
|
|
33
|
+
*/
|
|
34
|
+
private startCallbackServer;
|
|
35
|
+
/**
|
|
36
|
+
* Build the authorization URL with PKCE parameters
|
|
37
|
+
*/
|
|
38
|
+
private buildAuthorizationUrl;
|
|
39
|
+
/**
|
|
40
|
+
* Exchange authorization code for tokens
|
|
41
|
+
*/
|
|
42
|
+
private exchangeCodeForTokens;
|
|
43
|
+
/**
|
|
44
|
+
* Wait for the authorization code from the callback
|
|
45
|
+
*/
|
|
46
|
+
private waitForAuthorizationCode;
|
|
47
|
+
/**
|
|
48
|
+
* Initiates the OAuth2 authorization code flow with PKCE
|
|
49
|
+
*/
|
|
50
|
+
authenticate(): Promise<OAuth2AuthResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Refresh an access token using a refresh token
|
|
53
|
+
*/
|
|
54
|
+
refreshToken(refreshToken: string): Promise<OAuth2AuthResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Abort any ongoing authentication
|
|
57
|
+
*/
|
|
58
|
+
abort(): void;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=oauth2-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth2-auth.d.ts","sourceRoot":"","sources":["../../src/services/oauth2-auth.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,gBAAgB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAQD,qBAAa,aAAa;IACzB,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,OAAO,CAAC,kBAAkB,CAAC,CAA4B;IACvD,OAAO,CAAC,MAAM,CAAC,CAAkC;IACjD,OAAO,CAAC,iBAAiB,CAAC,CAAS;IACnC,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,KAAK,CAAC,CAAS;IAEvB;;OAEG;IACH,kBAAkB,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAI7D;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAc7B;;OAEG;IACH,OAAO,CAAC,aAAa;IAIrB;;OAEG;IACH,OAAO,CAAC,eAAe;IAKvB;;OAEG;YACW,mBAAmB;IAyHjC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAsB7B;;OAEG;YACW,qBAAqB;IA+CnC;;OAEG;YACW,wBAAwB;IAqCtC;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAwE/C;;OAEG;IACG,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA4CnE;;OAEG;IACH,KAAK,IAAI,IAAI;CAWb"}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'crypto';
|
|
2
|
+
import { createServer } from 'http';
|
|
3
|
+
import open from 'open';
|
|
4
|
+
import axios, { AxiosError } from 'axios';
|
|
5
|
+
import { getConfig } from '../utils/config.js';
|
|
6
|
+
import { getLogger } from '../utils/logger.js';
|
|
7
|
+
const logger = getLogger();
|
|
8
|
+
export class OAuth2Handler {
|
|
9
|
+
/**
|
|
10
|
+
* Set callback for when auth URL is ready
|
|
11
|
+
*/
|
|
12
|
+
setAuthUrlCallback(callback) {
|
|
13
|
+
this.onAuthCodeReceived = callback;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Generate PKCE challenge for OAuth2 flow
|
|
17
|
+
*/
|
|
18
|
+
generatePKCEChallenge() {
|
|
19
|
+
// Generate a cryptographically secure random verifier (43-128 chars)
|
|
20
|
+
const verifier = randomBytes(32).toString('base64url');
|
|
21
|
+
// Create challenge from verifier using SHA256
|
|
22
|
+
const challenge = createHash('sha256').update(verifier).digest('base64url');
|
|
23
|
+
return {
|
|
24
|
+
verifier,
|
|
25
|
+
challenge,
|
|
26
|
+
method: 'S256',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Generate a cryptographically secure state parameter
|
|
31
|
+
*/
|
|
32
|
+
generateState() {
|
|
33
|
+
return randomBytes(32).toString('base64url');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get the fixed port for the callback server
|
|
37
|
+
*/
|
|
38
|
+
getCallbackPort() {
|
|
39
|
+
// Use fixed port 34536 for OAuth callback
|
|
40
|
+
return 34536;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Start local HTTP server to handle OAuth callback
|
|
44
|
+
*/
|
|
45
|
+
async startCallbackServer(port) {
|
|
46
|
+
return new Promise(resolve => {
|
|
47
|
+
this.server = createServer((req, res) => {
|
|
48
|
+
const url = new URL(req.url || '', `http://127.0.0.1:${port}`);
|
|
49
|
+
// Handle OAuth callback
|
|
50
|
+
if (url.pathname === '/callback') {
|
|
51
|
+
const code = url.searchParams.get('code');
|
|
52
|
+
const error = url.searchParams.get('error');
|
|
53
|
+
const state = url.searchParams.get('state');
|
|
54
|
+
// Validate state parameter
|
|
55
|
+
if (state !== this.state) {
|
|
56
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
57
|
+
res.end(`
|
|
58
|
+
<html>
|
|
59
|
+
<body>
|
|
60
|
+
<h1>Authentication Failed</h1>
|
|
61
|
+
<p>Invalid state parameter. Please try again.</p>
|
|
62
|
+
<script>window.setTimeout(() => window.close(), 3000);</script>
|
|
63
|
+
</body>
|
|
64
|
+
</html>
|
|
65
|
+
`);
|
|
66
|
+
this.authError = 'Invalid state parameter';
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (error) {
|
|
70
|
+
// Handle error response
|
|
71
|
+
const errorDescription = url.searchParams.get('error_description') || 'Unknown error';
|
|
72
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
73
|
+
res.end(`
|
|
74
|
+
<html>
|
|
75
|
+
<body>
|
|
76
|
+
<h1>Authentication Failed</h1>
|
|
77
|
+
<p>${errorDescription}</p>
|
|
78
|
+
<script>window.setTimeout(() => window.close(), 3000);</script>
|
|
79
|
+
</body>
|
|
80
|
+
</html>
|
|
81
|
+
`);
|
|
82
|
+
this.authError = errorDescription;
|
|
83
|
+
}
|
|
84
|
+
else if (code) {
|
|
85
|
+
// Success! Store the code
|
|
86
|
+
this.authorizationCode = code;
|
|
87
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
88
|
+
res.end(`
|
|
89
|
+
<html>
|
|
90
|
+
<head>
|
|
91
|
+
<style>
|
|
92
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
|
|
93
|
+
.container { background: white; padding: 2rem; border-radius: 10px; box-shadow: 0 10px 40px rgba(0,0,0,0.1); text-align: center; }
|
|
94
|
+
h1 { color: #2d3748; margin-bottom: 1rem; }
|
|
95
|
+
p { color: #718096; }
|
|
96
|
+
.success-icon { font-size: 3rem; margin-bottom: 1rem; }
|
|
97
|
+
</style>
|
|
98
|
+
</head>
|
|
99
|
+
<body>
|
|
100
|
+
<div class="container">
|
|
101
|
+
<div class="success-icon">✅</div>
|
|
102
|
+
<h1>Authentication Successful!</h1>
|
|
103
|
+
<p>You can now close this window and return to the CLI.</p>
|
|
104
|
+
<script>window.setTimeout(() => window.close(), 2000);</script>
|
|
105
|
+
</div>
|
|
106
|
+
</body>
|
|
107
|
+
</html>
|
|
108
|
+
`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// No code or error
|
|
112
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
113
|
+
res.end(`
|
|
114
|
+
<html>
|
|
115
|
+
<body>
|
|
116
|
+
<h1>Authentication Failed</h1>
|
|
117
|
+
<p>No authorization code received.</p>
|
|
118
|
+
<script>window.setTimeout(() => window.close(), 3000);</script>
|
|
119
|
+
</body>
|
|
120
|
+
</html>
|
|
121
|
+
`);
|
|
122
|
+
this.authError = 'No authorization code received';
|
|
123
|
+
}
|
|
124
|
+
// Close the server after handling the callback
|
|
125
|
+
setTimeout(() => {
|
|
126
|
+
if (this.server) {
|
|
127
|
+
this.server.close();
|
|
128
|
+
this.server = undefined;
|
|
129
|
+
}
|
|
130
|
+
}, 500);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Handle other routes (redirect to /callback or show waiting page)
|
|
134
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
135
|
+
res.end(`
|
|
136
|
+
<html>
|
|
137
|
+
<head>
|
|
138
|
+
<style>
|
|
139
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
|
|
140
|
+
.container { background: white; padding: 2rem; border-radius: 10px; box-shadow: 0 10px 40px rgba(0,0,0,0.1); text-align: center; }
|
|
141
|
+
h1 { color: #2d3748; }
|
|
142
|
+
.spinner { border: 3px solid #f3f4f6; border-top: 3px solid #667eea; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 1rem auto; }
|
|
143
|
+
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
|
144
|
+
</style>
|
|
145
|
+
</head>
|
|
146
|
+
<body>
|
|
147
|
+
<div class="container">
|
|
148
|
+
<h1>Waiting for authentication...</h1>
|
|
149
|
+
<div class="spinner"></div>
|
|
150
|
+
<p>Please complete the authentication in your browser.</p>
|
|
151
|
+
</div>
|
|
152
|
+
</body>
|
|
153
|
+
</html>
|
|
154
|
+
`);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
this.server.listen(port, '127.0.0.1', () => {
|
|
158
|
+
logger.debug(`Callback server listening on http://127.0.0.1:${port}`);
|
|
159
|
+
resolve();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Build the authorization URL with PKCE parameters
|
|
165
|
+
*/
|
|
166
|
+
buildAuthorizationUrl(pkce, redirectUri) {
|
|
167
|
+
const config = getConfig();
|
|
168
|
+
// Use WorkOS AuthKit directly like the MCP server does
|
|
169
|
+
const authUrl = config.getWorkOSAuthUrl();
|
|
170
|
+
// Generate state for CSRF protection
|
|
171
|
+
this.state = this.generateState();
|
|
172
|
+
const params = new URLSearchParams({
|
|
173
|
+
client_id: config.oauthClientId || 'braingrid-cli',
|
|
174
|
+
redirect_uri: redirectUri,
|
|
175
|
+
response_type: 'code',
|
|
176
|
+
scope: 'openid email profile offline_access',
|
|
177
|
+
state: this.state,
|
|
178
|
+
code_challenge: pkce.challenge,
|
|
179
|
+
code_challenge_method: pkce.method,
|
|
180
|
+
});
|
|
181
|
+
return `${authUrl}/oauth2/authorize?${params.toString()}`;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Exchange authorization code for tokens
|
|
185
|
+
*/
|
|
186
|
+
async exchangeCodeForTokens(code, verifier, redirectUri) {
|
|
187
|
+
const config = getConfig();
|
|
188
|
+
// Use WorkOS AuthKit token endpoint
|
|
189
|
+
const tokenUrl = `${config.getWorkOSAuthUrl()}/oauth2/token`;
|
|
190
|
+
const params = new URLSearchParams({
|
|
191
|
+
grant_type: 'authorization_code',
|
|
192
|
+
client_id: config.oauthClientId || 'braingrid-cli',
|
|
193
|
+
code: code,
|
|
194
|
+
redirect_uri: redirectUri,
|
|
195
|
+
code_verifier: verifier,
|
|
196
|
+
});
|
|
197
|
+
logger.debug('Exchanging authorization code for tokens', { tokenUrl });
|
|
198
|
+
try {
|
|
199
|
+
const response = await axios.post(tokenUrl, params.toString(), {
|
|
200
|
+
headers: {
|
|
201
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
logger.debug('Token exchange successful', {
|
|
205
|
+
hasAccessToken: !!response.data.access_token,
|
|
206
|
+
hasRefreshToken: !!response.data.refresh_token,
|
|
207
|
+
});
|
|
208
|
+
return response.data;
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
if (error instanceof AxiosError && error.response) {
|
|
212
|
+
const errorData = error.response.data;
|
|
213
|
+
logger.error('Token exchange failed', {
|
|
214
|
+
status: error.response.status,
|
|
215
|
+
error: errorData,
|
|
216
|
+
});
|
|
217
|
+
throw new Error(`Token exchange failed: ${errorData.error_description || errorData.error}`);
|
|
218
|
+
}
|
|
219
|
+
throw new Error(`Token exchange failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Wait for the authorization code from the callback
|
|
224
|
+
*/
|
|
225
|
+
async waitForAuthorizationCode(timeoutMs = 300000) {
|
|
226
|
+
// 5 minutes timeout
|
|
227
|
+
return new Promise((resolve, reject) => {
|
|
228
|
+
const startTime = Date.now();
|
|
229
|
+
const checkInterval = setInterval(() => {
|
|
230
|
+
// Check if we should abort
|
|
231
|
+
if (this.abortController?.signal.aborted) {
|
|
232
|
+
clearInterval(checkInterval);
|
|
233
|
+
reject(new Error('Authentication cancelled'));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// Check for error
|
|
237
|
+
if (this.authError) {
|
|
238
|
+
clearInterval(checkInterval);
|
|
239
|
+
reject(new Error(this.authError));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Check for success
|
|
243
|
+
if (this.authorizationCode) {
|
|
244
|
+
clearInterval(checkInterval);
|
|
245
|
+
resolve(this.authorizationCode);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
// Check for timeout
|
|
249
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
250
|
+
clearInterval(checkInterval);
|
|
251
|
+
reject(new Error('Authentication timeout - no response received'));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}, 500); // Check every 500ms
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Initiates the OAuth2 authorization code flow with PKCE
|
|
259
|
+
*/
|
|
260
|
+
async authenticate() {
|
|
261
|
+
logger.info('Starting OAuth2 authentication flow with PKCE');
|
|
262
|
+
// Create new AbortController for this authentication session
|
|
263
|
+
this.abortController = new AbortController();
|
|
264
|
+
// Reset state
|
|
265
|
+
this.authorizationCode = undefined;
|
|
266
|
+
this.authError = undefined;
|
|
267
|
+
this.state = undefined;
|
|
268
|
+
try {
|
|
269
|
+
// Step 1: Generate PKCE challenge
|
|
270
|
+
const pkce = this.generatePKCEChallenge();
|
|
271
|
+
logger.debug('PKCE challenge generated');
|
|
272
|
+
// Step 2: Use fixed port and start callback server
|
|
273
|
+
const port = this.getCallbackPort();
|
|
274
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
275
|
+
logger.info('Starting callback server', { port, redirectUri });
|
|
276
|
+
await this.startCallbackServer(port);
|
|
277
|
+
// Step 3: Build authorization URL
|
|
278
|
+
const authUrl = this.buildAuthorizationUrl(pkce, redirectUri);
|
|
279
|
+
logger.info('Authorization URL ready', { authUrl });
|
|
280
|
+
// Notify callback if set
|
|
281
|
+
if (this.onAuthCodeReceived) {
|
|
282
|
+
this.onAuthCodeReceived(authUrl);
|
|
283
|
+
}
|
|
284
|
+
// Step 4: Open browser
|
|
285
|
+
try {
|
|
286
|
+
logger.debug('Opening browser for authentication');
|
|
287
|
+
await open(authUrl);
|
|
288
|
+
logger.info('Browser opened automatically');
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
logger.warn('Failed to open browser automatically', { error });
|
|
292
|
+
logger.info(`Please open the following URL manually:\n${authUrl}`);
|
|
293
|
+
}
|
|
294
|
+
// Step 5: Wait for authorization code
|
|
295
|
+
logger.info('Waiting for user to complete authentication...');
|
|
296
|
+
const code = await this.waitForAuthorizationCode();
|
|
297
|
+
logger.info('Authorization code received');
|
|
298
|
+
// Step 6: Exchange code for tokens
|
|
299
|
+
const tokens = await this.exchangeCodeForTokens(code, pkce.verifier, redirectUri);
|
|
300
|
+
logger.info('Authentication successful');
|
|
301
|
+
// Step 7: Return token info
|
|
302
|
+
return {
|
|
303
|
+
accessToken: tokens.access_token,
|
|
304
|
+
refreshToken: tokens.refresh_token,
|
|
305
|
+
idToken: tokens.id_token,
|
|
306
|
+
expiresIn: tokens.expires_in,
|
|
307
|
+
tokenType: tokens.token_type,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
// Abort any ongoing operations
|
|
312
|
+
this.abort();
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
// Clean up server if still running
|
|
317
|
+
if (this.server) {
|
|
318
|
+
this.server.close();
|
|
319
|
+
this.server = undefined;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Refresh an access token using a refresh token
|
|
325
|
+
*/
|
|
326
|
+
async refreshToken(refreshToken) {
|
|
327
|
+
const config = getConfig();
|
|
328
|
+
// Use WorkOS AuthKit token endpoint for refresh
|
|
329
|
+
const tokenUrl = `${config.getWorkOSAuthUrl()}/oauth2/token`;
|
|
330
|
+
const params = new URLSearchParams({
|
|
331
|
+
grant_type: 'refresh_token',
|
|
332
|
+
client_id: config.oauthClientId || 'braingrid-cli',
|
|
333
|
+
refresh_token: refreshToken,
|
|
334
|
+
});
|
|
335
|
+
logger.debug('Refreshing access token');
|
|
336
|
+
try {
|
|
337
|
+
const response = await axios.post(tokenUrl, params.toString(), {
|
|
338
|
+
headers: {
|
|
339
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
logger.debug('Token refresh successful');
|
|
343
|
+
return {
|
|
344
|
+
accessToken: response.data.access_token,
|
|
345
|
+
refreshToken: response.data.refresh_token,
|
|
346
|
+
idToken: response.data.id_token,
|
|
347
|
+
expiresIn: response.data.expires_in,
|
|
348
|
+
tokenType: response.data.token_type,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
if (error instanceof AxiosError && error.response) {
|
|
353
|
+
const errorData = error.response.data;
|
|
354
|
+
logger.error('Token refresh failed', {
|
|
355
|
+
status: error.response.status,
|
|
356
|
+
error: errorData,
|
|
357
|
+
});
|
|
358
|
+
throw new Error(`Token refresh failed: ${errorData.error_description || errorData.error}`);
|
|
359
|
+
}
|
|
360
|
+
throw new Error(`Token refresh failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Abort any ongoing authentication
|
|
365
|
+
*/
|
|
366
|
+
abort() {
|
|
367
|
+
if (this.abortController) {
|
|
368
|
+
this.abortController.abort();
|
|
369
|
+
this.abortController = undefined;
|
|
370
|
+
}
|
|
371
|
+
if (this.server) {
|
|
372
|
+
this.server.close();
|
|
373
|
+
this.server = undefined;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
//# sourceMappingURL=oauth2-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth2-auth.js","sourceRoot":"","sources":["../../src/services/oauth2-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAE,YAAY,EAAmC,MAAM,MAAM,CAAC;AACrE,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAG/C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;AAgB3B,MAAM,OAAO,aAAa;IAQzB;;OAEG;IACH,kBAAkB,CAAC,QAAmC;QACrD,IAAI,CAAC,kBAAkB,GAAG,QAAQ,CAAC;IACpC,CAAC;IAED;;OAEG;IACK,qBAAqB;QAC5B,qEAAqE;QACrE,MAAM,QAAQ,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAEvD,8CAA8C;QAC9C,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAE5E,OAAO;YACN,QAAQ;YACR,SAAS;YACT,MAAM,EAAE,MAAM;SACd,CAAC;IACH,CAAC;IAED;;OAEG;IACK,aAAa;QACpB,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC9C,CAAC;IAED;;OAEG;IACK,eAAe;QACtB,0CAA0C;QAC1C,OAAO,KAAK,CAAC;IACd,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,mBAAmB,CAAC,IAAY;QAC7C,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;YAC5B,IAAI,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC,GAAoB,EAAE,GAAmB,EAAE,EAAE;gBACxE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,oBAAoB,IAAI,EAAE,CAAC,CAAC;gBAE/D,wBAAwB;gBACxB,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;oBAClC,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;oBAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBAC5C,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBAE5C,2BAA2B;oBAC3B,IAAI,KAAK,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;wBAC1B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;wBACnE,GAAG,CAAC,GAAG,CAAC;;;;;;;;OAQP,CAAC,CAAC;wBACH,IAAI,CAAC,SAAS,GAAG,yBAAyB,CAAC;wBAC3C,OAAO;oBACR,CAAC;oBAED,IAAI,KAAK,EAAE,CAAC;wBACX,wBAAwB;wBACxB,MAAM,gBAAgB,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,eAAe,CAAC;wBACtF,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;wBACnE,GAAG,CAAC,GAAG,CAAC;;;;cAIA,gBAAgB;;;;OAIvB,CAAC,CAAC;wBACH,IAAI,CAAC,SAAS,GAAG,gBAAgB,CAAC;oBACnC,CAAC;yBAAM,IAAI,IAAI,EAAE,CAAC;wBACjB,0BAA0B;wBAC1B,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;wBAC9B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;wBACnE,GAAG,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;OAoBP,CAAC,CAAC;oBACJ,CAAC;yBAAM,CAAC;wBACP,mBAAmB;wBACnB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;wBACnE,GAAG,CAAC,GAAG,CAAC;;;;;;;;OAQP,CAAC,CAAC;wBACH,IAAI,CAAC,SAAS,GAAG,gCAAgC,CAAC;oBACnD,CAAC;oBAED,+CAA+C;oBAC/C,UAAU,CAAC,GAAG,EAAE;wBACf,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;4BACjB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;4BACpB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;wBACzB,CAAC;oBACF,CAAC,EAAE,GAAG,CAAC,CAAC;gBACT,CAAC;qBAAM,CAAC;oBACP,mEAAmE;oBACnE,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;oBACnE,GAAG,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;MAmBP,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE;gBAC1C,MAAM,CAAC,KAAK,CAAC,iDAAiD,IAAI,EAAE,CAAC,CAAC;gBACtE,OAAO,EAAE,CAAC;YACX,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,qBAAqB,CAAC,IAAmB,EAAE,WAAmB;QACrE,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAE3B,uDAAuD;QACvD,MAAM,OAAO,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;QAE1C,qCAAqC;QACrC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QAElC,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YAClC,SAAS,EAAE,MAAM,CAAC,aAAa,IAAI,eAAe;YAClD,YAAY,EAAE,WAAW;YACzB,aAAa,EAAE,MAAM;YACrB,KAAK,EAAE,qCAAqC;YAC5C,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,cAAc,EAAE,IAAI,CAAC,SAAS;YAC9B,qBAAqB,EAAE,IAAI,CAAC,MAAM;SAClC,CAAC,CAAC;QAEH,OAAO,GAAG,OAAO,qBAAqB,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;IAC3D,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,qBAAqB,CAClC,IAAY,EACZ,QAAgB,EAChB,WAAmB;QAEnB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,oCAAoC;QACpC,MAAM,QAAQ,GAAG,GAAG,MAAM,CAAC,gBAAgB,EAAE,eAAe,CAAC;QAE7D,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YAClC,UAAU,EAAE,oBAAoB;YAChC,SAAS,EAAE,MAAM,CAAC,aAAa,IAAI,eAAe;YAClD,IAAI,EAAE,IAAI;YACV,YAAY,EAAE,WAAW;YACzB,aAAa,EAAE,QAAQ;SACvB,CAAC,CAAC;QAEH,MAAM,CAAC,KAAK,CAAC,0CAA0C,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;QAEvE,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAsB,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE;gBACnF,OAAO,EAAE;oBACR,cAAc,EAAE,mCAAmC;iBACnD;aACD,CAAC,CAAC;YAEH,MAAM,CAAC,KAAK,CAAC,2BAA2B,EAAE;gBACzC,cAAc,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY;gBAC5C,eAAe,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa;aAC9C,CAAC,CAAC;YAEH,OAAO,QAAQ,CAAC,IAAI,CAAC;QACtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,KAAK,YAAY,UAAU,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnD,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,IAA2B,CAAC;gBAC7D,MAAM,CAAC,KAAK,CAAC,uBAAuB,EAAE;oBACrC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,MAAM;oBAC7B,KAAK,EAAE,SAAS;iBAChB,CAAC,CAAC;gBACH,MAAM,IAAI,KAAK,CAAC,0BAA0B,SAAS,CAAC,iBAAiB,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;YAC7F,CAAC;YACD,MAAM,IAAI,KAAK,CACd,0BAA0B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAClF,CAAC;QACH,CAAC;IACF,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,wBAAwB,CAAC,YAAoB,MAAM;QAChE,oBAAoB;QACpB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACtC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAE7B,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;gBACtC,2BAA2B;gBAC3B,IAAI,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;oBAC1C,aAAa,CAAC,aAAa,CAAC,CAAC;oBAC7B,MAAM,CAAC,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC,CAAC;oBAC9C,OAAO;gBACR,CAAC;gBAED,kBAAkB;gBAClB,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;oBACpB,aAAa,CAAC,aAAa,CAAC,CAAC;oBAC7B,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;oBAClC,OAAO;gBACR,CAAC;gBAED,oBAAoB;gBACpB,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;oBAC5B,aAAa,CAAC,aAAa,CAAC,CAAC;oBAC7B,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;oBAChC,OAAO;gBACR,CAAC;gBAED,oBAAoB;gBACpB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,SAAS,EAAE,CAAC;oBACxC,aAAa,CAAC,aAAa,CAAC,CAAC;oBAC7B,MAAM,CAAC,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC,CAAC;oBACnE,OAAO;gBACR,CAAC;YACF,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,oBAAoB;QAC9B,CAAC,CAAC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY;QACjB,MAAM,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;QAE7D,6DAA6D;QAC7D,IAAI,CAAC,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;QAE7C,cAAc;QACd,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;QACnC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QAEvB,IAAI,CAAC;YACJ,kCAAkC;YAClC,MAAM,IAAI,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC1C,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;YAEzC,mDAAmD;YACnD,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;YACpC,MAAM,WAAW,GAAG,oBAAoB,IAAI,WAAW,CAAC;YACxD,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;YAE/D,MAAM,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;YAErC,kCAAkC;YAClC,MAAM,OAAO,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;YAC9D,MAAM,CAAC,IAAI,CAAC,yBAAyB,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;YAEpD,yBAAyB;YACzB,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC7B,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;YAClC,CAAC;YAED,uBAAuB;YACvB,IAAI,CAAC;gBACJ,MAAM,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAC;gBACnD,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC;gBACpB,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;YAC7C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,MAAM,CAAC,IAAI,CAAC,sCAAsC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC/D,MAAM,CAAC,IAAI,CAAC,4CAA4C,OAAO,EAAE,CAAC,CAAC;YACpE,CAAC;YAED,sCAAsC;YACtC,MAAM,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;YAC9D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,wBAAwB,EAAE,CAAC;YACnD,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;YAE3C,mCAAmC;YACnC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;YAClF,MAAM,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;YAEzC,4BAA4B;YAC5B,OAAO;gBACN,WAAW,EAAE,MAAM,CAAC,YAAY;gBAChC,YAAY,EAAE,MAAM,CAAC,aAAa;gBAClC,OAAO,EAAE,MAAM,CAAC,QAAQ;gBACxB,SAAS,EAAE,MAAM,CAAC,UAAU;gBAC5B,SAAS,EAAE,MAAM,CAAC,UAAU;aAC5B,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,+BAA+B;YAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,MAAM,KAAK,CAAC;QACb,CAAC;gBAAS,CAAC;YACV,mCAAmC;YACnC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBACjB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;gBACpB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;YACzB,CAAC;QACF,CAAC;IACF,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY,CAAC,YAAoB;QACtC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,gDAAgD;QAChD,MAAM,QAAQ,GAAG,GAAG,MAAM,CAAC,gBAAgB,EAAE,eAAe,CAAC;QAE7D,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YAClC,UAAU,EAAE,eAAe;YAC3B,SAAS,EAAE,MAAM,CAAC,aAAa,IAAI,eAAe;YAClD,aAAa,EAAE,YAAY;SAC3B,CAAC,CAAC;QAEH,MAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAExC,IAAI,CAAC;YACJ,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAsB,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE;gBACnF,OAAO,EAAE;oBACR,cAAc,EAAE,mCAAmC;iBACnD;aACD,CAAC,CAAC;YAEH,MAAM,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;YAEzC,OAAO;gBACN,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,YAAY;gBACvC,YAAY,EAAE,QAAQ,CAAC,IAAI,CAAC,aAAa;gBACzC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,QAAQ;gBAC/B,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU;gBACnC,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU;aACnC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,KAAK,YAAY,UAAU,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnD,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,IAA2B,CAAC;gBAC7D,MAAM,CAAC,KAAK,CAAC,sBAAsB,EAAE;oBACpC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,MAAM;oBAC7B,KAAK,EAAE,SAAS;iBAChB,CAAC,CAAC;gBACH,MAAM,IAAI,KAAK,CAAC,yBAAyB,SAAS,CAAC,iBAAiB,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;YAC5F,CAAC;YACD,MAAM,IAAI,KAAK,CACd,yBAAyB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACjF,CAAC;QACH,CAAC;IACF,CAAC;IAED;;OAEG;IACH,KAAK;QACJ,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;YAC7B,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC;QAClC,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACpB,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACzB,CAAC;IACF,CAAC;CACD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Service
|
|
3
|
+
* Handles all API interactions for projects
|
|
4
|
+
*/
|
|
5
|
+
import { BraingridAuth } from './auth.js';
|
|
6
|
+
import { Project, CreateProjectRequest, UpdateProjectRequest, ListProjectsResponse } from '../types/project.js';
|
|
7
|
+
export declare class ProjectService {
|
|
8
|
+
private baseUrl;
|
|
9
|
+
private auth;
|
|
10
|
+
private axios;
|
|
11
|
+
constructor(baseUrl: string, auth: BraingridAuth);
|
|
12
|
+
private getHeaders;
|
|
13
|
+
listProjects(params: {
|
|
14
|
+
page?: number;
|
|
15
|
+
limit?: number;
|
|
16
|
+
}): Promise<ListProjectsResponse>;
|
|
17
|
+
getProject(projectId: string): Promise<Project>;
|
|
18
|
+
createProject(data: CreateProjectRequest): Promise<Project>;
|
|
19
|
+
updateProject(projectId: string, data: UpdateProjectRequest): Promise<Project>;
|
|
20
|
+
deleteProject(projectId: string): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=project-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-service.d.ts","sourceRoot":"","sources":["../../src/services/project-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EACN,OAAO,EACP,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,MAAM,qBAAqB,CAAC;AAE7B,qBAAa,cAAc;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,IAAI,CAAgB;IAC5B,OAAO,CAAC,KAAK,CAAgB;gBAEjB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa;IAMhD,OAAO,CAAC,UAAU;IAMZ,YAAY,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAYtF,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ/C,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ3D,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ9E,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAMrD"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Service
|
|
3
|
+
* Handles all API interactions for projects
|
|
4
|
+
*/
|
|
5
|
+
import { createAuthenticatedAxios } from '../utils/axios-with-auth.js';
|
|
6
|
+
export class ProjectService {
|
|
7
|
+
constructor(baseUrl, auth) {
|
|
8
|
+
this.baseUrl = baseUrl;
|
|
9
|
+
this.auth = auth;
|
|
10
|
+
this.axios = createAuthenticatedAxios(auth);
|
|
11
|
+
}
|
|
12
|
+
getHeaders() {
|
|
13
|
+
return {
|
|
14
|
+
'Content-Type': 'application/json',
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
async listProjects(params) {
|
|
18
|
+
const queryParams = new URLSearchParams();
|
|
19
|
+
if (params.page)
|
|
20
|
+
queryParams.append('page', params.page.toString());
|
|
21
|
+
if (params.limit)
|
|
22
|
+
queryParams.append('limit', params.limit.toString());
|
|
23
|
+
const url = `${this.baseUrl}/api/v1/projects?${queryParams.toString()}`;
|
|
24
|
+
const headers = this.getHeaders();
|
|
25
|
+
const response = await this.axios.get(url, { headers });
|
|
26
|
+
return response.data;
|
|
27
|
+
}
|
|
28
|
+
async getProject(projectId) {
|
|
29
|
+
const url = `${this.baseUrl}/api/v1/projects/${projectId}`;
|
|
30
|
+
const headers = this.getHeaders();
|
|
31
|
+
const response = await this.axios.get(url, { headers });
|
|
32
|
+
return response.data;
|
|
33
|
+
}
|
|
34
|
+
async createProject(data) {
|
|
35
|
+
const url = `${this.baseUrl}/api/v1/projects`;
|
|
36
|
+
const headers = this.getHeaders();
|
|
37
|
+
const response = await this.axios.post(url, data, { headers });
|
|
38
|
+
return response.data;
|
|
39
|
+
}
|
|
40
|
+
async updateProject(projectId, data) {
|
|
41
|
+
const url = `${this.baseUrl}/api/v1/projects/${projectId}`;
|
|
42
|
+
const headers = this.getHeaders();
|
|
43
|
+
const response = await this.axios.patch(url, data, { headers });
|
|
44
|
+
return response.data;
|
|
45
|
+
}
|
|
46
|
+
async deleteProject(projectId) {
|
|
47
|
+
const url = `${this.baseUrl}/api/v1/projects/${projectId}`;
|
|
48
|
+
const headers = this.getHeaders();
|
|
49
|
+
await this.axios.delete(url, { headers });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=project-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-service.js","sourceRoot":"","sources":["../../src/services/project-service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAQvE,MAAM,OAAO,cAAc;IAK1B,YAAY,OAAe,EAAE,IAAmB;QAC/C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IAEO,UAAU;QACjB,OAAO;YACN,cAAc,EAAE,kBAAkB;SAClC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,MAAyC;QAC3D,MAAM,WAAW,GAAG,IAAI,eAAe,EAAE,CAAC;QAC1C,IAAI,MAAM,CAAC,IAAI;YAAE,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACpE,IAAI,MAAM,CAAC,KAAK;YAAE,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QAEvE,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,oBAAoB,WAAW,CAAC,QAAQ,EAAE,EAAE,CAAC;QACxE,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAElC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuB,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QAC9E,OAAO,QAAQ,CAAC,IAAI,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,SAAiB;QACjC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,oBAAoB,SAAS,EAAE,CAAC;QAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAElC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAU,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACjE,OAAO,QAAQ,CAAC,IAAI,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,IAA0B;QAC7C,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,kBAAkB,CAAC;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAElC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAU,GAAG,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACxE,OAAO,QAAQ,CAAC,IAAI,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,SAAiB,EAAE,IAA0B;QAChE,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,oBAAoB,SAAS,EAAE,CAAC;QAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAElC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAU,GAAG,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;QACzE,OAAO,QAAQ,CAAC,IAAI,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,SAAiB;QACpC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,oBAAoB,SAAS,EAAE,CAAC;QAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAElC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC3C,CAAC;CACD"}
|