opencode-plugin-auto-update 0.1.1 → 0.3.0

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
@@ -16,7 +16,7 @@ Automatically updates OpenCode plugins in the background on startup. No prompts,
16
16
 
17
17
  - Zero startup delay (async, detached update)
18
18
  - Bun-first installs with npm fallback
19
- - Silent by default; debug logging via env
19
+ - Shows a brief toast summary after updates
20
20
  - Skips local/path/git plugins
21
21
 
22
22
  ## 📦 Installation
@@ -94,29 +94,34 @@ Configure via environment variables:
94
94
  |----------|---------|-------------|
95
95
  | `OPENCODE_AUTO_UPDATE_DISABLED` | `false` | Disable all updates when `true` |
96
96
  | `OPENCODE_AUTO_UPDATE_INTERVAL_HOURS` | `24` | Throttle interval in hours |
97
- | `OPENCODE_AUTO_UPDATE_DEBUG` | `false` | Enable debug logs |
98
97
  | `OPENCODE_AUTO_UPDATE_PINNED` | `false` | Preserve pinned versions |
99
98
  | `OPENCODE_AUTO_UPDATE_BYPASS_THROTTLE` | `false` | Ignore throttle (useful for testing) |
100
99
 
101
- ### CLI Flags
100
+ ### Local Config File
102
101
 
103
- - `opencode --log-level DEBUG`: enable debug mode for OpenCode and trigger verbose update logs with throttle bypass.
102
+ You can bypass throttling without relying on CLI flags by creating:
103
+
104
+ `~/.config/opencode/opencode-plugin-auto-update.json`
105
+
106
+ ```json
107
+ {
108
+ "ignoreThrottle": true
109
+ }
110
+ ```
104
111
 
105
112
  ## ❓ Troubleshooting
106
113
 
107
114
  1. **Updates not running**: ensure `OPENCODE_AUTO_UPDATE_DISABLED` is not set to `true`.
108
- 2. **No logs**: run `opencode --log-level DEBUG` or set `OPENCODE_AUTO_UPDATE_DEBUG=true` for verbose output.
115
+ 2. **No logs**: confirm the plugin is enabled and watch for the "Auto-update logs" output after startup.
109
116
  3. **Plugin not loading**: check the `plugin` array in `~/.config/opencode/opencode.json`.
110
- 4. **Testing updates**: run `opencode --log-level DEBUG` to bypass the 24h throttle and see detailed update logs.
117
+ 4. **Testing updates**: set `OPENCODE_AUTO_UPDATE_BYPASS_THROTTLE=true` or the local config `ignoreThrottle=true`.
111
118
 
112
119
  ## 🚀 Release Process
113
120
 
114
- 1. Update version in `package.json`
115
- 2. Update `CHANGELOG.md`
116
- 3. `bun run build`
117
- 4. `npm publish`
118
- 5. `git tag vX.Y.Z && git push --tags`
119
- 6. `gh release create vX.Y.Z --notes "..."`
121
+ 1. Land changes on `main` using conventional commits.
122
+ 2. The Release workflow opens a Release Please PR with version + changelog updates.
123
+ 3. Merge the Release Please PR to create the tag + GitHub release.
124
+ 4. The Publish workflow (environment: `release`) builds and publishes to npm via OIDC.
120
125
 
121
126
  ## 📄 License
122
127
 
