@koriit/opencode-claude-bridge 0.1.0 → 0.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koriit/opencode-claude-bridge",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "An OpenCode plugin that bridges enabled Claude Code plugins (commands, agents, skills, MCP, LSP) into OpenCode at runtime, namespaced so they never shadow your existing items.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/inject.ts CHANGED
@@ -202,6 +202,209 @@ function modelIfMappable(model: unknown): string | undefined {
202
202
  return model.includes("/") ? model : undefined
203
203
  }
204
204
 
205
+ // ── Agent color sanitization ──────────────────────────────────────────────────
206
+
207
+ /**
208
+ * OpenCode's agent `color` field accepts exactly two forms (from ConfigAgentV1.Info):
209
+ * 1. A hex string matching `^#[0-9a-fA-F]{6}$`
210
+ * 2. One of the 7 semantic enum values: primary | secondary | accent | success | warning | error | info
211
+ *
212
+ * Claude Code agents use free-form CSS color names (e.g. "magenta", "cyan"). Writing these
213
+ * raw into cfg.agent causes OpenCode's schema validation to throw at config.get — outside
214
+ * our try/catch — crashing the entire instance.
215
+ *
216
+ * This regex matches form 1.
217
+ */
218
+ const OPENCODE_HEX_COLOR_RE = /^#[0-9a-fA-F]{6}$/
219
+
220
+ /**
221
+ * The 7 semantic color tokens OpenCode accepts as an alternative to a hex code.
222
+ */
223
+ const OPENCODE_COLOR_ENUMS = new Set(["primary", "secondary", "accent", "success", "warning", "error", "info"])
224
+
225
+ /**
226
+ * Mapping from CSS named colors to their `#rrggbb` equivalents.
227
+ * This covers the named colors Claude Code agents realistically use in frontmatter.
228
+ * Standard CSS named-color hex values (per the CSS Color Level 4 spec).
229
+ */
230
+ const CSS_COLOR_TO_HEX: Record<string, string> = {
231
+ // Common single-word CSS colors used in Claude agent frontmatter
232
+ aliceblue: "#f0f8ff",
233
+ antiquewhite: "#faebd7",
234
+ aqua: "#00ffff",
235
+ aquamarine: "#7fffd4",
236
+ azure: "#f0ffff",
237
+ beige: "#f5f5dc",
238
+ bisque: "#ffe4c4",
239
+ black: "#000000",
240
+ blanchedalmond: "#ffebcd",
241
+ blue: "#0000ff",
242
+ blueviolet: "#8a2be2",
243
+ brown: "#a52a2a",
244
+ burlywood: "#deb887",
245
+ cadetblue: "#5f9ea0",
246
+ chartreuse: "#7fff00",
247
+ chocolate: "#d2691e",
248
+ coral: "#ff7f50",
249
+ cornflowerblue: "#6495ed",
250
+ cornsilk: "#fff8dc",
251
+ crimson: "#dc143c",
252
+ cyan: "#00ffff",
253
+ darkblue: "#00008b",
254
+ darkcyan: "#008b8b",
255
+ darkgoldenrod: "#b8860b",
256
+ darkgray: "#a9a9a9",
257
+ darkgreen: "#006400",
258
+ darkgrey: "#a9a9a9",
259
+ darkkhaki: "#bdb76b",
260
+ darkmagenta: "#8b008b",
261
+ darkolivegreen: "#556b2f",
262
+ darkorange: "#ff8c00",
263
+ darkorchid: "#9932cc",
264
+ darkred: "#8b0000",
265
+ darksalmon: "#e9967a",
266
+ darkseagreen: "#8fbc8f",
267
+ darkslateblue: "#483d8b",
268
+ darkslategray: "#2f4f4f",
269
+ darkslategrey: "#2f4f4f",
270
+ darkturquoise: "#00ced1",
271
+ darkviolet: "#9400d3",
272
+ deeppink: "#ff1493",
273
+ deepskyblue: "#00bfff",
274
+ dimgray: "#696969",
275
+ dimgrey: "#696969",
276
+ dodgerblue: "#1e90ff",
277
+ firebrick: "#b22222",
278
+ floralwhite: "#fffaf0",
279
+ forestgreen: "#228b22",
280
+ fuchsia: "#ff00ff",
281
+ gainsboro: "#dcdcdc",
282
+ ghostwhite: "#f8f8ff",
283
+ gold: "#ffd700",
284
+ goldenrod: "#daa520",
285
+ gray: "#808080",
286
+ green: "#008000",
287
+ greenyellow: "#adff2f",
288
+ grey: "#808080",
289
+ honeydew: "#f0fff0",
290
+ hotpink: "#ff69b4",
291
+ indianred: "#cd5c5c",
292
+ indigo: "#4b0082",
293
+ ivory: "#fffff0",
294
+ khaki: "#f0e68c",
295
+ lavender: "#e6e6fa",
296
+ lavenderblush: "#fff0f5",
297
+ lawngreen: "#7cfc00",
298
+ lemonchiffon: "#fffacd",
299
+ lightblue: "#add8e6",
300
+ lightcoral: "#f08080",
301
+ lightcyan: "#e0ffff",
302
+ lightgoldenrodyellow: "#fafad2",
303
+ lightgray: "#d3d3d3",
304
+ lightgreen: "#90ee90",
305
+ lightgrey: "#d3d3d3",
306
+ lightpink: "#ffb6c1",
307
+ lightsalmon: "#ffa07a",
308
+ lightseagreen: "#20b2aa",
309
+ lightskyblue: "#87cefa",
310
+ lightslategray: "#778899",
311
+ lightslategrey: "#778899",
312
+ lightsteelblue: "#b0c4de",
313
+ lightyellow: "#ffffe0",
314
+ lime: "#00ff00",
315
+ limegreen: "#32cd32",
316
+ linen: "#faf0e6",
317
+ magenta: "#ff00ff",
318
+ maroon: "#800000",
319
+ mediumaquamarine: "#66cdaa",
320
+ mediumblue: "#0000cd",
321
+ mediumorchid: "#ba55d3",
322
+ mediumpurple: "#9370db",
323
+ mediumseagreen: "#3cb371",
324
+ mediumslateblue: "#7b68ee",
325
+ mediumspringgreen: "#00fa9a",
326
+ mediumturquoise: "#48d1cc",
327
+ mediumvioletred: "#c71585",
328
+ midnightblue: "#191970",
329
+ mintcream: "#f5fffa",
330
+ mistyrose: "#ffe4e1",
331
+ moccasin: "#ffe4b5",
332
+ navajowhite: "#ffdead",
333
+ navy: "#000080",
334
+ oldlace: "#fdf5e6",
335
+ olive: "#808000",
336
+ olivedrab: "#6b8e23",
337
+ orange: "#ffa500",
338
+ orangered: "#ff4500",
339
+ orchid: "#da70d6",
340
+ palegoldenrod: "#eee8aa",
341
+ palegreen: "#98fb98",
342
+ paleturquoise: "#afeeee",
343
+ palevioletred: "#db7093",
344
+ papayawhip: "#ffefd5",
345
+ peachpuff: "#ffdab9",
346
+ peru: "#cd853f",
347
+ pink: "#ffc0cb",
348
+ plum: "#dda0dd",
349
+ powderblue: "#b0e0e6",
350
+ purple: "#800080",
351
+ rebeccapurple: "#663399",
352
+ red: "#ff0000",
353
+ rosybrown: "#bc8f8f",
354
+ royalblue: "#4169e1",
355
+ saddlebrown: "#8b4513",
356
+ salmon: "#fa8072",
357
+ sandybrown: "#f4a460",
358
+ seagreen: "#2e8b57",
359
+ seashell: "#fff5ee",
360
+ sienna: "#a0522d",
361
+ silver: "#c0c0c0",
362
+ skyblue: "#87ceeb",
363
+ slateblue: "#6a5acd",
364
+ slategray: "#708090",
365
+ slategrey: "#708090",
366
+ snow: "#fffafa",
367
+ springgreen: "#00ff7f",
368
+ steelblue: "#4682b4",
369
+ tan: "#d2b48c",
370
+ teal: "#008080",
371
+ thistle: "#d8bfd8",
372
+ tomato: "#ff6347",
373
+ turquoise: "#40e0d0",
374
+ violet: "#ee82ee",
375
+ wheat: "#f5deb3",
376
+ white: "#ffffff",
377
+ whitesmoke: "#f5f5f5",
378
+ yellow: "#ffff00",
379
+ yellowgreen: "#9acd32",
380
+ }
381
+
382
+ /**
383
+ * Sanitize an agent `color` value for injection into OpenCode's config.
384
+ *
385
+ * Returns the sanitized string if it can be mapped to a valid OpenCode color,
386
+ * or `null` if the value is unrecognizable and must be dropped. Never returns
387
+ * a value that would fail OpenCode's `^#[0-9a-fA-F]{6}$` / enum check.
388
+ *
389
+ * Decision table:
390
+ * - Already a valid hex `#rrggbb` → pass through as-is
391
+ * - Already one of the 7 OpenCode enum tokens → pass through as-is
392
+ * - A known CSS named color (case-insensitive) → map to hex
393
+ * - Anything else → null (caller drops + warns)
394
+ */
395
+ export function sanitizeAgentColor(value: string): string | null {
396
+ // Fast-path: already valid.
397
+ if (OPENCODE_HEX_COLOR_RE.test(value)) return value
398
+ if (OPENCODE_COLOR_ENUMS.has(value)) return value
399
+
400
+ // Try CSS named color (case-insensitive lookup).
401
+ const hex = CSS_COLOR_TO_HEX[value.toLowerCase()]
402
+ if (hex !== undefined) return hex
403
+
404
+ // Unknown — must be dropped to avoid an OpenCode schema error.
405
+ return null
406
+ }
407
+
205
408
  // ── Command injection for one plugin ─────────────────────────────────────────
