@koriit/opencode-claude-bridge 0.1.3 → 0.1.7
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 +1 -1
- package/src/index.ts +2 -2
- package/src/logger.ts +64 -17
- package/src/skill-inject.ts +7 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koriit/opencode-claude-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { injectCommandsAndAgents } from "./inject.js"
|
|
|
5
5
|
import { injectSkills } from "./skill-inject.js"
|
|
6
6
|
import { injectMcp } from "./mcp-inject.js"
|
|
7
7
|
import { injectLsp } from "./lsp-inject.js"
|
|
8
|
-
import { createLogger } from "./logger.js"
|
|
8
|
+
import { createLogger, type LoggingClient } from "./logger.js"
|
|
9
9
|
import { listClaudePlugins, selectEnabledPlugins } from "./selection.js"
|
|
10
10
|
import { collectExistingSkillNames } from "./skill-scan.js"
|
|
11
11
|
|
|
@@ -51,7 +51,7 @@ export const server: Plugin = async (_input, options) => {
|
|
|
51
51
|
|
|
52
52
|
return {
|
|
53
53
|
config: async (cfg) => {
|
|
54
|
-
const logger = createLogger(bridge.strict)
|
|
54
|
+
const logger = createLogger(bridge.strict, _input.client as unknown as LoggingClient)
|
|
55
55
|
try {
|
|
56
56
|
// Replay parse-time validation warnings (strict-promotable).
|
|
57
57
|
for (const w of warnings) logger.warn(w)
|
package/src/logger.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
+
/** Minimal duck-type for the OpenCode client — only the log() method we use. */
|
|
2
|
+
export interface LoggingClient {
|
|
3
|
+
log(params: { service?: string; level?: "debug" | "info" | "warn" | "error"; message?: string }): unknown
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
/** Thrown when a soft warning is promoted to a hard error under `strict` mode. */
|
|
2
7
|
export class BridgeError extends Error {
|
|
3
8
|
override name = "BridgeError"
|
|
4
9
|
}
|
|
5
10
|
|
|
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
11
|
export interface WarnOptions {
|
|
10
12
|
/**
|
|
11
13
|
* Whether this warning should be promoted to a hard error under `strict`.
|
|
12
14
|
* Defaults to `true` (parse failures, missing CLI). Set `false` for advisory
|
|
13
|
-
* warnings that must never abort the hook even in strict mode
|
|
14
|
-
* version-range notice, which always still attempts injection).
|
|
15
|
+
* warnings that must never abort the hook even in strict mode.
|
|
15
16
|
*/
|
|
16
17
|
fatalInStrict?: boolean
|
|
17
18
|
}
|
|
@@ -28,26 +29,72 @@ export interface Logger {
|
|
|
28
29
|
hadWarnings(): boolean
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
|
|
31
33
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
34
|
+
* Fallback output logger used when no real OpenCode client is available (tests,
|
|
35
|
+
* edge-case early errors). Writes to process.stderr in OpenCode's structured
|
|
36
|
+
* format and only when --print-logs is in argv — matching OpenCode's own
|
|
37
|
+
* log-visibility behaviour.
|
|
35
38
|
*/
|
|
36
|
-
|
|
39
|
+
const fallbackLog = (() => {
|
|
40
|
+
const enabled = process.argv.includes("--print-logs")
|
|
41
|
+
let last = Date.now()
|
|
42
|
+
|
|
43
|
+
function write(level: "INFO" | "WARN", msg: string): void {
|
|
44
|
+
if (!enabled) return
|
|
45
|
+
const now = Date.now()
|
|
46
|
+
const ts = new Date(now).toISOString().split(".")[0]
|
|
47
|
+
const diff = now - last
|
|
48
|
+
last = now
|
|
49
|
+
process.stderr.write(`${level.padEnd(5)} ${ts} +${diff}ms service=opencode-claude-bridge ${msg}\n`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
info: (msg: string) => write("INFO", msg),
|
|
54
|
+
warn: (msg: string) => write("WARN", msg),
|
|
55
|
+
}
|
|
56
|
+
})()
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create a logger bound to the resolved `strict` flag and the OpenCode client.
|
|
60
|
+
*
|
|
61
|
+
* When a client is provided, log entries are posted to the server via
|
|
62
|
+
* `client.log()` — they flow through OpenCode's own log pipeline, appear in
|
|
63
|
+
* the log file, and respect `--print-logs` automatically.
|
|
64
|
+
*
|
|
65
|
+
* When no client is provided (tests, early-startup errors) the fallback logger
|
|
66
|
+
* writes to process.stderr in the same format, gated on `--print-logs`.
|
|
67
|
+
*/
|
|
68
|
+
export function createLogger(strict: boolean, client?: LoggingClient): Logger {
|
|
37
69
|
let warningCount = 0
|
|
70
|
+
|
|
71
|
+
function logInfo(msg: string): void {
|
|
72
|
+
if (typeof client?.log === "function") {
|
|
73
|
+
void client.log({ service: "opencode-claude-bridge", level: "info", message: msg })
|
|
74
|
+
} else {
|
|
75
|
+
fallbackLog.info(msg)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function logWarn(msg: string): void {
|
|
80
|
+
if (typeof client?.log === "function") {
|
|
81
|
+
void client.log({ service: "opencode-claude-bridge", level: "warn", message: msg })
|
|
82
|
+
} else {
|
|
83
|
+
fallbackLog.warn(msg)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
38
87
|
return {
|
|
39
|
-
info(msg
|
|
40
|
-
|
|
88
|
+
info(msg) {
|
|
89
|
+
logInfo(msg)
|
|
41
90
|
},
|
|
42
|
-
warn(msg
|
|
91
|
+
warn(msg, opts) {
|
|
43
92
|
const fatalInStrict = opts?.fatalInStrict ?? true
|
|
44
93
|
warningCount++
|
|
45
|
-
if (strict && fatalInStrict)
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
console.warn(`${LOG_PREFIX} warning: ${msg}`)
|
|
94
|
+
if (strict && fatalInStrict) throw new BridgeError(msg)
|
|
95
|
+
logWarn(msg)
|
|
49
96
|
},
|
|
50
|
-
hadWarnings()
|
|
97
|
+
hadWarnings() {
|
|
51
98
|
return warningCount > 0
|
|
52
99
|
},
|
|
53
100
|
}
|
package/src/skill-inject.ts
CHANGED
|
@@ -353,12 +353,14 @@ async function injectPluginSkills(
|
|
|
353
353
|
continue
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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)
|