package/dist/index.d.ts CHANGED
@@ -1,30 +1,22 @@
1
1
  interface PluginInput {
2
2
  directory: string;
3
- serverUrl?: URL | string;
4
3
  client: {
5
- session: {
6
- status(): Promise<{
7
- data?: Record<string, {
8
- type: string;
9
- }>;
10
- }>;
11
- subscribe(callback: (event: {
12
- type: string;
13
- properties?: unknown;
14
- }) => void): () => void;
4
+ tui?: {
5
+ showToast?: (input: {
6
+ body: {
7
+ title?: string;
8
+ message: string;
9
+ variant?: 'info' | 'success' | 'warning' | 'error';
10
+ };
11
+ }) => Promise<unknown>;
15
12
  };
16
13
  };
17
14
  }
18
15
  interface PluginOutput {
19
16
  name: string;
20
- event?: (input: {
21
- event: {
22
- type: string;
23
- properties?: unknown;
24
- };
17
+ config?: (input: {
18
+ logLevel?: string;
25
19
  }) => Promise<void>;
26
- tool?: Record<string, unknown>;
27
- config?: unknown;
28
20
  }
29
21
  declare function export_default(ctx: PluginInput): Promise<PluginOutput>;
30
22
 
package/dist/index.js CHANGED
@@ -1,3 +1,8 @@
1
+ // src/index.ts
2
+ import { appendFile, readFile as readFile3 } from "fs/promises";
3
+ import { join as join3 } from "path";
4
+ import { homedir as homedir3 } from "os";
5
+
1
6
  // src/update.ts
2
7
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
3
8
  import { dirname, join as join2 } from "path";
@@ -133,14 +138,18 @@ async function runAutoUpdate(options = {}) {
133
138
  const configDir = options.configDir ?? DEFAULT_CONFIG_DIR2;
134
139
  const configPath = join2(configDir, "opencode.json");
135
140
  const log = (...args) => {
141
+ const message = formatLogMessage(args);
136
142
  if (debug) {
137
- console.log(...args);
143
+ console.log(message);
138
144
  }
145
+ options.onLog?.(message);
139
146
  };
140
147
  const error = (...args) => {
148
+ const message = formatLogMessage(args);
141
149
  if (debug) {
142
- console.error(...args);
150
+ console.error(message);
143
151
  }
152
+ options.onError?.(message);
144
153
  };
145
154
  const lockAcquired = await acquireLock({ debug, configDir });
146
155
  if (!lockAcquired) {
@@ -181,6 +190,21 @@ async function runAutoUpdate(options = {}) {
181
190
  const updatedConfig = { ...config, [key]: updateResult.plugins };
182
191
  await writeConfig(configPath, updatedConfig);
183
192
  }
193
+ const hasOcx = await commandExists("ocx");
194
+ if (hasOcx) {
195
+ log("[auto-update] Found ocx, checking for extension updates...");
196
+ const ocxResult = await runCommand("ocx", ["update"]);
197
+ if (ocxResult.code === 0) {
198
+ const output = ocxResult.stdout.trim();
199
+ if (output) {
200
+ log("[auto-update] ocx update result:", output);
201
+ } else {
202
+ log("[auto-update] ocx update complete (no output).");
203
+ }
204
+ } else {
205
+ error("[auto-update] ocx update failed:", ocxResult.stderr || ocxResult.stdout);
206
+ }
207
+ }
184
208
  await writeThrottleState(
185
209
  { ...state, lastRun: now, lastSuccess: Date.now() },
186
210
  { debug, configDir }
@@ -361,26 +385,139 @@ function envNumber(name, fallback) {
361
385
  const parsed = Number(raw);
362
386
  return Number.isFinite(parsed) ? parsed : fallback;
363
387
  }
388
+ function formatLogMessage(args) {
389
+ return args.map((arg) => {
390
+ if (typeof arg === "string") {
391
+ return arg;
392
+ }
393
+ if (arg instanceof Error) {
394
+ return arg.message || arg.name;
395
+ }
396
+ if (arg && typeof arg === "object" && "message" in arg) {
397
+ const message = arg.message;
398
+ if (typeof message === "string" && message.trim().length > 0) {
399
+ return message;
400
+ }
401
+ }
402
+ try {
403
+ return JSON.stringify(arg);
404
+ } catch {
405
+ return String(arg);
406
+ }
407
+ }).join(" ");
408
+ }
364
409
 
365
410
  // src/index.ts
411
+ var CONFIG_PATH = join3(homedir3(), ".config", "opencode", "opencode-plugin-auto-update.json");
412
+ var DEBUG_FILE = "/tmp/opencode-auto-update-debug.log";
413
+ var DEBUG_ENABLED = false;
366
414
  async function src_default(ctx) {
367
- const args = process.argv ?? [];
368
- const debugLevelFlag = args.includes("--log-level") && args.includes("DEBUG");
369
- const envDebug = process.env.OPENCODE_AUTO_UPDATE_DEBUG?.toLowerCase() === "true";
370
415
  const envBypassThrottle = process.env.OPENCODE_AUTO_UPDATE_BYPASS_THROTTLE?.toLowerCase() === "true";
371
- const debug = debugLevelFlag || envDebug;
372
- const ignoreThrottle = debugLevelFlag || envBypassThrottle;
373
- setTimeout(() => {
374
- runAutoUpdate({ debug, ignoreThrottle }).catch((error) => {
375
- if (debug) {
376
- console.error("[opencode-plugin-auto-update] Update check failed:", error);
377
- }
416
+ let configIgnoreThrottle = false;
417
+ let updateStarted = false;
418
+ let startupTimer = null;
419
+ let updateMessage = null;
420
+ let lastToastMessage = null;
421
+ const localConfig = await readLocalConfig();
422
+ configIgnoreThrottle = localConfig.ignoreThrottle ?? false;
423
+ const configDebug = localConfig.debug ?? false;
424
+ const shouldIgnoreThrottle = () => envBypassThrottle || configIgnoreThrottle;
425
+ const notifyUser = async (message) => {
426
+ await writeDebug("notifyUser invoked");
427
+ const toastMessage = summarizeMessage(message);
428
+ if (toastMessage === lastToastMessage) {
429
+ return;
430
+ }
431
+ lastToastMessage = toastMessage;
432
+ if (typeof ctx?.client?.tui?.showToast === "function") {
433
+ await ctx.client.tui.showToast({
434
+ body: {
435
+ title: "Auto-update",
436
+ message: toastMessage,
437
+ variant: "info"
438
+ }
439
+ });
440
+ await writeDebug("toast shown");
441
+ }
442
+ };
443
+ const startUpdate = () => {
444
+ if (updateStarted) {
445
+ return;
446
+ }
447
+ updateStarted = true;
448
+ void writeDebug(`startUpdate invoked (ignoreThrottle=${shouldIgnoreThrottle()})`);
449
+ const logEntries = [];
450
+ const errorEntries = [];
451
+ runAutoUpdate({
452
+ debug: configDebug,
453
+ ignoreThrottle: shouldIgnoreThrottle(),
454
+ onLog: (message) => logEntries.push(message),
455
+ onError: (message) => errorEntries.push(message)
456
+ }).then(() => {
457
+ updateMessage = formatUpdateMessage(logEntries, errorEntries);
458
+ void writeDebug(`update finished (logs=${logEntries.length}, errors=${errorEntries.length})`);
459
+ notifyUser(updateMessage).catch((error) => {
460
+ void writeDebug(`notifyUser error: ${String(error)}`);
461
+ });
462
+ }).catch((error) => {
463
+ void writeDebug(`runAutoUpdate error: ${String(error)}`);
378
464
  });
379
- }, 0);
465
+ };
466
+ startupTimer = setTimeout(() => {
467
+ if (!updateStarted) {
468
+ void writeDebug("startup timer fired");
469
+ startUpdate();
470
+ }
471
+ }, 1500);
380
472
  return {
381
- name: "opencode-plugin-auto-update"
473
+ name: "opencode-plugin-auto-update",
474
+ config: async (config) => {
475
+ await writeDebug(`config hook invoked (logLevel=${config?.logLevel ?? "unset"})`);
476
+ if (startupTimer) {
477
+ clearTimeout(startupTimer);
478
+ startupTimer = null;
479
+ }
480
+ startUpdate();
481
+ }
382
482
  };
383
483
  }
484
+ function formatUpdateMessage(logs, errors) {
485
+ const lines = ["Auto-update logs"];
486
+ const logLines = logs.length > 0 ? logs : ["No update output recorded."];
487
+ lines.push("", ...limitLines(logLines, 40));
488
+ if (errors.length > 0) {
489
+ lines.push("", "Errors:", ...limitLines(errors, 10));
490
+ }
491
+ return lines.join("\n");
492
+ }
493
+ function limitLines(lines, maxLines) {
494
+ if (lines.length <= maxLines) {
495
+ return lines;
496
+ }
497
+ return [...lines.slice(0, maxLines), `... (${lines.length - maxLines} more lines)`];
498
+ }
499
+ async function writeDebug(message) {
500
+ if (!DEBUG_ENABLED) {
501
+ return;
502
+ }
503
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
504
+ await appendFile(DEBUG_FILE, `[${timestamp}] ${message}
505
+ `);
506
+ }
507
+ async function readLocalConfig() {
508
+ try {
509
+ const raw = await readFile3(CONFIG_PATH, "utf-8");
510
+ const parsed = JSON.parse(raw);
511
+ return parsed ?? {};
512
+ } catch {
513
+ return {};
514
+ }
515
+ }
516
+ function summarizeMessage(message) {
517
+ const lines = message.split("\n").filter((line) => line.trim().length > 0);
518
+ const summary = lines.slice(0, 4).join(" | ");
519
+ return summary.length > 240 ? `${summary.slice(0, 237)}...` : summary;
520
+ }
384
521
  export {
385
522
  src_default as default
386
523
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-plugin-auto-update",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "OpenCode plugin that auto-updates plugins in the background",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",