@quantiya/codevibe-core 1.0.4 → 1.0.5
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/appsync/appsync-client.d.ts +132 -0
- package/dist/appsync/index.d.ts +2 -0
- package/dist/appsync/queries.d.ts +16 -0
- package/dist/auth/auth-cli.d.ts +5 -0
- package/dist/auth/auth-service.d.ts +87 -0
- package/dist/auth/fetch-helpers.d.ts +11 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/config/config.d.ts +53 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/crypto/crypto-service.d.ts +118 -0
- package/dist/crypto/index.d.ts +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/keychain/index.d.ts +1 -0
- package/dist/keychain/keychain-manager.d.ts +125 -0
- package/dist/logger/index.d.ts +1 -0
- package/dist/logger/logger.d.ts +35 -0
- package/dist/prompt-parser.d.ts +39 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/session-resume.d.ts +55 -0
- package/dist/types/auth.d.ts +15 -0
- package/dist/types/encryption.d.ts +54 -0
- package/dist/types/events.d.ts +74 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/session.d.ts +59 -0
- package/package.json +5 -2
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { CreateEventInput, CreateSessionInput, UpdateSessionInput, UpdateEventStatusInput, Event, Session, EventSource, DeviceKey } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Download URL response
|
|
4
|
+
*/
|
|
5
|
+
export interface DownloadUrlResponse {
|
|
6
|
+
downloadUrl: string;
|
|
7
|
+
expiresAt: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* AppSync GraphQL client with WebSocket subscriptions
|
|
11
|
+
*/
|
|
12
|
+
export declare class AppSyncClient {
|
|
13
|
+
private authenticated;
|
|
14
|
+
private currentUserId;
|
|
15
|
+
private currentEmail;
|
|
16
|
+
private tokens;
|
|
17
|
+
private activeSubscriptions;
|
|
18
|
+
private environment;
|
|
19
|
+
constructor();
|
|
20
|
+
/**
|
|
21
|
+
* Get the current authenticated user ID
|
|
22
|
+
*/
|
|
23
|
+
getCurrentUserId(): string;
|
|
24
|
+
/**
|
|
25
|
+
* Get the current authenticated user email
|
|
26
|
+
*/
|
|
27
|
+
getCurrentUserEmail(): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Authenticate using stored OAuth tokens from keychain
|
|
30
|
+
*/
|
|
31
|
+
authenticateWithStoredTokens(): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* Refresh expired tokens
|
|
34
|
+
*/
|
|
35
|
+
private refreshTokens;
|
|
36
|
+
/**
|
|
37
|
+
* Check if authenticated
|
|
38
|
+
*/
|
|
39
|
+
isAuthenticated(): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Sign out
|
|
42
|
+
*/
|
|
43
|
+
signOut(): void;
|
|
44
|
+
/**
|
|
45
|
+
* Make a GraphQL request
|
|
46
|
+
*/
|
|
47
|
+
private graphqlRequest;
|
|
48
|
+
/**
|
|
49
|
+
* Create a session
|
|
50
|
+
*/
|
|
51
|
+
createSession(input: CreateSessionInput): Promise<Session>;
|
|
52
|
+
/**
|
|
53
|
+
* Update a session
|
|
54
|
+
*/
|
|
55
|
+
updateSession(input: UpdateSessionInput): Promise<Session>;
|
|
56
|
+
/**
|
|
57
|
+
* Get a session
|
|
58
|
+
*/
|
|
59
|
+
getSession(sessionId: string): Promise<Session | null>;
|
|
60
|
+
/**
|
|
61
|
+
* Create an event
|
|
62
|
+
*/
|
|
63
|
+
createEvent(input: CreateEventInput): Promise<Event>;
|
|
64
|
+
/**
|
|
65
|
+
* Update event status
|
|
66
|
+
*/
|
|
67
|
+
updateEventStatus(input: UpdateEventStatusInput): Promise<Event>;
|
|
68
|
+
/**
|
|
69
|
+
* List events for a session
|
|
70
|
+
*/
|
|
71
|
+
listEvents(sessionId: string, source?: EventSource, limit?: number): Promise<Event[]>;
|
|
72
|
+
/**
|
|
73
|
+
* List user device keys
|
|
74
|
+
*/
|
|
75
|
+
listUserDeviceKeys(): Promise<DeviceKey[]>;
|
|
76
|
+
/**
|
|
77
|
+
* Register device key
|
|
78
|
+
*/
|
|
79
|
+
registerDeviceKey(deviceId: string, publicKey: string, platform: string, deviceName: string): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Get attachment download URL
|
|
82
|
+
*/
|
|
83
|
+
getAttachmentDownloadUrl(s3Key: string): Promise<DownloadUrlResponse>;
|
|
84
|
+
/**
|
|
85
|
+
* Subscribe to events for a session
|
|
86
|
+
*/
|
|
87
|
+
subscribeToEvents(sessionId: string, onEvent: (event: Event) => void, onError?: (error: Error) => void): () => void;
|
|
88
|
+
/**
|
|
89
|
+
* Build WebSocket URL
|
|
90
|
+
*/
|
|
91
|
+
private buildRealtimeUrl;
|
|
92
|
+
/**
|
|
93
|
+
* Create WebSocket subscription
|
|
94
|
+
*/
|
|
95
|
+
private createSubscription;
|
|
96
|
+
/**
|
|
97
|
+
* Send subscription start message
|
|
98
|
+
*/
|
|
99
|
+
private sendSubscriptionStart;
|
|
100
|
+
/**
|
|
101
|
+
* Reset keep-alive timer
|
|
102
|
+
*/
|
|
103
|
+
private resetKeepAliveTimer;
|
|
104
|
+
/**
|
|
105
|
+
* Handle subscription error with two-phase reconnection:
|
|
106
|
+
* Phase 1 (urgent): Exponential backoff for first N attempts
|
|
107
|
+
* Phase 2 (persistent): Fixed interval retry indefinitely
|
|
108
|
+
*/
|
|
109
|
+
private handleSubscriptionError;
|
|
110
|
+
/**
|
|
111
|
+
* Cleanup subscription state
|
|
112
|
+
*/
|
|
113
|
+
private cleanupSubscriptionState;
|
|
114
|
+
private heartbeatTimers;
|
|
115
|
+
/**
|
|
116
|
+
* Start periodic heartbeat for a session.
|
|
117
|
+
* Updates lastHeartbeatAt on the session every intervalMs (default 2 minutes).
|
|
118
|
+
*/
|
|
119
|
+
startHeartbeat(sessionId: string, intervalMs?: number): void;
|
|
120
|
+
/**
|
|
121
|
+
* Stop heartbeat for a session.
|
|
122
|
+
*/
|
|
123
|
+
stopHeartbeat(sessionId: string): void;
|
|
124
|
+
/**
|
|
125
|
+
* Send a single heartbeat update.
|
|
126
|
+
*/
|
|
127
|
+
private sendHeartbeat;
|
|
128
|
+
/**
|
|
129
|
+
* Cleanup all subscriptions and heartbeats
|
|
130
|
+
*/
|
|
131
|
+
cleanupSubscriptions(): void;
|
|
132
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const queries: {
|
|
2
|
+
getSession: string;
|
|
3
|
+
listEvents: string;
|
|
4
|
+
listUserDeviceKeys: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const mutations: {
|
|
7
|
+
createSession: string;
|
|
8
|
+
updateSession: string;
|
|
9
|
+
createEvent: string;
|
|
10
|
+
updateEventStatus: string;
|
|
11
|
+
registerDeviceKey: string;
|
|
12
|
+
getAttachmentDownloadUrl: string;
|
|
13
|
+
};
|
|
14
|
+
export declare const subscriptions: {
|
|
15
|
+
onEventCreated: string;
|
|
16
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { TokenData } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Authentication service for OAuth flows
|
|
4
|
+
*/
|
|
5
|
+
export declare class AuthService {
|
|
6
|
+
private static instance;
|
|
7
|
+
private constructor();
|
|
8
|
+
static getInstance(): AuthService;
|
|
9
|
+
/**
|
|
10
|
+
* Open URL in the user's default browser. Cross-platform: macOS, Linux,
|
|
11
|
+
* WSL, Windows. Always prints the URL to stdout first as a fallback —
|
|
12
|
+
* if no browser-opening command is available, the user can copy-paste.
|
|
13
|
+
*
|
|
14
|
+
* On WSL, prefers opening the Windows host browser via WSL interop
|
|
15
|
+
* (wslview → cmd.exe → powershell.exe) before falling back to xdg-open.
|
|
16
|
+
*/
|
|
17
|
+
private openBrowser;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the list of browser-opening commands to try in order, based on
|
|
20
|
+
* the current platform. On WSL, returns WSL interop commands first so the
|
|
21
|
+
* Windows browser opens (which is what users actually want on WSL).
|
|
22
|
+
*/
|
|
23
|
+
private getBrowserCommands;
|
|
24
|
+
/**
|
|
25
|
+
* Detect whether we're running inside WSL (1 or 2). Returns false on
|
|
26
|
+
* any non-Linux platform or if /proc/sys/kernel/osrelease is not readable.
|
|
27
|
+
*/
|
|
28
|
+
private isRunningInWSL;
|
|
29
|
+
/**
|
|
30
|
+
* Try each browser-opening command in order. Advances to the next fallback on:
|
|
31
|
+
* - Spawn failure (ENOENT / the command not being installed)
|
|
32
|
+
* - Synchronous throw from spawn()
|
|
33
|
+
* - Runtime failure where the command spawns but exits with a non-zero
|
|
34
|
+
* code (e.g. wslview when WSL interop is disabled, xdg-open when no
|
|
35
|
+
* default browser is registered, cmd.exe failing to launch start)
|
|
36
|
+
* - Process terminated by signal
|
|
37
|
+
*
|
|
38
|
+
* Stays on the current command when:
|
|
39
|
+
* - Process exits with code 0 (success)
|
|
40
|
+
* - Process is still running after 3 seconds (assumed success — opener
|
|
41
|
+
* is doing real work like launching a slow app, not hung)
|
|
42
|
+
*
|
|
43
|
+
* If all attempts exhaust, logs at debug level — the user still has the
|
|
44
|
+
* sign-in URL printed to stdout as a copy-paste fallback.
|
|
45
|
+
*/
|
|
46
|
+
private tryBrowserCommand;
|
|
47
|
+
/**
|
|
48
|
+
* Generate state for CSRF protection
|
|
49
|
+
*/
|
|
50
|
+
private generateState;
|
|
51
|
+
/**
|
|
52
|
+
* Build authorization URL
|
|
53
|
+
*/
|
|
54
|
+
private buildAuthUrl;
|
|
55
|
+
/**
|
|
56
|
+
* Exchange authorization code for tokens
|
|
57
|
+
*/
|
|
58
|
+
private exchangeCodeForTokens;
|
|
59
|
+
/**
|
|
60
|
+
* Decode JWT payload
|
|
61
|
+
*/
|
|
62
|
+
private decodeJwt;
|
|
63
|
+
/**
|
|
64
|
+
* Refresh tokens
|
|
65
|
+
*/
|
|
66
|
+
refreshTokens(refreshToken: string): Promise<{
|
|
67
|
+
accessToken: string;
|
|
68
|
+
idToken: string;
|
|
69
|
+
expiresIn: number;
|
|
70
|
+
}>;
|
|
71
|
+
/**
|
|
72
|
+
* Login via OAuth browser flow
|
|
73
|
+
*/
|
|
74
|
+
login(): Promise<TokenData | null>;
|
|
75
|
+
/**
|
|
76
|
+
* Logout
|
|
77
|
+
*/
|
|
78
|
+
logout(): Promise<boolean>;
|
|
79
|
+
/**
|
|
80
|
+
* Get current auth status
|
|
81
|
+
*/
|
|
82
|
+
getStatus(): Promise<{
|
|
83
|
+
authenticated: boolean;
|
|
84
|
+
tokens?: TokenData;
|
|
85
|
+
}>;
|
|
86
|
+
}
|
|
87
|
+
export declare const authService: AuthService;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps fetch() and rewrites "fetch failed" errors to include the underlying
|
|
3
|
+
* cause and a user-actionable diagnostic tip when possible. Non-network errors
|
|
4
|
+
* and HTTP error responses (non-2xx) are not affected — the caller still
|
|
5
|
+
* handles response.ok checks themselves.
|
|
6
|
+
*
|
|
7
|
+
* @param url The URL to fetch
|
|
8
|
+
* @param init Standard fetch init options
|
|
9
|
+
* @param context Optional short label (e.g. "token exchange") for the error message
|
|
10
|
+
*/
|
|
11
|
+
export declare function fetchWithDiagnostics(url: string, init?: any, context?: string): Promise<Response>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment type
|
|
3
|
+
*/
|
|
4
|
+
export type Environment = 'development' | 'production';
|
|
5
|
+
/**
|
|
6
|
+
* Configuration interface
|
|
7
|
+
*/
|
|
8
|
+
export interface Config {
|
|
9
|
+
environment: Environment;
|
|
10
|
+
aws: {
|
|
11
|
+
region: string;
|
|
12
|
+
appsyncUrl: string;
|
|
13
|
+
cognitoUserPoolId: string;
|
|
14
|
+
cognitoClientId: string;
|
|
15
|
+
cognitoDomain: string;
|
|
16
|
+
};
|
|
17
|
+
keychain: {
|
|
18
|
+
serviceName: string;
|
|
19
|
+
};
|
|
20
|
+
server: {
|
|
21
|
+
port: number;
|
|
22
|
+
host: string;
|
|
23
|
+
dynamicPort: boolean;
|
|
24
|
+
};
|
|
25
|
+
claude: {
|
|
26
|
+
command: string;
|
|
27
|
+
defaultTimeout: number;
|
|
28
|
+
};
|
|
29
|
+
codex: {
|
|
30
|
+
command: string;
|
|
31
|
+
defaultTimeout: number;
|
|
32
|
+
sessionsDir: string;
|
|
33
|
+
approvalTimeoutMs: number;
|
|
34
|
+
};
|
|
35
|
+
gemini: {
|
|
36
|
+
command: string;
|
|
37
|
+
defaultTimeout: number;
|
|
38
|
+
transcriptDir: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get environment from process.env.ENVIRONMENT, defaults to 'production'
|
|
43
|
+
*/
|
|
44
|
+
export declare function getEnvironment(): Environment;
|
|
45
|
+
/**
|
|
46
|
+
* Load configuration for specific environment
|
|
47
|
+
* If no environment specified, uses process.env.ENVIRONMENT or defaults to 'production'
|
|
48
|
+
*/
|
|
49
|
+
export declare function loadConfig(environment?: Environment): Config;
|
|
50
|
+
/**
|
|
51
|
+
* Get current configuration (auto-initializes if not already loaded)
|
|
52
|
+
*/
|
|
53
|
+
export declare function getConfig(): Config;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { EncryptedSessionKey, KeyPair } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Error class for cryptographic operations
|
|
4
|
+
*/
|
|
5
|
+
export declare class CryptoError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Current encryption version for future algorithm upgrades
|
|
10
|
+
*/
|
|
11
|
+
export declare const ENCRYPTION_VERSION = 1;
|
|
12
|
+
/**
|
|
13
|
+
* Service for end-to-end encryption operations
|
|
14
|
+
*/
|
|
15
|
+
export declare class CryptoService {
|
|
16
|
+
private static instance;
|
|
17
|
+
private constructor();
|
|
18
|
+
static getInstance(): CryptoService;
|
|
19
|
+
/**
|
|
20
|
+
* Generate a new ECDH P-256 key pair
|
|
21
|
+
* @returns Object with privateKey (base64), publicKey (base64 raw)
|
|
22
|
+
*/
|
|
23
|
+
generateKeyPair(): KeyPair;
|
|
24
|
+
/**
|
|
25
|
+
* Generate a random 256-bit session key
|
|
26
|
+
* @returns Base64-encoded session key
|
|
27
|
+
*/
|
|
28
|
+
generateSessionKey(): string;
|
|
29
|
+
/**
|
|
30
|
+
* Derive a shared secret using ECDH and HKDF
|
|
31
|
+
* @param privateKeyBase64 Our private key (base64)
|
|
32
|
+
* @param publicKeyBase64 Other party's public key (base64)
|
|
33
|
+
* @returns 256-bit derived key as Buffer
|
|
34
|
+
*/
|
|
35
|
+
deriveSharedKey(privateKeyBase64: string, publicKeyBase64: string): Buffer;
|
|
36
|
+
/**
|
|
37
|
+
* Encrypt a session key for a target device using ECDH
|
|
38
|
+
* @param sessionKeyBase64 The session key to encrypt (base64)
|
|
39
|
+
* @param targetPublicKeyBase64 Target device's public key (base64)
|
|
40
|
+
* @returns EncryptedSessionKey containing encrypted key and ephemeral public key
|
|
41
|
+
*/
|
|
42
|
+
encryptSessionKey(sessionKeyBase64: string, targetPublicKeyBase64: string): Omit<EncryptedSessionKey, 'deviceId'>;
|
|
43
|
+
/**
|
|
44
|
+
* Decrypt a session key using our private key
|
|
45
|
+
* @param encryptedSessionKey The encrypted session key data
|
|
46
|
+
* @param privateKeyBase64 Our device's private key (base64)
|
|
47
|
+
* @returns Decrypted session key (base64)
|
|
48
|
+
*/
|
|
49
|
+
decryptSessionKey(encryptedSessionKey: EncryptedSessionKey, privateKeyBase64: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Encrypt content using AES-256-GCM
|
|
52
|
+
* @param content String content to encrypt
|
|
53
|
+
* @param sessionKeyBase64 Session key (base64)
|
|
54
|
+
* @returns Base64-encoded ciphertext (nonce + ciphertext + tag)
|
|
55
|
+
*/
|
|
56
|
+
encryptContent(content: string, sessionKeyBase64: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Decrypt content using AES-256-GCM
|
|
59
|
+
* @param encryptedContent Base64-encoded ciphertext
|
|
60
|
+
* @param sessionKeyBase64 Session key (base64)
|
|
61
|
+
* @returns Decrypted string content
|
|
62
|
+
*/
|
|
63
|
+
decryptContent(encryptedContent: string, sessionKeyBase64: string): string;
|
|
64
|
+
/**
|
|
65
|
+
* Encrypt JSON-serializable metadata
|
|
66
|
+
* @param metadata Object to encrypt
|
|
67
|
+
* @param sessionKeyBase64 Session key (base64)
|
|
68
|
+
* @returns Base64-encoded encrypted JSON
|
|
69
|
+
*/
|
|
70
|
+
encryptMetadata(metadata: Record<string, any>, sessionKeyBase64: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* Decrypt encrypted metadata
|
|
73
|
+
* @param encryptedMetadata Base64-encoded encrypted JSON
|
|
74
|
+
* @param sessionKeyBase64 Session key (base64)
|
|
75
|
+
* @returns Decrypted object
|
|
76
|
+
*/
|
|
77
|
+
decryptMetadata(encryptedMetadata: string, sessionKeyBase64: string): Record<string, any>;
|
|
78
|
+
/**
|
|
79
|
+
* Encrypt binary data using AES-256-GCM
|
|
80
|
+
* @param data Binary data to encrypt (Buffer)
|
|
81
|
+
* @param sessionKeyBase64 Session key (base64)
|
|
82
|
+
* @returns Encrypted data (Buffer containing nonce + ciphertext + tag)
|
|
83
|
+
*/
|
|
84
|
+
encryptData(data: Buffer, sessionKeyBase64: string): Buffer;
|
|
85
|
+
/**
|
|
86
|
+
* Decrypt binary data using AES-256-GCM
|
|
87
|
+
* @param encryptedData Encrypted data (Buffer containing nonce + ciphertext + tag)
|
|
88
|
+
* @param sessionKeyBase64 Session key (base64)
|
|
89
|
+
* @returns Decrypted binary data (Buffer)
|
|
90
|
+
*/
|
|
91
|
+
decryptData(encryptedData: Buffer, sessionKeyBase64: string): Buffer;
|
|
92
|
+
/**
|
|
93
|
+
* Encrypt data using AES-256-GCM
|
|
94
|
+
* @param data Data to encrypt
|
|
95
|
+
* @param key Symmetric key (32 bytes)
|
|
96
|
+
* @returns Combined nonce + ciphertext + tag
|
|
97
|
+
*/
|
|
98
|
+
private encrypt;
|
|
99
|
+
/**
|
|
100
|
+
* Decrypt data using AES-256-GCM
|
|
101
|
+
* @param data Combined nonce + ciphertext + tag
|
|
102
|
+
* @param key Symmetric key (32 bytes)
|
|
103
|
+
* @returns Decrypted data
|
|
104
|
+
*/
|
|
105
|
+
private decrypt;
|
|
106
|
+
/**
|
|
107
|
+
* Serialize a private key for storage
|
|
108
|
+
*/
|
|
109
|
+
serializePrivateKey(privateKeyBase64: string): string;
|
|
110
|
+
/**
|
|
111
|
+
* Deserialize a private key from storage
|
|
112
|
+
*/
|
|
113
|
+
deserializePrivateKey(base64: string): string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Export singleton instance
|
|
117
|
+
*/
|
|
118
|
+
export declare const cryptoService: CryptoService;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CryptoService, cryptoService, CryptoError, ENCRYPTION_VERSION } from './crypto-service';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { KeychainManager, keychainManager, KeychainError } from './keychain';
|
|
2
|
+
export { CryptoService, cryptoService, CryptoError, ENCRYPTION_VERSION } from './crypto';
|
|
3
|
+
export { AppSyncClient, DownloadUrlResponse } from './appsync';
|
|
4
|
+
export { queries, mutations, subscriptions } from './appsync';
|
|
5
|
+
export { AuthService, authService } from './auth';
|
|
6
|
+
export { runAuthCli } from './auth';
|
|
7
|
+
export { loadConfig, getConfig, getEnvironment } from './config';
|
|
8
|
+
export type { Config, Environment } from './config';
|
|
9
|
+
export { Logger, logger, createLogger } from './logger';
|
|
10
|
+
export { parseInteractivePrompt, normalizeSnapshot, } from './prompt-parser';
|
|
11
|
+
export type { ParsedInteractivePrompt, PromptKind, InteractivePromptOption, } from './prompt-parser';
|
|
12
|
+
export { resumeOrCreateSession, prepareSessionEncryption } from './session';
|
|
13
|
+
export type { ResumeOrCreateSessionInput, ResumeOrCreateSessionResult } from './session';
|
|
14
|
+
export * from './types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { KeychainManager, keychainManager, KeychainError } from './keychain-manager';
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { DeviceIdentity, TokenData, EncryptedSessionKey } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Error class for keychain operations
|
|
4
|
+
*/
|
|
5
|
+
export declare class KeychainError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Manages device identity and OAuth tokens using native keychain
|
|
10
|
+
*/
|
|
11
|
+
export declare class KeychainManager {
|
|
12
|
+
private static instance;
|
|
13
|
+
private deviceIdentity;
|
|
14
|
+
private sessionKeyCache;
|
|
15
|
+
private isRegistered;
|
|
16
|
+
private _serviceName;
|
|
17
|
+
private constructor();
|
|
18
|
+
/**
|
|
19
|
+
* Get the keychain service name (lazy-loaded from config)
|
|
20
|
+
*/
|
|
21
|
+
private get serviceName();
|
|
22
|
+
static getInstance(): KeychainManager;
|
|
23
|
+
/**
|
|
24
|
+
* Get the device identity from keychain
|
|
25
|
+
*/
|
|
26
|
+
getDeviceIdentity(): Promise<DeviceIdentity | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Set the device identity in keychain
|
|
29
|
+
*/
|
|
30
|
+
setDeviceIdentity(identity: DeviceIdentity): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Get or create device identity
|
|
33
|
+
*/
|
|
34
|
+
getOrCreateDeviceIdentity(): Promise<DeviceIdentity>;
|
|
35
|
+
/**
|
|
36
|
+
* Get device ID (creates identity if needed)
|
|
37
|
+
*/
|
|
38
|
+
getDeviceId(): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Get device public key (creates identity if needed)
|
|
41
|
+
*/
|
|
42
|
+
getDevicePublicKey(): Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Get device private key (creates identity if needed)
|
|
45
|
+
*/
|
|
46
|
+
getDevicePrivateKey(): Promise<string>;
|
|
47
|
+
/**
|
|
48
|
+
* Check if device identity exists
|
|
49
|
+
*/
|
|
50
|
+
hasDeviceIdentity(): Promise<boolean>;
|
|
51
|
+
/**
|
|
52
|
+
* Delete device identity (reset device)
|
|
53
|
+
*/
|
|
54
|
+
deleteDeviceIdentity(): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Get token account name for environment
|
|
57
|
+
*/
|
|
58
|
+
private getTokenAccount;
|
|
59
|
+
/**
|
|
60
|
+
* Get OAuth tokens for environment
|
|
61
|
+
*/
|
|
62
|
+
getTokens(environment?: string): Promise<TokenData | null>;
|
|
63
|
+
/**
|
|
64
|
+
* Save OAuth tokens for environment
|
|
65
|
+
*/
|
|
66
|
+
setTokens(tokens: TokenData, environment?: string): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Delete OAuth tokens for environment
|
|
69
|
+
*/
|
|
70
|
+
deleteTokens(environment?: string): Promise<boolean>;
|
|
71
|
+
/**
|
|
72
|
+
* Check if token is expired
|
|
73
|
+
*/
|
|
74
|
+
isTokenExpired(tokens: TokenData): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Get session key for a session, decrypting from encryptedKeys if needed
|
|
77
|
+
*/
|
|
78
|
+
getSessionKey(sessionId: string, encryptedKeys?: EncryptedSessionKey[]): Promise<string | null>;
|
|
79
|
+
/**
|
|
80
|
+
* Generate and encrypt a new session key for all devices
|
|
81
|
+
*/
|
|
82
|
+
createSessionKey(devicePublicKeys: Array<{
|
|
83
|
+
deviceId: string;
|
|
84
|
+
publicKey: string;
|
|
85
|
+
}>): {
|
|
86
|
+
sessionKey: string;
|
|
87
|
+
encryptedKeys: EncryptedSessionKey[];
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Cache a session key
|
|
91
|
+
*/
|
|
92
|
+
cacheSessionKey(sessionId: string, sessionKey: string): void;
|
|
93
|
+
/**
|
|
94
|
+
* Clear cached session key
|
|
95
|
+
*/
|
|
96
|
+
clearSessionKey(sessionId: string): void;
|
|
97
|
+
/**
|
|
98
|
+
* Clear all cached session keys
|
|
99
|
+
*/
|
|
100
|
+
clearAllSessionKeys(): void;
|
|
101
|
+
/**
|
|
102
|
+
* Get registration status
|
|
103
|
+
*/
|
|
104
|
+
getIsRegistered(): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Set registration status
|
|
107
|
+
*/
|
|
108
|
+
setIsRegistered(registered: boolean): void;
|
|
109
|
+
/**
|
|
110
|
+
* Get device name for registration
|
|
111
|
+
*/
|
|
112
|
+
getDeviceName(): string;
|
|
113
|
+
/**
|
|
114
|
+
* Get platform for registration
|
|
115
|
+
*/
|
|
116
|
+
getDevicePlatform(): string;
|
|
117
|
+
/**
|
|
118
|
+
* Clear all data (device identity + all tokens)
|
|
119
|
+
*/
|
|
120
|
+
clearAllData(): Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Export singleton instance
|
|
124
|
+
*/
|
|
125
|
+
export declare const keychainManager: KeychainManager;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Logger, logger, createLogger } from './logger';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
2
|
+
interface LoggerOptions {
|
|
3
|
+
name: string;
|
|
4
|
+
logFile?: string;
|
|
5
|
+
level?: LogLevel;
|
|
6
|
+
console?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Simple logger for CodeVibe plugins
|
|
10
|
+
*/
|
|
11
|
+
export declare class Logger {
|
|
12
|
+
private name;
|
|
13
|
+
private logFile?;
|
|
14
|
+
private level;
|
|
15
|
+
private enableConsole;
|
|
16
|
+
constructor(options: LoggerOptions);
|
|
17
|
+
private ensureLogDir;
|
|
18
|
+
private shouldLog;
|
|
19
|
+
private formatMessage;
|
|
20
|
+
private log;
|
|
21
|
+
debug(message: string, data?: any): void;
|
|
22
|
+
info(message: string, data?: any): void;
|
|
23
|
+
warn(message: string, data?: any): void;
|
|
24
|
+
error(message: string, data?: any): void;
|
|
25
|
+
setLevel(level: LogLevel): void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a logger with custom options
|
|
29
|
+
*/
|
|
30
|
+
export declare function createLogger(options: LoggerOptions): Logger;
|
|
31
|
+
/**
|
|
32
|
+
* Default shared logger instance
|
|
33
|
+
*/
|
|
34
|
+
export declare const logger: Logger;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared prompt parser for interactive terminal prompts.
|
|
3
|
+
*
|
|
4
|
+
* Parses terminal snapshots (from tmux capture-pane) to extract
|
|
5
|
+
* interactive prompt options displayed by CLI tools (Gemini, Codex, etc.).
|
|
6
|
+
*
|
|
7
|
+
* Used by multiple plugins to dynamically capture options instead of hardcoding.
|
|
8
|
+
*/
|
|
9
|
+
export type PromptKind = 'yes_no' | 'numbered' | 'text';
|
|
10
|
+
export interface InteractivePromptOption {
|
|
11
|
+
number: string;
|
|
12
|
+
text: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ParsedInteractivePrompt {
|
|
15
|
+
kind: PromptKind;
|
|
16
|
+
promptText: string;
|
|
17
|
+
options: InteractivePromptOption[];
|
|
18
|
+
submitMap: Record<string, string>;
|
|
19
|
+
requiresFollowUpText?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse a terminal snapshot to extract interactive prompt options.
|
|
23
|
+
*
|
|
24
|
+
* Detects two prompt types:
|
|
25
|
+
* - Yes/No prompts: [y/n] patterns
|
|
26
|
+
* - Numbered prompts: "1. Allow once", "2. Allow for this session", etc.
|
|
27
|
+
*
|
|
28
|
+
* Returns null if no prompt is detected.
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseInteractivePrompt(snapshot: string): ParsedInteractivePrompt | null;
|
|
31
|
+
/**
|
|
32
|
+
* Strip ANSI escape codes, box-drawing characters, and normalize whitespace
|
|
33
|
+
* from terminal output.
|
|
34
|
+
*
|
|
35
|
+
* Box-drawing characters (│, ┌, ┐, └, ┘, ─, etc.) are used by TUI apps like
|
|
36
|
+
* Gemini CLI to render bordered panels. Stripping them exposes the text content
|
|
37
|
+
* so the parser can match option lines inside boxes.
|
|
38
|
+
*/
|
|
39
|
+
export declare function normalizeSnapshot(snapshot: string): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized session resume/create logic for all CodeVibe plugins.
|
|
3
|
+
*
|
|
4
|
+
* All three plugins (Claude, Codex, Gemini) need the same pattern:
|
|
5
|
+
* 1. Check if session exists in backend (getSession)
|
|
6
|
+
* 2. If exists: reactivate + restore E2E session key from encryptedKeys
|
|
7
|
+
* 3. If not exists: generate encryption keys + create session
|
|
8
|
+
*
|
|
9
|
+
* This module eliminates the duplication across plugins.
|
|
10
|
+
*/
|
|
11
|
+
import { AppSyncClient } from '../appsync';
|
|
12
|
+
import { Logger } from '../logger';
|
|
13
|
+
import { AgentType, EncryptedSessionKey } from '../types';
|
|
14
|
+
export interface ResumeOrCreateSessionInput {
|
|
15
|
+
sessionId: string;
|
|
16
|
+
userId: string;
|
|
17
|
+
agentType: AgentType;
|
|
18
|
+
projectPath: string;
|
|
19
|
+
metadata?: Record<string, any>;
|
|
20
|
+
}
|
|
21
|
+
export interface ResumeOrCreateSessionResult {
|
|
22
|
+
/** Whether an existing session was found and reactivated */
|
|
23
|
+
resumed: boolean;
|
|
24
|
+
/** The E2E session key (for encrypting/decrypting events), or null if no encryption */
|
|
25
|
+
sessionKey: string | null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Prepare E2E encryption for a new session.
|
|
29
|
+
*
|
|
30
|
+
* Generates a random session key and encrypts it for all registered devices.
|
|
31
|
+
* Does NOT cache the session key — callers should cache only after the session
|
|
32
|
+
* is successfully created in the backend.
|
|
33
|
+
*
|
|
34
|
+
* @returns Encryption data or null if no device keys found / error
|
|
35
|
+
*/
|
|
36
|
+
export declare function prepareSessionEncryption(sessionId: string, appSyncClient: AppSyncClient, logger: Logger): Promise<{
|
|
37
|
+
sessionKey: string;
|
|
38
|
+
encryptedKeys: EncryptedSessionKey[];
|
|
39
|
+
} | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Resume an existing session or create a new one.
|
|
42
|
+
*
|
|
43
|
+
* Resume path (session exists in backend):
|
|
44
|
+
* - Reactivates session (status → ACTIVE)
|
|
45
|
+
* - Restores E2E session key from encryptedKeys
|
|
46
|
+
*
|
|
47
|
+
* Create path (session does not exist):
|
|
48
|
+
* - Prepares E2E encryption (generates key, encrypts for all devices)
|
|
49
|
+
* - Encrypts projectPath and metadata
|
|
50
|
+
* - Creates session in backend
|
|
51
|
+
* - Caches session key only after successful creation
|
|
52
|
+
*
|
|
53
|
+
* @throws Propagates createSession errors (e.g., SESSION_LIMIT_EXCEEDED)
|
|
54
|
+
*/
|
|
55
|
+
export declare function resumeOrCreateSession(input: ResumeOrCreateSessionInput, appSyncClient: AppSyncClient, logger: Logger): Promise<ResumeOrCreateSessionResult>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stored OAuth tokens
|
|
3
|
+
*/
|
|
4
|
+
export interface TokenData {
|
|
5
|
+
accessToken: string;
|
|
6
|
+
idToken: string;
|
|
7
|
+
refreshToken: string;
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
userId: string;
|
|
10
|
+
email: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Environment configuration
|
|
14
|
+
*/
|
|
15
|
+
export type Environment = 'development' | 'production';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypted session key for E2E encryption
|
|
3
|
+
*/
|
|
4
|
+
export interface EncryptedSessionKey {
|
|
5
|
+
deviceId: string;
|
|
6
|
+
encryptedKey: string;
|
|
7
|
+
ephemeralPublicKey: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Device key stored with backend
|
|
11
|
+
*/
|
|
12
|
+
export interface DeviceKey {
|
|
13
|
+
userId: string;
|
|
14
|
+
deviceId: string;
|
|
15
|
+
publicKey: string;
|
|
16
|
+
platform: string;
|
|
17
|
+
deviceName?: string;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
lastUsedAt?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Key pair for ECDH
|
|
23
|
+
*/
|
|
24
|
+
export interface KeyPair {
|
|
25
|
+
privateKey: string;
|
|
26
|
+
publicKey: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Device identity stored in keychain
|
|
30
|
+
*/
|
|
31
|
+
export interface DeviceIdentity {
|
|
32
|
+
deviceId: string;
|
|
33
|
+
privateKey: string;
|
|
34
|
+
publicKey: string;
|
|
35
|
+
createdAt: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* GraphQL input for registering device key
|
|
39
|
+
*/
|
|
40
|
+
export interface RegisterDeviceKeyInput {
|
|
41
|
+
deviceId: string;
|
|
42
|
+
publicKey: string;
|
|
43
|
+
platform: string;
|
|
44
|
+
deviceName?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* GraphQL input for granting session key
|
|
48
|
+
*/
|
|
49
|
+
export interface GrantSessionKeyInput {
|
|
50
|
+
sessionId: string;
|
|
51
|
+
deviceId: string;
|
|
52
|
+
encryptedKey: string;
|
|
53
|
+
ephemeralPublicKey: string;
|
|
54
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event types matching GraphQL schema
|
|
3
|
+
*/
|
|
4
|
+
export declare enum EventType {
|
|
5
|
+
USER_PROMPT = "USER_PROMPT",
|
|
6
|
+
ASSISTANT_RESPONSE = "ASSISTANT_RESPONSE",
|
|
7
|
+
TOOL_USE = "TOOL_USE",
|
|
8
|
+
NOTIFICATION = "NOTIFICATION",
|
|
9
|
+
INTERACTIVE_PROMPT = "INTERACTIVE_PROMPT",
|
|
10
|
+
PROMPT_RESPONSE = "PROMPT_RESPONSE",
|
|
11
|
+
REASONING = "REASONING"
|
|
12
|
+
}
|
|
13
|
+
export declare enum EventSource {
|
|
14
|
+
DESKTOP = "DESKTOP",
|
|
15
|
+
MOBILE = "MOBILE"
|
|
16
|
+
}
|
|
17
|
+
export declare enum DeliveryStatus {
|
|
18
|
+
SENT = "SENT",
|
|
19
|
+
DELIVERED = "DELIVERED",
|
|
20
|
+
EXECUTED = "EXECUTED"
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Attachment type (for images/files attached to messages)
|
|
24
|
+
*/
|
|
25
|
+
export interface Attachment {
|
|
26
|
+
id: string;
|
|
27
|
+
type: string;
|
|
28
|
+
filename?: string;
|
|
29
|
+
s3Key: string;
|
|
30
|
+
size?: number;
|
|
31
|
+
width?: number;
|
|
32
|
+
height?: number;
|
|
33
|
+
isEncrypted?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Event type
|
|
37
|
+
*/
|
|
38
|
+
export interface Event {
|
|
39
|
+
eventId: string;
|
|
40
|
+
sessionId: string;
|
|
41
|
+
type: EventType;
|
|
42
|
+
source: EventSource;
|
|
43
|
+
content: string;
|
|
44
|
+
timestamp: string;
|
|
45
|
+
metadata?: Record<string, any>;
|
|
46
|
+
promptId?: string;
|
|
47
|
+
attachments?: Attachment[];
|
|
48
|
+
deliveryStatus?: DeliveryStatus;
|
|
49
|
+
deliveredAt?: string;
|
|
50
|
+
executedAt?: string;
|
|
51
|
+
isEncrypted?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* GraphQL input for creating events
|
|
55
|
+
*/
|
|
56
|
+
export interface CreateEventInput {
|
|
57
|
+
sessionId: string;
|
|
58
|
+
type: EventType;
|
|
59
|
+
source: EventSource;
|
|
60
|
+
content: string;
|
|
61
|
+
metadata?: Record<string, any>;
|
|
62
|
+
promptId?: string;
|
|
63
|
+
timestamp?: string;
|
|
64
|
+
isEncrypted?: boolean;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* GraphQL input for updating event status
|
|
68
|
+
*/
|
|
69
|
+
export interface UpdateEventStatusInput {
|
|
70
|
+
eventId: string;
|
|
71
|
+
sessionId: string;
|
|
72
|
+
timestamp: string;
|
|
73
|
+
deliveryStatus: DeliveryStatus;
|
|
74
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { EncryptedSessionKey } from './encryption';
|
|
2
|
+
/**
|
|
3
|
+
* Session status enum
|
|
4
|
+
*/
|
|
5
|
+
export declare enum SessionStatus {
|
|
6
|
+
ACTIVE = "ACTIVE",
|
|
7
|
+
INACTIVE = "INACTIVE",
|
|
8
|
+
PAUSED = "PAUSED"
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Agent type for multi-agent support
|
|
12
|
+
*/
|
|
13
|
+
export declare enum AgentType {
|
|
14
|
+
CLAUDE = "CLAUDE",
|
|
15
|
+
GEMINI = "GEMINI",
|
|
16
|
+
CODEX = "CODEX"
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Session type
|
|
20
|
+
*/
|
|
21
|
+
export interface Session {
|
|
22
|
+
sessionId: string;
|
|
23
|
+
userId: string;
|
|
24
|
+
agentType: AgentType;
|
|
25
|
+
projectPath: string;
|
|
26
|
+
status: SessionStatus;
|
|
27
|
+
createdAt: string;
|
|
28
|
+
updatedAt: string;
|
|
29
|
+
metadata?: Record<string, any>;
|
|
30
|
+
isEncrypted?: boolean;
|
|
31
|
+
encryptedKeys?: EncryptedSessionKey[];
|
|
32
|
+
creatorDeviceId?: string;
|
|
33
|
+
encryptionVersion?: number;
|
|
34
|
+
lastHeartbeatAt?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* GraphQL input for creating sessions
|
|
38
|
+
*/
|
|
39
|
+
export interface CreateSessionInput {
|
|
40
|
+
sessionId?: string;
|
|
41
|
+
userId: string;
|
|
42
|
+
agentType?: AgentType;
|
|
43
|
+
projectPath: string;
|
|
44
|
+
status?: SessionStatus;
|
|
45
|
+
metadata?: Record<string, any>;
|
|
46
|
+
encryptedKeys?: EncryptedSessionKey[];
|
|
47
|
+
creatorDeviceId?: string;
|
|
48
|
+
isEncrypted?: boolean;
|
|
49
|
+
encryptionVersion?: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* GraphQL input for updating sessions
|
|
53
|
+
*/
|
|
54
|
+
export interface UpdateSessionInput {
|
|
55
|
+
sessionId: string;
|
|
56
|
+
status?: SessionStatus;
|
|
57
|
+
metadata?: Record<string, any>;
|
|
58
|
+
lastHeartbeatAt?: string;
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quantiya/codevibe-core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Core library for CodeVibe plugins - shared keychain, crypto, AppSync, and auth functionality",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
6
7
|
"bin": {
|
|
7
8
|
"codevibe": "./bin/codevibe.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
11
|
+
"dist/**/*.d.ts",
|
|
10
12
|
"dist/index.js",
|
|
11
13
|
"bin"
|
|
12
14
|
],
|
|
13
15
|
"scripts": {
|
|
14
16
|
"typecheck": "tsc --noEmit",
|
|
15
|
-
"
|
|
17
|
+
"emit-types": "tsc --emitDeclarationOnly",
|
|
18
|
+
"build": "rm -rf dist && npm run emit-types && esbuild src/index.ts --bundle --platform=node --target=node18 --minify --packages=external --outfile=dist/index.js",
|
|
16
19
|
"clean": "rm -rf dist",
|
|
17
20
|
"prepublishOnly": "npm run build",
|
|
18
21
|
"test": "echo \"No tests yet\" && exit 0"
|