206
409
 
207
410
  /**
@@ -411,10 +614,12 @@ async function injectPluginAgents(
411
614
  if (typeof variant === "string") entry.variant = variant
412
615
 
413
616
  const temperature = data["temperature"]
414
- if (typeof temperature === "number") entry.temperature = temperature
617
+ // OpenCode uses Schema.Finite which rejects NaN, Infinity, and -Infinity.
618
+ if (typeof temperature === "number" && isFinite(temperature)) entry.temperature = temperature
415
619
 
416
620
  const top_p = data["top_p"]
417
- if (typeof top_p === "number") entry.top_p = top_p
621
+ // Same finite constraint as temperature.
622
+ if (typeof top_p === "number" && isFinite(top_p)) entry.top_p = top_p
418
623
 
419
624
  const steps = data["steps"]
420
625
  if (typeof steps === "number" && Number.isInteger(steps) && steps > 0) entry.steps = steps
@@ -422,8 +627,18 @@ async function injectPluginAgents(
422
627
  const hidden = data["hidden"]
423
628
  if (typeof hidden === "boolean") entry.hidden = hidden
424
629
 
425
- const color = data["color"]
426
- if (typeof color === "string") entry.color = color
630
+ const colorRaw = data["color"]
631
+ if (typeof colorRaw === "string") {
632
+ const color = sanitizeAgentColor(colorRaw)
633
+ if (color !== null) {
634
+ entry.color = color
635
+ } else {
636
+ logger.warn(
637
+ `agent "${bareName}" from plugin "${plugin.id}" has unrecognized color value "${colorRaw}"; dropping color field`,
638
+ { fatalInStrict: false },
639
+ )
640
+ }
641
+ }
427
642
 
428
643
  cfg.agent[name] = entry
429
644
  summary.agents++
package/src/mcp-inject.ts CHANGED
@@ -178,7 +178,15 @@ export function mapClaudeMcpServer(
178
178
  if (typeof server.oauth.clientId === "string") oauth.clientId = server.oauth.clientId
179
179
  if (typeof server.oauth.clientSecret === "string") oauth.clientSecret = server.oauth.clientSecret
180
180
  if (typeof server.oauth.scope === "string") oauth.scope = server.oauth.scope
181
- if (typeof server.oauth.callbackPort === "number") oauth.callbackPort = server.oauth.callbackPort
181
+ // OpenCode enforces callbackPort as an integer in [1, 65535] (Schema.isBetween).
182
+ if (
183
+ typeof server.oauth.callbackPort === "number" &&
184
+ Number.isInteger(server.oauth.callbackPort) &&
185
+ server.oauth.callbackPort >= 1 &&
186
+ server.oauth.callbackPort <= 65535
187
+ ) {
188
+ oauth.callbackPort = server.oauth.callbackPort
189
+ }
182
190
  if (typeof server.oauth.redirectUri === "string") oauth.redirectUri = server.oauth.redirectUri
183
191
  entry.oauth = oauth
184
192
  }