claude-chrome-parallel 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -149,6 +149,19 @@ All sessions work without conflicts!
149
149
  | `javascript_tool` | Execute JavaScript |
150
150
  | `tabs_context_mcp` | Get available tabs |
151
151
  | `tabs_create_mcp` | Create new tab |
152
+ | `network` | Simulate network conditions (3G, 4G, offline, custom) |
153
+
154
+ ### Network Simulation
155
+
156
+ Test how your app behaves under different network conditions:
157
+
158
+ ```
159
+ You: Simulate 3G network and navigate to myapp.com
160
+
161
+ Claude: [Applies 3G throttling: 1.5Mbps down, 750Kbps up, 100ms latency]
162
+ ```
163
+
164
+ Available presets: `offline`, `slow-2g`, `2g`, `3g`, `4g`, `fast-wifi`, `custom`, `clear`
152
165
 
153
166
  ---
154
167
 
@@ -196,6 +209,15 @@ claude-chrome-parallel serve --port 9223
196
209
 
197
210
  # Check installation health
198
211
  claude-chrome-parallel doctor
212
+
213
+ # View session status and statistics
214
+ claude-chrome-parallel status
215
+
216
+ # View status as JSON (for automation)
217
+ claude-chrome-parallel status --json
218
+
219
+ # Clean up stale sessions and old backups
220
+ claude-chrome-parallel cleanup --max-age 24 --keep-backups 10
199
221
  ```
200
222
 
201
223
  ---
@@ -259,6 +281,9 @@ claude-chrome-parallel recover --list-backups
259
281
  | Connection type | Shared extension state | Independent CDP |
260
282
  | Max concurrent sessions | 1 | 20+ tested |
261
283
  | Auto Chrome launch | ❌ | ✅ |
284
+ | Network simulation | ❌ | ✅ 3G/4G/offline presets |
285
+ | Session auto-cleanup | ❌ | ✅ TTL-based |
286
+ | Connection pooling | ❌ | ✅ Pre-warmed pages |
262
287
 
263
288
  ---
264
289
 
@@ -0,0 +1,88 @@
1
+ /**
2
+ * CDP Connection Pool - Pre-allocate and manage page instances for faster session creation
3
+ */
4
+ import { Page } from 'puppeteer-core';
5
+ import { CDPClient } from './client';
6
+ export interface PoolConfig {
7
+ /** Minimum number of pre-allocated pages to keep ready (default: 2) */
8
+ minPoolSize?: number;
9
+ /** Maximum number of pre-allocated pages (default: 10) */
10
+ maxPoolSize?: number;
11
+ /** Page idle timeout in ms before returning to pool (default: 5 minutes) */
12
+ pageIdleTimeout?: number;
13
+ /** Whether to pre-warm pages on startup (default: true) */
14
+ preWarm?: boolean;
15
+ }
16
+ export interface PoolStats {
17
+ /** Number of pages currently in the pool (ready to use) */
18
+ availablePages: number;
19
+ /** Number of pages currently in use */
20
+ inUsePages: number;
21
+ /** Total pages created since pool initialization */
22
+ totalPagesCreated: number;
23
+ /** Number of pages reused from pool */
24
+ pagesReused: number;
25
+ /** Number of pages created on-demand (pool was empty) */
26
+ pagesCreatedOnDemand: number;
27
+ /** Average time to acquire a page (ms) */
28
+ avgAcquireTimeMs: number;
29
+ }
30
+ export declare class CDPConnectionPool {
31
+ private cdpClient;
32
+ private config;
33
+ private availablePages;
34
+ private inUsePages;
35
+ private maintenanceTimer;
36
+ private isInitialized;
37
+ private totalPagesCreated;
38
+ private pagesReused;
39
+ private pagesCreatedOnDemand;
40
+ private acquireTimes;
41
+ constructor(cdpClient?: CDPClient, config?: PoolConfig);
42
+ /**
43
+ * Initialize the pool with pre-warmed pages
44
+ */
45
+ initialize(): Promise<void>;
46
+ /**
47
+ * Acquire a page from the pool
48
+ */
49
+ acquirePage(): Promise<Page>;
50
+ /**
51
+ * Release a page back to the pool
52
+ */
53
+ releasePage(page: Page): Promise<void>;
54
+ /**
55
+ * Create a new page
56
+ */
57
+ private createNewPage;
58
+ /**
59
+ * Ensure minimum number of pages in pool
60
+ */
61
+ private ensureMinimumPages;
62
+ /**
63
+ * Replenish pool asynchronously
64
+ */
65
+ private replenishPoolAsync;
66
+ /**
67
+ * Perform maintenance on the pool
68
+ */
69
+ private performMaintenance;
70
+ /**
71
+ * Get pool statistics
72
+ */
73
+ getStats(): PoolStats;
74
+ /**
75
+ * Get current configuration
76
+ */
77
+ getConfig(): Required<PoolConfig>;
78
+ /**
79
+ * Update configuration
80
+ */
81
+ updateConfig(config: Partial<PoolConfig>): void;
82
+ /**
83
+ * Shutdown the pool
84
+ */
85
+ shutdown(): Promise<void>;
86
+ }
87
+ export declare function getCDPConnectionPool(config?: PoolConfig): CDPConnectionPool;
88
+ //# sourceMappingURL=connection-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-pool.d.ts","sourceRoot":"","sources":["../../src/cdp/connection-pool.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAgB,MAAM,UAAU,CAAC;AAEnD,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,2DAA2D;IAC3D,cAAc,EAAE,MAAM,CAAC;IACvB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,oBAAoB,EAAE,MAAM,CAAC;IAC7B,0CAA0C;IAC1C,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAeD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,cAAc,CAAoB;IAC1C,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,gBAAgB,CAA+B;IACvD,OAAO,CAAC,aAAa,CAAS;IAG9B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,YAAY,CAAgB;gBAExB,SAAS,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,UAAU;IAKtD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBjC;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IA2ClC;;OAEG;IACG,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAmD5C;;OAEG;YACW,aAAa;IAM3B;;OAEG;YACW,kBAAkB;IAsBhC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;OAEG;YACW,kBAAkB;IAiChC;;OAEG;IACH,QAAQ,IAAI,SAAS;IAgBrB;;OAEG;IACH,SAAS,IAAI,QAAQ,CAAC,UAAU,CAAC;IAIjC;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI;IAI/C;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CA6BhC;AAKD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,iBAAiB,CAK3E"}
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ /**
3
+ * CDP Connection Pool - Pre-allocate and manage page instances for faster session creation
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CDPConnectionPool = void 0;
7
+ exports.getCDPConnectionPool = getCDPConnectionPool;
8
+ const client_1 = require("./client");
9
+ const DEFAULT_CONFIG = {
10
+ minPoolSize: 2,
11
+ maxPoolSize: 10,
12
+ pageIdleTimeout: 5 * 60 * 1000, // 5 minutes
13
+ preWarm: true,
14
+ };
15
+ class CDPConnectionPool {
16
+ cdpClient;
17
+ config;
18
+ availablePages = [];
19
+ inUsePages = new Map();
20
+ maintenanceTimer = null;
21
+ isInitialized = false;
22
+ // Stats
23
+ totalPagesCreated = 0;
24
+ pagesReused = 0;
25
+ pagesCreatedOnDemand = 0;
26
+ acquireTimes = [];
27
+ constructor(cdpClient, config) {
28
+ this.cdpClient = cdpClient || (0, client_1.getCDPClient)();
29
+ this.config = { ...DEFAULT_CONFIG, ...config };
30
+ }
31
+ /**
32
+ * Initialize the pool with pre-warmed pages
33
+ */
34
+ async initialize() {
35
+ if (this.isInitialized)
36
+ return;
37
+ await this.cdpClient.connect();
38
+ if (this.config.preWarm) {
39
+ console.error(`[Pool] Pre-warming ${this.config.minPoolSize} pages...`);
40
+ await this.ensureMinimumPages();
41
+ }
42
+ // Start maintenance timer
43
+ this.maintenanceTimer = setInterval(() => {
44
+ this.performMaintenance();
45
+ }, 30000); // Every 30 seconds
46
+ this.maintenanceTimer.unref();
47
+ this.isInitialized = true;
48
+ console.error('[Pool] Connection pool initialized');
49
+ }
50
+ /**
51
+ * Acquire a page from the pool
52
+ */
53
+ async acquirePage() {
54
+ const startTime = Date.now();
55
+ // Ensure initialized
56
+ if (!this.isInitialized) {
57
+ await this.initialize();
58
+ }
59
+ let page;
60
+ let pooledPage;
61
+ // Try to get from pool
62
+ if (this.availablePages.length > 0) {
63
+ pooledPage = this.availablePages.pop();
64
+ page = pooledPage.page;
65
+ pooledPage.lastUsedAt = Date.now();
66
+ this.pagesReused++;
67
+ }
68
+ else {
69
+ // Create new page on demand
70
+ page = await this.createNewPage();
71
+ pooledPage = {
72
+ page,
73
+ createdAt: Date.now(),
74
+ lastUsedAt: Date.now(),
75
+ };
76
+ this.pagesCreatedOnDemand++;
77
+ }
78
+ this.inUsePages.set(page, pooledPage);
79
+ // Track acquire time
80
+ const acquireTime = Date.now() - startTime;
81
+ this.acquireTimes.push(acquireTime);
82
+ if (this.acquireTimes.length > 100) {
83
+ this.acquireTimes.shift();
84
+ }
85
+ // Replenish pool in background if needed
86
+ this.replenishPoolAsync();
87
+ return page;
88
+ }
89
+ /**
90
+ * Release a page back to the pool
91
+ */
92
+ async releasePage(page) {
93
+ const pooledPage = this.inUsePages.get(page);
94
+ if (!pooledPage) {
95
+ // Page not managed by this pool, just close it
96
+ try {
97
+ await page.close();
98
+ }
99
+ catch {
100
+ // Ignore close errors
101
+ }
102
+ return;
103
+ }
104
+ this.inUsePages.delete(page);
105
+ // Check if pool is at max capacity
106
+ if (this.availablePages.length >= this.config.maxPoolSize) {
107
+ // Close the page instead of returning to pool
108
+ try {
109
+ await page.close();
110
+ }
111
+ catch {
112
+ // Ignore close errors
113
+ }
114
+ return;
115
+ }
116
+ // Reset the page state before returning to pool
117
+ try {
118
+ // Navigate to blank page to clear state
119
+ await page.goto('about:blank', { waitUntil: 'domcontentloaded', timeout: 5000 });
120
+ // Clear cookies and storage
121
+ const client = await page.createCDPSession();
122
+ await client.send('Network.clearBrowserCookies');
123
+ await client.send('Storage.clearDataForOrigin', {
124
+ origin: '*',
125
+ storageTypes: 'all',
126
+ }).catch(() => { }); // Ignore if not supported
127
+ await client.detach();
128
+ pooledPage.lastUsedAt = Date.now();
129
+ this.availablePages.push(pooledPage);
130
+ }
131
+ catch {
132
+ // Failed to reset, close the page
133
+ try {
134
+ await page.close();
135
+ }
136
+ catch {
137
+ // Ignore close errors
138
+ }
139
+ }
140
+ }
141
+ /**
142
+ * Create a new page
143
+ */
144
+ async createNewPage() {
145
+ const page = await this.cdpClient.createPage();
146
+ this.totalPagesCreated++;
147
+ return page;
148
+ }
149
+ /**
150
+ * Ensure minimum number of pages in pool
151
+ */
152
+ async ensureMinimumPages() {
153
+ const pagesToCreate = this.config.minPoolSize - this.availablePages.length;
154
+ if (pagesToCreate <= 0)
155
+ return;
156
+ const promises = [];
157
+ for (let i = 0; i < pagesToCreate; i++) {
158
+ promises.push(this.createNewPage().then((page) => {
159
+ this.availablePages.push({
160
+ page,
161
+ createdAt: Date.now(),
162
+ lastUsedAt: Date.now(),
163
+ });
164
+ }).catch((err) => {
165
+ console.error('[Pool] Failed to pre-warm page:', err);
166
+ }));
167
+ }
168
+ await Promise.all(promises);
169
+ }
170
+ /**
171
+ * Replenish pool asynchronously
172
+ */
173
+ replenishPoolAsync() {
174
+ if (this.availablePages.length < this.config.minPoolSize) {
175
+ this.ensureMinimumPages().catch((err) => {
176
+ console.error('[Pool] Failed to replenish pool:', err);
177
+ });
178
+ }
179
+ }
180
+ /**
181
+ * Perform maintenance on the pool
182
+ */
183
+ async performMaintenance() {
184
+ const now = Date.now();
185
+ const pagesToRemove = [];
186
+ // Find pages that have been idle too long
187
+ for (const pooledPage of this.availablePages) {
188
+ const idleTime = now - pooledPage.lastUsedAt;
189
+ if (idleTime > this.config.pageIdleTimeout &&
190
+ this.availablePages.length > this.config.minPoolSize) {
191
+ pagesToRemove.push(pooledPage);
192
+ }
193
+ }
194
+ // Remove idle pages
195
+ for (const pooledPage of pagesToRemove) {
196
+ const index = this.availablePages.indexOf(pooledPage);
197
+ if (index !== -1) {
198
+ this.availablePages.splice(index, 1);
199
+ try {
200
+ await pooledPage.page.close();
201
+ }
202
+ catch {
203
+ // Ignore close errors
204
+ }
205
+ }
206
+ }
207
+ if (pagesToRemove.length > 0) {
208
+ console.error(`[Pool] Maintenance: closed ${pagesToRemove.length} idle page(s)`);
209
+ }
210
+ }
211
+ /**
212
+ * Get pool statistics
213
+ */
214
+ getStats() {
215
+ const avgAcquireTime = this.acquireTimes.length > 0
216
+ ? this.acquireTimes.reduce((a, b) => a + b, 0) / this.acquireTimes.length
217
+ : 0;
218
+ return {
219
+ availablePages: this.availablePages.length,
220
+ inUsePages: this.inUsePages.size,
221
+ totalPagesCreated: this.totalPagesCreated,
222
+ pagesReused: this.pagesReused,
223
+ pagesCreatedOnDemand: this.pagesCreatedOnDemand,
224
+ avgAcquireTimeMs: Math.round(avgAcquireTime * 100) / 100,
225
+ };
226
+ }
227
+ /**
228
+ * Get current configuration
229
+ */
230
+ getConfig() {
231
+ return { ...this.config };
232
+ }
233
+ /**
234
+ * Update configuration
235
+ */
236
+ updateConfig(config) {
237
+ this.config = { ...this.config, ...config };
238
+ }
239
+ /**
240
+ * Shutdown the pool
241
+ */
242
+ async shutdown() {
243
+ if (this.maintenanceTimer) {
244
+ clearInterval(this.maintenanceTimer);
245
+ this.maintenanceTimer = null;
246
+ }
247
+ // Close all available pages
248
+ for (const pooledPage of this.availablePages) {
249
+ try {
250
+ await pooledPage.page.close();
251
+ }
252
+ catch {
253
+ // Ignore close errors
254
+ }
255
+ }
256
+ this.availablePages = [];
257
+ // Close all in-use pages
258
+ for (const [page] of this.inUsePages) {
259
+ try {
260
+ await page.close();
261
+ }
262
+ catch {
263
+ // Ignore close errors
264
+ }
265
+ }
266
+ this.inUsePages.clear();
267
+ this.isInitialized = false;
268
+ console.error('[Pool] Connection pool shutdown');
269
+ }
270
+ }
271
+ exports.CDPConnectionPool = CDPConnectionPool;
272
+ // Singleton instance
273
+ let poolInstance = null;
274
+ function getCDPConnectionPool(config) {
275
+ if (!poolInstance) {
276
+ poolInstance = new CDPConnectionPool(undefined, config);
277
+ }
278
+ return poolInstance;
279
+ }
280
+ //# sourceMappingURL=connection-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-pool.js","sourceRoot":"","sources":["../../src/cdp/connection-pool.ts"],"names":[],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -354,6 +354,134 @@ program
354
354
  console.log('⚠️ Could not recover - created new empty config');
355
355
  console.log('Your corrupted file has been backed up');
356
356
  });
357
+ program
358
+ .command('status')
359
+ .description('Show session manager status and statistics')
360
+ .option('--json', 'Output as JSON')
361
+ .action(async (options) => {
362
+ const sessionsDir = getSessionsDir();
363
+ const backupDir = path.join(os.homedir(), '.claude-chrome-parallel', 'backups');
364
+ const configPath = path.join(os.homedir(), '.claude.json');
365
+ // Gather statistics
366
+ let activeSessions = 0;
367
+ let totalSessionsSize = 0;
368
+ const sessionDetails = [];
369
+ if (fs.existsSync(sessionsDir)) {
370
+ const entries = fs.readdirSync(sessionsDir, { withFileTypes: true });
371
+ const now = Date.now();
372
+ for (const entry of entries) {
373
+ if (!entry.isDirectory())
374
+ continue;
375
+ const sessionDir = path.join(sessionsDir, entry.name);
376
+ const metadataPath = path.join(sessionDir, '.session-metadata.json');
377
+ activeSessions++;
378
+ const size = getDirSize(sessionDir);
379
+ totalSessionsSize += size;
380
+ let age = 'unknown';
381
+ if (fs.existsSync(metadataPath)) {
382
+ try {
383
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
384
+ const createdAt = new Date(metadata.createdAt).getTime();
385
+ age = formatDuration(now - createdAt);
386
+ }
387
+ catch {
388
+ // ignore
389
+ }
390
+ }
391
+ sessionDetails.push({
392
+ id: entry.name,
393
+ age,
394
+ size: formatBytes(size),
395
+ });
396
+ }
397
+ }
398
+ // Count backups
399
+ let backupCount = 0;
400
+ let backupSize = 0;
401
+ if (fs.existsSync(backupDir)) {
402
+ const backups = fs.readdirSync(backupDir).filter(f => f.startsWith('.claude.json.'));
403
+ backupCount = backups.length;
404
+ for (const backup of backups) {
405
+ const stats = fs.statSync(path.join(backupDir, backup));
406
+ backupSize += stats.size;
407
+ }
408
+ }
409
+ // Check config health
410
+ let configHealthy = true;
411
+ let configError = '';
412
+ if (fs.existsSync(configPath)) {
413
+ const content = fs.readFileSync(configPath, 'utf8');
414
+ if (!isValidJson(content)) {
415
+ configHealthy = false;
416
+ configError = 'Invalid JSON (corrupted)';
417
+ }
418
+ }
419
+ // Memory usage
420
+ const memUsage = process.memoryUsage();
421
+ const status = {
422
+ sessions: {
423
+ active: activeSessions,
424
+ totalSize: formatBytes(totalSessionsSize),
425
+ details: sessionDetails,
426
+ },
427
+ backups: {
428
+ count: backupCount,
429
+ totalSize: formatBytes(backupSize),
430
+ },
431
+ config: {
432
+ healthy: configHealthy,
433
+ error: configError || undefined,
434
+ },
435
+ memory: {
436
+ heapUsed: formatBytes(memUsage.heapUsed),
437
+ heapTotal: formatBytes(memUsage.heapTotal),
438
+ rss: formatBytes(memUsage.rss),
439
+ },
440
+ };
441
+ if (options.json) {
442
+ console.log(JSON.stringify(status, null, 2));
443
+ return;
444
+ }
445
+ // Pretty print
446
+ console.log('Claude Chrome Parallel Status');
447
+ console.log('═'.repeat(40));
448
+ console.log();
449
+ // Sessions
450
+ console.log('Sessions');
451
+ console.log('─'.repeat(20));
452
+ console.log(` Active: ${activeSessions}`);
453
+ console.log(` Total Size: ${formatBytes(totalSessionsSize)}`);
454
+ if (sessionDetails.length > 0) {
455
+ console.log(' Details:');
456
+ for (const s of sessionDetails) {
457
+ console.log(` - ${s.id} (${s.age}, ${s.size})`);
458
+ }
459
+ }
460
+ console.log();
461
+ // Backups
462
+ console.log('Backups');
463
+ console.log('─'.repeat(20));
464
+ console.log(` Count: ${backupCount}`);
465
+ console.log(` Total Size: ${formatBytes(backupSize)}`);
466
+ console.log();
467
+ // Config
468
+ console.log('Config Health');
469
+ console.log('─'.repeat(20));
470
+ if (configHealthy) {
471
+ console.log(' ✅ .claude.json is healthy');
472
+ }
473
+ else {
474
+ console.log(` ❌ .claude.json: ${configError}`);
475
+ console.log(' Run: claude-chrome-parallel recover');
476
+ }
477
+ console.log();
478
+ // Memory
479
+ console.log('Memory');
480
+ console.log('─'.repeat(20));
481
+ console.log(` Heap Used: ${formatBytes(memUsage.heapUsed)}`);
482
+ console.log(` Heap Total: ${formatBytes(memUsage.heapTotal)}`);
483
+ console.log(` RSS: ${formatBytes(memUsage.rss)}`);
484
+ });
357
485
  program
358
486
  .command('cleanup')
359
487
  .description('Clean up stale sessions and old backups')
@@ -506,6 +634,44 @@ function formatBytes(bytes) {
506
634
  const i = Math.floor(Math.log(bytes) / Math.log(k));
507
635
  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
508
636
  }
637
+ /**
638
+ * Format duration in milliseconds as human readable string
639
+ */
640
+ function formatDuration(ms) {
641
+ const seconds = Math.floor(ms / 1000);
642
+ const minutes = Math.floor(seconds / 60);
643
+ const hours = Math.floor(minutes / 60);
644
+ const days = Math.floor(hours / 24);
645
+ if (days > 0)
646
+ return `${days}d ${hours % 24}h`;
647
+ if (hours > 0)
648
+ return `${hours}h ${minutes % 60}m`;
649
+ if (minutes > 0)
650
+ return `${minutes}m ${seconds % 60}s`;
651
+ return `${seconds}s`;
652
+ }
653
+ /**
654
+ * Get total size of a directory recursively
655
+ */
656
+ function getDirSize(dirPath) {
657
+ let totalSize = 0;
658
+ try {
659
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
660
+ for (const entry of entries) {
661
+ const fullPath = path.join(dirPath, entry.name);
662
+ if (entry.isDirectory()) {
663
+ totalSize += getDirSize(fullPath);
664
+ }
665
+ else if (entry.isFile()) {
666
+ totalSize += fs.statSync(fullPath).size;
667
+ }
668
+ }
669
+ }
670
+ catch {
671
+ // Permission denied or other errors
672
+ }
673
+ return totalSize;
674
+ }
509
675
  /**
510
676
  * Attempt to recover valid JSON from corrupted content
511
677
  */