@elvatis_com/openclaw-cli-bridge-elvatis 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > OpenClaw plugin that bridges locally installed AI CLIs (Codex, Gemini, Claude Code) as model providers — with slash commands for instant model switching, restore, health testing, and model listing.
4
4
 
5
- **Current version:** `1.3.0`
5
+ **Current version:** `1.3.1`
6
6
 
7
7
  ---
8
8
 
@@ -287,6 +287,11 @@ npm test # vitest run (45 tests)
287
287
 
288
288
  ## Changelog
289
289
 
290
+ ### v1.3.1
291
+ - **fix:** /claude-login, /gemini-login, /chatgpt-login now bake cookies into persistent profile dirs
292
+ - **fix:** After gateway restart, providers auto-reconnect from saved profile (no browser tabs needed)
293
+ - **fix:** Better debug logging when persistent headless context fails (Cloudflare etc.)
294
+
290
295
  ### v1.3.0
291
296
  - **fix:** Browser persistence after gateway restart — each provider launches its own persistent Chromium if OpenClaw browser is unavailable
292
297
  - **feat:** `ensureAllProviderContexts()` — unified startup connect for all 4 providers
package/SKILL.md CHANGED
@@ -53,4 +53,4 @@ Each command runs `openclaw models set <model>` atomically and replies with a co
53
53
 
54
54
  See `README.md` for full configuration reference and architecture diagram.
55
55
 
56
- **Version:** 1.3.0
56
+ **Version:** 1.3.1
package/index.ts CHANGED
@@ -322,12 +322,32 @@ async function cleanupBrowsers(log: (msg: string) => void): Promise<void> {
322
322
  log("[cli-bridge] browser resources cleaned up");
323
323
  }
324
324
 
325
+ /**
326
+ * Singleton guard for ensureAllProviderContexts — prevents concurrent spawns.
327
+ * If a run is already in progress, callers await the same promise instead of
328
+ * spawning a new Chromium each time.
329
+ */
330
+ let _ensureAllRunning: Promise<void> | null = null;
331
+
325
332
  /**
326
333
  * Ensure all browser provider contexts are connected.
327
334
  * 1. Try the shared OpenClaw browser (CDP 18800)
328
335
  * 2. Fallback: launch a persistent headless Chromium per provider (saved profile with cookies)
336
+ *
337
+ * SAFETY: Only one concurrent run allowed. Extra callers await the existing run.
329
338
  */
330
339
  async function ensureAllProviderContexts(log: (msg: string) => void): Promise<void> {
340
+ if (_ensureAllRunning) {
341
+ log("[cli-bridge] ensureAllProviderContexts already running — awaiting existing run");
342
+ return _ensureAllRunning;
343
+ }
344
+ _ensureAllRunning = _doEnsureAllProviderContexts(log).finally(() => {
345
+ _ensureAllRunning = null;
346
+ });
347
+ return _ensureAllRunning;
348
+ }
349
+
350
+ async function _doEnsureAllProviderContexts(log: (msg: string) => void): Promise<void> {
331
351
  const { chromium } = await import("playwright");
332
352
 
333
353
  // Try CDP first (OpenClaw browser)
@@ -399,14 +419,16 @@ async function ensureAllProviderContexts(log: (msg: string) => void): Promise<vo
399
419
  });
400
420
  const page = await pCtx.newPage();
401
421
  await page.goto(cfg.homeUrl, { waitUntil: "domcontentloaded", timeout: 15_000 });
402
- await new Promise(r => setTimeout(r, 3000));
422
+ await new Promise(r => setTimeout(r, 4000));
403
423
  const visible = await page.locator(cfg.verifySelector).isVisible().catch(() => false);
404
424
  if (visible) {
405
425
  ctx = pCtx;
406
426
  log(`[cli-bridge:${cfg.name}] launched persistent context ✅`);
407
427
  } else {
428
+ const title = await page.title().catch(() => "unknown");
429
+ const bodySnippet = await page.evaluate(() => document.body?.innerText?.substring(0, 100) ?? "").catch(() => "");
430
+ log(`[cli-bridge:${cfg.name}] persistent headless: editor not visible — title="${title}" body="${bodySnippet}"`);
408
431
  await pCtx.close().catch(() => {});
409
- log(`[cli-bridge:${cfg.name}] persistent context: editor not visible (not logged in?)`);
410
432
  }
