kibi-opencode 0.15.4 → 0.16.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
@@ -22,6 +22,23 @@ Or via OpenCode's plugin system in `opencode.json`:
22
22
  }
23
23
  ```
24
24
 
25
+ By default, the plugin checks npm on OpenCode startup and refreshes only the
26
+ cached `kibi-opencode` plugin package when a newer version is available. The
27
+ base `kibi-cli`, `kibi-mcp`, and `kibi-core` project dependencies are never
28
+ updated by this plugin updater.
29
+
30
+ Use an exact semver entry when you want a fixed plugin version:
31
+
32
+ ```json
33
+ {
34
+ "plugin": ["kibi-opencode@0.15.0"]
35
+ }
36
+ ```
37
+
38
+ Exact semver pins are respected: the updater will notify that a newer plugin is
39
+ available, but it will not replace the pinned version until you change the
40
+ plugin array. Dist-tags such as `kibi-opencode@latest` remain auto-updatable.
41
+
25
42
  Configure the MCP server separately with the project-local `kibi-mcp` binary.
26
43
  The plugin may run internal maintenance commands such as `kibi sync` and
27
44
  `kibi check`, so the project-local `kibi` CLI should resolve from the OpenCode
@@ -177,6 +194,7 @@ Config files (project overrides global):
177
194
  | Key | Type | Default | Description |
178
195
  |-----|------|---------|-------------|
179
196
  | `enabled` | boolean | `true` | Enable/disable all plugin features |
197
+ | `autoUpdate` | boolean | `true` | Automatically refresh the cached `kibi-opencode` plugin package on OpenCode startup unless the plugin array uses an exact semver pin |
180
198
  | `prompt.enabled` | boolean | `true` | Enable prompt guidance injection |
181
199
  | `prompt.hookMode` | string | `"auto"` | Hook mode: `auto`, `chat-params`, `system-transform`, `compat` |
182
200
  | `sync.enabled` | boolean | `true` | Enable automatic sync |
@@ -187,7 +205,6 @@ Config files (project overrides global):
187
205
  | `ux.toastFailures` | boolean | `true` | Show failure toasts for sync/check issues |
188
206
  | `ux.toastSuccesses` | boolean | `false` | Show success toasts for sync/check completion |
189
207
  | `ux.toastCooldownMs` | number | `10000` | Cooldown between repeated UX toasts |
190
- PP|| `guidance.dynamic` | boolean | `true` | Enable dynamic contextual guidance |
191
208
  | `guidance.dynamic` | boolean | `true` | Enable dynamic contextual guidance |
192
209
  | `guidance.warnOnKbEdits` | boolean | `true` | Enable loud warnings for .kb/** edits |
193
210
  | `guidance.factFirstDomainRouting` | boolean | `true` | Enable FACT-first domain routing suggestions |
@@ -0,0 +1,31 @@
1
+ export interface KibiOpencodePluginEntry {
2
+ entry: string;
3
+ isPinned: boolean;
4
+ requestedVersion: string | null;
5
+ configPath: string;
6
+ }
7
+ export type AutoUpdateStatus = "disabled" | "plugin-not-found" | "current-version-unknown" | "latest-version-unknown" | "up-to-date" | "pinned" | "updated" | "install-failed";
8
+ export interface AutoUpdateResult {
9
+ status: AutoUpdateStatus;
10
+ currentVersion?: string;
11
+ latestVersion?: string;
12
+ }
13
+ export interface AutoUpdateInput {
14
+ directory: string;
15
+ enabled: boolean;
16
+ }
17
+ export interface AutoUpdateRunnerDeps {
18
+ getCurrentVersion: () => string | null;
19
+ getLatestVersion: (channel?: string) => Promise<string | null>;
20
+ invalidatePackage: () => boolean;
21
+ runInstall: () => Promise<boolean>;
22
+ notify: (message: string) => Promise<void>;
23
+ log: (message: string, metadata?: Record<string, unknown>) => void;
24
+ }
25
+ export declare function findKibiOpencodePluginEntry(directory: string): KibiOpencodePluginEntry | null;
26
+ export declare function getCachedPluginVersion(): string | null;
27
+ export declare function getLatestPluginVersion(channel?: string): Promise<string | null>;
28
+ export declare function invalidateKibiOpencodePackage(): boolean;
29
+ export declare function runBunInstallForOpenCodePlugin(): Promise<boolean>;
30
+ export declare function createAutoUpdateRunner(deps: AutoUpdateRunnerDeps): (input: AutoUpdateInput) => Promise<AutoUpdateResult>;
31
+ export declare const runKibiOpencodeAutoUpdate: (input: AutoUpdateInput) => Promise<AutoUpdateResult>;
@@ -0,0 +1,396 @@
1
+ import { execFile } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import * as logger from "./logger.js";
7
+ const PACKAGE_NAME = "kibi-opencode";
8
+ const NPM_DIST_TAGS_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
9
+ const NPM_FETCH_TIMEOUT_MS = 5000;
10
+ const EXACT_SEMVER_REGEX = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
11
+ function parseJsonObject(raw) {
12
+ try {
13
+ const parsed = JSON.parse(raw);
14
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
15
+ return parsed;
16
+ }
17
+ return null;
18
+ }
19
+ catch (err) {
20
+ logger.info("auto-update: failed to parse JSON", {
21
+ error: err instanceof Error ? err.message : String(err),
22
+ });
23
+ return null;
24
+ }
25
+ }
26
+ function stripJsonComments(raw) {
27
+ return raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/.*$/gm, "$1");
28
+ }
29
+ function configPaths(directory) {
30
+ const home = os.homedir();
31
+ return [
32
+ path.join(directory, "opencode.json"),
33
+ path.join(directory, "opencode.jsonc"),
34
+ path.join(directory, ".opencode", "opencode.json"),
35
+ path.join(directory, ".opencode", "opencode.jsonc"),
36
+ path.join(home, ".config", "opencode", "opencode.json"),
37
+ path.join(home, ".config", "opencode", "opencode.jsonc"),
38
+ ];
39
+ }
40
+ export function findKibiOpencodePluginEntry(directory) {
41
+ for (const configPath of configPaths(directory)) {
42
+ if (!fs.existsSync(configPath)) {
43
+ continue;
44
+ }
45
+ const parsed = parseJsonObject(stripJsonComments(fs.readFileSync(configPath, "utf8")));
46
+ const plugins = parsed?.plugin;
47
+ if (!Array.isArray(plugins)) {
48
+ continue;
49
+ }
50
+ for (const candidate of plugins) {
51
+ if (typeof candidate !== "string") {
52
+ continue;
53
+ }
54
+ if (candidate === PACKAGE_NAME) {
55
+ return {
56
+ entry: candidate,
57
+ isPinned: false,
58
+ requestedVersion: null,
59
+ configPath,
60
+ };
61
+ }
62
+ if (candidate.startsWith(`${PACKAGE_NAME}@`)) {
63
+ const requestedVersion = candidate
64
+ .slice(PACKAGE_NAME.length + 1)
65
+ .trim();
66
+ return {
67
+ entry: candidate,
68
+ isPinned: EXACT_SEMVER_REGEX.test(requestedVersion),
69
+ requestedVersion,
70
+ configPath,
71
+ };
72
+ }
73
+ if (candidate.startsWith("file://") && candidate.includes(PACKAGE_NAME)) {
74
+ return {
75
+ entry: candidate,
76
+ isPinned: true,
77
+ requestedVersion: null,
78
+ configPath,
79
+ };
80
+ }
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ function getOpenCodeConfigDir() {
86
+ if (process.platform === "win32") {
87
+ const appData = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
88
+ return path.join(appData, "opencode");
89
+ }
90
+ return path.join(os.homedir(), ".config", "opencode");
91
+ }
92
+ function getOpenCodeCacheRoot() {
93
+ if (process.platform === "win32") {
94
+ const localAppData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local");
95
+ return path.join(localAppData, "opencode");
96
+ }
97
+ if (process.platform === "darwin") {
98
+ return path.join(os.homedir(), "Library", "Caches", "opencode");
99
+ }
100
+ return path.join(process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache"), "opencode");
101
+ }
102
+ function getOpenCodePackagesCacheDir() {
103
+ return path.join(getOpenCodeCacheRoot(), "packages");
104
+ }
105
+ function parseSemver(version) {
106
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/);
107
+ if (!match) {
108
+ return null;
109
+ }
110
+ const major = Number(match[1]);
111
+ const minor = Number(match[2]);
112
+ const patch = Number(match[3]);
113
+ if (!Number.isInteger(major) ||
114
+ !Number.isInteger(minor) ||
115
+ !Number.isInteger(patch)) {
116
+ return null;
117
+ }
118
+ return {
119
+ core: [major, minor, patch],
120
+ prerelease: match[4]?.split(".") ?? [],
121
+ };
122
+ }
123
+ function isNumericPrereleaseIdentifier(value) {
124
+ return /^(0|[1-9]\d*)$/.test(value);
125
+ }
126
+ function comparePrereleaseIdentifiers(left, right) {
127
+ const leftIsNumeric = isNumericPrereleaseIdentifier(left);
128
+ const rightIsNumeric = isNumericPrereleaseIdentifier(right);
129
+ if (leftIsNumeric && rightIsNumeric) {
130
+ return Number(left) - Number(right);
131
+ }
132
+ if (leftIsNumeric) {
133
+ return -1;
134
+ }
135
+ if (rightIsNumeric) {
136
+ return 1;
137
+ }
138
+ return left.localeCompare(right, "en", { sensitivity: "variant" });
139
+ }
140
+ function comparePrerelease(left, right) {
141
+ if (left.length === 0 && right.length === 0) {
142
+ return 0;
143
+ }
144
+ if (left.length === 0) {
145
+ return 1;
146
+ }
147
+ if (right.length === 0) {
148
+ return -1;
149
+ }
150
+ const maxLength = Math.max(left.length, right.length);
151
+ for (let index = 0; index < maxLength; index += 1) {
152
+ const leftPart = left[index];
153
+ const rightPart = right[index];
154
+ if (leftPart === undefined) {
155
+ return -1;
156
+ }
157
+ if (rightPart === undefined) {
158
+ return 1;
159
+ }
160
+ const comparison = comparePrereleaseIdentifiers(leftPart, rightPart);
161
+ if (comparison !== 0) {
162
+ return comparison;
163
+ }
164
+ }
165
+ return 0;
166
+ }
167
+ function isSemverGreaterThan(candidate, current) {
168
+ const candidateVersion = parseSemver(candidate);
169
+ const currentVersion = parseSemver(current);
170
+ if (!candidateVersion || !currentVersion) {
171
+ return false;
172
+ }
173
+ for (const index of [0, 1, 2]) {
174
+ if (candidateVersion.core[index] > currentVersion.core[index]) {
175
+ return true;
176
+ }
177
+ if (candidateVersion.core[index] < currentVersion.core[index]) {
178
+ return false;
179
+ }
180
+ }
181
+ return (comparePrerelease(candidateVersion.prerelease, currentVersion.prerelease) >
182
+ 0);
183
+ }
184
+ function readVersionFromPackageJson(packageJsonPath) {
185
+ if (!fs.existsSync(packageJsonPath)) {
186
+ return null;
187
+ }
188
+ const parsed = parseJsonObject(fs.readFileSync(packageJsonPath, "utf8"));
189
+ return typeof parsed?.version === "string" ? parsed.version : null;
190
+ }
191
+ function findPackageJsonUp(startUrl) {
192
+ let current = path.dirname(fileURLToPath(startUrl));
193
+ while (true) {
194
+ const packageJsonPath = path.join(current, "package.json");
195
+ if (fs.existsSync(packageJsonPath)) {
196
+ const parsed = parseJsonObject(fs.readFileSync(packageJsonPath, "utf8"));
197
+ if (parsed?.name === PACKAGE_NAME) {
198
+ return packageJsonPath;
199
+ }
200
+ }
201
+ const parent = path.dirname(current);
202
+ if (parent === current) {
203
+ return null;
204
+ }
205
+ current = parent;
206
+ }
207
+ }
208
+ export function getCachedPluginVersion() {
209
+ const packageJsonCandidates = [
210
+ path.join(getOpenCodeConfigDir(), "node_modules", PACKAGE_NAME, "package.json"),
211
+ path.join(getOpenCodeCacheRoot(), "node_modules", PACKAGE_NAME, "package.json"),
212
+ path.join(getOpenCodePackagesCacheDir(), "node_modules", PACKAGE_NAME, "package.json"),
213
+ ];
214
+ for (const candidate of packageJsonCandidates) {
215
+ const version = readVersionFromPackageJson(candidate);
216
+ if (version) {
217
+ return version;
218
+ }
219
+ }
220
+ const runningPackageJson = findPackageJsonUp(import.meta.url);
221
+ return runningPackageJson
222
+ ? readVersionFromPackageJson(runningPackageJson)
223
+ : null;
224
+ }
225
+ export async function getLatestPluginVersion(channel = "latest") {
226
+ const controller = new AbortController();
227
+ const timeout = setTimeout(() => controller.abort(), NPM_FETCH_TIMEOUT_MS);
228
+ try {
229
+ const response = await fetch(NPM_DIST_TAGS_URL, {
230
+ signal: controller.signal,
231
+ headers: { Accept: "application/json" },
232
+ });
233
+ if (!response.ok) {
234
+ return null;
235
+ }
236
+ const tags = (await response.json());
237
+ const selected = tags[channel] ?? tags.latest;
238
+ return typeof selected === "string" ? selected : null;
239
+ }
240
+ catch (err) {
241
+ logger.info("auto-update: failed to fetch latest version", {
242
+ error: err instanceof Error ? err.message : String(err),
243
+ });
244
+ return null;
245
+ }
246
+ finally {
247
+ clearTimeout(timeout);
248
+ }
249
+ }
250
+ function removePackageDir(dir) {
251
+ if (!fs.existsSync(dir)) {
252
+ return false;
253
+ }
254
+ fs.rmSync(dir, { recursive: true, force: true });
255
+ return true;
256
+ }
257
+ function removeSpecifierCacheDirs(cacheDir) {
258
+ if (!fs.existsSync(cacheDir)) {
259
+ return false;
260
+ }
261
+ let removed = false;
262
+ for (const entry of fs.readdirSync(cacheDir, { withFileTypes: true })) {
263
+ if (entry.isDirectory() && entry.name.startsWith(`${PACKAGE_NAME}@`)) {
264
+ fs.rmSync(path.join(cacheDir, entry.name), {
265
+ recursive: true,
266
+ force: true,
267
+ });
268
+ removed = true;
269
+ }
270
+ }
271
+ return removed;
272
+ }
273
+ function removeEveryPath(paths) {
274
+ let removed = false;
275
+ for (const targetPath of paths) {
276
+ if (removePackageDir(targetPath)) {
277
+ removed = true;
278
+ }
279
+ }
280
+ return removed;
281
+ }
282
+ function removeSpecifiersFromEveryDir(dirs) {
283
+ let removed = false;
284
+ for (const dir of dirs) {
285
+ if (removeSpecifierCacheDirs(dir)) {
286
+ removed = true;
287
+ }
288
+ }
289
+ return removed;
290
+ }
291
+ export function invalidateKibiOpencodePackage() {
292
+ const configDir = getOpenCodeConfigDir();
293
+ const cacheRoot = getOpenCodeCacheRoot();
294
+ const packagesCacheDir = getOpenCodePackagesCacheDir();
295
+ const removedPackageDirs = removeEveryPath([
296
+ path.join(configDir, "node_modules", PACKAGE_NAME),
297
+ path.join(cacheRoot, "node_modules", PACKAGE_NAME),
298
+ path.join(packagesCacheDir, "node_modules", PACKAGE_NAME),
299
+ ]);
300
+ const removedSpecifierDirs = removeSpecifiersFromEveryDir([
301
+ cacheRoot,
302
+ packagesCacheDir,
303
+ ]);
304
+ const removedLock = removeEveryPath([
305
+ path.join(cacheRoot, "bun.lock"),
306
+ path.join(cacheRoot, "bun.lockb"),
307
+ path.join(packagesCacheDir, "bun.lock"),
308
+ path.join(packagesCacheDir, "bun.lockb"),
309
+ ]);
310
+ return removedPackageDirs || removedSpecifierDirs || removedLock;
311
+ }
312
+ function activeInstallWorkspace() {
313
+ const configDir = getOpenCodeConfigDir();
314
+ const cacheRoot = getOpenCodeCacheRoot();
315
+ const packagesCacheDir = getOpenCodePackagesCacheDir();
316
+ if (fs.existsSync(path.join(configDir, "node_modules", PACKAGE_NAME, "package.json"))) {
317
+ return configDir;
318
+ }
319
+ if (fs.existsSync(path.join(cacheRoot, "node_modules", PACKAGE_NAME, "package.json"))) {
320
+ return cacheRoot;
321
+ }
322
+ return packagesCacheDir;
323
+ }
324
+ export function runBunInstallForOpenCodePlugin() {
325
+ return new Promise((resolve) => {
326
+ const workspace = activeInstallWorkspace();
327
+ fs.mkdirSync(workspace, { recursive: true });
328
+ execFile("bun", ["install"], { cwd: workspace }, (error) => {
329
+ resolve(error === null);
330
+ });
331
+ });
332
+ }
333
+ function channelForEntry(entry) {
334
+ if (!entry.requestedVersion || entry.isPinned) {
335
+ return "latest";
336
+ }
337
+ return entry.requestedVersion;
338
+ }
339
+ export function createAutoUpdateRunner(deps) {
340
+ return async function runAutoUpdate(input) {
341
+ if (!input.enabled) {
342
+ deps.log("auto-update: disabled");
343
+ return { status: "disabled" };
344
+ }
345
+ const pluginEntry = findKibiOpencodePluginEntry(input.directory);
346
+ if (!pluginEntry) {
347
+ deps.log("auto-update: kibi-opencode plugin entry not found");
348
+ return { status: "plugin-not-found" };
349
+ }
350
+ const currentVersion = deps.getCurrentVersion() ?? pluginEntry.requestedVersion;
351
+ if (!currentVersion) {
352
+ deps.log("auto-update: current version unknown");
353
+ return { status: "current-version-unknown" };
354
+ }
355
+ const latestVersion = await deps.getLatestVersion(channelForEntry(pluginEntry));
356
+ if (!latestVersion) {
357
+ deps.log("auto-update: latest version unknown");
358
+ return { status: "latest-version-unknown", currentVersion };
359
+ }
360
+ if (currentVersion === latestVersion ||
361
+ !isSemverGreaterThan(latestVersion, currentVersion)) {
362
+ deps.log("auto-update: plugin already up to date", {
363
+ currentVersion,
364
+ latestVersion,
365
+ });
366
+ return { status: "up-to-date", currentVersion, latestVersion };
367
+ }
368
+ if (pluginEntry.isPinned) {
369
+ await deps.notify(`${PACKAGE_NAME} ${latestVersion} is available; current plugin entry is pinned to ${currentVersion}.`);
370
+ deps.log("auto-update: exact semver pin detected, skipping install", {
371
+ currentVersion,
372
+ latestVersion,
373
+ entry: pluginEntry.entry,
374
+ });
375
+ return { status: "pinned", currentVersion, latestVersion };
376
+ }
377
+ deps.invalidatePackage();
378
+ const installed = await deps.runInstall();
379
+ if (!installed) {
380
+ await deps.notify(`${PACKAGE_NAME} ${latestVersion} is available, but automatic install failed.`);
381
+ return { status: "install-failed", currentVersion, latestVersion };
382
+ }
383
+ await deps.notify(`${PACKAGE_NAME} updated from ${currentVersion} to ${latestVersion}. Restart OpenCode to apply.`);
384
+ return { status: "updated", currentVersion, latestVersion };
385
+ };
386
+ }
387
+ export const runKibiOpencodeAutoUpdate = createAutoUpdateRunner({
388
+ getCurrentVersion: getCachedPluginVersion,
389
+ getLatestVersion: getLatestPluginVersion,
390
+ invalidatePackage: invalidateKibiOpencodePackage,
391
+ runInstall: runBunInstallForOpenCodePlugin,
392
+ notify: async (message) => {
393
+ logger.info("auto-update.notification", { message });
394
+ },
395
+ log: (message, metadata) => logger.info(message, metadata),
396
+ });
package/dist/config.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export interface KibiConfig {
2
2
  enabled: boolean;
3
+ autoUpdate: boolean;
3
4
  prompt: {
4
5
  enabled: boolean;
5
6
  hookMode: "auto" | "chat-params" | "system-transform" | "compat";
package/dist/config.js CHANGED
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import * as logger from "./logger.js";
5
5
  const DEFAULTS = {
6
6
  enabled: true,
7
+ autoUpdate: true,
7
8
  prompt: { enabled: true, hookMode: "auto" },
8
9
  sync: { enabled: true, debounceMs: 2000, ignore: [], relevant: [] },
9
10
  ux: {
@@ -64,6 +65,8 @@ export function validateAndMerge(obj) {
64
65
  const out = { ...DEFAULTS };
65
66
  if (typeof src.enabled === "boolean")
66
67
  out.enabled = src.enabled;
68
+ if (typeof src.autoUpdate === "boolean")
69
+ out.autoUpdate = src.autoUpdate;
67
70
  if (src.prompt && typeof src.prompt === "object") {
68
71
  const p = src.prompt;
69
72
  out.prompt = { ...DEFAULTS.prompt };
package/dist/plugin.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as path from "node:path";
2
+ import { createAutoUpdateRunner, getLatestPluginVersion, invalidateKibiOpencodePackage, runBunInstallForOpenCodePlugin, } from "./auto-update.js";
2
3
  import { analyzeCodeFile, } from "./comment-analysis.js";
3
4
  import { getE2eCoverageSignal } from "./e2e-coverage-signals.js"; // implements REQ-opencode-file-context-guidance-v1
4
5
  import { buildDirtyRelevantFingerprint, buildEnforcementScopeKey, } from "./enforcement-scope.js";
@@ -20,6 +21,7 @@ import { createSyncScheduler } from "./scheduler.js";
20
21
  import { createSessionEditState, } from "./session-edit-state.js";
21
22
  import { getSessionTracker } from "./session-tracker.js";
22
23
  import { notifyStartup, } from "./startup-notifier.js";
24
+ import { sendToast, } from "./toast.js";
23
25
  import { readKibiPackageVersions } from "./version-metadata.js";
24
26
  import { resolveWorkContext, } from "./work-context-resolver.js";
25
27
  import * as fs from "node:fs";
@@ -1044,7 +1046,7 @@ const kibiOpencodePlugin = async (input) => {
1044
1046
  }
1045
1047
  }
1046
1048
  logger.info("kibi-opencode: setup complete");
1047
- if (input.client && !maintenanceDegraded) {
1049
+ if (input.client) {
1048
1050
  const client = input.client;
1049
1051
  const scheduleStartupNotify = startupNotifyGlobals.__kibi_test_schedule_startup_notify ??
1050
1052
  ((callback, delayMs) => {
@@ -1057,11 +1059,40 @@ const kibiOpencodePlugin = async (input) => {
1057
1059
  if (meta[key] !== "unknown")
1058
1060
  versions[key] = meta[key];
1059
1061
  }
1060
- notifyStartup(makeStartupClient(client), {
1061
- suppressToast: cfg.ux.toastStartup === false,
1062
+ if (!maintenanceDegraded) {
1063
+ notifyStartup(makeStartupClient(client), {
1064
+ suppressToast: cfg.ux.toastStartup === false,
1065
+ directory: input.directory,
1066
+ ...(Object.keys(versions).length > 0 ? { versions } : {}),
1067
+ versionMetadataSource: meta.source,
1068
+ });
1069
+ }
1070
+ const autoUpdateRunner = createAutoUpdateRunner({
1071
+ getCurrentVersion: () => meta.opencode === "unknown" ? null : meta.opencode,
1072
+ getLatestVersion: getLatestPluginVersion,
1073
+ invalidatePackage: invalidateKibiOpencodePackage,
1074
+ runInstall: runBunInstallForOpenCodePlugin,
1075
+ notify: async (message) => {
1076
+ if (cfg.ux.toastStartup !== false) {
1077
+ await sendToast(makeToastClient(client), {
1078
+ variant: "info",
1079
+ title: "Kibi OpenCode",
1080
+ message,
1081
+ duration: 8000,
1082
+ });
1083
+ }
1084
+ logger.info("auto-update.notification", { message });
1085
+ },
1086
+ log: (message, metadata) => logger.info(message, metadata),
1087
+ });
1088
+ const runAutoUpdate = startupNotifyGlobals.__kibi_test_auto_update_runner ?? autoUpdateRunner;
1089
+ void runAutoUpdate({
1062
1090
  directory: input.directory,
1063
- ...(Object.keys(versions).length > 0 ? { versions } : {}),
1064
- versionMetadataSource: meta.source,
1091
+ enabled: cfg.autoUpdate,
1092
+ }).catch((err) => {
1093
+ logger.info("auto-update: background update check failed", {
1094
+ error: err instanceof Error ? err.message : String(err),
1095
+ });
1065
1096
  });
1066
1097
  }, 2000);
1067
1098
  }
@@ -1 +1 @@
1
- {"opencode":"0.15.4","mcp":"0.15.3","cli":"0.12.3","core":"0.6.0"}
1
+ {"opencode":"0.16.0","mcp":"0.16.0","cli":"0.12.4","core":"0.6.0"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-opencode",
3
- "version": "0.15.4",
3
+ "version": "0.16.0",
4
4
  "description": "Kibi OpenCode plugin - thin adapter to integrate Kibi with OpenCode sessions",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -37,7 +37,9 @@
37
37
  "default": "./dist/tui.js"
38
38
  }
39
39
  },
40
- "files": ["dist"],
40
+ "files": [
41
+ "dist"
42
+ ],
41
43
  "engines": {
42
44
  "node": ">=18"
43
45
  },