ctxman 3.0.0 → 3.1.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.
Files changed (223) hide show
  1. package/.ctxman/llm-profiles.json +1067 -39
  2. package/.ctxman/profiles/backend-team.json +13 -0
  3. package/.ctxman/profiles/default.json +12 -0
  4. package/.ctxman/profiles/devops-team.json +21 -0
  5. package/.ctxman/profiles/frontend-team.json +20 -0
  6. package/.ctxman/templates/bug-fix.json +17 -0
  7. package/.ctxman/templates/code-review.json +9 -0
  8. package/.ctxman/templates/documentation.json +9 -0
  9. package/.ctxman/templates/feature.json +9 -0
  10. package/.ctxman/templates/full-context.json +9 -0
  11. package/.ctxman/templates/refactoring.json +18 -0
  12. package/.ctxman/templates/testing.json +9 -0
  13. package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
  14. package/LICENSE +21 -0
  15. package/README.md +380 -136
  16. package/bin/cli.js +1880 -529
  17. package/bin/cm-ask.js +18 -13
  18. package/bin/cm-audit.js +124 -0
  19. package/bin/cm-count.js +151 -0
  20. package/bin/cm-gitingest.js +128 -115
  21. package/bin/cm-map.js +242 -0
  22. package/bin/cm-mcp.js +293 -0
  23. package/bin/cm-pack.js +405 -0
  24. package/bin/cm-policy.js +170 -0
  25. package/bin/cm-pr.js +248 -0
  26. package/bin/cm-search.js +178 -0
  27. package/bin/cm-update.js +128 -128
  28. package/bin/cm-why.js +197 -0
  29. package/bin/mcp-server.js +8 -24
  30. package/ctxman.js +237 -183
  31. package/examples/README.md +19 -4
  32. package/examples/github-actions/ctxman-check.yml +49 -0
  33. package/examples/wizard-profiles/security-audit/profile.json +7 -1
  34. package/index.d.ts +1422 -0
  35. package/index.js +55 -31
  36. package/lib/agents/pr-failure-agent.js +48 -45
  37. package/lib/analyzers/ai-suggester.js +451 -0
  38. package/lib/analyzers/dependency-scanner.js +505 -0
  39. package/lib/analyzers/duplicate-detector.js +264 -0
  40. package/lib/analyzers/go-method-analyzer.js +151 -110
  41. package/lib/analyzers/import-tracker.js +431 -0
  42. package/lib/analyzers/method-analyzer.js +1055 -434
  43. package/lib/analyzers/performance-dashboard.js +683 -0
  44. package/lib/analyzers/token-calculator.js +895 -504
  45. package/lib/analyzers/unused-exports-detector.js +336 -0
  46. package/lib/api/mcp/install.js +424 -0
  47. package/lib/api/mcp/prompts.js +281 -281
  48. package/lib/api/mcp/resources.js +300 -241
  49. package/lib/api/mcp/sandbox.js +89 -0
  50. package/lib/api/mcp/server.js +675 -312
  51. package/lib/api/mcp/start.js +125 -0
  52. package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
  53. package/lib/api/rest/server.js +105 -28
  54. package/lib/cache/CacheManager.js +9 -17
  55. package/lib/core/Analyzer.js +16 -10
  56. package/lib/core/ContextBuilder.js +19 -28
  57. package/lib/core/Reporter.js +15 -14
  58. package/lib/core/Scanner.js +81 -12
  59. package/lib/count/index.js +100 -0
  60. package/lib/formatters/format-registry.js +394 -372
  61. package/lib/formatters/gitingest-formatter.js +656 -621
  62. package/lib/formatters/toon-formatter-v1.3.js +637 -632
  63. package/lib/formatters/toon-formatter.js +239 -240
  64. package/lib/integrations/git/BlameTracker.js +4 -4
  65. package/lib/integrations/git/DiffAnalyzer.js +38 -31
  66. package/lib/integrations/git/GitClient.js +71 -53
  67. package/lib/lsp/server.js +311 -0
  68. package/lib/map/graph.js +299 -0
  69. package/lib/map/imports.js +233 -0
  70. package/lib/map/index.js +33 -0
  71. package/lib/map/rank.js +253 -0
  72. package/lib/map/render.js +346 -0
  73. package/lib/map/resolver.js +574 -0
  74. package/lib/pack/budget.js +111 -0
  75. package/lib/pack/code-lexer.js +360 -0
  76. package/lib/pack/compress.js +414 -0
  77. package/lib/pack/index.js +354 -0
  78. package/lib/pack/renderers/common.js +134 -0
  79. package/lib/pack/renderers/index.js +24 -0
  80. package/lib/pack/renderers/json.js +47 -0
  81. package/lib/pack/renderers/markdown.js +54 -0
  82. package/lib/pack/renderers/plain.js +38 -0
  83. package/lib/pack/renderers/xml.js +85 -0
  84. package/lib/pack/select.js +177 -0
  85. package/lib/pack/split.js +182 -0
  86. package/lib/pack/tree.js +35 -0
  87. package/lib/parsers/gitignore-parser.js +113 -107
  88. package/lib/parsers/method-filter-parser.js +50 -33
  89. package/lib/plugins/ExporterPlugin.js +3 -3
  90. package/lib/plugins/LanguagePlugin.js +3 -3
  91. package/lib/plugins/PluginManager.js +7 -13
  92. package/lib/policy/audit.js +294 -0
  93. package/lib/policy/enforce.js +125 -0
  94. package/lib/policy/ignore-files.js +169 -0
  95. package/lib/policy/index.js +52 -0
  96. package/lib/policy/policy.js +489 -0
  97. package/lib/pr/diff-parser.js +267 -0
  98. package/lib/pr/method-mapper.js +148 -0
  99. package/lib/pr/pr-context.js +829 -0
  100. package/lib/pr/pr-git.js +149 -0
  101. package/lib/pr/pr-renderer.js +644 -0
  102. package/lib/pr/related-tests.js +296 -0
  103. package/lib/rag/DataSourcePlugin.js +5 -5
  104. package/lib/rag/EmbeddingProvider.js +19 -8
  105. package/lib/rag/Indexer.js +14 -5
  106. package/lib/rag/VectorStoreFactory.js +3 -3
  107. package/lib/rag/sources/FileDataSource.js +6 -6
  108. package/lib/rag/stores/BaseVectorStore.js +5 -5
  109. package/lib/rag/stores/ChromaDBStore.js +10 -10
  110. package/lib/rag/stores/LanceDBStore.js +29 -19
  111. package/lib/rag/stores/PineconeStore.js +11 -9
  112. package/lib/search/bm25.js +433 -0
  113. package/lib/search/code-units.js +201 -0
  114. package/lib/search/symbols.js +189 -0
  115. package/lib/ui/dashboard.js +120 -89
  116. package/lib/ui/index.js +1 -6
  117. package/lib/ui/init-wizard-ui.js +550 -0
  118. package/lib/ui/progress-bar.js +94 -88
  119. package/lib/ui/select-input.js +40 -32
  120. package/lib/ui/template-selector.js +99 -0
  121. package/lib/ui/wizard.js +374 -329
  122. package/lib/utils/claude-token-counter.js +151 -0
  123. package/lib/utils/cli-output.js +17 -0
  124. package/lib/utils/clipboard-utils.js +49 -50
  125. package/lib/utils/config-utils.js +81 -66
  126. package/lib/utils/context-versioning.js +532 -0
  127. package/lib/utils/error-handler.js +112 -112
  128. package/lib/utils/file-utils.js +153 -69
  129. package/lib/utils/format-converter.js +356 -283
  130. package/lib/utils/git-utils.js +366 -294
  131. package/lib/utils/llm-cost-estimator.js +281 -0
  132. package/lib/utils/llm-detector.js +242 -78
  133. package/lib/utils/logger.js +243 -237
  134. package/lib/utils/messages.js +355 -0
  135. package/lib/utils/multi-repo-manager.js +364 -0
  136. package/lib/utils/profile-manager.js +380 -0
  137. package/lib/utils/safe-read.js +61 -0
  138. package/lib/utils/secret-redactor.js +61 -0
  139. package/lib/utils/snapshot-manager.js +679 -0
  140. package/lib/utils/template-manager.js +378 -0
  141. package/lib/utils/token-utils.js +201 -170
  142. package/lib/utils/tokenizer-adapter.js +421 -377
  143. package/lib/utils/updater.js +539 -506
  144. package/lib/watch/FileWatcher.js +12 -8
  145. package/lib/watch/IncrementalAnalyzer.js +7 -7
  146. package/lib/why/coupling.js +81 -0
  147. package/lib/why/errors.js +23 -0
  148. package/lib/why/git.js +169 -0
  149. package/lib/why/history.js +108 -0
  150. package/lib/why/index.js +720 -0
  151. package/lib/why/ownership.js +79 -0
  152. package/lib/why/paths.js +207 -0
  153. package/lib/why/references.js +259 -0
  154. package/lib/why/render.js +722 -0
  155. package/lib/why/search.js +358 -0
  156. package/lib/why/target.js +389 -0
  157. package/lib/wizards/config-generator.js +255 -0
  158. package/lib/wizards/project-detector.js +571 -0
  159. package/package.json +44 -10
  160. package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
  161. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  162. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  163. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  164. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  165. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  166. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  167. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  168. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  169. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  170. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  171. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  172. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  173. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  174. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  175. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  176. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  177. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  178. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  179. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  180. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  181. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  182. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  183. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  184. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  185. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  186. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  187. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  188. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  189. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  190. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  191. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  192. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  193. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  194. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  195. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  196. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  197. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  198. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  199. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  200. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  201. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  202. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  203. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  204. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  205. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  206. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  207. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  208. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  209. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  210. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  211. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  212. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  213. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  214. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  215. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  216. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  217. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  218. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  219. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  220. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  221. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  222. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  223. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
