@elvatis_com/openclaw-cli-bridge-elvatis 0.2.27 → 0.2.28

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:** `0.2.27`
5
+ **Current version:** `0.2.28`
6
6
 
7
7
  ---
8
8
 
@@ -287,6 +287,12 @@ npm test # vitest run (45 tests)
287
287
 
288
288
  ## Changelog
289
289
 
290
+ ### v0.2.28
291
+ - **feat:** `/grok-login` scans auth cookie expiry (sso cookie) and saves to `~/.openclaw/grok-cookie-expiry.json`
292
+ - **feat:** `/grok-status` shows cookie expiry with color-coded warnings (🚨 <7d, āš ļø <14d, āœ… otherwise)
293
+ - **feat:** Startup log shows cookie expiry and refreshes the expiry file on session restore
294
+ - **fix:** Flaky cli-runner test improved (was pre-existing)
295
+
290
296
  ### v0.2.27
291
297
  - **feat:** Grok persistent Chromium profile (`~/.openclaw/grok-profile/`) — cookies survive gateway restarts
292
298
  - **feat:** `/grok-login` imports cookies from OpenClaw browser into persistent profile automatically
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:** 0.2.27
56
+ **Version:** 0.2.28
package/index.ts CHANGED
@@ -89,6 +89,53 @@ let grokContext: BrowserContext | null = null;
89
89
  // Persistent profile dir — survives gateway restarts, keeps cookies intact
90
90
  const GROK_PROFILE_DIR = join(homedir(), ".openclaw", "grok-profile");
91
91
 