411
433
  } catch (err) {
412
434
  log(`[cli-bridge:${cfg.name}] could not launch browser: ${(err as Error).message}`);
@@ -708,7 +730,7 @@ function proxyTestRequest(
708
730
  const plugin = {
709
731
  id: "openclaw-cli-bridge-elvatis",
710
732
  name: "OpenClaw CLI Bridge",
711
- version: "1.3.0",
733
+ version: "1.3.1",
712
734
  description:
713
735
  "Phase 1: openai-codex auth bridge. " +
714
736
  "Phase 2: HTTP proxy for gemini/claude CLIs. " +
@@ -846,8 +868,7 @@ const plugin = {
846
868
  const editor = await page.locator(".ProseMirror").isVisible().catch(() => false);
847
869
  if (editor) { claudeContext = ctx; return ctx; }
848
870
  }
849
- // Fallback: try persistent context
850
- await ensureAllProviderContexts((msg) => api.logger.info(msg));
871
+ // No fallback spawn — return existing context or null to avoid Chromium leak
851
872
  return claudeContext;
852
873
  },
853
874
  getGeminiContext: () => geminiContext,
@@ -859,8 +880,7 @@ const plugin = {
859
880
  const editor = await page.locator(".ql-editor").isVisible().catch(() => false);
860
881
  if (editor) { geminiContext = ctx; return ctx; }
861
882
  }
862
- // Fallback: try persistent context
863
- await ensureAllProviderContexts((msg) => api.logger.info(msg));
883
+ // No fallback spawn — return existing context or null to avoid Chromium leak
864
884
  return geminiContext;
865
885
  },
866
886
  getChatGPTContext: () => chatgptContext,
@@ -872,8 +892,7 @@ const plugin = {
872
892
  const editor = await page.locator("#prompt-textarea").isVisible().catch(() => false);
873
893
  if (editor) { chatgptContext = ctx; return ctx; }
874
894
  }
875
- // Fallback: try persistent context
876
- await ensureAllProviderContexts((msg) => api.logger.info(msg));
895
+ // No fallback spawn — return existing context or null to avoid Chromium leak
877
896
  return chatgptContext;
878
897
  },
879
898
  });
@@ -917,8 +936,7 @@ const plugin = {
917
936
  const editor = await page.locator(".ProseMirror").isVisible().catch(() => false);
918
937
  if (editor) { claudeContext = ctx; return ctx; }
919
938
  }
920
- // Fallback: try persistent context
921
- await ensureAllProviderContexts((msg) => api.logger.info(msg));
939
+ // No fallback spawn — return existing context or null to avoid Chromium leak
922
940
  return claudeContext;
923
941
  },
924
942
  getGeminiContext: () => geminiContext,
@@ -930,8 +948,7 @@ const plugin = {
930
948
  const editor = await page.locator(".ql-editor").isVisible().catch(() => false);
931
949
  if (editor) { geminiContext = ctx; return ctx; }
932
950
  }
933
- // Fallback: try persistent context
934
- await ensureAllProviderContexts((msg) => api.logger.info(msg));
951
+ // No fallback spawn — return existing context or null to avoid Chromium leak
935
952
  return geminiContext;
936
953
  },
937
954
  getChatGPTContext: () => chatgptContext,
@@ -943,8 +960,7 @@ const plugin = {
943
960
  const editor = await page.locator("#prompt-textarea").isVisible().catch(() => false);
944
961
  if (editor) { chatgptContext = ctx; return ctx; }
945
962
  }
946
- // Fallback: try persistent context
947
- await ensureAllProviderContexts((msg) => api.logger.info(msg));
963
+ // No fallback spawn — return existing context or null to avoid Chromium leak
948
964
  return chatgptContext;
949
965
  },
950
966
  });