@@ -8,558 +8,591 @@ import fs from 'fs';
8
8
  import path from 'path';
9
9
  import https from 'https';
10
10
  import { execSync } from 'child_process';
11
+ import { fileURLToPath } from 'url';
11
12
 
12
- class Updater {
13
- constructor(options = {}) {
14
- this.currentVersion = this.getCurrentVersion();
15
- this.channel = options.channel || 'stable'; // stable or insider
16
- this.autoUpdate = options.autoUpdate !== false;
17
- this.checkInterval = options.checkInterval || 86400000; // 24 hours
18
- this.configDir = options.configDir || path.join(process.env.HOME || process.env.USERPROFILE, '.ctxman');
19
- this.updateCacheFile = path.join(this.configDir, 'update-cache.json');
20
-
21
- // Update endpoints (placeholder)
22
- this.endpoints = {
23
- stable: 'https://api.github.com/repos/hakkisagdic/ctxman/releases/latest',
24
- insider: 'https://api.github.com/repos/hakkisagdic/ctxman/releases', // Get all releases
25
- version: 'https://raw.githubusercontent.com/hakkisagdic/ctxman/main/package.json'
26
- };
27
- }
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
28
14
 
29
- /**
30
- * Get current version from package.json
31
- */
32
- getCurrentVersion() {
33
- try {
34
- const packagePath = path.join(__dirname, '../../package.json');
35
- const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
36
- return pkg.version;
37
- } catch (error) {
38
- return '0.0.0';
39
- }
15
+ class Updater {
16
+ constructor(options = {}) {
17
+ this.currentVersion = this.getCurrentVersion();
18
+ this.channel = options.channel || 'stable'; // stable or insider
19
+ this.autoUpdate = options.autoUpdate !== false;
20
+ this.checkInterval = options.checkInterval || 86400000; // 24 hours
21
+ this.configDir =
22
+ options.configDir || path.join(process.env.HOME || process.env.USERPROFILE, '.ctxman');
23
+ this.updateCacheFile = path.join(this.configDir, 'update-cache.json');
24
+
25
+ // Without an explicit channel, use the one saved by switchChannel()
26
+ if (!options.channel) {
27
+ const savedChannel = this.loadConfig().updateChannel;
28
+ if (['stable', 'insider'].includes(savedChannel)) {
29
+ this.channel = savedChannel;
30
+ }
40
31
  }
41
32
 
42
- /**
43
- * Fetch JSON from URL
44
- */
45
- async fetchJSON(url) {
46
- return new Promise((resolve, reject) => {
47
- https.get(url, {
48
- headers: {
49
- 'User-Agent': 'ctxman-updater'
50
- }
51
- }, (res) => {
52
- let data = '';
53
-
54
- res.on('data', (chunk) => {
55
- data += chunk;
56
- });
57
-
58
- res.on('end', () => {
59
- try {
60
- resolve(JSON.parse(data));
61
- } catch (error) {
62
- reject(new Error('Failed to parse JSON response'));
63
- }
64
- });
65
- }).on('error', reject);
66
- });
33
+ // Update endpoints (placeholder)
34
+ this.endpoints = {
35
+ stable: 'https://api.github.com/repos/hakkisagdic/ctxman/releases/latest',
36
+ insider: 'https://api.github.com/repos/hakkisagdic/ctxman/releases', // Get all releases
37
+ version: 'https://raw.githubusercontent.com/hakkisagdic/ctxman/main/package.json',
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Get current version from package.json
43
+ */
44
+ getCurrentVersion() {
45
+ try {
46
+ const packagePath = path.join(__dirname, '../../package.json');
47
+ const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
48
+ return pkg.version;
49
+ } catch (_error) {
50
+ return '0.0.0';
67
51
  }
52
+ }
53
+
54
+ /**
55
+ * Fetch JSON from URL
56
+ */
57
+ async fetchJSON(url) {
58
+ return new Promise((resolve, reject) => {
59
+ https
60
+ .get(
61
+ url,
62
+ {
63
+ headers: {
64
+ 'User-Agent': 'ctxman-updater',
65
+ },
66
+ },
67
+ (res) => {
68
+ let data = '';
69
+
70
+ res.on('data', (chunk) => {
71
+ data += chunk;
72
+ });
68
73
 
69
- /**
70
- * Compare versions (returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2)
71
- */
72
- compareVersions(v1, v2) {
73
- const parts1 = v1.split('.').map(Number);
74
- const parts2 = v2.split('.').map(Number);
75
-
76
- for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
77
- const p1 = parts1[i] || 0;
78
- const p2 = parts2[i] || 0;
79
-
80
- if (p1 < p2) return -1;
81
- if (p1 > p2) return 1;
82
- }
83
-
84
- return 0;
74
+ res.on('end', () => {
75
+ try {
76
+ resolve(JSON.parse(data));
77
+ } catch (_error) {
78
+ reject(new Error('Failed to parse JSON response'));
79
+ }
80
+ });
81
+ }
82
+ )
83
+ .on('error', reject);
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Compare versions (returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2)
89
+ */
90
+ compareVersions(v1, v2) {
91
+ const parts1 = v1.split('.').map(Number);
92
+ const parts2 = v2.split('.').map(Number);
93
+
94
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
95
+ const p1 = parts1[i] || 0;
96
+ const p2 = parts2[i] || 0;
97
+
98
+ if (p1 < p2) return -1;
99
+ if (p1 > p2) return 1;
85
100
  }
86
101
 
87
- /**
88
- * Check for updates
89
- * @returns {Promise<Object>} Update information
90
- */
91
- async checkForUpdates() {
92
- try {
93
- // PLACEHOLDER: In production, this would fetch from actual API
94
- console.log('🔍 Checking for updates...');
95
- console.log(` Current version: ${this.currentVersion}`);
96
- console.log(` Channel: ${this.channel}`);
97
-
98
- // Simulate API call (placeholder)
99
- const latestVersion = await this.getLatestVersion();
100
-
101
- if (!latestVersion) {
102
- return {
103
- available: false,
104
- message: 'Could not check for updates'
105
- };
106
- }
107
-
108
- const comparison = this.compareVersions(latestVersion.version, this.currentVersion);
109
-
110
- if (comparison > 0) {
111
- return {
112
- available: true,
113
- currentVersion: this.currentVersion,
114
- latestVersion: latestVersion.version,
115
- channel: this.channel,
116
- releaseNotes: latestVersion.releaseNotes || '',
117
- downloadUrl: latestVersion.downloadUrl || '',
118
- publishedAt: latestVersion.publishedAt || new Date().toISOString()
119
- };
120
- }
121
-
122
- return {
123
- available: false,
124
- currentVersion: this.currentVersion,
125
- latestVersion: latestVersion.version,
126
- message: 'You are using the latest version'
127
- };
128
- } catch (error) {
129
- return {
130
- available: false,
131
- error: error.message,
132
- message: 'Failed to check for updates'
133
- };
134
- }
102
+ return 0;
103
+ }
104
+
105
+ /**
106
+ * Check for updates
107
+ * @returns {Promise<Object>} Update information
108
+ */
109
+ async checkForUpdates() {
110
+ try {
111
+ // PLACEHOLDER: In production, this would fetch from actual API
112
+ console.log('🔍 Checking for updates...');
113
+ console.log(` Current version: ${this.currentVersion}`);
114
+ console.log(` Channel: ${this.channel}`);
115
+
116
+ // Simulate API call (placeholder)
117
+ const latestVersion = await this.getLatestVersion();
118
+
119
+ if (!latestVersion) {
120
+ return {
121
+ available: false,
122
+ message: 'Could not check for updates',
123
+ };
124
+ }
125
+
126
+ const comparison = this.compareVersions(latestVersion.version, this.currentVersion);
127
+
128
+ if (comparison > 0) {
129
+ return {
130
+ available: true,
131
+ currentVersion: this.currentVersion,
132
+ latestVersion: latestVersion.version,
133
+ channel: this.channel,
134
+ releaseNotes: latestVersion.releaseNotes || '',
135
+ downloadUrl: latestVersion.downloadUrl || '',
136
+ publishedAt: latestVersion.publishedAt || new Date().toISOString(),
137
+ };
138
+ }
139
+
140
+ return {
141
+ available: false,
142
+ currentVersion: this.currentVersion,
143
+ latestVersion: latestVersion.version,
144
+ message: 'You are using the latest version',
145
+ };
146
+ } catch (error) {
147
+ return {
148
+ available: false,
149
+ error: error.message,
150
+ message: 'Failed to check for updates',
151
+ };
135
152
  }
136
-
137
- /**
138
- * Get latest version info from GitHub API
139
- */
140
- async getLatestVersion() {
141
- try {
142
- if (this.channel === 'stable') {
143
- // Stable channel - get latest stable release
144
- const release = await this.fetchJSON(this.endpoints.stable);
145
-
146
- return {
147
- version: release.tag_name.replace(/^v/, ''), // Remove 'v' prefix
148
- releaseNotes: release.body || 'No release notes available',
149
- downloadUrl: release.html_url,
150
- publishedAt: release.published_at,
151
- assets: release.assets || []
152
- };
153
- } else {
154
- // Insider channel - get latest pre-release
155
- const releases = await this.fetchJSON(this.endpoints.insider);
156
-
157
- // Find latest pre-release
158
- const preRelease = releases.find(r => r.prerelease === true);
159
-
160
- if (preRelease) {
161
- return {
162
- version: preRelease.tag_name.replace(/^v/, ''),
163
- releaseNotes: preRelease.body || 'No release notes available',
164
- downloadUrl: preRelease.html_url,
165
- publishedAt: preRelease.published_at,
166
- assets: preRelease.assets || []
167
- };
168
- }
169
-
170
- // Fallback to latest stable if no pre-release
171
- const latestRelease = releases[0];
172
- return {
173
- version: latestRelease.tag_name.replace(/^v/, ''),
174
- releaseNotes: latestRelease.body || 'No release notes available',
175
- downloadUrl: latestRelease.html_url,
176
- publishedAt: latestRelease.published_at,
177
- assets: latestRelease.assets || []
178
- };
179
- }
180
- } catch (error) {
181
- console.error('Failed to fetch latest version:', error.message);
182
- return null;
153
+ }
154
+
155
+ /**
156
+ * Get latest version info from GitHub API
157
+ */
158
+ async getLatestVersion() {
159
+ try {
160
+ if (this.channel === 'stable') {
161
+ // Stable channel - get latest stable release
162
+ const release = await this.fetchJSON(this.endpoints.stable);
163
+
164
+ return {
165
+ version: release.tag_name.replace(/^v/, ''), // Remove 'v' prefix
166
+ releaseNotes: release.body || 'No release notes available',
167
+ downloadUrl: release.html_url,
168
+ publishedAt: release.published_at,
169
+ assets: release.assets || [],
170
+ };
171
+ } else {
172
+ // Insider channel - get latest pre-release
173
+ const releases = await this.fetchJSON(this.endpoints.insider);
174
+
175
+ // Find latest pre-release
176
+ const preRelease = releases.find((r) => r.prerelease === true);
177
+
178
+ if (preRelease) {
179
+ return {
180
+ version: preRelease.tag_name.replace(/^v/, ''),
181
+ releaseNotes: preRelease.body || 'No release notes available',
182
+ downloadUrl: preRelease.html_url,
183
+ publishedAt: preRelease.published_at,
184
+ assets: preRelease.assets || [],
185
+ };
183
186
  }
184
- }
185
187
 
186
- /**
187
- * Download and install update
188
- */
189
- async installUpdate(updateInfo) {
190
-
191
- try {
192
- console.log('📦 Installing update...');
193
- console.log(` From: ${updateInfo.currentVersion}`);
194
- console.log(` To: ${updateInfo.latestVersion}`);
195
- console.log();
196
-
197
- // Step 1: Detect installation type
198
- const installType = this.detectInstallationType();
199
- console.log(` Installation type: ${installType}`);
200
-
201
- // Step 2: Create backup
202
- console.log(' Creating backup...');
203
- const backupPath = this.createBackup();
204
- console.log(` ✓ Backup created: ${backupPath}`);
205
-
206
- // Step 3: Install based on type
207
- if (installType === 'global') {
208
- console.log(' Updating global installation...');
209
- const result = execSync('npm update -g ctxman', {
210
- encoding: 'utf8',
211
- stdio: 'pipe'
212
- });
213
- console.log(' ✓ Global update complete');
214
- } else if (installType === 'local') {
215
- console.log(' Updating local installation...');
216
- const result = execSync('npm update ctxman', {
217
- encoding: 'utf8',
218
- stdio: 'pipe',
219
- cwd: this.getInstallDir()
220
- });
221
- console.log(' ✓ Local update complete');
222
- } else {
223
- // Source installation - show manual instructions
224
- console.log('\n⚠️ Source-based installation detected.');
225
- console.log(' Please update manually:\n');
226
- console.log(' 1. git pull origin main');
227
- console.log(' 2. npm install');
228
- console.log(` 3. Or visit: ${updateInfo.downloadUrl}\n`);
229
-
230
- return {
231
- success: false,
232
- manualUpdateRequired: true,
233
- instructions: 'Manual update required for source installation'
234
- };
235
- }
236
-
237
- // Step 4: Verify installation
238
- console.log(' Verifying update...');
239
- const newVersion = this.getCurrentVersion();
240
-
241
- if (this.compareVersions(newVersion, updateInfo.latestVersion) === 0) {
242
- console.log(' ✓ Update verified');
243
-
244
- // Update config
245
- this.saveConfig({
246
- channel: this.channel,
247
- autoUpdate: this.autoUpdate
248
- });
249
-
250
- return {
251
- success: true,
252
- oldVersion: updateInfo.currentVersion,
253
- newVersion: newVersion,
254
- backupPath
255
- };
256
- } else {
257
- console.log(' ⚠️ Version mismatch after update');
258
- console.log(` Expected: ${updateInfo.latestVersion}, Got: ${newVersion}`);
259
-
260
- return {
261
- success: false,
262
- error: 'Version verification failed'
263
- };
264
- }
265
- } catch (error) {
266
- console.error(' ✗ Update failed:', error.message);
267
-
268
- return {
269
- success: false,
270
- error: error.message
271
- };
272
- }
188
+ // Fallback to latest stable if no pre-release
189
+ const latestRelease = releases[0];
190
+ return {
191
+ version: latestRelease.tag_name.replace(/^v/, ''),
192
+ releaseNotes: latestRelease.body || 'No release notes available',
193
+ downloadUrl: latestRelease.html_url,
194
+ publishedAt: latestRelease.published_at,
195
+ assets: latestRelease.assets || [],
196
+ };
197
+ }
198
+ } catch (error) {
199
+ console.error('Failed to fetch latest version:', error.message);
200
+ return null;
273
201
  }
202
+ }
203
+
204
+ /**
205
+ * Download and install update
206
+ */
207
+ async installUpdate(updateInfo) {
208
+ try {
209
+ console.log('📦 Installing update...');
210
+ console.log(` From: ${updateInfo.currentVersion}`);
211
+ console.log(` To: ${updateInfo.latestVersion}`);
212
+ console.log();
213
+
214
+ // Step 1: Detect installation type
215
+ const installType = this.detectInstallationType();
216
+ console.log(` Installation type: ${installType}`);
217
+
218
+ // Step 2: Create backup
219
+ console.log(' Creating backup...');
220
+ const backupPath = this.createBackup();
221
+ console.log(` ✓ Backup created: ${backupPath}`);
222
+
223
+ // Step 3: Install based on type
224
+ if (installType === 'global') {
225
+ console.log(' Updating global installation...');
226
+ const _result = execSync('npm update -g ctxman', {
227
+ encoding: 'utf8',
228
+ stdio: 'pipe',
229
+ });
230
+ console.log(' ✓ Global update complete');
231
+ } else if (installType === 'local') {
232
+ console.log(' Updating local installation...');
233
+ const _result = execSync('npm update ctxman', {
234
+ encoding: 'utf8',
235
+ stdio: 'pipe',
236
+ cwd: this.getInstallDir(),
237
+ });
238
+ console.log(' ✓ Local update complete');
239
+ } else {
240
+ // Source installation - show manual instructions
241
+ console.log('\n⚠️ Source-based installation detected.');
242
+ console.log(' Please update manually:\n');
243
+ console.log(' 1. git pull origin main');
244
+ console.log(' 2. npm install');
245
+ console.log(` 3. Or visit: ${updateInfo.downloadUrl}\n`);
246
+
247
+ return {
248
+ success: false,
249
+ manualUpdateRequired: true,
250
+ instructions: 'Manual update required for source installation',
251
+ };
252
+ }
274
253
 
275
- /**
276
- * Detect installation type (global, local, or source)
277
- */
278
- detectInstallationType() {
279
- try {
280
- // Check if installed globally
281
- const globalList = execSync('npm list -g ctxman --depth=0', {
282
- encoding: 'utf8',
283
- stdio: 'pipe'
284
- });
254
+ // Step 4: Verify installation
255
+ console.log(' Verifying update...');
256
+ const newVersion = this.getCurrentVersion();
285
257
 
286
- if (globalList.includes('ctxman')) {
287
- return 'global';
288
- }
289
- } catch (error) {
290
- // Not global
291
- }
258
+ if (this.compareVersions(newVersion, updateInfo.latestVersion) === 0) {
259
+ console.log(' ✓ Update verified');
292
260
 
293
- // Check if in node_modules (local npm install)
294
- const nodeModulesPath = path.join(process.cwd(), 'node_modules', '@hakkisagdic', 'ctxman');
295
- if (fs.existsSync(nodeModulesPath)) {
296
- return 'local';
297
- }
261
+ // Update config
262
+ this.saveConfig({
263
+ channel: this.channel,
264
+ autoUpdate: this.autoUpdate,
265
+ });
298
266
 
299
- // Check if running from source (has .git directory)
300
- const gitPath = path.join(__dirname, '../../.git');
301
- if (fs.existsSync(gitPath)) {
302
- return 'source';
303
- }
267
+ return {
268
+ success: true,
269
+ oldVersion: updateInfo.currentVersion,
270
+ newVersion: newVersion,
271
+ backupPath,
272
+ };
273
+ } else {
274
+ console.log(' ⚠️ Version mismatch after update');
275
+ console.log(` Expected: ${updateInfo.latestVersion}, Got: ${newVersion}`);
304
276
 
305
- return 'unknown';
277
+ return {
278
+ success: false,
279
+ error: 'Version verification failed',
280
+ };
281
+ }
282
+ } catch (error) {
283
+ console.error(' ✗ Update failed:', error.message);
284
+
285
+ return {
286
+ success: false,
287
+ error: error.message,
288
+ };
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Detect installation type (global, local, or source)
294
+ */
295
+ detectInstallationType() {
296
+ try {
297
+ // Check if installed globally
298
+ const globalList = execSync('npm list -g ctxman --depth=0', {
299
+ encoding: 'utf8',
300
+ stdio: 'pipe',
301
+ });
302
+
303
+ if (globalList.includes('ctxman')) {
304
+ return 'global';
305
+ }
306
+ } catch (_error) {
307
+ // Not global
306
308
  }
307
309
 
308
- /**
309
- * Get installation directory
310
- */
311
- getInstallDir() {
312
- const installType = this.detectInstallationType();
313
-
314
- if (installType === 'global') {
315
- try {
316
- const globalRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
317
- return path.join(globalRoot, 'ctxman');
318
- } catch (error) {
319
- return null;
320
- }
321
- }
322
-
323
- if (installType === 'local') {
324
- return path.join(process.cwd(), 'node_modules', '@hakkisagdic', 'ctxman');
325
- }
310
+ // Check if in node_modules (local npm install)
311
+ const nodeModulesPath = path.join(process.cwd(), 'node_modules', '@hakkisagdic', 'ctxman');
312
+ if (fs.existsSync(nodeModulesPath)) {
313
+ return 'local';
314
+ }
326
315
 
327
- return path.join(__dirname, '../..');
316
+ // Check if running from source (has .git directory)
317
+ const gitPath = path.join(__dirname, '../../.git');
318
+ if (fs.existsSync(gitPath)) {
319
+ return 'source';
328
320
  }
329
321
 
330
- /**
331
- * Create backup before update
332
- */
333
- createBackup() {
322
+ return 'unknown';
323
+ }
334
324
 
335
- const installDir = this.getInstallDir();
336
- const backupDir = path.join(this.configDir, 'backups');
337
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
338
- const backupPath = path.join(backupDir, `backup-${this.currentVersion}-${timestamp}`);
325
+ /**
326
+ * Get installation directory
327
+ */
328
+ getInstallDir() {
329
+ const installType = this.detectInstallationType();
339
330
 
340
- if (!fs.existsSync(backupDir)) {
341
- fs.mkdirSync(backupDir, { recursive: true });
342
- }
331
+ if (installType === 'global') {
332
+ try {
333
+ const globalRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
334
+ return path.join(globalRoot, 'ctxman');
335
+ } catch (_error) {
336
+ return null;
337
+ }
338
+ }
343
339
 
344
- // Create backup info file
345
- const backupInfo = {
346
- version: this.currentVersion,
347
- timestamp: new Date().toISOString(),
348
- installType: this.detectInstallationType(),
349
- installDir: installDir
350
- };
340
+ if (installType === 'local') {
341
+ return path.join(process.cwd(), 'node_modules', '@hakkisagdic', 'ctxman');
342
+ }
351
343
 
352
- fs.writeFileSync(
353
- path.join(backupPath, 'backup-info.json'),
354
- JSON.stringify(backupInfo, null, 2),
355
- 'utf8'
356
- );
344
+ return path.join(__dirname, '../..');
345
+ }
357
346
 
358
- return backupPath;
347
+ /**
348
+ * Create backup before update
349
+ */
350
+ createBackup() {
351
+ const installDir = this.getInstallDir();
352
+ const backupDir = path.join(this.configDir, 'backups');
353
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
354
+ const backupPath = path.join(backupDir, `backup-${this.currentVersion}-${timestamp}`);
355
+
356
+ if (!fs.existsSync(backupDir)) {
357
+ fs.mkdirSync(backupDir, { recursive: true });
359
358
  }
360
359
 
361
- /**
362
- * Rollback to previous version
363
- */
364
- async rollback() {
365
-
366
- try {
367
- console.log('🔄 Rolling back to previous version...');
368
-
369
- const backupDir = path.join(this.configDir, 'backups');
370
- if (!fs.existsSync(backupDir)) {
371
- throw new Error('No backups found');
372
- }
373
-
374
- // Get latest backup
375
- const backups = fs.readdirSync(backupDir)
376
- .filter(name => name.startsWith('backup-'))
377
- .sort()
378
- .reverse();
379
-
380
- if (backups.length === 0) {
381
- throw new Error('No backups available');
382
- }
383
-
384
- const latestBackup = backups[0];
385
- const backupPath = path.join(backupDir, latestBackup);
386
- const backupInfoPath = path.join(backupPath, 'backup-info.json');
387
-
388
- if (!fs.existsSync(backupInfoPath)) {
389
- throw new Error('Invalid backup: missing backup-info.json');
390
- }
391
-
392
- const backupInfo = JSON.parse(fs.readFileSync(backupInfoPath, 'utf8'));
393
-
394
- console.log(` Restoring version: ${backupInfo.version}`);
395
- console.log(` Backup date: ${backupInfo.timestamp}`);
396
-
397
- // Install the backed up version
398
- const installType = backupInfo.installType;
399
-
400
- if (installType === 'global') {
401
- execSync(`npm install -g ctxman@${backupInfo.version}`, {
402
- stdio: 'inherit'
403
- });
404
- } else if (installType === 'local') {
405
- execSync(`npm install ctxman@${backupInfo.version}`, {
406
- stdio: 'inherit',
407
- cwd: this.getInstallDir()
408
- });
409
- } else {
410
- throw new Error('Cannot rollback source installation');
411
- }
412
-
413
- console.log('\n✅ Rollback successful!');
414
- console.log(` Restored to version: ${backupInfo.version}`);
415
-
416
- return {
417
- success: true,
418
- restoredVersion: backupInfo.version
419
- };
420
- } catch (error) {
421
- console.error('✗ Rollback failed:', error.message);
422
- return {
423
- success: false,
424
- error: error.message
425
- };
426
- }
360
+ // Create backup info file
361
+ const backupInfo = {
362
+ version: this.currentVersion,
363
+ timestamp: new Date().toISOString(),
364
+ installType: this.detectInstallationType(),
365
+ installDir: installDir,
366
+ };
367
+
368
+ fs.writeFileSync(
369
+ path.join(backupPath, 'backup-info.json'),
370
+ JSON.stringify(backupInfo, null, 2),
371
+ 'utf8'
372
+ );
373
+
374
+ return backupPath;
375
+ }
376
+
377
+ /**
378
+ * Rollback to previous version
379
+ */
380
+ async rollback() {
381
+ try {
382
+ console.log('🔄 Rolling back to previous version...');
383
+
384
+ const backupDir = path.join(this.configDir, 'backups');
385
+ if (!fs.existsSync(backupDir)) {
386
+ throw new Error('No backups found');
387
+ }
388
+
389
+ // Get latest backup
390
+ const backups = fs
391
+ .readdirSync(backupDir)
392
+ .filter((name) => name.startsWith('backup-'))
393
+ .sort()
394
+ .reverse();
395
+
396
+ if (backups.length === 0) {
397
+ throw new Error('No backups available');
398
+ }
399
+
400
+ const latestBackup = backups[0];
401
+ const backupPath = path.join(backupDir, latestBackup);
402
+ const backupInfoPath = path.join(backupPath, 'backup-info.json');
403
+
404
+ if (!fs.existsSync(backupInfoPath)) {
405
+ throw new Error('Invalid backup: missing backup-info.json');
406
+ }
407
+
408
+ const backupInfo = JSON.parse(fs.readFileSync(backupInfoPath, 'utf8'));
409
+
410
+ console.log(` Restoring version: ${backupInfo.version}`);
411
+ console.log(` Backup date: ${backupInfo.timestamp}`);
412
+
413
+ // Install the backed up version
414
+ const installType = backupInfo.installType;
415
+
416
+ if (installType === 'global') {
417
+ execSync(`npm install -g ctxman@${backupInfo.version}`, {
418
+ stdio: 'inherit',
419
+ });
420
+ } else if (installType === 'local') {
421
+ execSync(`npm install ctxman@${backupInfo.version}`, {
422
+ stdio: 'inherit',
423
+ cwd: this.getInstallDir(),
424
+ });
425
+ } else {
426
+ throw new Error('Cannot rollback source installation');
427
+ }
428
+
429
+ console.log('\n✅ Rollback successful!');
430
+ console.log(` Restored to version: ${backupInfo.version}`);
431
+
432
+ return {
433
+ success: true,
434
+ restoredVersion: backupInfo.version,
435
+ };
436
+ } catch (error) {
437
+ console.error('✗ Rollback failed:', error.message);
438
+ return {
439
+ success: false,
440
+ error: error.message,
441
+ };
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Switch update channel
447
+ */
448
+ async switchChannel(newChannel) {
449
+ if (!['stable', 'insider'].includes(newChannel)) {
450
+ throw new Error('Invalid channel. Must be "stable" or "insider"');
427
451
  }
428
452
 
429
- /**
430
- * Switch update channel
431
- */
432
- async switchChannel(newChannel) {
433
- if (!['stable', 'insider'].includes(newChannel)) {
434
- throw new Error('Invalid channel. Must be "stable" or "insider"');
435
- }
453
+ this.channel = newChannel;
436
454
 
437
- this.channel = newChannel;
455
+ // Save to config
456
+ this.saveConfig({ channel: newChannel });
438
457
 
439
- // Save to config
440
- this.saveConfig({ channel: newChannel });
458
+ console.log(`✓ Switched to ${newChannel} channel`);
459
+ console.log(' Checking for updates on new channel...\n');
441
460
 
442
- console.log(`✓ Switched to ${newChannel} channel`);
443
- console.log(' Checking for updates on new channel...\n');
461
+ return await this.checkForUpdates();
462
+ }
444
463
 
445
- return await this.checkForUpdates();
464
+ /**
465
+ * Load updater config written by saveConfig()
466
+ */
467
+ loadConfig() {
468
+ try {
469
+ const configFile = path.join(this.configDir, 'config.json');
470
+ if (fs.existsSync(configFile)) {
471
+ return JSON.parse(fs.readFileSync(configFile, 'utf8')) || {};
472
+ }
473
+ } catch (_error) {
474
+ // An unreadable config falls back to the defaults
446
475
  }
447
-
448
- /**
449
- * Save updater config
450
- */
451
- saveConfig(config) {
452
- try {
453
- const configFile = path.join(this.configDir, 'config.json');
454
- let existingConfig = {};
455
-
456
- if (fs.existsSync(configFile)) {
457
- existingConfig = JSON.parse(fs.readFileSync(configFile, 'utf8'));
458
- }
459
-
460
- const updatedConfig = {
461
- ...existingConfig,
462
- updateChannel: config.channel || this.channel,
463
- autoUpdate: config.autoUpdate !== undefined ? config.autoUpdate : this.autoUpdate,
464
- lastUpdateCheck: new Date().toISOString()
465
- };
466
-
467
- if (!fs.existsSync(this.configDir)) {
468
- fs.mkdirSync(this.configDir, { recursive: true });
469
- }
470
-
471
- fs.writeFileSync(configFile, JSON.stringify(updatedConfig, null, 2), 'utf8');
472
- } catch (error) {
473
- console.error('Failed to save config:', error.message);
474
- }
476
+ return {};
477
+ }
478
+
479
+ /**
480
+ * Save updater config
481
+ */
482
+ saveConfig(config) {
483
+ try {
484
+ const configFile = path.join(this.configDir, 'config.json');
485
+ let existingConfig = {};
486
+ try {
487
+ existingConfig = JSON.parse(fs.readFileSync(configFile, 'utf8'));
488
+ } catch (error) {
489
+ // No config yet; an unparsable one stops the save below
490
+ if (error.code !== 'ENOENT') throw error;
491
+ }
492
+
493
+ const updatedConfig = {
494
+ ...existingConfig,
495
+ updateChannel: config.channel || this.channel,
496
+ autoUpdate: config.autoUpdate !== undefined ? config.autoUpdate : this.autoUpdate,
497
+ lastUpdateCheck: new Date().toISOString(),
498
+ };
499
+
500
+ if (!fs.existsSync(this.configDir)) {
501
+ fs.mkdirSync(this.configDir, { recursive: true });
502
+ }
503
+
504
+ fs.writeFileSync(configFile, JSON.stringify(updatedConfig, null, 2), 'utf8');
505
+ } catch (error) {
506
+ console.error('Failed to save config:', error.message);
475
507
  }
476
-
477
- /**
478
- * Get update cache
479
- */
480
- getUpdateCache() {
481
- try {
482
- if (fs.existsSync(this.updateCacheFile)) {
483
- return JSON.parse(fs.readFileSync(this.updateCacheFile, 'utf8'));
484
- }
485
- } catch (error) {
486
- // Return null if cache can't be read
487
- }
488
- return null;
508
+ }
509
+
510
+ /**
511
+ * Get update cache
512
+ */
513
+ getUpdateCache() {
514
+ try {
515
+ if (fs.existsSync(this.updateCacheFile)) {
516
+ return JSON.parse(fs.readFileSync(this.updateCacheFile, 'utf8'));
517
+ }
518
+ } catch (_error) {
519
+ // Return null if cache can't be read
489
520
  }
490
-
491
- /**
492
- * Save update cache
493
- */
494
- saveUpdateCache(data) {
495
- try {
496
- if (!fs.existsSync(this.configDir)) {
497
- fs.mkdirSync(this.configDir, { recursive: true });
498
- }
499
-
500
- fs.writeFileSync(this.updateCacheFile, JSON.stringify(data, null, 2), 'utf8');
501
- } catch (error) {
502
- // Fail silently
503
- }
521
+ return null;
522
+ }
523
+
524
+ /**
525
+ * Save update cache
526
+ */
527
+ saveUpdateCache(data) {
528
+ try {
529
+ if (!fs.existsSync(this.configDir)) {
530
+ fs.mkdirSync(this.configDir, { recursive: true });
531
+ }
532
+
533
+ fs.writeFileSync(this.updateCacheFile, JSON.stringify(data, null, 2), 'utf8');
534
+ } catch (_error) {
535
+ // Fail silently
504
536
  }
505
-
506
- /**
507
- * Should check for updates (based on interval)
508
- */
509
- shouldCheckForUpdates() {
510
- const cache = this.getUpdateCache();
511
- if (!cache || !cache.lastCheck) {
512
- return true;
513
- }
514
-
515
- const lastCheck = new Date(cache.lastCheck).getTime();
516
- const now = Date.now();
517
-
518
- return (now - lastCheck) > this.checkInterval;
537
+ }
538
+
539
+ /**
540
+ * Should check for updates (based on interval)
541
+ */
542
+ shouldCheckForUpdates() {
543
+ const cache = this.getUpdateCache();
544
+ if (!cache || !cache.lastCheck) {
545
+ return true;
519
546
  }
520
547
 
521
- /**
522
- * Check and notify about updates
523
- */
524
- async checkAndNotify() {
525
- if (!this.shouldCheckForUpdates()) {
526
- const cache = this.getUpdateCache();
527
- if (cache && cache.available) {
528
- this.showUpdateNotification(cache);
529
- }
530
- return cache;
531
- }
548
+ const lastCheck = new Date(cache.lastCheck).getTime();
549
+ const now = Date.now();
550
+
551
+ return now - lastCheck > this.checkInterval;
552
+ }
553
+
554
+ /**
555
+ * Check and notify about updates
556
+ */
557
+ async checkAndNotify() {
558
+ if (!this.shouldCheckForUpdates()) {
559
+ const cache = this.getUpdateCache();
560
+ if (cache && cache.available) {
561
+ this.showUpdateNotification(cache);
562
+ }
563
+ return cache;
564
+ }
532
565
 
533
- const updateInfo = await this.checkForUpdates();
566
+ const updateInfo = await this.checkForUpdates();
534
567
 
535
- // Save to cache
536
- this.saveUpdateCache({
537
- ...updateInfo,
538
- lastCheck: new Date().toISOString()
539
- });
568
+ // Save to cache
569
+ this.saveUpdateCache({
570
+ ...updateInfo,
571
+ lastCheck: new Date().toISOString(),
572
+ });
540
573
 
541
- if (updateInfo.available) {
542
- this.showUpdateNotification(updateInfo);
543
- }
544
-
545
- return updateInfo;
574
+ if (updateInfo.available) {
575
+ this.showUpdateNotification(updateInfo);
546
576
  }
547
577
 
548
- /**
549
- * Show update notification
550
- */
551
- showUpdateNotification(updateInfo) {
552
- console.log('\n╔════════════════════════════════════════════════════════╗');
553
- console.log('║ 🎉 Update Available! ║');
554
- console.log('╚════════════════════════════════════════════════════════╝\n');
555
- console.log(` Current version: ${updateInfo.currentVersion}`);
556
- console.log(` Latest version: ${updateInfo.latestVersion}`);
557
- console.log(` Channel: ${updateInfo.channel}\n`);
558
- console.log(' To update, run:');
559
- console.log(' ctxman update\n');
560
- console.log(' Or update manually:');
561
- console.log(' npm update -g ctxman\n');
562
- }
578
+ return updateInfo;
579
+ }
580
+
581
+ /**
582
+ * Show update notification
583
+ */
584
+ showUpdateNotification(updateInfo) {
585
+ console.log('\n╔════════════════════════════════════════════════════════╗');
586
+ console.log('║ 🎉 Update Available! ║');
587
+ console.log('╚════════════════════════════════════════════════════════╝\n');
588
+ console.log(` Current version: ${updateInfo.currentVersion}`);
589
+ console.log(` Latest version: ${updateInfo.latestVersion}`);
590
+ console.log(` Channel: ${updateInfo.channel}\n`);
591
+ console.log(' To update, run:');
592
+ console.log(' ctxman update\n');
593
+ console.log(' Or update manually:');
594
+ console.log(' npm update -g ctxman\n');
595
+ }
563
596
  }
564
597
 
565
598
  export default Updater;