@elvatis_com/openclaw-cli-bridge-elvatis 0.2.19 → 0.2.21

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,14 +1,15 @@
1
1
  # STATUS.md — openclaw-cli-bridge-elvatis
2
2
 
3
- _Last updated: 2026-03-08 by Akido (claude-sonnet-4-6)_
3
+ _Last updated: 2026-03-11 by Akido (claude-sonnet-4-6)_
4
4
 
5
- ## Current Version: 0.2.19 — STABLE
5
+ ## Current Version: 0.2.21 — STABLE
6
6
 
7
7
  ## What is done
8
8
 
9
9
  - ✅ Repo: `https://github.com/elvatis/openclaw-cli-bridge-elvatis`
10
- - ✅ npm: `@@@@elvatis_com/openclaw-cli-bridge-elvatis@0.2.19`
11
- - ✅ ClawHub: `openclaw-cli-bridge-elvatis@0.2.19`
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
12
13
  - ✅ Phase 1: `openai-codex` provider via `~/.codex/auth.json` (no re-login)
13
14
  - ✅ Phase 2: Local OpenAI-compatible proxy on `127.0.0.1:31337` (Gemini + Claude CLI)
14
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`)
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.19`
5
+ **Current version:** `0.2.21`
6
6
 
7
7
  ---
8
8
 
@@ -263,6 +263,12 @@ Slash commands (requireAuth=false, gateway commands.allowFrom is the auth layer)
263
263
  **Cause:** Gateway injects large values into `process.env` at runtime. Spreading it into `spawn()` exceeds Linux's `ARG_MAX` (~2MB).
264
264
  **Fix:** `buildMinimalEnv()` — only passes `HOME`, `PATH`, `USER`, and auth keys.
265
265
 
266
+ ### Claude Code 401 / timeout on OAuth login (fixed in v0.2.21)
267
+ **Symptom:** `/cli-test cli-claude/*` times out after 30s; logs show `401 Invalid authentication credentials`.
268
+ **Cause:** `buildMinimalEnv()` did not forward `XDG_RUNTIME_DIR` and `DBUS_SESSION_BUS_ADDRESS` to the spawned `claude` subprocess. Claude Code authenticated via `claude.ai` OAuth (Claude Max plan) stores its tokens in the system keyring (Gnome Keyring / libsecret) and needs these env vars to access it.
269
+ **Affects:** Only systems using `claude auth` OAuth login (Claude Max / Teams). API-key users (`ANTHROPIC_API_KEY`) are not affected.
270
+ **Fix:** Added `XDG_RUNTIME_DIR` and `DBUS_SESSION_BUS_ADDRESS` to the forwarded env keys in `buildMinimalEnv()`.
271
+
266
272
  ### Gemini agentic mode / hangs (fixed in v0.2.4)
267
273
  **Symptom:** Gemini hangs, returns wrong answers, or says "directory does not exist".
268
274
  **Cause:** `@file` syntax (`gemini -p @/tmp/xxx.txt`) triggers agentic mode — Gemini scans the working directory for project context and treats prompts as task instructions.
@@ -281,6 +287,14 @@ npm test # vitest run (45 tests)
281
287
 
282
288
  ## Changelog
283
289
 
290
+ ### v0.2.21
291
+ - **fix:** `buildMinimalEnv()` now forwards `XDG_RUNTIME_DIR` and `DBUS_SESSION_BUS_ADDRESS` to Claude Code subprocesses — required for Gnome Keyring / libsecret access when Claude Code is authenticated via `claude.ai` OAuth (Claude Max). Without these, the spawned `claude` process cannot read its OAuth token from the system keyring, resulting in `401 Invalid authentication credentials` and a 30-second timeout on `/cli-test` and all `/cli-claude/*` requests.
292
+
293
+ ### v0.2.20
294
+ - **fix:** `formatPrompt` now defensively coerces `content` to string via `contentToString()` — prevents `[object Object]` reaching the CLI when WhatsApp group messages contain structured content objects instead of plain strings
295
+ - **feat:** `ChatMessage.content` now accepts `string | ContentPart[] | unknown` (OpenAI multimodal content arrays supported)
296
+ - **feat:** New `contentToString()` helper: handles string, OpenAI ContentPart arrays, arbitrary objects (JSON.stringify), null/undefined
297
+
284
298
  ### v0.2.19
285
299
  - **feat:** `/cli-list` command — formatted overview of all registered models grouped by provider
286
300
  - **docs:** Rewrite README to reflect current state (correct model names, command count, requireAuth, test count, /cli-list docs)
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.19
56
+ **Version:** 0.2.21
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "openclaw-cli-bridge-elvatis",
3
3
  "name": "OpenClaw CLI Bridge",
4
- "version": "0.2.19",
4
+ "version": "0.2.21",
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.19",
3
+ "version": "0.2.21",
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
+ }
package/src/cli-runner.ts CHANGED
@@ -24,9 +24,15 @@ const MAX_MSG_CHARS = 4000;
24
24
  // Message formatting
