@comfanion/workflow 4.36.18 → 4.36.20

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/bin/cli.js CHANGED
@@ -6,6 +6,7 @@ import inquirer from 'inquirer';
6
6
  import ora from 'ora';
7
7
  import fs from 'fs-extra';
8
8
  import path from 'path';
9
+ import crypto from 'crypto';
9
10
  import { fileURLToPath } from 'url';
10
11
  import { execSync } from 'child_process';
11
12
  import yaml from 'js-yaml';
@@ -293,7 +294,7 @@ program
293
294
  const oldPkgPath = path.join(vectorizerDir, 'package.json');
294
295
  if (await fs.pathExists(oldPkgPath)) {
295
296
  const oldPkg = await fs.readFile(oldPkgPath, 'utf8');
296
- oldPkgHash = require('crypto').createHash('md5').update(oldPkg).digest('hex');
297
+ oldPkgHash = crypto.createHash('md5').update(oldPkg).digest('hex');
297
298
  }
298
299
 
299
300
  // Read existing config.yaml for merge
@@ -342,7 +343,7 @@ program
342
343
  // Check if package.json changed
343
344
  const newPkgPath = path.join(newVectorizerDir, 'package.json');
344
345
  const newPkg = await fs.readFile(newPkgPath, 'utf8');
345
- const newPkgHash = require('crypto').createHash('md5').update(newPkg).digest('hex');
346
+ const newPkgHash = crypto.createHash('md5').update(newPkg).digest('hex');
346
347
 
