@elvatis_com/openclaw-cli-bridge-elvatis 1.2.0 → 1.3.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 +6 -1
- package/SKILL.md +1 -1
- package/index.ts +116 -50
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
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.
|
|
5
|
+
**Current version:** `1.3.0`
|
|
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.0
|
|
291
|
+
- **fix:** Browser persistence after gateway restart — each provider launches its own persistent Chromium if OpenClaw browser is unavailable
|
|
292
|
+
- **feat:** `ensureAllProviderContexts()` — unified startup connect for all 4 providers
|
|
293
|
+
- **feat:** Lazy-connect fallback to persistent context when CDP unavailable
|
|
294
|
+
|
|
290
295
|
### v1.2.0
|
|
291
296
|
- **fix:** Fresh page per request — no more message accumulation across calls
|
|
292
297
|
- **feat:** ChatGPT model switching via URL param (?model=gpt-4o, o3, etc.)
|
package/SKILL.md
CHANGED
package/index.ts
CHANGED
|
@@ -322,6 +322,101 @@ async function cleanupBrowsers(log: (msg: string) => void): Promise<void> {
|
|
|
322
322
|
log("[cli-bridge] browser resources cleaned up");
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Ensure all browser provider contexts are connected.
|
|
327
|
+
* 1. Try the shared OpenClaw browser (CDP 18800)
|
|
328
|
+
* 2. Fallback: launch a persistent headless Chromium per provider (saved profile with cookies)
|
|
329
|
+
*/
|
|
330
|
+
async function ensureAllProviderContexts(log: (msg: string) => void): Promise<void> {
|
|
331
|
+
const { chromium } = await import("playwright");
|
|
332
|
+
|
|
333
|
+
// Try CDP first (OpenClaw browser)
|
|
334
|
+
let sharedCtx: BrowserContext | null = null;
|
|
335
|
+
try {
|
|
336
|
+
const b = await chromium.connectOverCDP("http://127.0.0.1:18800", { timeout: 2000 });
|
|
337
|
+
sharedCtx = b.contexts()[0] ?? null;
|
|
338
|
+
if (sharedCtx) log("[cli-bridge] using OpenClaw browser for all providers");
|
|
339
|
+
} catch { /* not available */ }
|
|
340
|
+
|
|
341
|
+
// For each provider: if no context yet, try shared ctx or launch own persistent context
|
|
342
|
+
const providerConfigs = [
|
|
343
|
+
{
|
|
344
|
+
name: "claude",
|
|
345
|
+
profileDir: join(homedir(), ".openclaw", "claude-profile"),
|
|
346
|
+
getCtx: () => claudeContext,
|
|
347
|
+
setCtx: (c: BrowserContext) => { claudeContext = c; },
|
|
348
|
+
homeUrl: "https://claude.ai/new",
|
|
349
|
+
verifySelector: ".ProseMirror",
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: "gemini",
|
|
353
|
+
profileDir: join(homedir(), ".openclaw", "gemini-profile"),
|
|
354
|
+
getCtx: () => geminiContext,
|
|
355
|
+
setCtx: (c: BrowserContext) => { geminiContext = c; },
|
|
356
|
+
homeUrl: "https://gemini.google.com/app",
|
|
357
|
+
verifySelector: ".ql-editor",
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: "chatgpt",
|
|
361
|
+
profileDir: join(homedir(), ".openclaw", "chatgpt-profile"),
|
|
362
|
+
getCtx: () => chatgptContext,
|
|
363
|
+
setCtx: (c: BrowserContext) => { chatgptContext = c; },
|
|
364
|
+
homeUrl: "https://chatgpt.com",
|
|
365
|
+
verifySelector: "#prompt-textarea",
|
|
366
|
+
},
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
for (const cfg of providerConfigs) {
|
|
370
|
+
if (cfg.getCtx()) continue; // already connected
|
|
371
|
+
|
|
372
|
+
let ctx: BrowserContext | null = null;
|
|
373
|
+
|
|
374
|
+
// 1. Try shared OpenClaw browser context
|
|
375
|
+
if (sharedCtx) {
|
|
376
|
+
try {
|
|
377
|
+
const pages = sharedCtx.pages();
|
|
378
|
+
const existing = pages.find(p => p.url().includes(new URL(cfg.homeUrl).hostname));
|
|
379
|
+
const page = existing ?? await sharedCtx.newPage();
|
|
380
|
+
if (!existing) {
|
|
381
|
+
await page.goto(cfg.homeUrl, { waitUntil: "domcontentloaded", timeout: 10_000 });
|
|
382
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
383
|
+
}
|
|
384
|
+
const visible = await page.locator(cfg.verifySelector).isVisible().catch(() => false);
|
|
385
|
+
if (visible) {
|
|
386
|
+
ctx = sharedCtx;
|
|
387
|
+
log(`[cli-bridge:${cfg.name}] connected via OpenClaw browser ✅`);
|
|
388
|
+
}
|
|
389
|
+
} catch { /* fall through */ }
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// 2. Launch own persistent context (has saved cookies)
|
|
393
|
+
if (!ctx) {
|
|
394
|
+
try {
|
|
395
|
+
mkdirSync(cfg.profileDir, { recursive: true });
|
|
396
|
+
const pCtx = await chromium.launchPersistentContext(cfg.profileDir, {
|
|
397
|
+
headless: true,
|
|
398
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
399
|
+
});
|
|
400
|
+
const page = await pCtx.newPage();
|
|
401
|
+
await page.goto(cfg.homeUrl, { waitUntil: "domcontentloaded", timeout: 15_000 });
|
|
402
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
403
|
+
const visible = await page.locator(cfg.verifySelector).isVisible().catch(() => false);
|
|
404
|
+
if (visible) {
|
|
405
|
+
ctx = pCtx;
|
|
406
|
+
log(`[cli-bridge:${cfg.name}] launched persistent context ✅`);
|
|
407
|
+
} else {
|
|
408
|
+
await pCtx.close().catch(() => {});
|
|
409
|
+
log(`[cli-bridge:${cfg.name}] persistent context: editor not visible (not logged in?)`);
|
|
410
|
+
}
|
|
411
|
+
} catch (err) {
|
|
412
|
+
log(`[cli-bridge:${cfg.name}] could not launch browser: ${(err as Error).message}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (ctx) cfg.setCtx(ctx);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
325
420
|
async function tryRestoreGrokSession(
|
|
326
421
|
_sessionPath: string,
|
|
327
422
|
log: (msg: string) => void
|
|
@@ -613,7 +708,7 @@ function proxyTestRequest(
|
|
|
613
708
|
const plugin = {
|
|
614
709
|
id: "openclaw-cli-bridge-elvatis",
|
|
615
710
|
name: "OpenClaw CLI Bridge",
|
|
616
|
-
version: "1.
|
|
711
|
+
version: "1.3.0",
|
|
617
712
|
description:
|
|
618
713
|
"Phase 1: openai-codex auth bridge. " +
|
|
619
714
|
"Phase 2: HTTP proxy for gemini/claude CLIs. " +
|
|
@@ -634,49 +729,8 @@ const plugin = {
|
|
|
634
729
|
|
|
635
730
|
// ── Auto-connect all browser providers on startup (non-blocking) ──────────
|
|
636
731
|
void (async () => {
|
|
637
|
-
//
|
|
638
|
-
await
|
|
639
|
-
const log = (msg: string) => api.logger.info(msg);
|
|
640
|
-
const ctx = await connectToOpenClawBrowser(log);
|
|
641
|
-
if (!ctx) { log("[cli-bridge] startup auto-connect: OpenClaw browser not available"); return; }
|
|
642
|
-
const pages = ctx.pages().map(p => p.url());
|
|
643
|
-
log(`[cli-bridge] startup auto-connect: ${pages.length} pages open`);
|
|
644
|
-
|
|
645
|
-
// Claude
|
|
646
|
-
if (pages.some(u => u.includes("claude.ai")) && !claudeContext) {
|
|
647
|
-
try {
|
|
648
|
-
const { getOrCreateClaudePage } = await import("./src/claude-browser.js");
|
|
649
|
-
const { page } = await getOrCreateClaudePage(ctx);
|
|
650
|
-
if (await page.locator(".ProseMirror").isVisible().catch(() => false)) {
|
|
651
|
-
claudeContext = ctx;
|
|
652
|
-
log("[cli-bridge:claude] auto-connected ✅");
|
|
653
|
-
}
|
|
654
|
-
} catch { /* not available */ }
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// Gemini
|
|
658
|
-
if (pages.some(u => u.includes("gemini.google.com")) && !geminiContext) {
|
|
659
|
-
try {
|
|
660
|
-
const { getOrCreateGeminiPage } = await import("./src/gemini-browser.js");
|
|
661
|
-
const { page } = await getOrCreateGeminiPage(ctx);
|
|
662
|
-
if (await page.locator(".ql-editor").isVisible().catch(() => false)) {
|
|
663
|
-
geminiContext = ctx;
|
|
664
|
-
log("[cli-bridge:gemini] auto-connected ✅");
|
|
665
|
-
}
|
|
666
|
-
} catch { /* not available */ }
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// ChatGPT
|
|
670
|
-
if (pages.some(u => u.includes("chatgpt.com")) && !chatgptContext) {
|
|
671
|
-
try {
|
|
672
|
-
const { getOrCreateChatGPTPage } = await import("./src/chatgpt-browser.js");
|
|
673
|
-
const { page } = await getOrCreateChatGPTPage(ctx);
|
|
674
|
-
if (await page.locator("#prompt-textarea").isVisible().catch(() => false)) {
|
|
675
|
-
chatgptContext = ctx;
|
|
676
|
-
log("[cli-bridge:chatgpt] auto-connected ✅");
|
|
677
|
-
}
|
|
678
|
-
} catch { /* not available */ }
|
|
679
|
-
}
|
|
732
|
+
await new Promise(r => setTimeout(r, 3000)); // wait for proxy to start
|
|
733
|
+
await ensureAllProviderContexts((msg) => api.logger.info(msg));
|
|
680
734
|
})();
|
|
681
735
|
|
|
682
736
|
// ── Phase 1: openai-codex auth bridge ─────────────────────────────────────
|
|
@@ -792,7 +846,9 @@ const plugin = {
|
|
|
792
846
|
const editor = await page.locator(".ProseMirror").isVisible().catch(() => false);
|
|
793
847
|
if (editor) { claudeContext = ctx; return ctx; }
|
|
794
848
|
}
|
|
795
|
-
|
|
849
|
+
// Fallback: try persistent context
|
|
850
|
+
await ensureAllProviderContexts((msg) => api.logger.info(msg));
|
|
851
|
+
return claudeContext;
|
|
796
852
|
},
|
|
797
853
|
getGeminiContext: () => geminiContext,
|
|
798
854
|
connectGeminiContext: async () => {
|
|
@@ -803,7 +859,9 @@ const plugin = {
|
|
|
803
859
|
const editor = await page.locator(".ql-editor").isVisible().catch(() => false);
|
|
804
860
|
if (editor) { geminiContext = ctx; return ctx; }
|
|
805
861
|
}
|
|
806
|
-
|
|
862
|
+
// Fallback: try persistent context
|
|
863
|
+
await ensureAllProviderContexts((msg) => api.logger.info(msg));
|
|
864
|
+
return geminiContext;
|
|
807
865
|
},
|
|
808
866
|
getChatGPTContext: () => chatgptContext,
|
|
809
867
|
connectChatGPTContext: async () => {
|
|
@@ -814,7 +872,9 @@ const plugin = {
|
|
|
814
872
|
const editor = await page.locator("#prompt-textarea").isVisible().catch(() => false);
|
|
815
873
|
if (editor) { chatgptContext = ctx; return ctx; }
|
|
816
874
|
}
|
|
817
|
-
|
|
875
|
+
// Fallback: try persistent context
|
|
876
|
+
await ensureAllProviderContexts((msg) => api.logger.info(msg));
|
|
877
|
+
return chatgptContext;
|
|
818
878
|
},
|
|
819
879
|
});
|
|
820
880
|
proxyServer = server;
|
|
@@ -857,7 +917,9 @@ const plugin = {
|
|
|
857
917
|
const editor = await page.locator(".ProseMirror").isVisible().catch(() => false);
|
|
858
918
|
if (editor) { claudeContext = ctx; return ctx; }
|
|
859
919
|
}
|
|
860
|
-
|
|
920
|
+
// Fallback: try persistent context
|
|
921
|
+
await ensureAllProviderContexts((msg) => api.logger.info(msg));
|
|
922
|
+
return claudeContext;
|
|
861
923
|
},
|
|
862
924
|
getGeminiContext: () => geminiContext,
|
|
863
925
|
connectGeminiContext: async () => {
|
|
@@ -868,7 +930,9 @@ const plugin = {
|
|
|
868
930
|
const editor = await page.locator(".ql-editor").isVisible().catch(() => false);
|
|
869
931
|
if (editor) { geminiContext = ctx; return ctx; }
|
|
870
932
|
}
|
|
871
|
-
|
|
933
|
+
// Fallback: try persistent context
|
|
934
|
+
await ensureAllProviderContexts((msg) => api.logger.info(msg));
|
|
935
|
+
return geminiContext;
|
|
872
936
|
},
|
|
873
937
|
getChatGPTContext: () => chatgptContext,
|
|
874
938
|
connectChatGPTContext: async () => {
|
|
@@ -879,7 +943,9 @@ const plugin = {
|
|
|
879
943
|
const editor = await page.locator("#prompt-textarea").isVisible().catch(() => false);
|
|
880
944
|
if (editor) { chatgptContext = ctx; return ctx; }
|
|
881
945
|
}
|
|
882
|
-
|
|
946
|
+
// Fallback: try persistent context
|
|
947
|
+
await ensureAllProviderContexts((msg) => api.logger.info(msg));
|
|
948
|
+
return chatgptContext;
|
|
883
949
|
},
|
|
884
950
|
});
|
|
885
951
|
proxyServer = server;
|
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.
|
|
4
|
+
"version": "1.3.0",
|
|
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
|
+
"version": "1.3.0",
|
|
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": {
|