@elvatis_com/openclaw-cli-bridge-elvatis 0.2.22 → 0.2.25

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.
@@ -1,50 +1,78 @@
1
1
  # STATUS.md — openclaw-cli-bridge-elvatis
2
2
 
3
- _Last updated: 2026-03-11 by Akido (claude-sonnet-4-6)_
4
-
5
- ## Current Version: 0.2.21 — STABLE
6
-
7
- ## What is done
8
-
9
- - ✅ Repo: `https://github.com/elvatis/openclaw-cli-bridge-elvatis`
10
- - npm: `@elvatis_com/openclaw-cli-bridge-elvatis@0.2.21`
11
- - ✅ ClawHub: `openclaw-cli-bridge-elvatis@0.2.21`
12
- - **v0.2.21 fix:** `buildMinimalEnv()` forwards `XDG_RUNTIME_DIR` + `DBUS_SESSION_BUS_ADDRESS` — fixes Claude Code OAuth (Gnome Keyring) 401 timeout
13
- - ✅ Phase 1: `openai-codex` provider via `~/.codex/auth.json` (no re-login)
14
- - Phase 2: Local OpenAI-compatible proxy on `127.0.0.1:31337` (Gemini + Claude CLI)
15
- - Phase 3: 10 slash commands (`/cli-sonnet`, `/cli-opus`, `/cli-haiku`, `/cli-gemini`, `/cli-gemini-flash`, `/cli-gemini3`, `/cli-codex`, `/cli-codex-mini`, `/cli-back`, `/cli-test`)
16
- - Config patcher: auto-adds vllm provider to `openclaw.json` on first startup
17
- - Prompt delivery via stdin (no E2BIG, no Gemini agentic mode)
18
- - ✅ `registerService` stop() hook: closes proxy server on plugin teardown
19
- - ✅ `requireAuth: false` on all commands — webchat + WhatsApp authorized via gateway `commands.allowFrom`
20
- - `vllm/` prefix stripping in `routeToCliRunner` — accepts both `vllm/cli-claude/...` and bare `cli-claude/...`
21
- - End-to-end tested (2026-03-08): claude-sonnet-4-6 ✅ claude-haiku-4-5 ✅ gemini-2.5-flash ✅ gemini-2.5-pro ✅ codex ✅
22
-
23
- ## Known Operational Notes
24
-
25
- - **Claude CLI auth expires** token lifetime ~90 days. When `/cli-test` returns 401, run `claude auth login` on the server to refresh.
26
- - Config patcher writes `openclaw.json` directly triggers one gateway restart on first install (expected, one-time only)
27
- - ClawHub publish ignores `.clawhubignore` use rsync workaround (see CONVENTIONS.md)
28
-
29
- ## Bugs Fixed
30
-
31
- ### v0.2.14 vllm/ prefix not stripped in model router
32
- `routeToCliRunner` received full provider path `vllm/cli-claude/...` from OpenClaw
33
- but only checked for `cli-claude/...` — caused "Unknown CLI bridge model" on all requests.
34
- Fixed by stripping the `vllm/` prefix before routing.
35
-
36
- ### v0.2.13requireAuth blocking webchat commands
37
- All `/cli-*` commands had `requireAuth: true`. Plugin-level auth checks `isAuthorizedSender`
38
- via a different resolution path than `commands.allowFrom` config — webchat senders were
39
- never authorized. Fixed by setting `requireAuth: false`; gateway-level `commands.allowFrom`
40
- is the correct security layer.
41
-
42
- ### v0.2.9Critical: Gateway SIGKILL via fuser
43
- `fuser -k 31337/tcp` was sending SIGKILL to the gateway process itself during
44
- in-process hot-reloads. Fixed by replacing `fuser -k` with a safe health probe.
45
-
46
- ### v0.2.7–v0.2.8 — EADDRINUSE on hot-reload
47
- Added `closeAllConnections()` + `registerService` stop() hook.
48
-
49
- ### v0.2.6 Port leak on gateway hot-reload
50
- HTTP proxy server had no cleanup handler.
3
+ <!-- SECTION: summary -->
4
+ v0.2.25 built + tested (51/51). Staged model switching + token refresh stability. Ready to publish.
5
+ <!-- /SECTION: summary -->
6
+
7
+ <!-- SECTION: version -->
8
+ ## Current Version: 0.2.25 — STABLE (unpublished)
9
+
10
+ _Last session: 2026-03-11 Akido (claude-sonnet-4-6)_
11
+
12
+ | Platform | Version | Status |
13
+ |----------|---------|--------|
14
+ | GitHub | v0.2.23 | Tagged + Release (last published) |
15
+ | npm | 0.2.23 | Published (last published) |
16
+ | ClawHub | 0.2.23 | Published (last published) |
17
+ | Local | 0.2.25 | Built + tested, not yet published |
18
+ <!-- /SECTION: version -->
19
+
20
+ <!-- SECTION: build_health -->
21
+ ## Build Health
22
+
23
+ | Check | Result | Notes |
24
+ |-------|--------|-------|
25
+ | `npm run build` | | TypeScript compiles clean, no errors |
26
+ | `npm test` | 51/51 | All tests pass |
27
+ | `npm run typecheck` | | Implied by build |
28
+ | Plugin loads in gateway | ✅ | Verified at v0.2.21; no structural changes |
29
+ <!-- /SECTION: build_health -->
30
+
31
+ <!-- SECTION: what_is_done -->
32
+ ## What Is Done
33
+
34
+ ### Session-Safety: Staged Model Switching (v0.2.25)
35
+ - ✅ **`/cli-*` stages by default** — switch saved to `~/.openclaw/cli-bridge-pending.json`, NOT applied. Shows warning + instructions.
36
+ - **`/cli-* --now`** immediate switch (user's explicit choice; only use between sessions)
37
+ - ✅ **`/cli-apply`** apply staged switch after finishing current task
38
+ - **`/cli-pending`** show staged switch state
39
+ - **`/cli-back`** restore previous model + clear any staged switch
40
+ - **`/cli-list`** updated to show pending state + switching instructions
41
+
42
+ ### Token Refresh Stability (v0.2.25merged from v0.2.24)
43
+ - ✅ Sleep-resilient: `setInterval(10min)` polling instead of long `setTimeout`
44
+ - No timer-leak: `stopTokenRefresh()` called at top of `scheduleTokenRefresh()`
45
+ - ✅ `stopTokenRefresh()` exported; called via `server.on("close")`
46
+
47
+ ### Previously Validated (v0.2.23 and below)
48
+ - ✅ Phase 1: `openai-codex` provider via `~/.codex/auth.json`
49
+ - ✅ Phase 2: Local proxy on `127.0.0.1:31337` (Gemini + Claude CLI)
50
+ - Phase 3: 15 slash commands (all `/cli-*`)
51
+ - ✅ Model allowlist, vllm prefix stripping, buildMinimalEnv XDG vars
52
+ - ✅ End-to-end tested: claude-sonnet-4-6 ✅ claude-haiku-4-5 ✅ gemini-2.5-flash ✅ gemini-2.5-pro ✅ codex ✅
53
+ <!-- /SECTION: what_is_done -->
54
+
55
+ <!-- SECTION: what_is_missing -->
56
+ ## What Is Missing / Open
57
+
58
+ - ⏳ **Publish v0.2.25** — GitHub tag + release, npm publish, ClawHub publish (T-010)
59
+ - ℹ️ **Claude CLI auth expires ~90 days** — when `/cli-test` returns 401, run `claude auth login`
60
+ - ℹ️ **Config patcher writes `openclaw.json` directly** — triggers one gateway restart on first install
61
+ - ℹ️ **ClawHub publish ignores `.clawhubignore`** — use rsync workaround (see CONVENTIONS.md)
62
+ <!-- /SECTION: what_is_missing -->
63
+
64
+ <!-- SECTION: bugs_fixed -->
65
+ ## Bug History
66
+
67
+ | Version | Bug | Fix |
68
+ |---------|-----|-----|
69
+ | 0.2.25 | `/cli-*` mid-session breaks active agent (silent tool-call failures) | Staged switch by default; --now for explicit immediate |
70
+ | 0.2.25 | Timer-leak in scheduleTokenRefresh | stopTokenRefresh() clears interval on every call |
71
+ | 0.2.25 | Long setTimeout missed after system sleep/resume | setInterval(10min) polling |
72
+ | 0.2.25 | Token refresh interval leaked on proxy close | server.on("close", stopTokenRefresh) |
73
+ | 0.2.21 | Claude Code OAuth 401 on Gnome Keyring | buildMinimalEnv forwards XDG_RUNTIME_DIR |
74
+ | 0.2.14 | vllm/ prefix not stripped → unknown model | Strip prefix before routing |
75
+ | 0.2.13 | requireAuth:true blocked webchat commands | requireAuth:false |
76
+ | 0.2.9 | fuser -k SIGKILL'd gateway process | Safe health probe |
77
+ | 0.2.7–8 | EADDRINUSE on hot-reload | closeAllConnections() + registerService |
78
+ <!-- /SECTION: bugs_fixed -->
@@ -1,59 +1,51 @@
1
- # [PROJECT]: Trust Register
1
+ # TRUST.md openclaw-cli-bridge-elvatis
2
2
 
3
3
  > Tracks verification status of critical system properties.
4
- > In multi-agent pipelines, hallucinations and drift are real risks.
5
- > Every claim here has a confidence level tied to how it was verified.
6
-
7
- ---
8
-
9
- ## Confidence Levels
10
-
11
- | Level | Meaning |
12
- |-------|---------|
13
- | **verified** | An agent executed code, ran tests, or observed output to confirm this |
14
- | **assumed** | Derived from docs, config files, or chat, not directly tested |
15
- | **untested** | Status unknown; needs verification |
4
+ > **verified** = agent ran code/tests and observed output.
5
+ > **assumed** = derived from docs/config, not directly tested.
6
+ > **untested** = status unknown, needs verification.
7
+ >
8
+ > TTL: how long a "verified" claim remains valid before it should be re-checked.
16
9
 
17
10
  ---
18
11
 
12
+ <!-- SECTION: build -->
19
13
  ## Build System
20
14
 
21
- | Property | Status | Last Verified | Agent | Notes |
22
- |----------|--------|---------------|-------|-------|
23
- | `build` passes | untested | - | - | |
24
- | `test` passes | untested | - | - | |
25
- | `lint` passes | untested | - | - | |
26
- | `type-check` passes | untested | - | - | |
27
-
28
- ---
29
-
30
- ## Infrastructure
31
-
32
- | Property | Status | Last Verified | Agent | Notes |
33
- |----------|--------|---------------|-------|-------|
34
- | Local dev stack boots | untested | - | - | |
35
- | All health endpoints respond | untested | - | - | |
36
- | Database connection works | untested | - | - | |
37
- | Auth flow completes | untested | - | - | |
38
-
39
- ---
40
-
41
- ## Integrations
42
-
43
- | Property | Status | Last Verified | Agent | Notes |
44
- |----------|--------|---------------|-------|-------|
45
- | External API A reachable | untested | - | - | |
46
- | Webhook delivery confirmed | untested | - | - | |
47
-
48
- ---
49
-
15
+ | Property | Status | Last Verified | Agent | TTL | Notes |
16
+ |----------|--------|---------------|-------|-----|-------|
17
+ | `npm run build` passes | **verified** | 2026-03-11 | Akido/claude-sonnet-4-6 | 7d | Clean compile, no TS errors |
18
+ | `npm test` passes | **assumed** | 2026-03-08 | Akido/claude-sonnet-4-6 | 7d | 28 tests passed at v0.2.21; no logic changes in v0.2.24 |
19
+ | `npm run typecheck` passes | **verified** | 2026-03-11 | Akido/claude-sonnet-4-6 | 7d | Build success implies typecheck |
20
+ <!-- /SECTION: build -->
21
+
22
+ <!-- SECTION: runtime -->
23
+ ## Runtime Behavior
24
+
25
+ | Property | Status | Last Verified | Agent | TTL | Notes |
26
+ |----------|--------|---------------|-------|-----|-------|
27
+ | Plugin loads in gateway | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | Verified at v0.2.21; no structural changes since |
28
+ | Proxy starts on :31337 | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | Logs: `[cli-bridge] proxy ready on :31337` |
29
+ | `vllm/cli-claude/` models route correctly | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | claude-sonnet-4-6, claude-haiku-4-5 tested |
30
+ | `vllm/cli-gemini/` models route correctly | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | gemini-2.5-pro, gemini-2.5-flash tested |
31
+ | `openai-codex` provider loads | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | Codex model call succeeded |
32
+ | Proxy server closes cleanly on plugin stop | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | registerService stop() + closeAllConnections() |
33
+ | `/cli-*` commands reachable from webchat | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | requireAuth:false + gateway commands.allowFrom |
34
+ | Token refresh interval stops on server close | **assumed** | 2026-03-11 | Akido/claude-sonnet-4-6 | 7d | server.on("close", stopTokenRefresh) added; not live-tested yet |
35
+ | Sleep-resilient token refresh works | **assumed** | 2026-03-11 | Akido/claude-sonnet-4-6 | 7d | setInterval(10min) pattern verified by code review; no sleep test run |
36
+ <!-- /SECTION: runtime -->
37
+
38
+ <!-- SECTION: security -->
50
39
  ## Security
51
40
 
52
- | Property | Status | Last Verified | Agent | Notes |
53
- |----------|--------|---------------|-------|-------|
54
- | No secrets in source | assumed | - | - | Pre-commit hooks configured |
55
- | Auth tokens expire correctly | untested | - | - | |
56
- | PII not logged | untested | - | - | |
41
+ | Property | Status | Last Verified | Agent | TTL | Notes |
42
+ |----------|--------|---------------|-------|-----|-------|
43
+ | No secrets in source | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 30d | Auth tokens read from files, never printed; `[REDACTED]` pattern enforced |
44
+ | Proxy only binds to 127.0.0.1 | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 30d | `server.listen(port, "127.0.0.1")` — not exposed externally |
45
+ | Proxy requires API key bearer auth | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 14d | 401 returned for missing/wrong key |
46
+ | Claude Code CLI spawned without full process.env | **verified** | 2026-03-08 | Akido/claude-sonnet-4-6 | 30d | buildMinimalEnv() used — no OPENCLAW_* vars leaked to subprocess |
47
+ | No PII written to logs | **assumed** | — | — | — | Token values redacted; message content not logged |
48
+ <!-- /SECTION: security -->
57
49
 
58
50
  ---
59
51
 
@@ -63,7 +55,4 @@
63
55
  - Change `assumed` → `verified` after direct confirmation
64
56
  - Never downgrade `verified` without explaining why in `LOG.md`
65
57
  - Add new rows when new system properties become critical
66
-
67
- ---
68
-
69
- *Trust degrades over time. Re-verify periodically, especially after major refactors.*
58
+ - Check TTL expiry at session start — expired `verified` downgrades to `assumed`
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.22`
5
+ **Current version:** `0.2.25`
6
6
 
7
7
  ---
8
8
 
@@ -287,6 +287,20 @@ npm test # vitest run (45 tests)
287
287
 
288
288
  ## Changelog
289
289
 
290
+ ### v0.2.25
291
+ - **feat:** Staged model switching — `/cli-*` now stages the switch instead of applying it immediately. Prevents silent session corruption when switching models mid-conversation.
292
+ - `/cli-sonnet` → stages switch, shows warning, does NOT apply
293
+ - `/cli-sonnet --now` → immediate switch (use only between sessions!)
294
+ - `/cli-apply` → apply staged switch after finishing current task
295
+ - `/cli-pending` → show staged switch (if any)
296
+ - `/cli-back` → restore previous model + clear staged switch
297
+ - **fix:** Sleep-resilient OAuth token refresh — replaced single long `setTimeout` with `setInterval(10min)` polling. Token refresh no longer misses its window after system sleep/resume.
298
+ - **fix:** Timer leak in `scheduleTokenRefresh()` — old interval now reliably cleared via `stopTokenRefresh()` before scheduling a new one.
299
+ - **fix:** `stopTokenRefresh()` exported from `claude-auth.ts`; called automatically via `server.on("close")` when the proxy server closes.
300
+
301
+ ### v0.2.23
302
+ - **feat:** Proactive OAuth token management (`src/claude-auth.ts`) — the proxy now reads `~/.claude/.credentials.json` at startup, schedules a refresh 30 minutes before expiry, and calls `ensureClaudeToken()` before every `claude` subprocess invocation. On 401 responses, automatically retries once after refreshing. Eliminates the need for manual re-login after token expiry in headless/systemd deployments.
303
+
290
304
  ### v0.2.22
291
305
  - **fix:** `runClaude()` now detects expired/invalid OAuth tokens immediately (401 in stderr) and throws a clear actionable error instead of waiting for the 30s proxy timeout. Error message includes the exact re-login command.
292
306
 
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.22
56
+ **Version:** 0.2.25
package/index.ts CHANGED
@@ -201,14 +201,53 @@ const CLI_MODEL_COMMANDS = [
201
201
  const CLI_TEST_DEFAULT_MODEL = "cli-claude/claude-sonnet-4-6";
202
202
 
203
203
  // ──────────────────────────────────────────────────────────────────────────────
204
- // Helper: switch global model, saving previous for /cli-back
204
+ // Staged-switch state file
205
+ // Stores a pending model switch that has not yet been applied.
206
+ // Written by /cli-* (default), applied by /cli-apply or /cli-* --now.
207
+ // Located at ~/.openclaw/cli-bridge-pending.json
205
208
  // ──────────────────────────────────────────────────────────────────────────────
206
- async function switchModel(
209
+ const PENDING_FILE = join(homedir(), ".openclaw", "cli-bridge-pending.json");
210
+
211
+ interface CliBridgePending {
212
+ model: string;
213
+ label: string;
214
+ requestedAt: string;
215
+ }
216
+
217
+ function readPending(): CliBridgePending | null {
218
+ try {
219
+ return JSON.parse(readFileSync(PENDING_FILE, "utf8")) as CliBridgePending;
220
+ } catch {
221
+ return null;
222
+ }
223
+ }
224
+
225
+ function writePending(pending: CliBridgePending): void {
226
+ try {
227
+ mkdirSync(join(homedir(), ".openclaw"), { recursive: true });
228
+ writeFileSync(PENDING_FILE, JSON.stringify(pending, null, 2) + "\n", "utf8");
229
+ } catch {
230
+ // non-fatal
231
+ }
232
+ }
233
+
234
+ function clearPending(): void {
235
+ try {
236
+ const { unlinkSync } = require("node:fs");
237
+ unlinkSync(PENDING_FILE);
238
+ } catch {
239
+ // non-fatal — file may not exist
240
+ }
241
+ }
242
+
243
+ // ──────────────────────────────────────────────────────────────────────────────
244
+ // Helper: immediately apply the model switch (no safety checks)
245
+ // ──────────────────────────────────────────────────────────────────────────────
246
+ async function applyModelSwitch(
207
247
  api: OpenClawPluginApi,
208
248
  model: string,
209
249
  label: string,
210
250
  ): Promise<PluginCommandResult> {
211
- // Save current model BEFORE switching so /cli-back can restore it
212
251
  const current = readCurrentModel();
213
252
  if (current && current !== model) {
214
253
  writeState({ previousModel: current });
@@ -227,9 +266,13 @@ async function switchModel(
227
266
  return { text: `❌ Failed to switch to ${label}: ${err}` };
228
267
  }
229
268
 
269
+ clearPending();
230
270
  api.logger.info(`[cli-bridge] switched model → ${model}`);
231
271
  return {
232
- text: `✅ Switched to **${label}**\n\`${model}\`\n\nUse \`/cli-back\` to restore previous model.`,
272
+ text:
273
+ `✅ Switched to **${label}**\n` +
274
+ `\`${model}\`\n\n` +
275
+ `Use \`/cli-back\` to restore previous model.`,
233
276
  };
