@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 +6 -1
- package/SKILL.md +1 -1
- package/index.ts +94 -15
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
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.
|
|
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
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,
|
|
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.
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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)}` : "";
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-cli-bridge-elvatis",
|
|
3
3
|
"name": "OpenClaw CLI Bridge",
|
|
4
|
-
"version": "1.3.
|
|
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.
|
|
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
|
+
}
|