@elvatis_com/openclaw-cli-bridge-elvatis 1.3.4 → 1.3.5
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 +77 -1
- package/index.ts +96 -87
- 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.3.
|
|
5
|
+
**Current version:** `1.3.5`
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -81,6 +81,77 @@ All commands use gateway-level `commands.allowFrom` for authorization (`requireA
|
|
|
81
81
|
|
|
82
82
|
---
|
|
83
83
|
|
|
84
|
+
### Phase 4 — Web Browser Providers (headless, no API key needed)
|
|
85
|
+
|
|
86
|
+
Routes requests through real browser sessions on the provider's web UI. Requires a valid login session (free or paid tier). Uses persistent Chromium profiles — sessions survive gateway restarts.
|
|
87
|
+
|
|
88
|
+
**Grok** (grok.com — SuperGrok subscription):
|
|
89
|
+
|
|
90
|
+
| Model | Notes |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `web-grok/grok-3` | Full model |
|
|
93
|
+
| `web-grok/grok-3-fast` | Faster variant |
|
|
94
|
+
| `web-grok/grok-3-mini` | Lightweight |
|
|
95
|
+
| `web-grok/grok-3-mini-fast` | Fastest |
|
|
96
|
+
|
|
97
|
+
| Command | What it does |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `/grok-login` | Authenticate via X.com OAuth, save session to `~/.openclaw/grok-profile/` |
|
|
100
|
+
| `/grok-status` | Show session validity + cookie expiry |
|
|
101
|
+
| `/grok-logout` | Clear session |
|
|
102
|
+
|
|
103
|
+
**Claude** (claude.ai):
|
|
104
|
+
|
|
105
|
+
| Model | Notes |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `web-claude/claude-sonnet` | Sonnet |
|
|
108
|
+
| `web-claude/claude-opus` | Opus |
|
|
109
|
+
| `web-claude/claude-haiku` | Haiku |
|
|
110
|
+
|
|
111
|
+
| Command | What it does |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `/claude-login` | Authenticate, save cookies to `~/.openclaw/claude-profile/` |
|
|
114
|
+
| `/claude-status` | Show session validity + cookie expiry |
|
|
115
|
+
| `/claude-logout` | Clear session |
|
|
116
|
+
|
|
117
|
+
**Gemini** (gemini.google.com):
|
|
118
|
+
|
|
119
|
+
| Model | Notes |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `web-gemini/gemini-2-5-pro` | Gemini 2.5 Pro |
|
|
122
|
+
| `web-gemini/gemini-2-5-flash` | Gemini 2.5 Flash |
|
|
123
|
+
| `web-gemini/gemini-3-pro` | Gemini 3 Pro |
|
|
124
|
+
| `web-gemini/gemini-3-flash` | Gemini 3 Flash |
|
|
125
|
+
|
|
126
|
+
| Command | What it does |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `/gemini-login` | Authenticate, save cookies to `~/.openclaw/gemini-profile/` |
|
|
129
|
+
| `/gemini-status` | Show session validity + cookie expiry |
|
|
130
|
+
| `/gemini-logout` | Clear session |
|
|
131
|
+
|
|
132
|
+
**ChatGPT** (chatgpt.com):
|
|
133
|
+
|
|
134
|
+
| Model | Notes |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `web-chatgpt/gpt-4o` | GPT-4o |
|
|
137
|
+
| `web-chatgpt/gpt-4o-mini` | GPT-4o Mini |
|
|
138
|
+
| `web-chatgpt/gpt-o3` | o3 |
|
|
139
|
+
| `web-chatgpt/gpt-o4-mini` | o4-mini |
|
|
140
|
+
| `web-chatgpt/gpt-5` | GPT-5 |
|
|
141
|
+
|
|
142
|
+
| Command | What it does |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `/chatgpt-login` | Authenticate, save cookies to `~/.openclaw/chatgpt-profile/` |
|
|
145
|
+
| `/chatgpt-status` | Show session validity + cookie expiry |
|
|
146
|
+
| `/chatgpt-logout` | Clear session |
|
|
147
|
+
|
|
148
|
+
**Session lifecycle:**
|
|
149
|
+
- First use: run `/xxx-login` once (opens Chromium, authenticate in browser)
|
|
150
|
+
- After gateway restart: sessions are **automatically restored** from saved profiles on startup (sequential, ~25s after start)
|
|
151
|
+
- `/bridge-status` — shows all 4 providers at a glance with login state + expiry info
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
84
155
|
## Requirements
|
|
85
156
|
|
|
86
157
|
- [OpenClaw](https://openclaw.ai) gateway (tested with `2026.3.x`)
|
|
@@ -287,6 +358,11 @@ npm test # vitest run (45 tests)
|
|
|
287
358
|
|
|
288
359
|
## Changelog
|
|
289
360
|
|
|
361
|
+
### v1.3.5
|
|
362
|
+
- **fix:** Startup session restore now runs only once per process lifetime — `_startupRestoreDone` module-level guard prevents re-running on every hot-reload (SIGUSR1), which was triggered every ~60s by the openclaw-control-ui dashboard poll
|
|
363
|
+
- **root cause:** Gateway `reload.mode=hybrid` + dashboard status polling caused plugin to reinitialize every 60s → each reload spawned a new Gemini Chromium instance → RAM/CPU OOM loop
|
|
364
|
+
- **behavior:** First load after gateway start: sequential profile restore runs once. All subsequent hot-reloads: skip restore, reuse existing in-memory contexts
|
|
365
|
+
|
|
290
366
|
### v1.3.4
|
|
291
367
|
- **feat:** Safe sequential session restore on startup — if a saved profile exists, providers are reconnected automatically after gateway restart (one at a time, 3s delay between each, headless)
|
|
292
368
|
- **fix:** No manual `/xxx-login` needed after reboot if profile is already saved
|
package/index.ts
CHANGED
|
@@ -233,6 +233,10 @@ async function scanCookieExpiry(ctx: import("playwright").BrowserContext): Promi
|
|
|
233
233
|
let _cdpBrowser: import("playwright").Browser | null = null;
|
|
234
234
|
let _cdpBrowserLaunchPromise: Promise<import("playwright").BrowserContext | null> | null = null;
|
|
235
235
|
|
|
236
|
+
// Startup restore guard — module-level so it survives hot-reloads (SIGUSR1).
|
|
237
|
+
// Set to true after first run; hot-reloads see true and skip the restore loop.
|
|
238
|
+
let _startupRestoreDone = false;
|
|
239
|
+
|
|
236
240
|
/**
|
|
237
241
|
* Connect to the OpenClaw managed browser (CDP port 18800).
|
|
238
242
|
* Singleton: reuses the same connection. Falls back to persistent Chromium for Grok only.
|
|
@@ -746,97 +750,102 @@ const plugin = {
|
|
|
746
750
|
const codexAuthPath = cfg.codexAuthPath ?? DEFAULT_CODEX_AUTH_PATH;
|
|
747
751
|
const grokSessionPath = cfg.grokSessionPath ?? DEFAULT_SESSION_PATH;
|
|
748
752
|
|
|
749
|
-
// ──
|
|
750
|
-
//
|
|
751
|
-
//
|
|
752
|
-
//
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
753
|
+
// ── Session restore: only on first plugin load (not on hot-reloads) ──────
|
|
754
|
+
// The gateway polls every ~60s via openclaw status, which triggers a hot-reload
|
|
755
|
+
// (SIGUSR1 + hybrid mode). Module-level contexts (grokContext etc.) survive
|
|
756
|
+
// hot-reloads because Node keeps the module in memory — so we only need to
|
|
757
|
+
// restore once, on the very first load (when all contexts are null).
|
|
758
|
+
//
|
|
759
|
+
// Guard: _startupRestoreDone is module-level and persists across hot-reloads.
|
|
760
|
+
if (!_startupRestoreDone) {
|
|
761
|
+
_startupRestoreDone = true;
|
|
762
|
+
void (async () => {
|
|
763
|
+
await new Promise(r => setTimeout(r, 5000)); // wait for proxy + gateway to settle
|
|
764
|
+
const { chromium } = await import("playwright");
|
|
765
|
+
const { existsSync } = await import("node:fs");
|
|
766
|
+
|
|
767
|
+
const profileProviders: Array<{
|
|
768
|
+
name: string;
|
|
769
|
+
profileDir: string;
|
|
770
|
+
cookieFile: string;
|
|
771
|
+
verifySelector: string;
|
|
772
|
+
homeUrl: string;
|
|
773
|
+
setCtx: (c: BrowserContext) => void;
|
|
774
|
+
getCtx: () => BrowserContext | null;
|
|
775
|
+
}> = [
|
|
776
|
+
{
|
|
777
|
+
name: "grok",
|
|
778
|
+
profileDir: GROK_PROFILE_DIR,
|
|
779
|
+
cookieFile: join(homedir(), ".openclaw", "grok-session.json"),
|
|
780
|
+
verifySelector: "textarea",
|
|
781
|
+
homeUrl: "https://grok.com",
|
|
782
|
+
getCtx: () => grokContext,
|
|
783
|
+
setCtx: (c) => { grokContext = c; },
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
name: "claude",
|
|
787
|
+
profileDir: join(homedir(), ".openclaw", "claude-profile"),
|
|
788
|
+
cookieFile: join(homedir(), ".openclaw", "claude-cookie-expiry.json"),
|
|
789
|
+
verifySelector: ".ProseMirror",
|
|
790
|
+
homeUrl: "https://claude.ai/new",
|
|
791
|
+
getCtx: () => claudeContext,
|
|
792
|
+
setCtx: (c) => { claudeContext = c; },
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
name: "gemini",
|
|
796
|
+
profileDir: join(homedir(), ".openclaw", "gemini-profile"),
|
|
797
|
+
cookieFile: join(homedir(), ".openclaw", "gemini-cookie-expiry.json"),
|
|
798
|
+
verifySelector: ".ql-editor",
|
|
799
|
+
homeUrl: "https://gemini.google.com/app",
|
|
800
|
+
getCtx: () => geminiContext,
|
|
801
|
+
setCtx: (c) => { geminiContext = c; },
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
name: "chatgpt",
|
|
805
|
+
profileDir: join(homedir(), ".openclaw", "chatgpt-profile"),
|
|
806
|
+
cookieFile: join(homedir(), ".openclaw", "chatgpt-cookie-expiry.json"),
|
|
807
|
+
verifySelector: "#prompt-textarea",
|
|
808
|
+
homeUrl: "https://chatgpt.com",
|
|
809
|
+
getCtx: () => chatgptContext,
|
|
810
|
+
setCtx: (c) => { chatgptContext = c; },
|
|
811
|
+
},
|
|
812
|
+
];
|
|
804
813
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
if (p.getCtx()) continue; // already connected
|
|
814
|
+
for (const p of profileProviders) {
|
|
815
|
+
if (!existsSync(p.profileDir) && !existsSync(p.cookieFile)) {
|
|
816
|
+
api.logger.info(`[cli-bridge:${p.name}] no saved profile — skipping startup restore`);
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
if (p.getCtx()) continue; // already connected
|
|
812
820
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
821
|
+
try {
|
|
822
|
+
api.logger.info(`[cli-bridge:${p.name}] restoring session from profile…`);
|
|
823
|
+
const ctx = await chromium.launchPersistentContext(p.profileDir, {
|
|
824
|
+
headless: true,
|
|
825
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
826
|
+
});
|
|
827
|
+
const page = await ctx.newPage();
|
|
828
|
+
await page.goto(p.homeUrl, { waitUntil: "domcontentloaded", timeout: 20_000 });
|
|
829
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
830
|
+
const ok = await page.locator(p.verifySelector).isVisible().catch(() => false);
|
|
831
|
+
await page.close().catch(() => {});
|
|
832
|
+
if (ok) {
|
|
833
|
+
p.setCtx(ctx);
|
|
834
|
+
ctx.on("close", () => { p.setCtx(null as unknown as BrowserContext); });
|
|
835
|
+
api.logger.info(`[cli-bridge:${p.name}] session restored from profile ✅`);
|
|
836
|
+
} else {
|
|
837
|
+
await ctx.close().catch(() => {});
|
|
838
|
+
api.logger.info(`[cli-bridge:${p.name}] profile exists but not logged in — needs /xxx-login`);
|
|
839
|
+
}
|
|
840
|
+
} catch (err) {
|
|
841
|
+
api.logger.warn(`[cli-bridge:${p.name}] startup restore failed: ${(err as Error).message}`);
|
|
831
842
|
}
|
|
832
|
-
} catch (err) {
|
|
833
|
-
api.logger.warn(`[cli-bridge:${p.name}] startup restore failed: ${(err as Error).message}`);
|
|
834
|
-
}
|
|
835
843
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
844
|
+
// Sequential — never spawn all 4 Chromium instances at once
|
|
845
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
846
|
+
}
|
|
847
|
+
})();
|
|
848
|
+
}
|
|
840
849
|
|
|
841
850
|
// ── Phase 1: openai-codex auth bridge ─────────────────────────────────────
|
|
842
851
|
if (enableCodex) {
|
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.5",
|
|
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.5",
|
|
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": {
|