neuro-cli 4.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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +510 -0
  3. package/dist/advisor/advisor.d.ts +50 -0
  4. package/dist/advisor/advisor.js +178 -0
  5. package/dist/agents/base.d.ts +62 -0
  6. package/dist/agents/base.js +215 -0
  7. package/dist/agents/orchestrator.d.ts +46 -0
  8. package/dist/agents/orchestrator.js +192 -0
  9. package/dist/agents/team.d.ts +51 -0
  10. package/dist/agents/team.js +210 -0
  11. package/dist/api/models.d.ts +23 -0
  12. package/dist/api/models.js +514 -0
  13. package/dist/api/ollama.d.ts +153 -0
  14. package/dist/api/ollama.js +751 -0
  15. package/dist/api/openrouter.d.ts +55 -0
  16. package/dist/api/openrouter.js +223 -0
  17. package/dist/commands/commands.d.ts +43 -0
  18. package/dist/commands/commands.js +308 -0
  19. package/dist/config/config.d.ts +8 -0
  20. package/dist/config/config.js +311 -0
  21. package/dist/context/compaction.d.ts +54 -0
  22. package/dist/context/compaction.js +251 -0
  23. package/dist/context/custom-agents.d.ts +107 -0
  24. package/dist/context/custom-agents.js +397 -0
  25. package/dist/context/custom-tools.d.ts +120 -0
  26. package/dist/context/custom-tools.js +564 -0
  27. package/dist/context/git-checkpoint.d.ts +52 -0
  28. package/dist/context/git-checkpoint.js +240 -0
  29. package/dist/context/neuro-md.d.ts +48 -0
  30. package/dist/context/neuro-md.js +202 -0
  31. package/dist/context/neuroignore.d.ts +102 -0
  32. package/dist/context/neuroignore.js +441 -0
  33. package/dist/context/repo-map.d.ts +38 -0
  34. package/dist/context/repo-map.js +220 -0
  35. package/dist/context/skill-standard.d.ts +262 -0
  36. package/dist/context/skill-standard.js +1156 -0
  37. package/dist/context/skill-system.d.ts +75 -0
  38. package/dist/context/skill-system.js +578 -0
  39. package/dist/context/tree-sitter.d.ts +99 -0
  40. package/dist/context/tree-sitter.js +1956 -0
  41. package/dist/core/acp.d.ts +325 -0
  42. package/dist/core/acp.js +1498 -0
  43. package/dist/core/api-server.d.ts +143 -0
  44. package/dist/core/api-server.js +550 -0
  45. package/dist/core/approval.d.ts +81 -0
  46. package/dist/core/approval.js +432 -0
  47. package/dist/core/auto-compact.d.ts +127 -0
  48. package/dist/core/auto-compact.js +436 -0
  49. package/dist/core/auto-mode.d.ts +232 -0
  50. package/dist/core/auto-mode.js +831 -0
  51. package/dist/core/background-session.d.ts +166 -0
  52. package/dist/core/background-session.js +696 -0
  53. package/dist/core/cicd.d.ts +257 -0
  54. package/dist/core/cicd.js +1443 -0
  55. package/dist/core/cloud-sync.d.ts +156 -0
  56. package/dist/core/cloud-sync.js +582 -0
  57. package/dist/core/code-review.d.ts +132 -0
  58. package/dist/core/code-review.js +1191 -0
  59. package/dist/core/completion.d.ts +49 -0
  60. package/dist/core/completion.js +384 -0
  61. package/dist/core/context.d.ts +38 -0
  62. package/dist/core/context.js +144 -0
  63. package/dist/core/diff-preview.d.ts +35 -0
  64. package/dist/core/diff-preview.js +173 -0
  65. package/dist/core/doom-loop.d.ts +51 -0
  66. package/dist/core/doom-loop.js +179 -0
  67. package/dist/core/engine.d.ts +183 -0
  68. package/dist/core/engine.js +942 -0
  69. package/dist/core/extended-thinking.d.ts +103 -0
  70. package/dist/core/extended-thinking.js +269 -0
  71. package/dist/core/fallback.d.ts +54 -0
  72. package/dist/core/fallback.js +104 -0
  73. package/dist/core/git-worktree.d.ts +139 -0
  74. package/dist/core/git-worktree.js +614 -0
  75. package/dist/core/headless.d.ts +30 -0
  76. package/dist/core/headless.js +101 -0
  77. package/dist/core/i18n.d.ts +83 -0
  78. package/dist/core/i18n.js +739 -0
  79. package/dist/core/linting.d.ts +129 -0
  80. package/dist/core/linting.js +699 -0
  81. package/dist/core/model-router.d.ts +109 -0
  82. package/dist/core/model-router.js +581 -0
  83. package/dist/core/multi-model.d.ts +243 -0
  84. package/dist/core/multi-model.js +1099 -0
  85. package/dist/core/multi-session.d.ts +144 -0
  86. package/dist/core/multi-session.js +442 -0
  87. package/dist/core/multimodal.d.ts +125 -0
  88. package/dist/core/multimodal.js +286 -0
  89. package/dist/core/observability.d.ts +93 -0
  90. package/dist/core/observability.js +737 -0
  91. package/dist/core/os-sandbox.d.ts +122 -0
  92. package/dist/core/os-sandbox.js +1193 -0
  93. package/dist/core/outcome-grading.d.ts +228 -0
  94. package/dist/core/outcome-grading.js +1123 -0
  95. package/dist/core/output-styles.d.ts +57 -0
  96. package/dist/core/output-styles.js +382 -0
  97. package/dist/core/parallel-agents.d.ts +183 -0
  98. package/dist/core/parallel-agents.js +563 -0
  99. package/dist/core/plugin-bundle.d.ts +236 -0
  100. package/dist/core/plugin-bundle.js +887 -0
  101. package/dist/core/plugin-sdk.d.ts +139 -0
  102. package/dist/core/plugin-sdk.js +273 -0
  103. package/dist/core/prompt-cache.d.ts +163 -0
  104. package/dist/core/prompt-cache.js +599 -0
  105. package/dist/core/sandbox.d.ts +127 -0
  106. package/dist/core/sandbox.js +369 -0
  107. package/dist/core/scheduled-tasks.d.ts +151 -0
  108. package/dist/core/scheduled-tasks.js +502 -0
  109. package/dist/core/security-scanner.d.ts +160 -0
  110. package/dist/core/security-scanner.js +1494 -0
  111. package/dist/core/session.d.ts +83 -0
  112. package/dist/core/session.js +269 -0
  113. package/dist/core/shell-completion.d.ts +51 -0
  114. package/dist/core/shell-completion.js +674 -0
  115. package/dist/core/smart-monitor.d.ts +146 -0
  116. package/dist/core/smart-monitor.js +1199 -0
  117. package/dist/core/spec-driven.d.ts +233 -0
  118. package/dist/core/spec-driven.js +1485 -0
  119. package/dist/core/spending-warnings.d.ts +123 -0
  120. package/dist/core/spending-warnings.js +456 -0
  121. package/dist/core/sub-agent.d.ts +298 -0
  122. package/dist/core/sub-agent.js +1023 -0
  123. package/dist/core/telemetry.d.ts +157 -0
  124. package/dist/core/telemetry.js +412 -0
  125. package/dist/core/terminal-ux.d.ts +134 -0
  126. package/dist/core/terminal-ux.js +649 -0
  127. package/dist/core/testing.d.ts +146 -0
  128. package/dist/core/testing.js +1200 -0
  129. package/dist/core/types.d.ts +439 -0
  130. package/dist/core/types.js +6 -0
  131. package/dist/core/undo-redo.d.ts +112 -0
  132. package/dist/core/undo-redo.js +290 -0
  133. package/dist/core/updater.d.ts +159 -0
  134. package/dist/core/updater.js +608 -0
  135. package/dist/core/vim-mode.d.ts +151 -0
  136. package/dist/core/vim-mode.js +771 -0
  137. package/dist/core/voice.d.ts +137 -0
  138. package/dist/core/voice.js +538 -0
  139. package/dist/core/web-dashboard.d.ts +109 -0
  140. package/dist/core/web-dashboard.js +484 -0
  141. package/dist/hooks/hooks.d.ts +74 -0
  142. package/dist/hooks/hooks.js +160 -0
  143. package/dist/hooks/llm-evaluator.d.ts +165 -0
  144. package/dist/hooks/llm-evaluator.js +560 -0
  145. package/dist/index.d.ts +3 -0
  146. package/dist/index.js +1186 -0
  147. package/dist/lsp/lsp-manager.d.ts +63 -0
  148. package/dist/lsp/lsp-manager.js +351 -0
  149. package/dist/mcp/client.d.ts +133 -0
  150. package/dist/mcp/client.js +684 -0
  151. package/dist/mcp/mcp-apps.d.ts +70 -0
  152. package/dist/mcp/mcp-apps.js +1007 -0
  153. package/dist/tools/bash.d.ts +5 -0
  154. package/dist/tools/bash.js +195 -0
  155. package/dist/tools/browser.d.ts +92 -0
  156. package/dist/tools/browser.js +1570 -0
  157. package/dist/tools/extended.d.ts +6 -0
  158. package/dist/tools/extended.js +191 -0
  159. package/dist/tools/file.d.ts +10 -0
  160. package/dist/tools/file.js +382 -0
  161. package/dist/tools/github.d.ts +389 -0
  162. package/dist/tools/github.js +759 -0
  163. package/dist/tools/index.d.ts +9 -0
  164. package/dist/tools/index.js +40 -0
  165. package/dist/tools/memory.d.ts +6 -0
  166. package/dist/tools/memory.js +197 -0
  167. package/dist/tools/registry.d.ts +29 -0
  168. package/dist/tools/registry.js +64 -0
  169. package/dist/tools/web.d.ts +6 -0
  170. package/dist/tools/web.js +150 -0
  171. package/dist/ui/renderer.d.ts +97 -0
  172. package/dist/ui/renderer.js +279 -0
  173. package/dist/ui/theme.d.ts +27 -0
  174. package/dist/ui/theme.js +106 -0
  175. package/package.json +83 -0