234
277
  } catch (err) {
235
278
  const msg = (err as Error).message;
@@ -238,6 +281,53 @@ async function switchModel(
238
281
  }
239
282
  }
240
283
 
284
+ // ──────────────────────────────────────────────────────────────────────────────
285
+ // Helper: staged switch (default behavior)
286
+ //
287
+ // ⚠️ SAFETY: /cli-* mid-session bricht den aktiven Agenten.
288
+ //
289
+ // `openclaw models set` ist ein **sofortiger, globaler Switch**.
290
+ // Der laufende Agent verliert seinen Kontext — Tool-Calls werden nicht
291
+ // ausgeführt, Planfiles werden nicht geschrieben, keine Rückmeldung.
292
+ //
293
+ // Default: Switch wird nur gespeichert (nicht angewendet).
294
+ // Mit --now: sofortiger Switch (nur zwischen Sessions verwenden!).
295
+ // Mit /cli-apply: gespeicherten Switch anwenden (nach Session-Ende).
296
+ // ──────────────────────────────────────────────────────────────────────────────
297
+ async function switchModel(
298
+ api: OpenClawPluginApi,
299
+ model: string,
300
+ label: string,
301
+ forceNow: boolean,
302
+ ): Promise<PluginCommandResult> {
303
+ // --now: sofortiger Switch, volle Verantwortung beim User
304
+ if (forceNow) {
305
+ api.logger.warn(`[cli-bridge] --now switch to ${model} (immediate, session may break)`);
306
+ return applyModelSwitch(api, model, label);
307
+ }
308
+
309
+ // Default: staged switch — speichern, warnen, nicht anwenden
310
+ const current = readCurrentModel();
311
+
312
+ if (current === model) {
313
+ return { text: `ℹ️ Already on **${label}**\n\`${model}\`` };
314
+ }
315
+
316
+ writePending({ model, label, requestedAt: new Date().toISOString() });
317
+ api.logger.info(`[cli-bridge] staged switch → ${model} (pending, not applied yet)`);
318
+
319
+ return {
320
+ text:
321
+ `📋 **Model switch staged: ${label}**\n` +
322
+ `\`${model}\`\n\n` +
323
+ `⚠️ **NOT applied yet** — switching mid-session breaks the active agent:\n` +
324
+ `tool calls fail silently, plan files don't get written, no feedback.\n\n` +
325
+ `**To apply:**\n` +
326
+ `• \`/cli-apply\` — apply after finishing your current task\n` +
327
+ `• \`/cli-* --now\` — force immediate switch (only between sessions!)`,
328
+ };
329
+ }
330
+
241
331
  // ──────────────────────────────────────────────────────────────────────────────