92
+ // Cookie expiry tracking file — written on /grok-login, read on startup
93
+ const GROK_EXPIRY_FILE = join(homedir(), ".openclaw", "grok-cookie-expiry.json");
94
+
95
+ interface GrokExpiryInfo {
96
+ expiresAt: number; // epoch ms — earliest auth cookie expiry
97
+ loginAt: number; // epoch ms — when /grok-login was last run
98
+ cookieName: string; // which cookie determines the expiry
99
+ }
100
+
101
+ function saveGrokExpiry(info: GrokExpiryInfo): void {
102
+ try {
103
+ writeFileSync(GROK_EXPIRY_FILE, JSON.stringify(info, null, 2));
104
+ } catch { /* ignore */ }
105
+ }
106
+
107
+ function loadGrokExpiry(): GrokExpiryInfo | null {
108
+ try {
109
+ const raw = readFileSync(GROK_EXPIRY_FILE, "utf-8");
110
+ return JSON.parse(raw) as GrokExpiryInfo;
111
+ } catch { return null; }
112
+ }
113
+
114
+ /** Returns human-readable expiry summary e.g. "179 days (2026-09-07)" */
115
+ function formatExpiryInfo(info: GrokExpiryInfo): string {
116
+ const daysLeft = Math.ceil((info.expiresAt - Date.now()) / 86_400_000);
117
+ const dateStr = new Date(info.expiresAt).toISOString().split("T")[0];
118
+ if (daysLeft <= 0) return `āš ļø EXPIRED (was ${dateStr})`;
119
+ if (daysLeft <= 7) return `🚨 expires in ${daysLeft}d (${dateStr}) — run /grok-login NOW`;
120
+ if (daysLeft <= 14) return `āš ļø expires in ${daysLeft}d (${dateStr}) — run /grok-login soon`;
121
+ return `āœ… valid for ${daysLeft} more days (expires ${dateStr})`;
122
+ }
123
+
124
+ /** Scan context cookies and return earliest auth cookie expiry */
125
+ async function scanCookieExpiry(ctx: import("playwright").BrowserContext): Promise<GrokExpiryInfo | null> {
126
+ try {
127
+ const cookies = await ctx.cookies(["https://grok.com", "https://x.ai"]);
128
+ const authCookies = cookies.filter((c) => ["sso", "sso-rw"].includes(c.name) && c.expires > 0);
129
+ if (authCookies.length === 0) return null;
130
+ const earliest = authCookies.reduce((min, c) => (c.expires < min.expires ? c : min));
131
+ return {
132
+ expiresAt: earliest.expires * 1000,
133
+ loginAt: Date.now(),
134
+ cookieName: earliest.name,
135
+ };
136
+ } catch { return null; }
137
+ }
138
+
92
139
  /**
93
140
  * Launch (or reuse) a persistent headless Chromium context for grok.com.
94
141
  * Uses launchPersistentContext so cookies survive gateway restarts.
@@ -160,6 +207,14 @@ async function tryRestoreGrokSession(
160
207
  }
161
208
  grokContext = ctx;
162
209
  log("[cli-bridge:grok] session restored āœ…");
210
+ // Log cookie expiry status on startup
211
+ const expiry = loadGrokExpiry();
212
+ if (expiry) {
213
+ log(`[cli-bridge:grok] cookie expiry: ${formatExpiryInfo(expiry)}`);
214
+ // Re-scan to keep expiry file fresh (cookies may have been renewed)
215
+ const freshExpiry = await scanCookieExpiry(ctx);
216
+ if (freshExpiry) saveGrokExpiry(freshExpiry);
217
+ }
163
218
  return true;
164
219
  } catch (err) {
165
220
  log(`[cli-bridge:grok] session restore error: ${(err as Error).message}`);
@@ -492,7 +547,7 @@ function proxyTestRequest(
492
547
  const plugin = {
493
548
  id: "openclaw-cli-bridge-elvatis",
494
549
  name: "OpenClaw CLI Bridge",
495
- version: "0.2.27",
550
+ version: "0.2.28",
496
551
  description:
497
552
  "Phase 1: openai-codex auth bridge. " +
498
553
  "Phase 2: HTTP proxy for gemini/claude CLIs. " +
@@ -943,7 +998,16 @@ const plugin = {
943
998
  return { text: `āŒ Session not valid: ${check.reason}\n\nMake sure grok.com is open in your browser and you're logged in, then run /grok-login again.` };
944
999
  }
945
1000
  grokContext = ctx;
946
- return { text: `āœ… Grok session ready!\n\nModels available:\n• \`vllm/web-grok/grok-3\`\n• \`vllm/web-grok/grok-3-fast\`\n• \`vllm/web-grok/grok-3-mini\`\n• \`vllm/web-grok/grok-3-mini-fast\`\n\nSession persists across gateway restarts (profile: ~/.openclaw/grok-profile/)` };
1001
+
1002
+ // Scan cookie expiry and persist it
1003
+ const expiry = await scanCookieExpiry(ctx);
1004
+ if (expiry) {
1005
+ saveGrokExpiry(expiry);
1006
+ api.logger.info(`[cli-bridge:grok] cookie expiry: ${new Date(expiry.expiresAt).toISOString()}`);
1007
+ }
1008
+ const expiryLine = expiry ? `\n\nšŸ• Cookie expiry: ${formatExpiryInfo(expiry)}` : "";
1009
+
1010
+ return { text: `āœ… Grok session ready!\n\nModels available:\n• \`vllm/web-grok/grok-3\`\n• \`vllm/web-grok/grok-3-fast\`\n• \`vllm/web-grok/grok-3-mini\`\n• \`vllm/web-grok/grok-3-mini-fast\`${expiryLine}` };
947
1011
  },
948
1012
  } satisfies OpenClawPluginCommandDefinition);
949
1013
 
@@ -956,7 +1020,9 @@ const plugin = {
956
1020
  }
957
1021
  const check = await verifySession(grokContext, (msg) => api.logger.info(msg));
958
1022
  if (check.valid) {
959
- return { text: `āœ… grok.com session active\nProxy: \`127.0.0.1:${port}\`\nModels: web-grok/grok-3, web-grok/grok-3-fast, web-grok/grok-3-mini, web-grok/grok-3-mini-fast` };
1023
+ const expiry = loadGrokExpiry();
1024
+ const expiryLine = expiry ? `\nšŸ• ${formatExpiryInfo(expiry)}` : "";
1025
+ return { text: `āœ… grok.com session active\nProxy: \`127.0.0.1:${port}\`\nModels: web-grok/grok-3, web-grok/grok-3-fast, web-grok/grok-3-mini, web-grok/grok-3-mini-fast${expiryLine}` };
960
1026
  }
961
1027
  grokContext = null;
962
1028
  return { text: `āŒ Session expired: ${check.reason}\nRun \`/grok-login\` to re-authenticate.` };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-cli-bridge-elvatis",
3
3
  "name": "OpenClaw CLI Bridge",
4
- "version": "0.2.27",
4
+ "version": "0.2.28",
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": "0.2.27",
3
+ "version": "0.2.28",
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": {