cc-control-shared 1.0.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.
@@ -0,0 +1,5 @@
1
+ export * from "./types/index.js";
2
+ export * from "./types/user.js";
3
+ export * from "./protocols/index.js";
4
+ export * from "./utils/index.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAGhC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ // ============================================================================
2
+ // CC-Control Shared Package
3
+ // ============================================================================
4
+ // Export all types
5
+ export * from "./types/index.js";
6
+ export * from "./types/user.js";
7
+ // Export all protocol definitions
8
+ export * from "./protocols/index.js";
9
+ // Export all utilities
10
+ export * from "./utils/index.js";
@@ -0,0 +1,131 @@
1
+ import type { Session, LogEntry } from "../types/index.js";
2
+ export interface MessageEnvelope<T extends MessageType = MessageType> {
3
+ version: "1.0";
4
+ type: T;
5
+ id: string;
6
+ timestamp: string;
7
+ deviceId: string;
8
+ payload: MessagePayloadTypeMap[T];
9
+ }
10
+ export type MessageType = "auth:register" | "auth:pair" | "auth:success" | "auth:error" | "state:session_update" | "state:session_list" | "state:task_progress" | "state:task_completion" | "logs:stream" | "command:send_prompt" | "command:switch_session" | "command:manage_task" | "command:file_operation" | "response:command_result" | "response:error" | "connection:status";
11
+ export interface MessagePayloadTypeMap {
12
+ "auth:register": AuthRegisterPayload;
13
+ "auth:pair": AuthPairPayload;
14
+ "auth:success": AuthSuccessPayload;
15
+ "auth:error": AuthErrorPayload;
16
+ "state:session_update": StateSessionUpdatePayload;
17
+ "state:session_list": StateSessionListPayload;
18
+ "state:task_progress": StateTaskProgressPayload;
19
+ "state:task_completion": StateTaskCompletionPayload;
20
+ "logs:stream": LogsStreamPayload;
21
+ "command:send_prompt": CommandSendPromptPayload;
22
+ "command:switch_session": CommandSwitchSessionPayload;
23
+ "command:manage_task": CommandManageTaskPayload;
24
+ "command:file_operation": CommandFileOperationPayload;
25
+ "response:command_result": ResponseCommandResultPayload;
26
+ "response:error": ResponseErrorPayload;
27
+ "connection:status": ConnectionStatusPayload;
28
+ }
29
+ export interface AuthRegisterPayload {
30
+ deviceName: string;
31
+ deviceType: "desktop" | "mobile";
32
+ capabilities: {
33
+ version: string;
34
+ features: string[];
35
+ };
36
+ }
37
+ export interface AuthPairPayload {
38
+ pairingCode: string;
39
+ deviceToken?: string;
40
+ }
41
+ export interface AuthSuccessPayload {
42
+ deviceId: string;
43
+ token: string;
44
+ expiresAt: number;
45
+ }
46
+ export interface AuthErrorPayload {
47
+ error: string;
48
+ code: string;
49
+ }
50
+ export interface StateSessionUpdatePayload {
51
+ session: Session;
52
+ }
53
+ export interface StateSessionListPayload {
54
+ sessions: Session[];
55
+ lastUpdate: string;
56
+ }
57
+ export interface StateTaskProgressPayload {
58
+ sessionId: string;
59
+ taskId: string;
60
+ status: "in_progress" | "blocked" | "completed" | "failed";
61
+ progress: number;
62
+ currentStep?: string;
63
+ startedAt: string;
64
+ eta?: string;
65
+ }
66
+ export interface StateTaskCompletionPayload {
67
+ sessionId: string;
68
+ taskId: string;
69
+ status: "completed" | "failed" | "cancelled";
70
+ result?: {
71
+ summary: string;
72
+ filesModified: number;
73
+ commandsRun: number;
74
+ duration: number;
75
+ };
76
+ error?: {
77
+ code: string;
78
+ message: string;
79
+ stack?: string;
80
+ };
81
+ completedAt: string;
82
+ }
83
+ export interface LogsStreamPayload {
84
+ sessionId: string;
85
+ logs: LogEntry[];
86
+ isChunk?: boolean;
87
+ chunkIndex?: number;
88
+ totalChunks?: number;
89
+ }
90
+ export interface CommandSendPromptPayload {
91
+ sessionId: string;
92
+ commandId: string;
93
+ prompt: string;
94
+ context?: {
95
+ workingDirectory?: string;
96
+ files?: string[];
97
+ };
98
+ }
99
+ export interface CommandSwitchSessionPayload {
100
+ targetSessionId: string;
101
+ }
102
+ export interface CommandManageTaskPayload {
103
+ taskId: string;
104
+ action: "cancel" | "pause" | "resume";
105
+ }
106
+ export interface CommandFileOperationPayload {
107
+ operation: "read" | "write" | "delete" | "list";
108
+ path: string;
109
+ content?: string;
110
+ }
111
+ export interface ResponseCommandResultPayload {
112
+ commandId: string;
113
+ status: "success" | "error" | "pending";
114
+ result?: unknown;
115
+ timestamp: string;
116
+ }
117
+ export interface ResponseErrorPayload {
118
+ commandId?: string;
119
+ error: {
120
+ code: string;
121
+ message: string;
122
+ details?: Record<string, unknown>;
123
+ };
124
+ timestamp: string;
125
+ }
126
+ export interface ConnectionStatusPayload {
127
+ status: "connected" | "disconnected" | "reconnecting";
128
+ reason?: string;
129
+ }
130
+ export declare function createMessage<T extends MessageType>(type: T, payload: MessagePayloadTypeMap[T], deviceId: string): MessageEnvelope<T>;
131
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/protocols/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ // ============================================================================
2
+ // WebSocket Message Protocol Definitions
3
+ // ============================================================================
4
+ // ----------------------------------------------------------------------------
5
+ // Message Builder Helpers
6
+ // ----------------------------------------------------------------------------
7
+ export function createMessage(type, payload, deviceId) {
8
+ return {
9
+ version: "1.0",
10
+ type,
11
+ id: crypto.randomUUID(),
12
+ timestamp: new Date().toISOString(),
13
+ deviceId,
14
+ payload: payload,
15
+ };
16
+ }
@@ -0,0 +1,164 @@
1
+ export type SessionStatus = "initializing" | "idle" | "processing" | "waiting_input" | "error" | "terminated";
2
+ export interface Session {
3
+ id: string;
4
+ deviceId: string;
5
+ status: SessionStatus;
6
+ currentTask?: Task;
7
+ messages: Message[];
8
+ filesAccessed: string[];
9
+ lastActivity: Date;
10
+ createdAt: Date;
11
+ metadata: SessionMetadata;
12
+ }
13
+ export interface SessionMetadata {
14
+ workingDirectory: string;
15
+ claudeVersion: string;
16
+ project?: string;
17
+ customTitle?: string;
18
+ summary?: string;
19
+ firstPrompt?: string;
20
+ messageCount?: number;
21
+ }
22
+ export type TaskStatus = "pending" | "in_progress" | "blocked" | "completed" | "failed" | "cancelled";
23
+ export interface Task {
24
+ id: string;
25
+ sessionId: string;
26
+ type: TaskType;
27
+ status: TaskStatus;
28
+ progress: number;
29
+ subject: string;
30
+ description?: string;
31
+ startedAt: Date;
32
+ completedAt?: Date;
33
+ error?: TaskError;
34
+ metadata?: Record<string, unknown>;
35
+ }
36
+ export type TaskType = "prompt" | "tool_execution" | "file_operation";
37
+ export interface TaskError {
38
+ code: string;
39
+ message: string;
40
+ recoverable: boolean;
41
+ }
42
+ export interface Message {
43
+ id: string;
44
+ sessionId: string;
45
+ role: "user" | "assistant" | "system";
46
+ content: string;
47
+ timestamp: Date;
48
+ toolCalls?: ToolCall[];
49
+ metadata?: {
50
+ model?: string;
51
+ gitBranch?: string;
52
+ cwd?: string;
53
+ usage?: {
54
+ input_tokens: number;
55
+ output_tokens: number;
56
+ };
57
+ };
58
+ }
59
+ export interface ToolCall {
60
+ id: string;
61
+ name: string;
62
+ arguments: Record<string, unknown>;
63
+ result?: unknown;
64
+ status: "pending" | "approved" | "rejected" | "completed";
65
+ }
66
+ export type DeviceType = "desktop" | "mobile";
67
+ export type DeviceStatus = "online" | "offline";
68
+ export interface Device {
69
+ id: string;
70
+ name: string;
71
+ type: DeviceType;
72
+ status: DeviceStatus;
73
+ lastSeen: Date;
74
+ pairedDevices: string[];
75
+ capabilities: DeviceCapabilities;
76
+ }
77
+ export interface DeviceCapabilities {
78
+ version: string;
79
+ features: string[];
80
+ }
81
+ export type LogLevel = "debug" | "info" | "warn" | "error";
82
+ export type LogSource = "claude_code" | "system" | "agent";
83
+ export interface LogEntry {
84
+ id: string;
85
+ sessionId: string;
86
+ source: LogSource;
87
+ level: LogLevel;
88
+ timestamp: Date;
89
+ message: string;
90
+ metadata?: Record<string, unknown>;
91
+ }
92
+ export type ConnectionStatus = "connected" | "disconnected" | "connecting" | "reconnecting";
93
+ export interface ConnectionState {
94
+ status: ConnectionStatus;
95
+ lastConnected?: Date;
96
+ lastDisconnected?: Date;
97
+ reconnectAttempts?: number;
98
+ }
99
+ export type CommandType = "send_prompt" | "switch_session" | "manage_task" | "file_operation";
100
+ export interface Command {
101
+ id: string;
102
+ type: CommandType;
103
+ sessionId: string;
104
+ payload: CommandPayload;
105
+ timestamp: Date;
106
+ }
107
+ export type CommandPayload = SendPromptPayload | SwitchSessionPayload | ManageTaskPayload | FileOperationPayload;
108
+ export interface SendPromptPayload {
109
+ prompt: string;
110
+ context?: {
111
+ workingDirectory?: string;
112
+ files?: string[];
113
+ };
114
+ }
115
+ export interface SwitchSessionPayload {
116
+ targetSessionId: string;
117
+ }
118
+ export interface ManageTaskPayload {
119
+ taskId: string;
120
+ action: "cancel" | "pause" | "resume";
121
+ }
122
+ export type FileOperationType = "read" | "write" | "delete" | "list";
123
+ export interface FileOperationPayload {
124
+ operation: FileOperationType;
125
+ path: string;
126
+ content?: string;
127
+ }
128
+ export type ResponseStatus = "success" | "error" | "pending";
129
+ export interface CommandResponse {
130
+ commandId: string;
131
+ status: ResponseStatus;
132
+ result?: unknown;
133
+ error?: ResponseError;
134
+ timestamp: Date;
135
+ }
136
+ export interface ResponseError {
137
+ code: string;
138
+ message: string;
139
+ details?: Record<string, unknown>;
140
+ }
141
+ export interface AuthTokens {
142
+ accessToken: string;
143
+ refreshToken: string;
144
+ expiresAt: Date;
145
+ }
146
+ export interface PairingInfo {
147
+ pairingCode: string;
148
+ desktopId: string;
149
+ desktopName: string;
150
+ qrData: string;
151
+ expiresAt: Date;
152
+ }
153
+ export type Permission = "read:sessions" | "read:logs" | "send:prompts" | "manage:tasks" | "read:files" | "write:files";
154
+ export interface AccessTokenPayload {
155
+ sub: string;
156
+ type: "access";
157
+ device_type: DeviceType;
158
+ paired_device: string;
159
+ permissions: Permission[];
160
+ iat: number;
161
+ exp: number;
162
+ jti: string;
163
+ }
164
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ // ============================================================================
2
+ // Core Type Definitions for CC-Control
3
+ // ============================================================================
4
+ export {};
@@ -0,0 +1,42 @@
1
+ export interface User {
2
+ id: string;
3
+ email: string;
4
+ emailVerified: boolean;
5
+ subscriptionTier: "free" | "pro" | "enterprise";
6
+ createdAt: Date;
7
+ updatedAt: Date;
8
+ lastLoginAt?: Date;
9
+ }
10
+ export interface CreateUserInput {
11
+ email: string;
12
+ password: string;
13
+ }
14
+ export interface LoginInput {
15
+ email: string;
16
+ password: string;
17
+ }
18
+ export interface UserSession {
19
+ user: User;
20
+ accessToken: string;
21
+ refreshToken: string;
22
+ expiresAt: Date;
23
+ }
24
+ export interface UserTokenPayload {
25
+ sub: string;
26
+ type: "access" | "refresh";
27
+ email: string;
28
+ tier: "free" | "pro" | "enterprise";
29
+ iat: number;
30
+ exp: number;
31
+ jti: string;
32
+ }
33
+ export interface DeviceTokenPayload {
34
+ sub: string;
35
+ type: "device";
36
+ userId: string;
37
+ deviceType: "desktop" | "mobile";
38
+ iat: number;
39
+ exp: number;
40
+ jti: string;
41
+ }
42
+ //# sourceMappingURL=user.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/types/user.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,MAAM,GAAG,KAAK,GAAG,YAAY,CAAC;IAChD,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,CAAC,EAAE,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,YAAY,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,SAAS,GAAG,QAAQ,CAAC;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb"}
@@ -0,0 +1,4 @@
1
+ // ============================================================================
2
+ // User & Auth Types for CC-Control SaaS
3
+ // ============================================================================
4
+ export {};
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Format a date for display
3
+ */
4
+ export declare function formatDate(date: Date): string;
5
+ /**
6
+ * Format a duration in milliseconds to human-readable
7
+ */
8
+ export declare function formatDuration(ms: number): string;
9
+ /**
10
+ * Format bytes to human-readable
11
+ */
12
+ export declare function formatBytes(bytes: number): string;
13
+ /**
14
+ * Generate a pairing code (XXX-XXX-XXX format)
15
+ */
16
+ export declare function generatePairingCode(): string;
17
+ /**
18
+ * Validate a pairing code format
19
+ */
20
+ export declare function isValidPairingCode(code: string): boolean;
21
+ /**
22
+ * Deep clone an object
23
+ */
24
+ export declare function deepClone<T>(obj: T): T;
25
+ /**
26
+ * Debounce a function
27
+ */
28
+ export declare function debounce<T extends (...args: unknown[]) => unknown>(func: T, wait: number): (...args: Parameters<T>) => void;
29
+ /**
30
+ * Throttle a function
31
+ */
32
+ export declare function throttle<T extends (...args: unknown[]) => unknown>(func: T, limit: number): (...args: Parameters<T>) => void;
33
+ /**
34
+ * Sleep for a specified duration
35
+ */
36
+ export declare function sleep(ms: number): Promise<void>;
37
+ /**
38
+ * Retry a function with exponential backoff
39
+ */
40
+ export declare function retry<T>(fn: () => Promise<T>, options?: {
41
+ maxAttempts?: number;
42
+ initialDelay?: number;
43
+ maxDelay?: number;
44
+ backoffMultiplier?: number;
45
+ }): Promise<T>;
46
+ /**
47
+ * Parse a user's home directory path
48
+ */
49
+ export declare function resolveHomePath(path: string): string;
50
+ /**
51
+ * Check if a path is within allowed directories
52
+ */
53
+ export declare function isPathAllowed(path: string, allowed: string[], denied: string[]): boolean;
54
+ /**
55
+ * Extract device ID from JWT token
56
+ */
57
+ export declare function extractDeviceId(token: string): string | null;
58
+ /**
59
+ * Check if a JWT token is expired
60
+ */
61
+ export declare function isTokenExpired(token: string): boolean;
62
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAe7C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWjD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAa5C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAEtC;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAChE,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,MAAM,GACX,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAYlC;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAChE,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,MAAM,GACZ,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAUlC;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED;;GAEG;AACH,wBAAsB,KAAK,CAAC,CAAC,EAC3B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACvB,GACL,OAAO,CAAC,CAAC,CAAC,CA2BZ;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,EACjB,MAAM,EAAE,MAAM,EAAE,GACf,OAAO,CAsBT;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO5D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAQrD"}
@@ -0,0 +1,187 @@
1
+ // ============================================================================
2
+ // Utility Functions
3
+ // ============================================================================
4
+ /**
5
+ * Format a date for display
6
+ */
7
+ export function formatDate(date) {
8
+ const now = new Date();
9
+ const diff = now.getTime() - date.getTime();
10
+ const seconds = Math.floor(diff / 1000);
11
+ const minutes = Math.floor(seconds / 60);
12
+ const hours = Math.floor(minutes / 60);
13
+ const days = Math.floor(hours / 24);
14
+ if (seconds < 60)
15
+ return "just now";
16
+ if (minutes < 60)
17
+ return `${minutes}m ago`;
18
+ if (hours < 24)
19
+ return `${hours}h ago`;
20
+ if (days < 7)
21
+ return `${days}d ago`;
22
+ return date.toLocaleDateString();
23
+ }
24
+ /**
25
+ * Format a duration in milliseconds to human-readable
26
+ */
27
+ export function formatDuration(ms) {
28
+ const seconds = Math.floor(ms / 1000);
29
+ const minutes = Math.floor(seconds / 60);
30
+ const hours = Math.floor(minutes / 60);
31
+ if (seconds < 60)
32
+ return `${seconds}s`;
33
+ if (minutes < 60)
34
+ return `${minutes}m ${seconds % 60}s`;
35
+ return `${hours}h ${minutes % 60}m`;
36
+ }
37
+ /**
38
+ * Format bytes to human-readable
39
+ */
40
+ export function formatBytes(bytes) {
41
+ const units = ["B", "KB", "MB", "GB"];
42
+ let size = bytes;
43
+ let unitIndex = 0;
44
+ while (size >= 1024 && unitIndex < units.length - 1) {
45
+ size /= 1024;
46
+ unitIndex++;
47
+ }
48
+ return `${size.toFixed(1)} ${units[unitIndex]}`;
49
+ }
50
+ /**
51
+ * Generate a pairing code (XXX-XXX-XXX format)
52
+ */
53
+ export function generatePairingCode() {
54
+ const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; // No ambiguous chars
55
+ const segments = [];
56
+ for (let i = 0; i < 3; i++) {
57
+ let segment = "";
58
+ for (let j = 0; j < 3; j++) {
59
+ segment += chars[Math.floor(Math.random() * chars.length)];
60
+ }
61
+ segments.push(segment);
62
+ }
63
+ return segments.join("-");
64
+ }
65
+ /**
66
+ * Validate a pairing code format
67
+ */
68
+ export function isValidPairingCode(code) {
69
+ return /^[A-Z0-9]{3}-[A-Z0-9]{3}-[A-Z0-9]{3}$/.test(code);
70
+ }
71
+ /**
72
+ * Deep clone an object
73
+ */
74
+ export function deepClone(obj) {
75
+ return JSON.parse(JSON.stringify(obj));
76
+ }
77
+ /**
78
+ * Debounce a function
79
+ */
80
+ export function debounce(func, wait) {
81
+ let timeout = null;
82
+ return function executedFunction(...args) {
83
+ const later = () => {
84
+ timeout = null;
85
+ func(...args);
86
+ };
87
+ if (timeout)
88
+ clearTimeout(timeout);
89
+ timeout = setTimeout(later, wait);
90
+ };
91
+ }
92
+ /**
93
+ * Throttle a function
94
+ */
95
+ export function throttle(func, limit) {
96
+ let inThrottle;
97
+ return function executedFunction(...args) {
98
+ if (!inThrottle) {
99
+ func(...args);
100
+ inThrottle = true;
101
+ setTimeout(() => (inThrottle = false), limit);
102
+ }
103
+ };
104
+ }
105
+ /**
106
+ * Sleep for a specified duration
107
+ */
108
+ export function sleep(ms) {
109
+ return new Promise((resolve) => setTimeout(resolve, ms));
110
+ }
111
+ /**
112
+ * Retry a function with exponential backoff
113
+ */
114
+ export async function retry(fn, options = {}) {
115
+ const { maxAttempts = 3, initialDelay = 1000, maxDelay = 10000, backoffMultiplier = 2, } = options;
116
+ let lastError;
117
+ let delay = initialDelay;
118
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
119
+ try {
120
+ return await fn();
121
+ }
122
+ catch (error) {
123
+ lastError = error;
124
+ if (attempt === maxAttempts) {
125
+ throw lastError;
126
+ }
127
+ await sleep(delay);
128
+ delay = Math.min(delay * backoffMultiplier, maxDelay);
129
+ }
130
+ }
131
+ throw lastError;
132
+ }
133
+ /**
134
+ * Parse a user's home directory path
135
+ */
136
+ export function resolveHomePath(path) {
137
+ if (path.startsWith("~")) {
138
+ return path.replace("~", process.env.HOME || process.env.USERPROFILE || "");
139
+ }
140
+ return path;
141
+ }
142
+ /**
143
+ * Check if a path is within allowed directories
144
+ */
145
+ export function isPathAllowed(path, allowed, denied) {
146
+ const resolved = resolveHomePath(path);
147
+ // Check if path is in allowed list
148
+ const isAllowed = allowed.some((dir) => resolved.startsWith(resolveHomePath(dir)));
149
+ if (!isAllowed)
150
+ return false;
151
+ // Check if path matches denied patterns
152
+ const isDenied = denied.some((pattern) => {
153
+ // Simple glob pattern matching
154
+ const regex = new RegExp("^" +
155
+ pattern
156
+ .replace(/\*/g, ".*")
157
+ .replace(/\?/g, ".") +
158
+ "$");
159
+ return regex.test(resolved);
160
+ });
161
+ return !isDenied;
162
+ }
163
+ /**
164
+ * Extract device ID from JWT token
165
+ */
166
+ export function extractDeviceId(token) {
167
+ try {
168
+ const payload = JSON.parse(atob(token.split(".")[1]));
169
+ return payload.sub || null;
170
+ }
171
+ catch {
172
+ return null;
173
+ }
174
+ }
175
+ /**
176
+ * Check if a JWT token is expired
177
+ */
178
+ export function isTokenExpired(token) {
179
+ try {
180
+ const payload = JSON.parse(atob(token.split(".")[1]));
181
+ const now = Math.floor(Date.now() / 1000);
182
+ return payload.exp < now;
183
+ }
184
+ catch {
185
+ return true;
186
+ }
187
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Unit Tests for Shared Package Utilities
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.test.d.ts","sourceRoot":"","sources":["../src/utils.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Unit Tests for Shared Package Utilities
3
+ */
4
+ import { describe, it, expect, vi } from 'vitest';
5
+ import { formatDate, formatDuration, formatBytes, generatePairingCode, isValidPairingCode, deepClone, debounce, throttle, sleep, retry, resolveHomePath, isPathAllowed, extractDeviceId, isTokenExpired, } from './index.js';
6
+ describe('formatDate', () => {
7
+ it('should format dates as "just now" for recent dates', () => {
8
+ const now = new Date();
9
+ const date = new Date(now.getTime() - 30 * 1000);
10
+ expect(formatDate(date)).toBe('just now');
11
+ });
12
+ it('should format dates as "Xm ago" for minutes', () => {
13
+ const now = new Date();
14
+ const date = new Date(now.getTime() - 30 * 60 * 1000);
15
+ expect(formatDate(date)).toBe('30m ago');
16
+ });
17
+ it('should format dates as "Xh ago" for hours', () => {
18
+ const now = new Date();
19
+ const date = new Date(now.getTime() - 5 * 60 * 60 * 1000);
20
+ expect(formatDate(date)).toBe('5h ago');
21
+ });
22
+ it('should format dates as "Xd ago" for days', () => {
23
+ const now = new Date();
24
+ const date = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000);
25
+ expect(formatDate(date)).toBe('3d ago');
26
+ });
27
+ it('should format older dates as locale date string', () => {
28
+ const date = new Date('2020-01-01');
29
+ const formatted = formatDate(date);
30
+ expect(formatted).toBe(date.toLocaleDateString());
31
+ });
32
+ });
33
+ describe('formatDuration', () => {
34
+ it('should format seconds', () => {
35
+ expect(formatDuration(5000)).toBe('5s');
36
+ expect(formatDuration(59000)).toBe('59s');
37
+ });
38
+ it('should format minutes and seconds', () => {
39
+ expect(formatDuration(60000)).toBe('1m 0s');
40
+ expect(formatDuration(125000)).toBe('2m 5s');
41
+ });
42
+ it('should format hours and minutes', () => {
43
+ expect(formatDuration(3600000)).toBe('1h 0m');
44
+ expect(formatDuration(3665000)).toBe('1h 1m');
45
+ expect(formatDuration(7265000)).toBe('2h 1m');
46
+ });
47
+ });
48
+ describe('formatBytes', () => {
49
+ it('should format bytes', () => {
50
+ expect(formatBytes(0)).toBe('0.0 B');
51
+ expect(formatBytes(512)).toBe('512.0 B');
52
+ expect(formatBytes(1023)).toBe('1023.0 B');
53
+ });
54
+ it('should format kilobytes', () => {
55
+ expect(formatBytes(1024)).toBe('1.0 KB');
56
+ expect(formatBytes(1536)).toBe('1.5 KB');
57
+ expect(formatBytes(10240)).toBe('10.0 KB');
58
+ });
59
+ it('should format megabytes', () => {
60
+ expect(formatBytes(1048576)).toBe('1.0 MB');
61
+ expect(formatBytes(5242880)).toBe('5.0 MB');
62
+ });
63
+ it('should format gigabytes', () => {
64
+ expect(formatBytes(1073741824)).toBe('1.0 GB');
65
+ expect(formatBytes(2147483648)).toBe('2.0 GB');
66
+ });
67
+ });
68
+ describe('generatePairingCode', () => {
69
+ it('should generate valid pairing code format', () => {
70
+ const code = generatePairingCode();
71
+ expect(code).toMatch(/^[A-Z0-9]{3}-[A-Z0-9]{3}-[A-Z0-9]{3}$/);
72
+ });
73
+ it('should generate unique codes', () => {
74
+ const codes = new Set();
75
+ for (let i = 0; i < 100; i++) {
76
+ codes.add(generatePairingCode());
77
+ }
78
+ expect(codes.size).toBe(100);
79
+ });
80
+ it('should not contain ambiguous characters', () => {
81
+ const code = generatePairingCode();
82
+ expect(code).not.toMatch(/[IO01]/);
83
+ });
84
+ });
85
+ describe('isValidPairingCode', () => {
86
+ it('should validate correct pairing codes', () => {
87
+ expect(isValidPairingCode('ABC-123-DEF')).toBe(true);
88
+ expect(isValidPairingCode('XYZ-987-ABC')).toBe(true);
89
+ });
90
+ it('should reject incorrect formats', () => {
91
+ expect(isValidPairingCode('abc-123-def')).toBe(false); // lowercase
92
+ expect(isValidPairingCode('ABC123DEF')).toBe(false); // no dashes
93
+ expect(isValidPairingCode('AB-123-DEF')).toBe(false); // too short
94
+ expect(isValidPairingCode('ABC-123-DEF-456')).toBe(false); // too long
95
+ expect(isValidPairingCode('')).toBe(false); // empty
96
+ expect(isValidPairingCode('ABC-1I3-DEF')).toBe(true); // valid format
97
+ });
98
+ });
99
+ describe('deepClone', () => {
100
+ it('should clone objects', () => {
101
+ const obj = { a: 1, b: 2 };
102
+ const cloned = deepClone(obj);
103
+ expect(cloned).toEqual(obj);
104
+ expect(cloned).not.toBe(obj);
105
+ });
106
+ it('should clone nested objects', () => {
107
+ const obj = { a: { b: { c: 1 } } };
108
+ const cloned = deepClone(obj);
109
+ expect(cloned).toEqual(obj);
110
+ expect(cloned.a).not.toBe(obj.a);
111
+ });
112
+ it('should clone arrays', () => {
113
+ const arr = [1, 2, 3];
114
+ const cloned = deepClone(arr);
115
+ expect(cloned).toEqual(arr);
116
+ expect(cloned).not.toBe(arr);
117
+ });
118
+ it('should clone objects with arrays', () => {
119
+ const obj = { arr: [1, 2, 3] };
120
+ const cloned = deepClone(obj);
121
+ expect(cloned).toEqual(obj);
122
+ expect(cloned.arr).not.toBe(obj.arr);
123
+ });
124
+ });
125
+ describe('debounce', () => {
126
+ it('should debounce function calls', () => {
127
+ vi.useFakeTimers();
128
+ const fn = vi.fn();
129
+ const debouncedFn = debounce(fn, 100);
130
+ debouncedFn();
131
+ debouncedFn();
132
+ debouncedFn();
133
+ expect(fn).not.toHaveBeenCalled();
134
+ vi.advanceTimersByTime(100);
135
+ expect(fn).toHaveBeenCalledTimes(1);
136
+ vi.useRealTimers();
137
+ });
138
+ it('should call function with latest arguments', () => {
139
+ vi.useFakeTimers();
140
+ const fn = vi.fn();
141
+ const debouncedFn = debounce(fn, 100);
142
+ debouncedFn(1);
143
+ debouncedFn(2);
144
+ debouncedFn(3);
145
+ vi.advanceTimersByTime(100);
146
+ expect(fn).toHaveBeenCalledWith(3);
147
+ expect(fn).toHaveBeenCalledTimes(1);
148
+ vi.useRealTimers();
149
+ });
150
+ });
151
+ describe('throttle', () => {
152
+ it('should throttle function calls', () => {
153
+ vi.useFakeTimers();
154
+ const fn = vi.fn();
155
+ const throttledFn = throttle(fn, 100);
156
+ throttledFn();
157
+ throttledFn();
158
+ throttledFn();
159
+ expect(fn).toHaveBeenCalledTimes(1);
160
+ vi.advanceTimersByTime(100);
161
+ throttledFn();
162
+ expect(fn).toHaveBeenCalledTimes(2);
163
+ vi.useRealTimers();
164
+ });
165
+ });
166
+ describe('sleep', () => {
167
+ it('should sleep for specified duration', async () => {
168
+ vi.useFakeTimers();
169
+ const start = Date.now();
170
+ const promise = sleep(100);
171
+ vi.advanceTimersByTime(100);
172
+ await promise;
173
+ vi.useRealTimers();
174
+ });
175
+ });
176
+ describe('retry', () => {
177
+ it('should succeed on first try', async () => {
178
+ const fn = vi.fn().mockResolvedValue('success');
179
+ const result = await retry(fn);
180
+ expect(result).toBe('success');
181
+ expect(fn).toHaveBeenCalledTimes(1);
182
+ });
183
+ it('should retry on failure', async () => {
184
+ const fn = vi.fn()
185
+ .mockRejectedValueOnce(new Error('fail'))
186
+ .mockResolvedValue('success');
187
+ const result = await retry(fn, { maxAttempts: 3, initialDelay: 10 });
188
+ expect(result).toBe('success');
189
+ expect(fn).toHaveBeenCalledTimes(2);
190
+ });
191
+ it('should throw after max attempts', async () => {
192
+ const fn = vi.fn().mockRejectedValue(new Error('fail'));
193
+ await expect(retry(fn, { maxAttempts: 2, initialDelay: 10 })).rejects.toThrow('fail');
194
+ expect(fn).toHaveBeenCalledTimes(2);
195
+ });
196
+ it('should use exponential backoff', async () => {
197
+ vi.useFakeTimers();
198
+ const fn = vi.fn()
199
+ .mockRejectedValueOnce(new Error('fail'))
200
+ .mockRejectedValueOnce(new Error('fail'))
201
+ .mockResolvedValue('success');
202
+ const promise = retry(fn, {
203
+ maxAttempts: 3,
204
+ initialDelay: 100,
205
+ backoffMultiplier: 2,
206
+ });
207
+ // First attempt fails
208
+ await vi.advanceTimersByTimeAsync(100);
209
+ // Second attempt fails
210
+ await vi.advanceTimersByTimeAsync(200);
211
+ await promise;
212
+ expect(fn).toHaveBeenCalledTimes(3);
213
+ vi.useRealTimers();
214
+ });
215
+ });
216
+ describe('resolveHomePath', () => {
217
+ it('should replace ~ with HOME env var', () => {
218
+ const originalHome = process.env.HOME;
219
+ process.env.HOME = '/Users/test';
220
+ expect(resolveHomePath('~/Documents')).toBe('/Users/test/Documents');
221
+ expect(resolveHomePath('~/')).toBe('/Users/test/');
222
+ process.env.HOME = originalHome;
223
+ });
224
+ it('should not replace ~ in middle of path', () => {
225
+ expect(resolveHomePath('/path/~test')).toBe('/path/~test');
226
+ });
227
+ it('should return unchanged path if no ~', () => {
228
+ expect(resolveHomePath('/absolute/path')).toBe('/absolute/path');
229
+ });
230
+ });
231
+ describe('isPathAllowed', () => {
232
+ it('should check allowed paths', () => {
233
+ expect(isPathAllowed('/Users/test/project', ['/Users/test'], []))
234
+ .toBe(true);
235
+ expect(isPathAllowed('/Users/test/project', ['/Users/other'], []))
236
+ .toBe(false);
237
+ });
238
+ it('should check denied patterns', () => {
239
+ // Denied patterns match the entire path
240
+ expect(isPathAllowed('/Users/test/project/node_modules', ['/Users/test'], ['*/node_modules']))
241
+ .toBe(false);
242
+ expect(isPathAllowed('/Users/test/project/src', ['/Users/test'], ['*/node_modules']))
243
+ .toBe(true);
244
+ });
245
+ it('should support glob patterns', () => {
246
+ // Test with simpler glob patterns
247
+ expect(isPathAllowed('/Users/test/.env', ['/Users/test'], ['*/.env']))
248
+ .toBe(false);
249
+ expect(isPathAllowed('/Users/test/src', ['/Users/test'], ['*/.env']))
250
+ .toBe(true);
251
+ });
252
+ });
253
+ describe('extractDeviceId', () => {
254
+ it('should extract device ID from valid JWT', () => {
255
+ // Create a mock JWT token
256
+ const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
257
+ const payload = btoa(JSON.stringify({ sub: 'device-123', exp: Date.now() / 1000 + 3600 }));
258
+ const signature = 'signature';
259
+ const token = `${header}.${payload}.${signature}`;
260
+ expect(extractDeviceId(token)).toBe('device-123');
261
+ });
262
+ it('should return null for invalid JWT', () => {
263
+ expect(extractDeviceId('invalid')).toBeNull();
264
+ expect(extractDeviceId('not.a.jwt')).toBeNull();
265
+ });
266
+ it('should return null if no sub in payload', () => {
267
+ const header = btoa(JSON.stringify({ alg: 'HS256' }));
268
+ const payload = btoa(JSON.stringify({ exp: 123 }));
269
+ const token = `${header}.${payload}.signature`;
270
+ expect(extractDeviceId(token)).toBeNull();
271
+ });
272
+ });
273
+ describe('isTokenExpired', () => {
274
+ it('should return false for valid token', () => {
275
+ const header = btoa(JSON.stringify({ alg: 'HS256' }));
276
+ const payload = btoa(JSON.stringify({ exp: Math.floor(Date.now() / 1000) + 3600 }));
277
+ const token = `${header}.${payload}.signature`;
278
+ expect(isTokenExpired(token)).toBe(false);
279
+ });
280
+ it('should return true for expired token', () => {
281
+ const header = btoa(JSON.stringify({ alg: 'HS256' }));
282
+ const payload = btoa(JSON.stringify({ exp: Math.floor(Date.now() / 1000) - 3600 }));
283
+ const token = `${header}.${payload}.signature`;
284
+ expect(isTokenExpired(token)).toBe(true);
285
+ });
286
+ it('should return true for invalid token', () => {
287
+ expect(isTokenExpired('invalid')).toBe(true);
288
+ expect(isTokenExpired('not.a.jwt')).toBe(true);
289
+ });
290
+ });
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "cc-control-shared",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "files": [
9
+ "dist",
10
+ "package.json"
11
+ ],
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js",
18
+ "require": "./dist/index.cjs"
19
+ },
20
+ "./types": {
21
+ "types": "./dist/types/index.d.ts",
22
+ "import": "./dist/types/index.js"
23
+ },
24
+ "./protocols": {
25
+ "types": "./dist/protocols/index.d.ts",
26
+ "import": "./dist/protocols/index.js"
27
+ },
28
+ "./utils": {
29
+ "types": "./dist/utils/index.d.ts",
30
+ "import": "./dist/utils/index.js"
31
+ }
32
+ },
33
+ "scripts": {
34
+ "build": "tsc",
35
+ "dev": "tsc --watch",
36
+ "clean": "rm -rf dist",
37
+ "lint": "eslint src --ext .ts",
38
+ "typecheck": "tsc --noEmit"
39
+ },
40
+ "dependencies": {
41
+ "zod": "^3.23.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^20.0.0",
45
+ "typescript": "^5.6.0"
46
+ }
47
+ }