25
25
  // ──────────────────────────────────────────────────────────────────────────────
26
26
 
27
+ export interface ContentPart {
28
+ type: string;
29
+ text?: string;
30
+ }
31
+
27
32
  export interface ChatMessage {
28
33
  role: "system" | "user" | "assistant";
29
- content: string;
34
+ /** Plain string or OpenAI-style content array (multimodal / structured). */
35
+ content: string | ContentPart[] | unknown;
30
36
  }
31
37
 
32
38
  /**
@@ -61,7 +67,30 @@ export function formatPrompt(messages: ChatMessage[]): string {
61
67
  .join("\n\n");
62
68
  }
63
69
 
64
- function truncateContent(s: string): string {
70
+ /**
71
+ * Coerce any message content value to a plain string.
72
+ *
73
+ * Handles:
74
+ * - string → as-is
75
+ * - ContentPart[] → join text parts (OpenAI multimodal format)
76
+ * - other object → JSON.stringify (prevents "[object Object]" from reaching the CLI)
77
+ * - null/undefined → ""
78
+ */
79
+ function contentToString(content: unknown): string {
80
+ if (typeof content === "string") return content;
81
+ if (content === null || content === undefined) return "";
82
+ if (Array.isArray(content)) {
83
+ return (content as ContentPart[])
84
+ .filter((c) => c?.type === "text" && typeof c.text === "string")
85
+ .map((c) => c.text!)
86
+ .join("\n");
87
+ }
88
+ if (typeof content === "object") return JSON.stringify(content);
89
+ return String(content);
90
+ }
91
+
92
+ function truncateContent(raw: unknown): string {
93
+ const s = contentToString(raw);
65
94
  if (s.length <= MAX_MSG_CHARS) return s;
66
95
  return s.slice(0, MAX_MSG_CHARS) + `\n...[truncated ${s.length - MAX_MSG_CHARS} chars]`;
67
96
  }
@@ -96,6 +125,9 @@ function buildMinimalEnv(): Record<string, string> {
96
125
  "XDG_CONFIG_HOME",
97
126
  "XDG_DATA_HOME",
98
127
  "XDG_CACHE_HOME",
128
+ // Required for Claude Code OAuth (Gnome Keyring / libsecret access)
129
+ "XDG_RUNTIME_DIR",
130
+ "DBUS_SESSION_BUS_ADDRESS",
99
131
  ]) {
100
132
  const v = pick(key);
101
133
  if (v) env[key] = v;
@@ -99,6 +99,60 @@ describe("formatPrompt", () => {
99
99
  expect(result).toContain("[System]");
100
100
  expect(result).toContain("[User]");
101
101
  });
102
+
103
+ // contentToString coercion tests (fix: [object Object] in WhatsApp group messages)
104
+ it("coerces ContentPart array to plain text", () => {
105
+ const result = formatPrompt([
106
+ { role: "user", content: [{ type: "text", text: "Hello from WA group" }] },
107
+ ]);
108
+ expect(result).toBe("Hello from WA group");
109
+ expect(result).not.toContain("[object Object]");
110
+ });
111
+
112
+ it("joins multiple text ContentParts with newline", () => {
113
+ const result = formatPrompt([
114
+ {
115
+ role: "user",
116
+ content: [
117
+ { type: "text", text: "Part one" },
118
+ { type: "text", text: "Part two" },
119
+ ],
120
+ },
121
+ ]);
122
+ expect(result).toContain("Part one");
123
+ expect(result).toContain("Part two");
124
+ });
125
+
126
+ it("ignores non-text ContentParts (e.g. image)", () => {
127
+ const result = formatPrompt([
128
+ {
129
+ role: "user",
130
+ content: [
131
+ { type: "image_url", url: "https://example.com/img.png" },
132
+ { type: "text", text: "describe this" },
133
+ ],
134
+ },
135
+ ]);
136
+ expect(result).toBe("describe this");
137
+ });
138
+
139
+ it("coerces plain object content to JSON string (not [object Object])", () => {
140
+ const result = formatPrompt([
141
+ { role: "user", content: { text: "structured", extra: 42 } as any },
142
+ ]);
143
+ expect(result).not.toBe("[object Object]");
144
+ expect(result).toContain("structured");
145
+ });
146
+
147
+ it("handles null content gracefully", () => {
148
+ const result = formatPrompt([{ role: "user", content: null as any }]);
149
+ expect(result).toBe("");
150
+ });
151
+
152
+ it("handles undefined content gracefully", () => {
153
+ const result = formatPrompt([{ role: "user", content: undefined as any }]);
154
+ expect(result).toBe("");
155
+ });
102
156
  });
103
157
 
104
158
  // ──────────────────────────────────────────────────────────────────────────────