opencode-plugin-auto-update 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +109 -21
  3. package/package.json +1 -1
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
- - Logs update output to the console sidebar with a toast summary
19
+ - Shows a brief toast summary after updates
20
20
  - Skips local/path/git plugins
21
21
 
22
22
  ## 📦 Installation
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { appendFile, readFile as readFile3 } from "fs/promises";
2
+ import { appendFile, readFile as readFile4 } from "fs/promises";
3
3
  import { join as join3 } from "path";
4
4
  import { homedir as homedir3 } from "os";
5
5
 
@@ -126,6 +126,22 @@ async function getHostname() {
126
126
 
127
127
  // src/update.ts
128
128
  var DEFAULT_CONFIG_DIR2 = join2(homedir2(), ".config", "opencode");
129
+ function normalizePluginConfig(config) {
130
+ const existingPlugin = Array.isArray(config.plugin) ? [...config.plugin] : [];
131
+ const existingPlugins = Array.isArray(config.plugins) ? [...config.plugins] : [];
132
+ if (existingPlugins.length === 0) {
133
+ return { config, changed: false };
134
+ }
135
+ const merged = [];
136
+ for (const entry of [...existingPlugin, ...existingPlugins]) {
137
+ if (!merged.includes(entry)) {
138
+ merged.push(entry);
139
+ }
140
+ }
141
+ config.plugin = merged;
142
+ delete config.plugins;
143
+ return { config, changed: true };
144
+ }
129
145
  async function runAutoUpdate(options = {}) {
130
146
  const disabled = options.disabled ?? envFlag("OPENCODE_AUTO_UPDATE_DISABLED");
131
147
  if (disabled) {
@@ -133,7 +149,7 @@ async function runAutoUpdate(options = {}) {
133
149
  }
134
150
  const debug = options.debug ?? envFlag("OPENCODE_AUTO_UPDATE_DEBUG");
135
151
  const ignoreThrottle = options.ignoreThrottle ?? envFlag("OPENCODE_AUTO_UPDATE_BYPASS_THROTTLE");
136
- const intervalHours = options.intervalHours ?? envNumber("OPENCODE_AUTO_UPDATE_INTERVAL_HOURS", 24);
152
+ const intervalHours = options.intervalHours ?? envNumber("OPENCODE_AUTO_UPDATE_INTERVAL_HOURS", 0);
137
153
  const preservePinned = options.preservePinned ?? envFlag("OPENCODE_AUTO_UPDATE_PINNED");
138
154
  const configDir = options.configDir ?? DEFAULT_CONFIG_DIR2;
139
155
  const configPath = join2(configDir, "opencode.json");
@@ -159,15 +175,24 @@ async function runAutoUpdate(options = {}) {
159
175
  try {
160
176
  const state = await readThrottleState({ configDir });
161
177
  const now = Date.now();
162
- const intervalMs = Math.max(intervalHours, 1) * 60 * 60 * 1e3;
178
+ const intervalMs = intervalHours * 60 * 60 * 1e3;
163
179
  if (!ignoreThrottle && state.lastRun && now - state.lastRun < intervalMs) {
164
180
  log("[auto-update] Throttled, skipping update.");
165
181
  return;
166
182
  }
167
183
  await writeThrottleState({ ...state, lastRun: now }, { debug, configDir });
168
- const config = await readConfig(configPath);
169
- const { plugins, key } = getPluginList(config);
170
- if (!plugins || plugins.length === 0 || !key) {
184
+ const rawConfig = await readConfig(configPath);
185
+ if (!rawConfig) {
186
+ log("[auto-update] No config found, skipping.");
187
+ return;
188
+ }
189
+ const normalized = normalizePluginConfig(rawConfig);
190
+ if (normalized.changed) {
191
+ await writeConfig(configPath, normalized.config);
192
+ log("[auto-update] Migrated config.plugins -> config.plugin");
193
+ }
194
+ const { plugins } = getPluginList(normalized.config);
195
+ if (!plugins || plugins.length === 0) {
171
196
  log("[auto-update] No plugins found to update.");
172
197
  return;
173
198
  }
@@ -187,9 +212,28 @@ async function runAutoUpdate(options = {}) {
187
212
  error
188
213
  });
189
214
  if (updateResult.changed) {
190
- const updatedConfig = { ...config, [key]: updateResult.plugins };
215
+ const updatedConfig = {
216
+ ...normalized.config,
217
+ plugin: updateResult.plugins
218
+ };
219
+ delete updatedConfig.plugins;
191
220
  await writeConfig(configPath, updatedConfig);
192
221
  }
222
+ const hasOcx = await commandExists("ocx");
223
+ if (hasOcx) {
224
+ log("[auto-update] Found ocx, checking for extension updates...");
225
+ const ocxResult = await runCommand("ocx", ["update"]);
226
+ if (ocxResult.code === 0) {
227
+ const output = ocxResult.stdout.trim();
228
+ if (output) {
229
+ log("[auto-update] ocx update result:", output);
230
+ } else {
231
+ log("[auto-update] ocx update complete (no output).");
232
+ }
233
+ } else {
234
+ error("[auto-update] ocx update failed:", ocxResult.stderr || ocxResult.stdout);
235
+ }
236
+ }
193
237
  await writeThrottleState(
194
238
  { ...state, lastRun: now, lastSuccess: Date.now() },
195
239
  { debug, configDir }
@@ -216,16 +260,10 @@ async function writeConfig(configPath, config) {
216
260
  `, "utf-8");
217
261
  }
218
262
  function getPluginList(config) {
219
- if (!config) {
220
- return { plugins: null, key: null };
221
- }
222
263
  if (Array.isArray(config.plugin)) {
223
- return { plugins: config.plugin, key: "plugin" };
224
- }
225
- if (Array.isArray(config.plugins)) {
226
- return { plugins: config.plugins, key: "plugins" };
264
+ return { plugins: config.plugin };
227
265
  }
228
- return { plugins: null, key: null };
266
+ return { plugins: null };
229
267
  }
230
268
  async function updatePlugins(options) {
231
269
  const { plugins, configDir, preservePinned, useBun, log, error } = options;
@@ -392,8 +430,47 @@ function formatLogMessage(args) {
392
430
  }).join(" ");
393
431
  }
394
432
 
433
+ // src/utils/circular-log.ts
434
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
435
+ var MAX_ENTRIES = 5;
436
+ async function readCircularLog(logPath) {
437
+ try {
438
+ const contents = await readFile3(logPath, "utf-8");
439
+ return JSON.parse(contents);
440
+ } catch {
441
+ return { entries: [] };
442
+ }
443
+ }
444
+ async function appendCircularLog(logPath, entry) {
445
+ const log = await readCircularLog(logPath);
446
+ log.entries.push(entry);
447
+ if (log.entries.length > MAX_ENTRIES) {
448
+ log.entries = log.entries.slice(-MAX_ENTRIES);
449
+ }
450
+ await writeFile3(logPath, JSON.stringify(log, null, 2) + "\n", "utf-8");
451
+ }
452
+ function formatLogEntry(plugins, errors, duration, success) {
453
+ return {
454
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
455
+ pluginsUpdated: plugins,
456
+ errors: errors.slice(0, 10),
457
+ duration,
458
+ success
459
+ };
460
+ }
461
+
395
462
  // src/index.ts
396
463
  var CONFIG_PATH = join3(homedir3(), ".config", "opencode", "opencode-plugin-auto-update.json");
464
+ function extractUpdatedPlugins(logEntries) {
465
+ const plugins = [];
466
+ for (const entry of logEntries) {
467
+ const match = entry.match(/Updated\s+(.+?)\s+from\s+v[\d.]+\s+to\s+v[\d.]+/i);
468
+ if (match && match[1]) {
469
+ plugins.push(match[1]);
470
+ }
471
+ }
472
+ return plugins;
473
+ }
397
474
  var DEBUG_FILE = "/tmp/opencode-auto-update-debug.log";
398
475
  var DEBUG_ENABLED = false;
399
476
  async function src_default(ctx) {
@@ -405,6 +482,7 @@ async function src_default(ctx) {
405
482
  let lastToastMessage = null;
406
483
  const localConfig = await readLocalConfig();
407
484
  configIgnoreThrottle = localConfig.ignoreThrottle ?? false;
485
+ const configDebug = localConfig.debug ?? false;
408
486
  const shouldIgnoreThrottle = () => envBypassThrottle || configIgnoreThrottle;
409
487
  const notifyUser = async (message) => {
410
488
  await writeDebug("notifyUser invoked");
@@ -423,9 +501,8 @@ async function src_default(ctx) {
423
501
  });
424
502
  await writeDebug("toast shown");
425
503
  }
426
- console.log(message);
427
- await writeDebug("console fallback used");
428
504
  };
505
+ const CIRCULAR_LOG_PATH = join3(ctx.directory, "..", ".auto-update-history.json");
429
506
  const startUpdate = () => {
430
507
  if (updateStarted) {
431
508
  return;
@@ -434,21 +511,32 @@ async function src_default(ctx) {
434
511
  void writeDebug(`startUpdate invoked (ignoreThrottle=${shouldIgnoreThrottle()})`);
435
512
  const logEntries = [];
436
513
  const errorEntries = [];
514
+ const startTime = Date.now();
437
515
  runAutoUpdate({
438
- debug: false,
516
+ debug: configDebug,
439
517
  ignoreThrottle: shouldIgnoreThrottle(),
440
518
  onLog: (message) => logEntries.push(message),
441
519
  onError: (message) => errorEntries.push(message)
442
520
  }).then(() => {
521
+ const duration = Date.now() - startTime;
522
+ const success = errorEntries.length === 0;
523
+ const pluginsUpdated = extractUpdatedPlugins(logEntries);
524
+ void appendCircularLog(
525
+ CIRCULAR_LOG_PATH,
526
+ formatLogEntry(pluginsUpdated, errorEntries, duration, success)
527
+ );
443
528
  updateMessage = formatUpdateMessage(logEntries, errorEntries);
444
529
  void writeDebug(`update finished (logs=${logEntries.length}, errors=${errorEntries.length})`);
445
530
  notifyUser(updateMessage).catch((error) => {
446
531
  void writeDebug(`notifyUser error: ${String(error)}`);
447
- console.error("[opencode-plugin-auto-update] Failed to notify user:", error);
448
532
  });
449
533
  }).catch((error) => {
534
+ const duration = Date.now() - startTime;
535
+ void appendCircularLog(
536
+ CIRCULAR_LOG_PATH,
537
+ formatLogEntry([], [String(error)], duration, false)
538
+ );
450
539
  void writeDebug(`runAutoUpdate error: ${String(error)}`);
451
- console.error("[opencode-plugin-auto-update] Update check failed:", error);
452
540
  });
453
541
  };
454
542
  startupTimer = setTimeout(() => {
@@ -494,7 +582,7 @@ async function writeDebug(message) {
494
582
  }
495
583
  async function readLocalConfig() {
496
584
  try {
497
- const raw = await readFile3(CONFIG_PATH, "utf-8");
585
+ const raw = await readFile4(CONFIG_PATH, "utf-8");
498
586
  const parsed = JSON.parse(raw);
499
587
  return parsed ?? {};
500
588
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-plugin-auto-update",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "OpenCode plugin that auto-updates plugins in the background",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",