@khalilgharbaoui/opencode-claude-code-plugin 0.4.16 → 0.4.17

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/README.md CHANGED
@@ -319,30 +319,68 @@ Set `permissionMode: "plan"` to forward `--permission-mode plan` to Claude. The
319
319
  - **Lazy `cwd`.** The working directory is re-resolved at every request, so opencode's project-aware behavior works without restarting the plugin.
320
320
  - **Variants survive merge.** opencode recalculates variant lists after the plugin loads; the plugin re-injects defaults into runtime config so your variants don't disappear.
321
321
 
322
- ## Debug logging
322
+ ## Logging
323
323
 
324
- Two independent knobs:
324
+ Configure via `opencode.jsonc` (launch-method-independent) or env vars
325
+ (temporary override for a single process). The plugin has four orthogonal
326
+ knobs:
325
327
 
326
- ```bash
327
- # Verbose logging to stderr (opencode surfaces stderr as UI warnings):
328
- DEBUG=opencode-claude-code opencode
328
+ | Field | Values | Default | Effect |
329
+ |---|---|---|---|
330
+ | `file` | `true \| false` | `false` | Persist log entries to disk |
331
+ | `dir` | path string | `~/.local/share/opencode-claude-code/` | Custom file location |
332
+ | `mode` | `"silent" \| "debug"` | `"silent"` | TUI policy |
333
+ | `level` | `"debug" \| "info" \| "notice" \| "warn" \| "error"` | `"info"` | Minimum level to emit |
334
+
335
+ Rails-style threshold: anything below `level` is dropped before either
336
+ destination decides what to do. `mode: "silent"` routes DEBUG/INFO/NOTICE
337
+ to file only and lets WARN/ERROR bubble in the TUI (they always do).
338
+ `mode: "debug"` additionally echoes every emitted level to the TUI (which
339
+ opencode surfaces as warning bubbles).
340
+
341
+ **Recommended dev setup** — capture audit trail to disk, keep TUI quiet:
342
+
343
+ ```jsonc
344
+ "@khalilgharbaoui/opencode-claude-code-plugin": {
345
+ "logging": { "file": true }
346
+ }
347
+ ```
348
+
349
+ **Full firehose for deep debugging** (every DEBUG stream event captured):
350
+
351
+ ```jsonc
352
+ "logging": { "file": true, "level": "debug" }
353
+ ```
329
354
 
330
- # Persistent file log (default: OFF file is not created at all):
331
- OPENCODE_CLAUDE_CODE_LOG_FILE=1 opencode
355
+ **Live TUI noise** (everything echoes to opencode's stderr warning bubbles):
332
356
 
333
- # Both:
334
- DEBUG=opencode-claude-code OPENCODE_CLAUDE_CODE_LOG_FILE=1 opencode
357
+ ```jsonc
358
+ "logging": { "file": true, "mode": "debug" }
335
359
  ```
336
360
 
337
- When `OPENCODE_CLAUDE_CODE_LOG_FILE` is set to any truthy value (`1`,
338
- `true`, `yes`, `on`), the plugin writes NOTICE/WARN/ERROR (plus INFO and
339
- DEBUG when `DEBUG=opencode-claude-code` is also set) to
340
- `~/.local/share/opencode-claude-code/plugin.log` with 5MB rotation. Override
341
- the directory with `OPENCODE_CLAUDE_CODE_LOG_DIR=/custom/path`.
361
+ ### Env-var overrides
362
+
363
+ Set explicitly to override config for one process — useful for one-off
364
+ debugging without editing `opencode.jsonc`:
365
+
366
+ ```bash
367
+ OPENCODE_CLAUDE_CODE_LOG_FILE=1 opencode # file on
368
+ OPENCODE_CLAUDE_CODE_LOG_FILE=0 opencode # file off (overrides config:true)
369
+ OPENCODE_CLAUDE_CODE_LOG_DIR=/tmp/cc opencode # custom dir
370
+ OPENCODE_CLAUDE_CODE_LOG_LEVEL=debug opencode # capture every level
371
+ DEBUG=opencode-claude-code opencode # promote to mode:"debug"
372
+ ```
373
+
374
+ Boolean env vars accept `1/true/on/yes` for on and `0/false/no/off` for
375
+ off; empty / unset falls through to config. Invalid `level` values fall
376
+ through to config.
377
+
378
+ ### Default behavior (no config, no env)
342
379
 
343
- Default is off so the plugin doesn't accrete a log file on every user's
344
- disk. Opt in when you need to inspect auto-continue decisions, broker
345
- state, or other plugin internals.
380
+ Nothing persists; only WARN and ERROR bubble in the TUI. The plugin
381
+ doesn't accrete a log file on every user's disk by default — opt in when
382
+ you need to inspect auto-continue decisions, broker state, or other
383
+ plugin internals.
346
384
 
347
385
  ## Known limitations
348
386
 
package/dist/index.d.ts CHANGED
@@ -117,7 +117,38 @@ interface ClaudeCodeConfig {
117
117
  proxyOpencodeMcpTools?: boolean;
118
118
  multiStepContinuation?: boolean;
119
119
  autoContinueIncompleteTurns?: boolean | "smart";
120
+ logging?: LoggingConfig;
120
121
  }
122
+ interface LoggingConfig {
123
+ /**
124
+ * Persist log activity (DEBUG / INFO / NOTICE / WARN / ERROR — those
125
+ * passing `level`) to a file. Default: `false`. When `false`, entries
126
+ * below WARN vanish entirely; WARN / ERROR still surface in the TUI via
127
+ * stderr. Set to `true` to capture the audit trail to disk for review
128
+ * via `tail` / `grep`.
129
+ */
130
+ file?: boolean;
131
+ /**
132
+ * Optional custom directory for the file log. Defaults to
133
+ * `~/.local/share/opencode-claude-code/`. Has no effect when `file:false`.
134
+ */
135
+ dir?: string;
136
+ /**
137
+ * TUI policy. `"silent"` (default) routes DEBUG / INFO / NOTICE to file
138
+ * only; WARN / ERROR still bubble in the TUI as they always do. `"debug"`
139
+ * additionally echoes every emitted level to stderr (which opencode's TUI
140
+ * surfaces as warning bubbles).
141
+ */
142
+ mode?: LogMode;
143
+ /**
144
+ * Minimum level to emit anywhere. Anything below the threshold is dropped
145
+ * before either destination decides what to do. Order:
146
+ * `debug` < `info` < `notice` < `warn` < `error`. Default: `"info"`.
147
+ */
148
+ level?: LogLevel;
149
+ }
150
+ type LogLevel = "debug" | "info" | "notice" | "warn" | "error";
151
+ type LogMode = "silent" | "debug";
121
152
  type WebSearchRouting = "claude" | "disabled" | (string & {});
122
153
  interface ClaudeCodeProviderSettings {
123
154
  cliPath?: string;
@@ -233,6 +264,14 @@ interface ClaudeCodeProviderSettings {
233
264
  * Set to `false` to disable.
234
265
  */
235
266
  autoContinueIncompleteTurns?: boolean | "smart";
267
+ /**
268
+ * Logger configuration. See `LoggingConfig` for fields. Env vars
269
+ * (`OPENCODE_CLAUDE_CODE_LOG_FILE`, `OPENCODE_CLAUDE_CODE_LOG_DIR`,
270
+ * `OPENCODE_CLAUDE_CODE_LOG_LEVEL`, `DEBUG=opencode-claude-code`) override
271
+ * these values when explicitly set, so a developer can flip behavior for
272
+ * one process without editing opencode.jsonc.
273
+ */
274
+ logging?: LoggingConfig;
236
275
  }
237
276
  type PermissionMode = "acceptEdits" | "auto" | "bypassPermissions" | "default" | "dontAsk" | "plan";
238
277
  type ControlRequestBehavior = "allow" | "deny";
package/dist/index.js CHANGED
@@ -5,34 +5,80 @@ import { generateId } from "@ai-sdk/provider-utils";
5
5
  import { appendFileSync, mkdirSync, renameSync, statSync } from "fs";
6
6
  import { homedir } from "os";
7
7
  import { dirname, join } from "path";
8
- var DEBUG = process.env.DEBUG?.includes("opencode-claude-code") ?? false;
9
- var LOG_DIR = process.env.OPENCODE_CLAUDE_CODE_LOG_DIR ?? join(homedir(), ".local", "share", "opencode-claude-code");
10
- var LOG_FILE = join(LOG_DIR, "plugin.log");
8
+ var LEVEL_RANK = {
9
+ debug: 0,
10
+ info: 1,
11
+ notice: 2,
12
+ warn: 3,
13
+ error: 4
14
+ };
11
15
  var MAX_LOG_BYTES = 5 * 1024 * 1024;
12
- function isTruthyEnv(v) {
13
- if (v == null) return false;
16
+ var DEFAULT_DIR = join(homedir(), ".local", "share", "opencode-claude-code");
17
+ var DEFAULT_CONFIG = {
18
+ file: false,
19
+ dir: null,
20
+ mode: "silent",
21
+ level: "info"
22
+ };
23
+ function parseBoolEnv(v) {
24
+ if (v == null) return void 0;
25
+ const s = v.toLowerCase().trim();
26
+ if (s === "") return void 0;
27
+ if (s === "0" || s === "false" || s === "no" || s === "off") return false;
28
+ return true;
29
+ }
30
+ function parseLevelEnv(v) {
31
+ if (v == null) return void 0;
14
32
  const s = v.toLowerCase().trim();
15
- if (s === "") return false;
16
- return s !== "0" && s !== "false" && s !== "no" && s !== "off";
33
+ if (s === "") return void 0;
34
+ if (s === "debug" || s === "info" || s === "notice" || s === "warn" || s === "error") {
35
+ return s;
36
+ }
37
+ return void 0;
17
38
  }
18
- var LOG_FILE_ENABLED = isTruthyEnv(process.env.OPENCODE_CLAUDE_CODE_LOG_FILE);
39
+ function parseModeFromDebugEnv(v) {
40
+ if (v == null || v === "") return void 0;
41
+ return v.includes("opencode-claude-code") ? "debug" : void 0;
42
+ }
43
+ function withEnvOverrides(base) {
44
+ const result = { ...base };
45
+ const envFile = parseBoolEnv(process.env.OPENCODE_CLAUDE_CODE_LOG_FILE);
46
+ if (envFile !== void 0) result.file = envFile;
47
+ const envDir = process.env.OPENCODE_CLAUDE_CODE_LOG_DIR;
48
+ if (envDir !== void 0 && envDir !== "") result.dir = envDir;
49
+ const envMode = parseModeFromDebugEnv(process.env.DEBUG);
50
+ if (envMode !== void 0) result.mode = envMode;
51
+ const envLevel = parseLevelEnv(process.env.OPENCODE_CLAUDE_CODE_LOG_LEVEL);
52
+ if (envLevel !== void 0) result.level = envLevel;
53
+ return result;
54
+ }
55
+ var activeConfig = withEnvOverrides(DEFAULT_CONFIG);
19
56
  var fileLoggingDisabled = false;
20
- function rotateIfNeeded() {
57
+ function configureLogger(input) {
58
+ const merged = { ...DEFAULT_CONFIG, ...input };
59
+ activeConfig = withEnvOverrides(merged);
60
+ fileLoggingDisabled = false;
61
+ }
62
+ function resolvedLogFile() {
63
+ return join(activeConfig.dir ?? DEFAULT_DIR, "plugin.log");
64
+ }
65
+ function rotateIfNeeded(logFile) {
21
66
  try {
22
- const stat = statSync(LOG_FILE);
67
+ const stat = statSync(logFile);
23
68
  if (stat.size > MAX_LOG_BYTES) {
24
- renameSync(LOG_FILE, `${LOG_FILE}.1`);
69
+ renameSync(logFile, `${logFile}.1`);
25
70
  }
26
71
  } catch {
27
72
  }
28
73
  }
29
74
  function writeToFile(line) {
30
- if (!LOG_FILE_ENABLED) return;
75
+ if (!activeConfig.file) return;
31
76
  if (fileLoggingDisabled) return;
32
77
  try {
33
- mkdirSync(dirname(LOG_FILE), { recursive: true });
34
- rotateIfNeeded();
35
- appendFileSync(LOG_FILE, line + "\n", "utf8");
78
+ const logFile = resolvedLogFile();
79
+ mkdirSync(dirname(logFile), { recursive: true });
80
+ rotateIfNeeded(logFile);
81
+ appendFileSync(logFile, line + "\n", "utf8");
36
82
  } catch {
37
83
  fileLoggingDisabled = true;
38
84
  }
@@ -45,30 +91,36 @@ function fmt(level, msg, data) {
45
91
  }
46
92
  return base;
47
93
  }
48
- function emit(level, msg, data, alwaysStderr = false) {
49
- const line = fmt(level, msg, data);
50
- if (alwaysStderr || DEBUG) {
94
+ function shouldEmit(level) {
95
+ return LEVEL_RANK[level] >= LEVEL_RANK[activeConfig.level];
96
+ }
97
+ function shouldTui(level) {
98
+ if (level === "warn" || level === "error") return true;
99
+ return activeConfig.mode === "debug";
100
+ }
101
+ function emit(level, msg, data) {
102
+ if (!shouldEmit(level)) return;
103
+ const line = fmt(level.toUpperCase(), msg, data);
104
+ if (shouldTui(level)) {
51
105
  console.error(line);
52
106
  }
53
107
  writeToFile(line);
54
108
  }
55
109
  var log = {
110
+ debug(msg, data) {
111
+ emit("debug", msg, data);
112
+ },
56
113
  info(msg, data) {
57
- if (DEBUG) emit("INFO", msg, data);
58
- else writeToFile(fmt("INFO", msg, data));
114
+ emit("info", msg, data);
59
115
  },
60
116
  notice(msg, data) {
61
- emit("NOTICE", msg, data, false);
117
+ emit("notice", msg, data);
62
118
  },
63
119
  warn(msg, data) {
64
- emit("WARN", msg, data, true);
120
+ emit("warn", msg, data);
65
121
  },
66
122
  error(msg, data) {
67
- emit("ERROR", msg, data, true);
68
- },
69
- debug(msg, data) {
70
- if (DEBUG) emit("DEBUG", msg, data);
71
- else writeToFile(fmt("DEBUG", msg, data));
123
+ emit("error", msg, data);
72
124
  }
73
125
  };
74
126
 
@@ -3791,6 +3843,14 @@ function pickOpencodeDirectory(input) {
3791
3843
  return void 0;
3792
3844
  }
3793
3845
  function createClaudeCode(settings = {}) {
3846
+ if (settings.logging) {
3847
+ configureLogger({
3848
+ file: settings.logging.file ?? false,
3849
+ dir: settings.logging.dir ?? null,
3850
+ mode: settings.logging.mode ?? "silent",
3851
+ level: settings.logging.level ?? "info"
3852
+ });
3853
+ }
3794
3854
  const cliPath = settings.cliPath ?? process.env.CLAUDE_CLI_PATH ?? "claude";
3795
3855
  const providerName = settings.providerID ?? settings.name ?? "claude-code";
3796
3856
  const proxyTools = settings.proxyTools ?? ["Bash", "Edit", "Write", "WebFetch"];