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.
- package/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- 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
|