347
348
  if (hadVectorizerModules && oldPkgHash === newPkgHash) {
348
349
  // Dependencies unchanged - restore cached node_modules
@@ -620,7 +621,7 @@ program
620
621
  const oldPkgPath = path.join(vectorizerDir, 'package.json');
621
622
  if (await fs.pathExists(oldPkgPath)) {
622
623
  const oldPkg = await fs.readFile(oldPkgPath, 'utf8');
623
- oldPkgHash = require('crypto').createHash('md5').update(oldPkg).digest('hex');
624
+ oldPkgHash = crypto.createHash('md5').update(oldPkg).digest('hex');
624
625
  }
625
626
 
626
627
  // Create full backup (unless --no-backup)
@@ -667,7 +668,7 @@ program
667
668
  // Check if package.json changed
668
669
  const newPkgPath = path.join(newVectorizerDir, 'package.json');
669
670
  const newPkg = await fs.readFile(newPkgPath, 'utf8');
670
- const newPkgHash = require('crypto').createHash('md5').update(newPkg).digest('hex');
671
+ const newPkgHash = crypto.createHash('md5').update(newPkg).digest('hex');
671
672
 
672
673
  if (hasVectorizerModules && oldPkgHash === newPkgHash) {
673
674
  // Dependencies unchanged - restore cached node_modules
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comfanion/workflow",
3
- "version": "4.36.18",
3
+ "version": "4.36.20",
4
4
  "description": "Initialize OpenCode Workflow system for AI-assisted development with semantic code search",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "3.0.0",
3
- "buildDate": "2026-01-24T16:07:11.824Z",
2
+ "version": "4.36.20",
3
+ "buildDate": "2026-01-24T17:33:20.798Z",
4
4
  "files": [
5
5
  "config.yaml",
6
6
  "FLOW.yaml",
@@ -32,6 +32,24 @@ npx opencode-workflow index --index code
32
32
 
33
33
  ---
34
34
 
35
+ ### version-check.ts
36
+
37
+ Checks for updates and shows a toast notification if a newer version is available.
38
+
39
+ **Features:**
40
+ - Checks npm registry for latest @comfanion/workflow version
41
+ - Compares with locally installed version
42
+ - Shows toast notification if update available
43
+ - Caches check result for 24 hours (avoids spam)
44
+ - Supports EN/UK/RU languages
45
+
46
+ **Toast Example:**
47
+ ```
48
+ 🚀 Update available! 4.36.19 → 4.37.0. Run: npx @comfanion/workflow update
49
+ ```
50
+
51
+ ---
52
+
35
53
  ### custom-compaction.ts
36
54
 
37
55
  Intelligent session compaction that preserves flow context.
@@ -0,0 +1,189 @@
1
+ import type { Plugin } from "@opencode-ai/plugin"
2
+ import path from "path"
3
+ import fs from "fs/promises"
4
+ import https from "https"
5
+
6
+ /**
7
+ * Version Check Plugin
8
+ *
9
+ * Checks if a newer version of @comfanion/workflow is available on npm.
10
+ * Shows a toast notification if an update is available.
11
+ *
12
+ * Configuration in .opencode/config.yaml:
13
+ * version_check:
14
+ * enabled: true # Enable version checking
15
+ * check_interval: 86400000 # Check once per day (ms)
16
+ */
17
+
18
+ const DEBUG = process.env.DEBUG?.includes('version-check') || process.env.DEBUG === '*'
19
+ const PACKAGE_NAME = '@comfanion/workflow'
20
+ const CACHE_FILE = '.version-check-cache.json'
21
+
22
+ function log(msg: string): void {
23
+ if (DEBUG) console.log(`[version-check] ${msg}`)
24
+ }
25
+
26
+ interface VersionCache {
27
+ lastCheck: number
28
+ latestVersion: string
29
+ }
30
+
31
+ async function getLocalVersion(directory: string): Promise<string | null> {
32
+ try {
33
+ // Try build-info.json first (created during npm publish)
34
+ const buildInfoPath = path.join(directory, '.opencode', 'build-info.json')
35
+ const buildInfo = JSON.parse(await fs.readFile(buildInfoPath, 'utf8'))
36
+ if (buildInfo.version) return buildInfo.version
37
+ } catch {}
38
+
39
+ try {
40
+ // Fallback to config.yaml version field
41
+ const configPath = path.join(directory, '.opencode', 'config.yaml')
42
+ const config = await fs.readFile(configPath, 'utf8')
43
+ const match = config.match(/^version:\s*["']?([\d.]+)["']?/m)
44
+ if (match) return match[1]
45
+ } catch {}
46
+
47
+ return null
48
+ }
49
+
50
+ async function getLatestVersion(): Promise<string | null> {
51
+ return new Promise((resolve) => {
52
+ const timeout = setTimeout(() => resolve(null), 5000) // 5s timeout
53
+
54
+ https.get(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, (res) => {
55
+ let data = ''
56
+ res.on('data', chunk => data += chunk)
57
+ res.on('end', () => {
58
+ clearTimeout(timeout)
59
+ try {
60
+ const json = JSON.parse(data)
61
+ resolve(json.version || null)
62
+ } catch {
63
+ resolve(null)
64
+ }
65
+ })
66
+ }).on('error', () => {
67
+ clearTimeout(timeout)
68
+ resolve(null)
69
+ })
70
+ })
71
+ }
72
+
73
+ async function loadCache(directory: string): Promise<VersionCache | null> {
74
+ try {
75
+ const cachePath = path.join(directory, '.opencode', CACHE_FILE)
76
+ const data = await fs.readFile(cachePath, 'utf8')
77
+ return JSON.parse(data)
78
+ } catch {
79
+ return null
80
+ }
81
+ }
82
+
83
+ async function saveCache(directory: string, cache: VersionCache): Promise<void> {
84
+ try {
85
+ const cachePath = path.join(directory, '.opencode', CACHE_FILE)
86
+ await fs.writeFile(cachePath, JSON.stringify(cache))
87
+ } catch {}
88
+ }
89
+
90
+ function compareVersions(local: string, latest: string): number {
91
+ const localParts = local.split('.').map(Number)
92
+ const latestParts = latest.split('.').map(Number)
93
+
94
+ for (let i = 0; i < 3; i++) {
95
+ const l = localParts[i] || 0
96
+ const r = latestParts[i] || 0
97
+ if (l < r) return -1
98
+ if (l > r) return 1
99
+ }
100
+ return 0
101
+ }
102
+
103
+ // Fun update messages
104
+ const UPDATE_MESSAGES = {
105
+ en: (local: string, latest: string) => `🚀 Update available! ${local} → ${latest}. Run: npx @comfanion/workflow update`,
106
+ uk: (local: string, latest: string) => `🚀 Є оновлення! ${local} → ${latest}. Виконай: npx @comfanion/workflow update`,
107
+ ru: (local: string, latest: string) => `🚀 Доступно обновление! ${local} → ${latest}. Выполни: npx @comfanion/workflow update`,
108
+ }
109
+
110
+ async function getLanguage(directory: string): Promise<'en' | 'uk' | 'ru'> {
111
+ try {
112
+ const configPath = path.join(directory, '.opencode', 'config.yaml')
113
+ const config = await fs.readFile(configPath, 'utf8')
114
+ if (config.includes('communication_language: Ukrainian') || config.includes('communication_language: uk')) return 'uk'
115
+ if (config.includes('communication_language: Russian') || config.includes('communication_language: ru')) return 'ru'
116
+ } catch {}
117
+ return 'en'
118
+ }
119
+
120
+ export const VersionCheckPlugin: Plugin = async ({ directory, client }) => {
121
+ const CHECK_INTERVAL = 24 * 60 * 60 * 1000 // 24 hours
122
+
123
+ const toast = async (message: string, variant: 'info' | 'success' | 'error' = 'info') => {
124
+ try {
125
+ await client?.tui?.showToast?.({ body: { message, variant } })
126
+ } catch {}
127
+ }
128
+
129
+ log(`Plugin loaded`)
130
+
131
+ // Run check after short delay (let TUI initialize)
132
+ setTimeout(async () => {
133
+ try {
134
+ // Check cache first
135
+ const cache = await loadCache(directory)
136
+ const now = Date.now()
137
+
138
+ // Skip if checked recently
139
+ if (cache && (now - cache.lastCheck) < CHECK_INTERVAL) {
140
+ log(`Skipping check (cached ${Math.round((now - cache.lastCheck) / 1000 / 60)}min ago)`)
141
+
142
+ // But still show toast if update was available
143
+ const localVersion = await getLocalVersion(directory)
144
+ if (localVersion && cache.latestVersion && compareVersions(localVersion, cache.latestVersion) < 0) {
145
+ const lang = await getLanguage(directory)
146
+ await toast(UPDATE_MESSAGES[lang](localVersion, cache.latestVersion), 'info')
147
+ }
148
+ return
149
+ }
150
+
151
+ // Get versions
152
+ const localVersion = await getLocalVersion(directory)
153
+ if (!localVersion) {
154
+ log(`Could not determine local version`)
155
+ return
156
+ }
157
+
158
+ log(`Local version: ${localVersion}`)
159
+
160
+ const latestVersion = await getLatestVersion()
161
+ if (!latestVersion) {
162
+ log(`Could not fetch latest version from npm`)
163
+ return
164
+ }
165
+
166
+ log(`Latest version: ${latestVersion}`)
167
+
168
+ // Save to cache
169
+ await saveCache(directory, { lastCheck: now, latestVersion })
170
+
171
+ // Compare and notify
172
+ if (compareVersions(localVersion, latestVersion) < 0) {
173
+ log(`Update available: ${localVersion} → ${latestVersion}`)
174
+ const lang = await getLanguage(directory)
175
+ await toast(UPDATE_MESSAGES[lang](localVersion, latestVersion), 'info')
176
+ } else {
177
+ log(`Up to date!`)
178
+ }
179
+ } catch (e) {
180
+ log(`Error: ${(e as Error).message}`)
181
+ }
182
+ }, 2000)
183
+
184
+ return {
185
+ event: async () => {}, // No events needed
186
+ }
187
+ }
188
+
189
+ export default VersionCheckPlugin