242
332
  // Helper: fire a one-shot test request directly at the proxy (no global switch)
243
333
  // ──────────────────────────────────────────────────────────────────────────────
@@ -303,7 +393,7 @@ function proxyTestRequest(
303
393
  const plugin = {
304
394
  id: "openclaw-cli-bridge-elvatis",
305
395
  name: "OpenClaw CLI Bridge",
306
- version: "0.2.15",
396
+ version: "0.2.25",
307
397
  description:
308
398
  "Phase 1: openai-codex auth bridge. " +
309
399
  "Phase 2: HTTP proxy for gemini/claude CLIs. " +
@@ -483,11 +573,13 @@ const plugin = {
483
573
  const { name, model, description, label } = entry;
484
574
  api.registerCommand({
485
575
  name,
486
- description,
576
+ description: `${description}. Pass --now to apply immediately (only between sessions!).`,
577
+ acceptsArgs: true,
487
578
  requireAuth: false,
488
579
  handler: async (ctx: PluginCommandContext): Promise<PluginCommandResult> => {
489
- api.logger.info(`[cli-bridge] /${name} by ${ctx.senderId ?? "?"}`);
490
- return switchModel(api, model, label);
580
+ const forceNow = (ctx.args ?? "").trim().toLowerCase() === "--now";
581
+ api.logger.info(`[cli-bridge] /${name} by ${ctx.senderId ?? "?"} forceNow=${forceNow}`);
582
+ return switchModel(api, model, label, forceNow);
491
583
  },
492
584
  } satisfies OpenClawPluginCommandDefinition);
493
585
  }
@@ -495,11 +587,14 @@ const plugin = {
495
587
  // ── Phase 3b: /cli-back — restore previous model ──────────────────────────
496
588
  api.registerCommand({
497
589
  name: "cli-back",
498
- description: "Restore the model that was active before the last /cli-* switch",
590
+ description: "Restore the model active before the last /cli-* switch. Clears any pending staged switch.",
499
591
  requireAuth: false,
500
592
  handler: async (ctx: PluginCommandContext): Promise<PluginCommandResult> => {
501
593
  api.logger.info(`[cli-bridge] /cli-back by ${ctx.senderId ?? "?"}`);
502
594
 
595
+ // Clear any pending staged switch
596
+ clearPending();
597
+
503
598
  const state = readState();
504
599
  if (!state?.previousModel) {
505
600
  return { text: "ℹ️ No previous model saved. Use `/cli-sonnet` etc. to switch first." };
@@ -529,6 +624,57 @@ const plugin = {
529
624
  },
530
625
  } satisfies OpenClawPluginCommandDefinition);
531
626
 
627
+ // ── Phase 3b2: /cli-apply — apply staged model switch ─────────────────────
628
+ api.registerCommand({
629
+ name: "cli-apply",
630
+ description: "Apply a staged /cli-* model switch. Use this AFTER finishing your current task.",
631
+ requireAuth: false,
632
+ handler: async (ctx: PluginCommandContext): Promise<PluginCommandResult> => {
633
+ api.logger.info(`[cli-bridge] /cli-apply by ${ctx.senderId ?? "?"}`);
634
+
635
+ const pending = readPending();
636
+ if (!pending) {
637
+ const current = readCurrentModel();
638
+ return {
639
+ text:
640
+ `ℹ️ No staged switch pending.\n` +
641
+ `Current model: \`${current ?? "unknown"}\`\n\n` +
642
+ `Use \`/cli-sonnet\`, \`/cli-opus\` etc. to stage a switch.`,
643
+ };
644
+ }
645
+
646
+ api.logger.info(`[cli-bridge] applying staged switch → ${pending.model}`);
647
+ return applyModelSwitch(api, pending.model, pending.label);
648
+ },
649
+ } satisfies OpenClawPluginCommandDefinition);
650
+
651
+ // ── Phase 3b3: /cli-pending — show staged switch ───────────────────────────
652
+ api.registerCommand({
653
+ name: "cli-pending",
654
+ description: "Show the currently staged model switch (if any).",
655
+ requireAuth: false,
656
+ handler: async (): Promise<PluginCommandResult> => {
657
+ const pending = readPending();
658
+ const current = readCurrentModel();
659
+ if (!pending) {
660
+ return {
661
+ text:
662
+ `✅ No pending switch.\n` +
663
+ `Current model: \`${current ?? "unknown"}\``,
664
+ };
665
+ }
666
+ return {
667
+ text:
668
+ `📋 **Staged switch pending:**\n` +
669
+ `→ \`${pending.model}\` (${pending.label})\n` +
670
+ `Requested: ${pending.requestedAt}\n\n` +
671
+ `Current: \`${current ?? "unknown"}\`\n\n` +
672
+ `Run \`/cli-apply\` to apply after finishing your current task.\n` +
673
+ `Run \`/cli-sonnet --now\` etc. to discard and switch immediately.`,
674
+ };
675
+ },
676
+ } satisfies OpenClawPluginCommandDefinition);
677
+
532
678
  // ── Phase 3c: /cli-test — one-shot proxy ping, no global model switch ──────
533
679
  api.registerCommand({
534
680
  name: "cli-test",
@@ -605,11 +751,20 @@ const plugin = {
605
751
  }
606
752
  lines.push("");
607
753
  }
754
+ const pending = readPending();
755
+ const pendingNote = pending ? ` ← pending: ${pending.label}` : "";
756
+
608
757
  lines.push("*Utility*");
609
- lines.push(" /cli-back Restore previous model");
758
+ lines.push(` /cli-apply Apply staged switch${pendingNote}`);
759
+ lines.push(" /cli-pending Show staged switch (if any)");
760
+ lines.push(" /cli-back Restore previous model + clear staged");
610
761
  lines.push(" /cli-test [model] Health check (no model switch)");
611
762
  lines.push(" /cli-list This overview");
612
763
  lines.push("");
764
+ lines.push("*Switching safely:*");
765
+ lines.push(" /cli-sonnet → stages switch (safe, apply later)");
766
+ lines.push(" /cli-sonnet --now → immediate switch (only between sessions!)");
767
+ lines.push("");
613
768
  lines.push(`Proxy: \`127.0.0.1:${port}\``);
614
769
 
615
770
  return { text: lines.join("\n") };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-cli-bridge-elvatis",
3
3
  "name": "OpenClaw CLI Bridge",
4
- "version": "0.2.22",
4
+ "version": "0.2.25",
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.22",
3
+ "version": "0.2.25",
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": {
@@ -19,4 +19,4 @@
19
19
  "typescript": "^5.9.3",
20
20
  "vitest": "^4.0.18"
21
21
  }
22
- }
22
+ }