@@ -0,0 +1,608 @@
1
+ // ============================================================
2
+ // NeuroCLI - Auto-Updater Module
3
+ // Remote update checking, self-updating, and changelog display
4
+ // v4.1.0
5
+ // ============================================================
6
+ import { execSync } from 'child_process';
7
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { homedir } from 'os';
10
+ import chalk from 'chalk';
11
+ function parseSemver(version) {
12
+ const cleaned = version.replace(/^v/, '');
13
+ const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
14
+ if (!match)
15
+ return null;
16
+ return {
17
+ major: parseInt(match[1], 10),
18
+ minor: parseInt(match[2], 10),
19
+ patch: parseInt(match[3], 10),
20
+ prerelease: match[4],
21
+ };
22
+ }
23
+ function compareSemver(a, b) {
24
+ const pa = parseSemver(a);
25
+ const pb = parseSemver(b);
26
+ if (!pa || !pb)
27
+ return 0;
28
+ if (pa.major !== pb.major)
29
+ return pa.major - pb.major;
30
+ if (pa.minor !== pb.minor)
31
+ return pa.minor - pb.minor;
32
+ if (pa.patch !== pb.patch)
33
+ return pa.patch - pb.patch;
34
+ // No prerelease > prerelease
35
+ if (!pa.prerelease && pb.prerelease)
36
+ return 1;
37
+ if (pa.prerelease && !pb.prerelease)
38
+ return -1;
39
+ if (pa.prerelease && pb.prerelease) {
40
+ return pa.prerelease.localeCompare(pb.prerelease);
41
+ }
42
+ return 0;
43
+ }
44
+ function isBreakingChange(current, latest) {
45
+ const pc = parseSemver(current);
46
+ const pl = parseSemver(latest);
47
+ if (!pc || !pl)
48
+ return false;
49
+ return pl.major > pc.major;
50
+ }
51
+ function getUpdateType(current, latest) {
52
+ const pc = parseSemver(current);
53
+ const pl = parseSemver(latest);
54
+ if (!pc || !pl)
55
+ return 'none';
56
+ if (pl.major > pc.major)
57
+ return 'major';
58
+ if (pl.minor > pc.minor)
59
+ return 'minor';
60
+ if (pl.patch > pc.patch)
61
+ return 'patch';
62
+ return 'none';
63
+ }
64
+ // ---- HTTP Helper ----
65
+ async function httpGet(url, timeout = 10000) {
66
+ const isHttps = url.startsWith('https');
67
+ const lib = isHttps ? await import('https') : await import('http');
68
+ return new Promise((resolve, reject) => {
69
+ const req = lib.get(url, { timeout }, (res) => {
70
+ let data = '';
71
+ res.on('data', (chunk) => { data += chunk.toString(); });
72
+ res.on('end', () => resolve(data));
73
+ });
74
+ req.on('error', reject);
75
+ req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout')); });
76
+ });
77
+ }
78
+ // ---- Auto-Updater Class ----
79
+ export class AutoUpdater {
80
+ config;
81
+ state;
82
+ stateFile;
83
+ lastCheckResult = null;
84
+ constructor(config) {
85
+ const defaultStateDir = join(homedir(), '.neuro');
86
+ this.config = {
87
+ packageName: 'neuro-cli',
88
+ githubRepo: 'muhammedturan65/neuro-cli',
89
+ checkInterval: 24 * 60 * 60 * 1000, // 24 hours
90
+ stateDir: defaultStateDir,
91
+ autoCheck: true,
92
+ autoUpdate: false,
93
+ registryUrl: 'https://registry.npmjs.org',
94
+ showChangelog: true,
95
+ includePrerelease: false,
96
+ ...config,
97
+ };
98
+ this.stateFile = join(this.config.stateDir, 'update-state.json');
99
+ this.state = this.loadState();
100
+ }
101
+ // ---- State Management ----
102
+ loadState() {
103
+ try {
104
+ if (existsSync(this.stateFile)) {
105
+ const data = JSON.parse(readFileSync(this.stateFile, 'utf-8'));
106
+ return {
107
+ lastCheckTime: data.lastCheckTime || 0,
108
+ lastKnownVersion: data.lastKnownVersion || this.config.currentVersion,
109
+ lastCheckSource: data.lastCheckSource || 'cache',
110
+ dismissedVersions: data.dismissedVersions || [],
111
+ };
112
+ }
113
+ }
114
+ catch {
115
+ // Corrupted state, start fresh
116
+ }
117
+ return {
118
+ lastCheckTime: 0,
119
+ lastKnownVersion: this.config.currentVersion,
120
+ lastCheckSource: 'cache',
121
+ dismissedVersions: [],
122
+ };
123
+ }
124
+ saveState() {
125
+ try {
126
+ if (!existsSync(this.config.stateDir)) {
127
+ mkdirSync(this.config.stateDir, { recursive: true });
128
+ }
129
+ writeFileSync(this.stateFile, JSON.stringify(this.state, null, 2), 'utf-8');
130
+ }
131
+ catch {
132
+ // Silently fail — update state is non-critical
133
+ }
134
+ }
135
+ // ---- Update Check ----
136
+ /**
137
+ * Check if enough time has passed since last check
138
+ */
139
+ shouldCheck() {
140
+ if (!this.config.autoCheck)
141
+ return false;
142
+ const elapsed = Date.now() - this.state.lastCheckTime;
143
+ return elapsed >= this.config.checkInterval;
144
+ }
145
+ /**
146
+ * Get time until next check is due
147
+ */
148
+ timeUntilNextCheck() {
149
+ const elapsed = Date.now() - this.state.lastCheckTime;
150
+ return Math.max(0, this.config.checkInterval - elapsed);
151
+ }
152
+ /**
153
+ * Check for updates from npm registry
154
+ */
155
+ async checkForUpdate(force = false) {
156
+ // Return cached result if checked recently and not forced
157
+ if (!force && this.lastCheckResult && (Date.now() - this.lastCheckResult.checkedAt) < 60000) {
158
+ return this.lastCheckResult;
159
+ }
160
+ // Try npm first
161
+ let result = await this.checkNpmRegistry();
162
+ if (!result) {
163
+ // Fall back to GitHub
164
+ result = await this.checkGitHubReleases();
165
+ }
166
+ if (!result) {
167
+ // Fall back to npm view command
168
+ result = await this.checkNpmView();
169
+ }
170
+ if (result) {
171
+ this.lastCheckResult = result;
172
+ this.state.lastCheckTime = Date.now();
173
+ this.state.lastKnownVersion = result.latestVersion;
174
+ this.state.lastCheckSource = result.source;
175
+ this.saveState();
176
+ return result;
177
+ }
178
+ // All checks failed — return "no update" with current version
179
+ const fallback = {
180
+ hasUpdate: false,
181
+ currentVersion: this.config.currentVersion,
182
+ latestVersion: this.config.currentVersion,
183
+ source: 'cache',
184
+ checkedAt: Date.now(),
185
+ };
186
+ this.lastCheckResult = fallback;
187
+ return fallback;
188
+ }
189
+ /**
190
+ * Check npm registry API directly
191
+ */
192
+ async checkNpmRegistry() {
193
+ try {
194
+ const url = `${this.config.registryUrl}/${this.config.packageName}/latest`;
195
+ const data = await httpGet(url, 8000);
196
+ const pkg = JSON.parse(data);
197
+ const latestVersion = pkg.version;
198
+ if (!latestVersion)
199
+ return null;
200
+ const hasUpdate = compareSemver(this.config.currentVersion, latestVersion) < 0;
201
+ return {
202
+ hasUpdate,
203
+ currentVersion: this.config.currentVersion,
204
+ latestVersion,
205
+ source: 'npm',
206
+ changelog: hasUpdate ? this.extractChangelog(pkg) : undefined,
207
+ checkedAt: Date.now(),
208
+ };
209
+ }
210
+ catch {
211
+ return null;
212
+ }
213
+ }
214
+ /**
215
+ * Check GitHub releases API
216
+ */
217
+ async checkGitHubReleases() {
218
+ try {
219
+ const url = `https://api.github.com/repos/${this.config.githubRepo}/releases/latest`;
220
+ const data = await httpGet(url, 8000);
221
+ const release = JSON.parse(data);
222
+ const tagName = release.tag_name;
223
+ if (!tagName)
224
+ return null;
225
+ const latestVersion = tagName.replace(/^v/, '');
226
+ const hasUpdate = compareSemver(this.config.currentVersion, latestVersion) < 0;
227
+ return {
228
+ hasUpdate,
229
+ currentVersion: this.config.currentVersion,
230
+ latestVersion,
231
+ source: 'github',
232
+ changelog: hasUpdate ? release.body || undefined : undefined,
233
+ checkedAt: Date.now(),
234
+ };
235
+ }
236
+ catch {
237
+ return null;
238
+ }
239
+ }
240
+ /**
241
+ * Check using `npm view` command (fallback)
242
+ */
243
+ async checkNpmView() {
244
+ try {
245
+ const output = execSync(`npm view ${this.config.packageName} version --json 2>/dev/null`, { encoding: 'utf-8', timeout: 15000 }).trim();
246
+ const latestVersion = output.replace(/^"|"$/g, '').replace(/\n/g, '');
247
+ if (!latestVersion || !parseSemver(latestVersion))
248
+ return null;
249
+ const hasUpdate = compareSemver(this.config.currentVersion, latestVersion) < 0;
250
+ return {
251
+ hasUpdate,
252
+ currentVersion: this.config.currentVersion,
253
+ latestVersion,
254
+ source: 'npm',
255
+ checkedAt: Date.now(),
256
+ };
257
+ }
258
+ catch {
259
+ return null;
260
+ }
261
+ }
262
+ /**
263
+ * Extract changelog from npm package data
264
+ */
265
+ extractChangelog(pkg) {
266
+ // Some packages include changelog in description or separate field
267
+ if (pkg.changelog)
268
+ return pkg.changelog;
269
+ // Build a simple changelog from available info
270
+ const lines = [];
271
+ if (pkg.description)
272
+ lines.push(pkg.description);
273
+ if (pkg.homepage)
274
+ lines.push(`Homepage: ${pkg.homepage}`);
275
+ if (pkg.repository?.url)
276
+ lines.push(`Repository: ${pkg.repository.url}`);
277
+ return lines.length > 0 ? lines.join('\n') : undefined;
278
+ }
279
+ // ---- Update Execution ----
280
+ /**
281
+ * Perform the self-update
282
+ * Returns true if update was successful
283
+ */
284
+ async performUpdate(version) {
285
+ const targetVersion = version || this.lastCheckResult?.latestVersion;
286
+ if (!targetVersion) {
287
+ // Check first
288
+ const check = await this.checkForUpdate(true);
289
+ if (!check.hasUpdate) {
290
+ return { success: false, message: 'Already on the latest version' };
291
+ }
292
+ }
293
+ const updateType = getUpdateType(this.config.currentVersion, targetVersion || this.config.currentVersion);
294
+ const isBreaking = isBreakingChange(this.config.currentVersion, targetVersion || this.config.currentVersion);
295
+ // Warn about breaking changes
296
+ if (isBreaking) {
297
+ console.log(chalk.yellow('\n ⚠ This is a MAJOR version update with potential breaking changes!'));
298
+ console.log(chalk.yellow(` ${this.config.currentVersion} → ${targetVersion}`));
299
+ console.log(chalk.gray(' Review the changelog before proceeding.\n'));
300
+ }
301
+ try {
302
+ const command = this.config.updateCommand || `npm update -g ${this.config.packageName}`;
303
+ console.log(chalk.cyan(` Updating ${this.config.packageName}...`));
304
+ console.log(chalk.gray(` Running: ${command}`));
305
+ const output = execSync(command, {
306
+ encoding: 'utf-8',
307
+ timeout: 120000, // 2 minutes
308
+ stdio: ['pipe', 'pipe', 'pipe'],
309
+ });
310
+ // Verify the update
311
+ const verifyResult = await this.verifyUpdate(targetVersion);
312
+ if (verifyResult.success) {
313
+ this.state.dismissedVersions = this.state.dismissedVersions.filter(v => v !== targetVersion);
314
+ this.saveState();
315
+ return {
316
+ success: true,
317
+ message: `Successfully updated to v${verifyResult.newVersion || targetVersion}`,
318
+ newVersion: verifyResult.newVersion || targetVersion,
319
+ };
320
+ }
321
+ return verifyResult;
322
+ }
323
+ catch (error) {
324
+ const errMsg = error instanceof Error ? error.message : String(error);
325
+ // Suggest alternative methods
326
+ console.log(chalk.red('\n Automatic update failed. Try one of these alternatives:'));
327
+ console.log(chalk.cyan(` npm install -g ${this.config.packageName}@latest`));
328
+ console.log(chalk.cyan(` yarn global add ${this.config.packageName}`));
329
+ console.log(chalk.cyan(` pnpm add -g ${this.config.packageName}`));
330
+ console.log();
331
+ return { success: false, message: `Update failed: ${errMsg}` };
332
+ }
333
+ }
334
+ /**
335
+ * Verify that the update was successful
336
+ */
337
+ async verifyUpdate(expectedVersion) {
338
+ try {
339
+ // Try to get the currently installed version
340
+ const installedVersion = execSync(`npm list -g ${this.config.packageName} --json 2>/dev/null`, { encoding: 'utf-8', timeout: 10000 });
341
+ const parsed = JSON.parse(installedVersion);
342
+ const deps = parsed.dependencies || {};
343
+ const pkg = deps[this.config.packageName];
344
+ if (pkg && pkg.version) {
345
+ const newVersion = pkg.version;
346
+ const isExpected = !expectedVersion || newVersion === expectedVersion;
347
+ if (isExpected || compareSemver(this.config.currentVersion, newVersion) >= 0) {
348
+ return {
349
+ success: true,
350
+ message: `Verified: v${newVersion} installed`,
351
+ newVersion,
352
+ };
353
+ }
354
+ }
355
+ }
356
+ catch {
357
+ // npm list failed, that's ok
358
+ }
359
+ // If we can't verify but the command succeeded, assume success
360
+ return {
361
+ success: true,
362
+ message: 'Update completed (could not verify version)',
363
+ newVersion: expectedVersion,
364
+ };
365
+ }
366
+ // ---- Dismiss / Skip ----
367
+ /**
368
+ * Dismiss a specific version (don't notify again)
369
+ */
370
+ dismissVersion(version) {
371
+ if (!this.state.dismissedVersions.includes(version)) {
372
+ this.state.dismissedVersions.push(version);
373
+ this.saveState();
374
+ }
375
+ }
376
+ /**
377
+ * Check if a version has been dismissed
378
+ */
379
+ isDismissed(version) {
380
+ return this.state.dismissedVersions.includes(version);
381
+ }
382
+ // ---- Display ----
383
+ /**
384
+ * Display update notification banner
385
+ */
386
+ showUpdateNotification(result) {
387
+ if (!result.hasUpdate)
388
+ return;
389
+ if (this.isDismissed(result.latestVersion))
390
+ return;
391
+ const updateType = getUpdateType(result.currentVersion, result.latestVersion);
392
+ const isBreaking = updateType === 'major';
393
+ const typeColors = {
394
+ major: chalk.red.bold,
395
+ minor: chalk.yellow.bold,
396
+ patch: chalk.green.bold,
397
+ };
398
+ const typeLabels = {
399
+ major: 'BREAKING',
400
+ minor: 'FEATURE',
401
+ patch: 'FIX',
402
+ };
403
+ const colorFn = typeColors[updateType] || chalk.cyan.bold;
404
+ const label = typeLabels[updateType] || 'UPDATE';
405
+ const lineWidth = 56;
406
+ const topBorder = '┌' + '─'.repeat(lineWidth) + '┐';
407
+ const bottomBorder = '└' + '─'.repeat(lineWidth) + '┘';
408
+ const sideBorder = '│';
409
+ console.log();
410
+ console.log(chalk.cyan(topBorder));
411
+ const titleLine = ` ${colorFn('⬆')} Update Available: ${label}`;
412
+ console.log(chalk.cyan(sideBorder) + titleLine.padEnd(lineWidth + 1) + chalk.cyan(sideBorder));
413
+ const versionLine = ` ${chalk.gray(result.currentVersion)} → ${chalk.bold.green(result.latestVersion)}`;
414
+ console.log(chalk.cyan(sideBorder) + versionLine.padEnd(lineWidth + 1) + chalk.cyan(sideBorder));
415
+ console.log(chalk.cyan(sideBorder) + ' '.repeat(lineWidth + 1) + chalk.cyan(sideBorder));
416
+ const cmdLine = ` Run ${chalk.cyan('/update')} to update now`;
417
+ console.log(chalk.cyan(sideBorder) + cmdLine.padEnd(lineWidth + 1) + chalk.cyan(sideBorder));
418
+ const dismissLine = ` Run ${chalk.gray('/update dismiss')} to skip this version`;
419
+ console.log(chalk.cyan(sideBorder) + dismissLine.padEnd(lineWidth + 1) + chalk.cyan(sideBorder));
420
+ console.log(chalk.cyan(bottomBorder));
421
+ console.log();
422
+ }
423
+ /**
424
+ * Display detailed update info with changelog
425
+ */
426
+ showUpdateDetails(result) {
427
+ const updateType = getUpdateType(result.currentVersion, result.latestVersion);
428
+ const typeLabels = {
429
+ major: '🔴 MAJOR (Breaking Changes)',
430
+ minor: '🟡 MINOR (New Features)',
431
+ patch: '🟢 PATCH (Bug Fixes)',
432
+ none: '✅ Up to date',
433
+ };
434
+ console.log(chalk.bold('\n NeuroCLI Update Check\n'));
435
+ console.log(` Current Version: ${chalk.cyan('v' + result.currentVersion)}`);
436
+ console.log(` Latest Version: ${chalk.green('v' + result.latestVersion)}`);
437
+ console.log(` Update Type: ${typeLabels[updateType] || updateType}`);
438
+ console.log(` Check Source: ${chalk.gray(result.source)}`);
439
+ console.log(` Checked At: ${chalk.gray(new Date(result.checkedAt).toLocaleString())}`);
440
+ if (result.hasUpdate) {
441
+ console.log();
442
+ console.log(chalk.bold(' Changelog:'));
443
+ if (result.changelog) {
444
+ // Display changelog with word wrapping
445
+ const lines = result.changelog.split('\n');
446
+ for (const line of lines.slice(0, 50)) { // Limit to 50 lines
447
+ console.log(` ${chalk.gray(line)}`);
448
+ }
449
+ if (lines.length > 50) {
450
+ console.log(chalk.gray(` ... and ${lines.length - 50} more lines`));
451
+ }
452
+ }
453
+ else {
454
+ console.log(chalk.gray(' No changelog available for this release.'));
455
+ console.log(chalk.gray(` View at: https://github.com/${this.config.githubRepo}/releases`));
456
+ }
457
+ }
458
+ console.log();
459
+ }
460
+ /**
461
+ * Show "up to date" message
462
+ */
463
+ showUpToDate() {
464
+ console.log(chalk.green('\n ✓ NeuroCLI is up to date!'));
465
+ console.log(chalk.gray(` Current version: v${this.config.currentVersion}`));
466
+ console.log(chalk.gray(` Last checked: ${new Date(this.state.lastCheckTime).toLocaleString()}`));
467
+ const nextCheck = this.timeUntilNextCheck();
468
+ if (nextCheck > 0) {
469
+ const hours = Math.floor(nextCheck / (60 * 60 * 1000));
470
+ const minutes = Math.floor((nextCheck % (60 * 60 * 1000)) / (60 * 1000));
471
+ console.log(chalk.gray(` Next check in: ${hours}h ${minutes}m`));
472
+ }
473
+ console.log();
474
+ }
475
+ /**
476
+ * Format time duration in human-readable format
477
+ */
478
+ formatDuration(ms) {
479
+ const hours = Math.floor(ms / (60 * 60 * 1000));
480
+ const minutes = Math.floor((ms % (60 * 60 * 1000)) / (60 * 1000));
481
+ if (hours > 0)
482
+ return `${hours}h ${minutes}m`;
483
+ if (minutes > 0)
484
+ return `${minutes}m`;
485
+ return `${Math.floor(ms / 1000)}s`;
486
+ }
487
+ // ---- Startup Integration ----
488
+ /**
489
+ * Run background update check on startup.
490
+ * Returns the check result if an update is available, null otherwise.
491
+ */
492
+ async checkOnStartup() {
493
+ if (!this.config.autoCheck)
494
+ return null;
495
+ if (!this.shouldCheck()) {
496
+ // Return cached result if it indicated an update
497
+ if (this.lastCheckResult?.hasUpdate && !this.isDismissed(this.lastCheckResult.latestVersion)) {
498
+ return this.lastCheckResult;
499
+ }
500
+ return null;
501
+ }
502
+ try {
503
+ const result = await this.checkForUpdate(true);
504
+ if (result.hasUpdate && !this.isDismissed(result.latestVersion)) {
505
+ return result;
506
+ }
507
+ return null;
508
+ }
509
+ catch {
510
+ // Silently fail — don't block startup
511
+ return null;
512
+ }
513
+ }
514
+ /**
515
+ * Interactive update flow — check, show, and optionally update
516
+ */
517
+ async interactiveUpdate() {
518
+ console.log(chalk.cyan('\n Checking for updates...'));
519
+ try {
520
+ const result = await this.checkForUpdate(true);
521
+ if (!result.hasUpdate) {
522
+ this.showUpToDate();
523
+ return;
524
+ }
525
+ // Show update details
526
+ this.showUpdateDetails(result);
527
+ this.showUpdateNotification(result);
528
+ // Auto-update if configured
529
+ if (this.config.autoUpdate) {
530
+ const updateResult = await this.performUpdate(result.latestVersion);
531
+ if (updateResult.success) {
532
+ console.log(chalk.green(`\n ✓ ${updateResult.message}`));
533
+ console.log(chalk.yellow('\n Please restart NeuroCLI to use the new version.\n'));
534
+ }
535
+ else {
536
+ console.log(chalk.red(`\n ✗ ${updateResult.message}\n`));
537
+ }
538
+ return;
539
+ }
540
+ // Manual mode — just show notification
541
+ console.log(chalk.cyan(' To update now, run: /update now'));
542
+ console.log(chalk.gray(' To skip this version: /update dismiss'));
543
+ console.log(chalk.gray(' To check again later: /update check'));
544
+ console.log();
545
+ }
546
+ catch (error) {
547
+ console.log(chalk.red(' Could not check for updates.'));
548
+ console.log(chalk.gray(` ${error instanceof Error ? error.message : String(error)}`));
549
+ console.log(chalk.gray(' Check your internet connection and try again later.'));
550
+ console.log();
551
+ }
552
+ }
553
+ // ---- Config ----
554
+ /**
555
+ * Set auto-check on/off
556
+ */
557
+ setAutoCheck(enabled) {
558
+ this.config.autoCheck = enabled;
559
+ }
560
+ /**
561
+ * Set auto-update on/off
562
+ */
563
+ setAutoUpdate(enabled) {
564
+ this.config.autoUpdate = enabled;
565
+ }
566
+ /**
567
+ * Set check interval in hours
568
+ */
569
+ setCheckInterval(hours) {
570
+ this.config.checkInterval = hours * 60 * 60 * 1000;
571
+ }
572
+ /**
573
+ * Get current updater config
574
+ */
575
+ getConfig() {
576
+ return { ...this.config };
577
+ }
578
+ /**
579
+ * Get last check result
580
+ */
581
+ getLastCheck() {
582
+ return this.lastCheckResult;
583
+ }
584
+ /**
585
+ * Reset dismissed versions
586
+ */
587
+ resetDismissed() {
588
+ this.state.dismissedVersions = [];
589
+ this.saveState();
590
+ }
591
+ /**
592
+ * Force next check on startup
593
+ */
594
+ forceNextCheck() {
595
+ this.state.lastCheckTime = 0;
596
+ this.saveState();
597
+ }
598
+ }
599
+ // ---- Standalone check function (for CLI subcommand) ----
600
+ export async function checkForUpdates(currentVersion) {
601
+ const updater = new AutoUpdater({ currentVersion });
602
+ return updater.checkForUpdate(true);
603
+ }
604
+ export async function performSelfUpdate(currentVersion, version) {
605
+ const updater = new AutoUpdater({ currentVersion });
606
+ return updater.performUpdate(version);
607
+ }
608
+ //# sourceMappingURL=updater.js.map