@@ -1325,6 +1341,26 @@ const plugin = {
1325
1341
 
1326
1342
  claudeContext = ctx;
1327
1343
 
1344
+ // Export + bake cookies into persistent profile
1345
+ const claudeProfileDir = join(homedir(), ".openclaw", "claude-profile");
1346
+ mkdirSync(claudeProfileDir, { recursive: true });
1347
+ try {
1348
+ const allCookies = await ctx.cookies([
1349
+ "https://claude.ai",
1350
+ "https://anthropic.com",
1351
+ ]);
1352
+ const { chromium } = await import("playwright");
1353
+ const pCtx = await chromium.launchPersistentContext(claudeProfileDir, {
1354
+ headless: true,
1355
+ args: ["--no-sandbox", "--disable-setuid-sandbox"],
1356
+ });
1357
+ await pCtx.addCookies(allCookies);
1358
+ await pCtx.close();
1359
+ api.logger.info(`[cli-bridge:claude] cookies baked into ${claudeProfileDir}`);
1360
+ } catch (err) {
1361
+ api.logger.warn(`[cli-bridge:claude] cookie bake failed: ${(err as Error).message}`);
1362
+ }
1363
+
1328
1364
  // Scan cookie expiry
1329
1365
  const expiry = await scanClaudeCookieExpiry(ctx);
1330
1366
  if (expiry) {
@@ -1402,6 +1438,27 @@ const plugin = {
1402
1438
 
1403
1439
  geminiContext = ctx;
1404
1440
 
1441
+ // Export + bake cookies into persistent profile
1442
+ const geminiProfileDir = join(homedir(), ".openclaw", "gemini-profile");
1443
+ mkdirSync(geminiProfileDir, { recursive: true });
1444
+ try {
1445
+ const allCookies = await ctx.cookies([
1446
+ "https://gemini.google.com",
1447
+ "https://accounts.google.com",
1448
+ "https://google.com",
1449
+ ]);
1450
+ const { chromium } = await import("playwright");
1451
+ const pCtx = await chromium.launchPersistentContext(geminiProfileDir, {
1452
+ headless: true,
1453
+ args: ["--no-sandbox", "--disable-setuid-sandbox"],
1454
+ });
1455
+ await pCtx.addCookies(allCookies);
1456
+ await pCtx.close();
1457
+ api.logger.info(`[cli-bridge:gemini] cookies baked into ${geminiProfileDir}`);
1458
+ } catch (err) {
1459
+ api.logger.warn(`[cli-bridge:gemini] cookie bake failed: ${(err as Error).message}`);
1460
+ }
1461
+
1405
1462
  const expiry = await scanGeminiCookieExpiry(ctx);
1406
1463
  if (expiry) {
1407
1464
  saveGeminiExpiry(expiry);
@@ -1471,6 +1528,28 @@ const plugin = {
1471
1528
  return { text: "❌ ChatGPT editor not visible — are you logged in?\nOpen chatgpt.com in your browser and try again." };
1472
1529
 
1473
1530
  chatgptContext = ctx;
1531
+
1532
+ // Export + bake cookies into persistent profile
1533
+ const chatgptProfileDir = join(homedir(), ".openclaw", "chatgpt-profile");
1534
+ mkdirSync(chatgptProfileDir, { recursive: true });
1535
+ try {
1536
+ const allCookies = await ctx.cookies([
1537
+ "https://chatgpt.com",
1538
+ "https://openai.com",
1539
+ "https://auth.openai.com",
1540
+ ]);
1541
+ const { chromium } = await import("playwright");
1542
+ const pCtx = await chromium.launchPersistentContext(chatgptProfileDir, {
1543
+ headless: true,
1544
+ args: ["--no-sandbox", "--disable-setuid-sandbox"],
1545
+ });
1546
+ await pCtx.addCookies(allCookies);
1547
+ await pCtx.close();
1548
+ api.logger.info(`[cli-bridge:chatgpt] cookies baked into ${chatgptProfileDir}`);
1549
+ } catch (err) {
1550
+ api.logger.warn(`[cli-bridge:chatgpt] cookie bake failed: ${(err as Error).message}`);
1551
+ }
1552
+
1474
1553
  const expiry = await scanChatGPTCookieExpiry(ctx);
1475
1554
  if (expiry) { saveChatGPTExpiry(expiry); api.logger.info(`[cli-bridge:chatgpt] cookie expiry: ${new Date(expiry.expiresAt).toISOString()}`); }
1476
1555
  const expiryLine = expiry ? `\n\n🕐 Cookie expiry: ${formatChatGPTExpiry(expiry)}` : "";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-cli-bridge-elvatis",
3
3
  "name": "OpenClaw CLI Bridge",
4
- "version": "1.3.0",
4
+ "version": "1.3.2",
5
5
  "description": "Phase 1: openai-codex auth bridge. Phase 2: local HTTP proxy routing model calls through gemini/claude CLIs (vllm provider).",
6
6
  "providers": [
7
7
  "openai-codex"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elvatis_com/openclaw-cli-bridge-elvatis",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Bridges gemini, claude, and codex CLI tools as OpenClaw model providers. Reads existing CLI auth without re-login.",
5
5
  "type": "module",
6
6
  "openclaw": {
@@ -22,4 +22,4 @@
22
22
  "dependencies": {
23
23
  "playwright": "^1.58.2"
24
24
  }
25
- }
25
+ }