@koriit/opencode-claude-bridge 0.1.3 → 0.1.8

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.3",
3
+ "version": "0.1.8",
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/logger.ts CHANGED
@@ -3,15 +3,11 @@ export class BridgeError extends Error {
3
3
  override name = "BridgeError"
4
4
  }
5
5
 
6
- /** Prefix every line the bridge emits so its output is greppable in OpenCode's logs. */
7
- export const LOG_PREFIX = "[opencode-claude-bridge]"
8
-
9
6
  export interface WarnOptions {
10
7
  /**
11
8
  * Whether this warning should be promoted to a hard error under `strict`.
12
9
  * Defaults to `true` (parse failures, missing CLI). Set `false` for advisory
13
- * warnings that must never abort the hook even in strict mode (e.g. the §9
14
- * version-range notice, which always still attempts injection).
10
+ * warnings that must never abort the hook even in strict mode.
15
11
  */
16
12
  fatalInStrict?: boolean
17
13
  }
@@ -29,25 +25,51 @@ export interface Logger {
29
25
  }
30
26
 
31
27
  /**
32
- * Create a logger bound to the resolved `strict` flag. The hook itself is responsible
33
- * for catching {@link BridgeError} in non-strict paths; in strict mode it lets the
34
- * error propagate so OpenCode surfaces a hard failure (design §10).
28
+ * Internal output logger. Writes to process.stderr in OpenCode's structured
29
+ * log format, gated on --print-logs to match OpenCode's own log-visibility
30
+ * behaviour.
31
+ *
32
+ * Note: client.log() (HTTP POST to /log) was tried but is unreliable during
33
+ * the config hook because it fires during server bootstrap before the /log
34
+ * endpoint is ready. Direct stderr write is the safe, synchronous alternative.
35
+ */
36
+ const log = (() => {
37
+ const enabled = process.argv.includes("--print-logs")
38
+ let last = Date.now()
39
+
40
+ function write(level: "INFO" | "WARN", msg: string): void {
41
+ if (!enabled) return
42
+ const now = Date.now()
43
+ const ts = new Date(now).toISOString().split(".")[0]
44
+ const diff = now - last
45
+ last = now
46
+ process.stderr.write(`${level.padEnd(5)} ${ts} +${diff}ms service=opencode-claude-bridge ${msg}\n`)
47
+ }
48
+
49
+ return {
50
+ info: (msg: string) => write("INFO", msg),
51
+ warn: (msg: string) => write("WARN", msg),
52
+ }
53
+ })()
54
+
55
+ /**
56
+ * Create a logger bound to the resolved `strict` flag. The hook itself is
57
+ * responsible for catching {@link BridgeError} in non-strict paths; in strict
58
+ * mode the error propagates so OpenCode surfaces a hard failure.
35
59
  */
36
60
  export function createLogger(strict: boolean): Logger {
37
61
  let warningCount = 0
38
62
  return {
39
- info(msg: string): void {
40
- console.log(`${LOG_PREFIX} ${msg}`)
63
+ info(msg) {
64
+ log.info(msg)
41
65
  },
42
- warn(msg: string, opts?: WarnOptions): void {
66
+ warn(msg, opts) {
43
67
  const fatalInStrict = opts?.fatalInStrict ?? true
44
68
  warningCount++
45
- if (strict && fatalInStrict) {
46
- throw new BridgeError(msg)
47
- }
48
- console.warn(`${LOG_PREFIX} warning: ${msg}`)
69
+ if (strict && fatalInStrict) throw new BridgeError(msg)
70
+ log.warn(msg)
49
71
  },
50
- hadWarnings(): boolean {
72
+ hadWarnings() {
51
73
  return warningCount > 0
52
74
  },
53
75
  }
@@ -353,12 +353,14 @@ async function injectPluginSkills(
353
353
  continue
354
354
  }
355
355
 
356
- const bareName = extractSkillName(content)
357
- if (bareName === null) {
358
- logger.warn(
359
- `SKILL.md at "${skillMdPath}" from plugin "${plugin.id}" has no frontmatter name; skipping`,
356
+ const extractedName = extractSkillName(content)
357
+ // Fall back to the directory name when the SKILL.md has no `name:` field —
358
+ // the directory name is the conventional skill identifier and is always present.
359
+ const bareName = extractedName ?? subdir
360
+ if (extractedName === null) {
361
+ logger.info(
362
+ `SKILL.md at "${skillMdPath}" from plugin "${plugin.id}" has no frontmatter name; using directory name "${subdir}"`,
360
363
  )
361
- continue
362
364
  }
363
365
 
364
366
  const { name: allocatedName, renamed } = allocator.claim(plugin.id, bareName)