helixevo 0.2.10 → 0.2.12
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/dashboard/app/api/upgrade/route.ts +37 -0
- package/dashboard/components/update-banner.tsx +163 -59
- package/dist/cli.js +40 -0
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
export async function POST() {
|
|
7
|
+
try {
|
|
8
|
+
// Run the upgrade command
|
|
9
|
+
const output = execSync('npm install -g helixevo@latest 2>&1', {
|
|
10
|
+
encoding: 'utf-8',
|
|
11
|
+
timeout: 120000, // 2 minute timeout
|
|
12
|
+
env: { ...process.env },
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// Get the new version
|
|
16
|
+
let newVersion = ''
|
|
17
|
+
try {
|
|
18
|
+
newVersion = execSync('helixevo --version 2>/dev/null', { encoding: 'utf-8' }).trim()
|
|
19
|
+
} catch {
|
|
20
|
+
// Try parsing from output
|
|
21
|
+
const match = output.match(/helixevo@(\d+\.\d+\.\d+)/)
|
|
22
|
+
newVersion = match?.[1] ?? 'unknown'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return NextResponse.json({
|
|
26
|
+
success: true,
|
|
27
|
+
version: newVersion,
|
|
28
|
+
output: output.slice(-500), // last 500 chars
|
|
29
|
+
})
|
|
30
|
+
} catch (err: unknown) {
|
|
31
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
32
|
+
return NextResponse.json({
|
|
33
|
+
success: false,
|
|
34
|
+
error: message.slice(-500),
|
|
35
|
+
}, { status: 500 })
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -15,10 +15,14 @@ function compareVersions(current: string, latest: string): boolean {
|
|
|
15
15
|
return false
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
type UpdateState = 'idle' | 'updating' | 'success' | 'error'
|
|
19
|
+
|
|
18
20
|
export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
19
21
|
const [latestVersion, setLatestVersion] = useState<string | null>(null)
|
|
20
22
|
const [dismissed, setDismissed] = useState(false)
|
|
21
|
-
const [
|
|
23
|
+
const [state, setState] = useState<UpdateState>('idle')
|
|
24
|
+
const [newVersion, setNewVersion] = useState<string | null>(null)
|
|
25
|
+
const [errorMsg, setErrorMsg] = useState<string | null>(null)
|
|
22
26
|
|
|
23
27
|
useEffect(() => {
|
|
24
28
|
let mounted = true
|
|
@@ -41,14 +45,29 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
41
45
|
|
|
42
46
|
if (!latestVersion || dismissed) return null
|
|
43
47
|
|
|
44
|
-
const
|
|
48
|
+
const handleUpdate = async () => {
|
|
49
|
+
setState('updating')
|
|
50
|
+
setErrorMsg(null)
|
|
45
51
|
|
|
46
|
-
const handleCopy = async () => {
|
|
47
52
|
try {
|
|
48
|
-
await
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
const res = await fetch('/api/upgrade', { method: 'POST' })
|
|
54
|
+
const data = await res.json()
|
|
55
|
+
|
|
56
|
+
if (data.success) {
|
|
57
|
+
setState('success')
|
|
58
|
+
setNewVersion(data.version)
|
|
59
|
+
// Reload after a brief delay to show success state
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
window.location.reload()
|
|
62
|
+
}, 2000)
|
|
63
|
+
} else {
|
|
64
|
+
setState('error')
|
|
65
|
+
setErrorMsg(data.error ?? 'Update failed')
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
setState('error')
|
|
69
|
+
setErrorMsg('Network error — try running: npm install -g helixevo@latest')
|
|
70
|
+
}
|
|
52
71
|
}
|
|
53
72
|
|
|
54
73
|
return (
|
|
@@ -58,7 +77,7 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
58
77
|
right: 20,
|
|
59
78
|
width: 320,
|
|
60
79
|
background: 'var(--bg-card)',
|
|
61
|
-
border:
|
|
80
|
+
border: `1px solid ${state === 'success' ? 'var(--green-border)' : state === 'error' ? 'var(--red-border)' : 'var(--purple-border)'}`,
|
|
62
81
|
borderRadius: 'var(--radius-lg)',
|
|
63
82
|
boxShadow: 'var(--shadow-xl)',
|
|
64
83
|
padding: '16px 18px',
|
|
@@ -66,83 +85,168 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
|
|
|
66
85
|
animation: 'updateSlideIn 0.4s ease-out',
|
|
67
86
|
}}>
|
|
68
87
|
{/* Close button */}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
{state !== 'updating' && (
|
|
89
|
+
<button
|
|
90
|
+
onClick={() => setDismissed(true)}
|
|
91
|
+
style={{
|
|
92
|
+
position: 'absolute', top: 8, right: 10,
|
|
93
|
+
background: 'none', border: 'none', cursor: 'pointer',
|
|
94
|
+
color: 'var(--text-dim)', fontSize: 18, lineHeight: 1,
|
|
95
|
+
padding: '2px 4px',
|
|
96
|
+
}}
|
|
97
|
+
aria-label="Dismiss"
|
|
98
|
+
>
|
|
99
|
+
×
|
|
100
|
+
</button>
|
|
101
|
+
)}
|
|
81
102
|
|
|
82
103
|
{/* Header */}
|
|
83
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom:
|
|
104
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
|
|
84
105
|
<div style={{
|
|
85
106
|
width: 28, height: 28, borderRadius: '50%',
|
|
86
|
-
background: 'var(--
|
|
107
|
+
background: state === 'success' ? 'var(--green-light)'
|
|
108
|
+
: state === 'error' ? 'var(--red-light)'
|
|
109
|
+
: 'var(--purple-light)',
|
|
110
|
+
display: 'flex',
|
|
87
111
|
alignItems: 'center', justifyContent: 'center',
|
|
88
112
|
}}>
|
|
89
|
-
|
|
90
|
-
<
|
|
91
|
-
|
|
113
|
+
{state === 'updating' ? (
|
|
114
|
+
<div style={{
|
|
115
|
+
width: 14, height: 14, border: '2px solid var(--purple-border)',
|
|
116
|
+
borderTopColor: 'var(--purple)', borderRadius: '50%',
|
|
117
|
+
animation: 'updateSpin 0.8s linear infinite',
|
|
118
|
+
}} />
|
|
119
|
+
) : state === 'success' ? (
|
|
120
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--green)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
121
|
+
<path d="M20 6L9 17l-5-5" />
|
|
122
|
+
</svg>
|
|
123
|
+
) : state === 'error' ? (
|
|
124
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--red)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
125
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
126
|
+
</svg>
|
|
127
|
+
) : (
|
|
128
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--purple)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
129
|
+
<path d="M12 19V5m-7 7l7-7 7 7" />
|
|
130
|
+
</svg>
|
|
131
|
+
)}
|
|
92
132
|
</div>
|
|
93
133
|
<div>
|
|
94
134
|
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)', letterSpacing: -0.2 }}>
|
|
95
|
-
|
|
135
|
+
{state === 'updating' ? 'Updating...'
|
|
136
|
+
: state === 'success' ? 'Updated!'
|
|
137
|
+
: state === 'error' ? 'Update Failed'
|
|
138
|
+
: 'Update Available'}
|
|
96
139
|
</div>
|
|
97
140
|
<div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
|
|
98
|
-
|
|
141
|
+
{state === 'success'
|
|
142
|
+
? <>Now on <span style={{ color: 'var(--green)', fontWeight: 600 }}>v{newVersion}</span> — restarting...</>
|
|
143
|
+
: <>v{currentVersion} → <span style={{ color: 'var(--green)', fontWeight: 600 }}>v{latestVersion}</span></>
|
|
144
|
+
}
|
|
99
145
|
</div>
|
|
100
146
|
</div>
|
|
101
147
|
</div>
|
|
102
148
|
|
|
103
|
-
{/*
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
149
|
+
{/* Action area */}
|
|
150
|
+
{state === 'idle' && (
|
|
151
|
+
<button
|
|
152
|
+
onClick={handleUpdate}
|
|
153
|
+
style={{
|
|
154
|
+
width: '100%',
|
|
155
|
+
padding: '9px 16px',
|
|
156
|
+
background: 'var(--purple)',
|
|
157
|
+
color: '#fff',
|
|
158
|
+
border: 'none',
|
|
159
|
+
borderRadius: 'var(--radius)',
|
|
160
|
+
fontSize: 13,
|
|
161
|
+
fontWeight: 600,
|
|
162
|
+
cursor: 'pointer',
|
|
163
|
+
display: 'flex',
|
|
164
|
+
alignItems: 'center',
|
|
165
|
+
justifyContent: 'center',
|
|
166
|
+
gap: 6,
|
|
167
|
+
transition: 'opacity 0.15s',
|
|
168
|
+
}}
|
|
169
|
+
onMouseOver={e => (e.currentTarget.style.opacity = '0.9')}
|
|
170
|
+
onMouseOut={e => (e.currentTarget.style.opacity = '1')}
|
|
171
|
+
>
|
|
172
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
173
|
+
<path d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9" />
|
|
174
|
+
</svg>
|
|
175
|
+
Update Now
|
|
176
|
+
</button>
|
|
177
|
+
)}
|
|
178
|
+
|
|
179
|
+
{state === 'updating' && (
|
|
180
|
+
<div style={{
|
|
181
|
+
width: '100%',
|
|
182
|
+
padding: '9px 16px',
|
|
107
183
|
background: 'var(--bg-section)',
|
|
108
|
-
border: '1px solid var(--border)',
|
|
109
184
|
borderRadius: 'var(--radius)',
|
|
110
|
-
|
|
111
|
-
fontFamily: 'var(--font-mono)',
|
|
112
|
-
fontSize: 11,
|
|
185
|
+
fontSize: 12,
|
|
113
186
|
color: 'var(--text-secondary)',
|
|
114
|
-
|
|
115
|
-
display: 'flex',
|
|
116
|
-
alignItems: 'center',
|
|
117
|
-
justifyContent: 'space-between',
|
|
118
|
-
gap: 8,
|
|
119
|
-
transition: 'border-color 0.15s',
|
|
120
|
-
}}
|
|
121
|
-
title="Click to copy"
|
|
122
|
-
>
|
|
123
|
-
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
124
|
-
$ {command}
|
|
125
|
-
</span>
|
|
126
|
-
<span style={{
|
|
127
|
-
fontSize: 10, fontFamily: 'var(--font)', fontWeight: 600,
|
|
128
|
-
color: copied ? 'var(--green)' : 'var(--purple)',
|
|
129
|
-
whiteSpace: 'nowrap',
|
|
130
|
-
flexShrink: 0,
|
|
187
|
+
textAlign: 'center',
|
|
131
188
|
}}>
|
|
132
|
-
|
|
133
|
-
</
|
|
134
|
-
|
|
189
|
+
Installing helixevo@latest...
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
135
192
|
|
|
136
|
-
{
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
193
|
+
{state === 'success' && (
|
|
194
|
+
<div style={{
|
|
195
|
+
width: '100%',
|
|
196
|
+
padding: '9px 16px',
|
|
197
|
+
background: 'var(--green-light)',
|
|
198
|
+
borderRadius: 'var(--radius)',
|
|
199
|
+
fontSize: 12,
|
|
200
|
+
color: 'var(--green)',
|
|
201
|
+
textAlign: 'center',
|
|
202
|
+
fontWeight: 600,
|
|
203
|
+
}}>
|
|
204
|
+
Restarting dashboard...
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
{state === 'error' && (
|
|
209
|
+
<>
|
|
210
|
+
<div style={{
|
|
211
|
+
width: '100%',
|
|
212
|
+
padding: '8px 12px',
|
|
213
|
+
background: 'var(--red-light)',
|
|
214
|
+
borderRadius: 'var(--radius)',
|
|
215
|
+
fontSize: 11,
|
|
216
|
+
color: 'var(--red)',
|
|
217
|
+
marginBottom: 8,
|
|
218
|
+
maxHeight: 60,
|
|
219
|
+
overflow: 'auto',
|
|
220
|
+
}}>
|
|
221
|
+
{errorMsg}
|
|
222
|
+
</div>
|
|
223
|
+
<button
|
|
224
|
+
onClick={handleUpdate}
|
|
225
|
+
style={{
|
|
226
|
+
width: '100%',
|
|
227
|
+
padding: '7px 12px',
|
|
228
|
+
background: 'var(--bg-section)',
|
|
229
|
+
color: 'var(--text-secondary)',
|
|
230
|
+
border: '1px solid var(--border)',
|
|
231
|
+
borderRadius: 'var(--radius)',
|
|
232
|
+
fontSize: 12,
|
|
233
|
+
fontWeight: 600,
|
|
234
|
+
cursor: 'pointer',
|
|
235
|
+
}}
|
|
236
|
+
>
|
|
237
|
+
Retry
|
|
238
|
+
</button>
|
|
239
|
+
</>
|
|
240
|
+
)}
|
|
140
241
|
|
|
141
242
|
<style>{`
|
|
142
243
|
@keyframes updateSlideIn {
|
|
143
244
|
from { opacity: 0; transform: translateY(16px) scale(0.97); }
|
|
144
245
|
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
145
246
|
}
|
|
247
|
+
@keyframes updateSpin {
|
|
248
|
+
to { transform: rotate(360deg); }
|
|
249
|
+
}
|
|
146
250
|
`}</style>
|
|
147
251
|
</div>
|
|
148
252
|
)
|
package/dist/cli.js
CHANGED
|
@@ -12350,6 +12350,8 @@ import { join as join15, dirname as dirname3 } from "node:path";
|
|
|
12350
12350
|
import { existsSync as existsSync10, cpSync as cpSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync9 } from "node:fs";
|
|
12351
12351
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12352
12352
|
import { homedir as homedir3 } from "node:os";
|
|
12353
|
+
init_skills();
|
|
12354
|
+
init_data();
|
|
12353
12355
|
var __filename = "/Users/tianchichen/Documents/GitHub/helixevo/src/commands/dashboard.ts";
|
|
12354
12356
|
var HELIX_DASHBOARD_DIR = join15(homedir3(), ".helix", "dashboard");
|
|
12355
12357
|
async function dashboardCommand() {
|
|
@@ -12373,6 +12375,7 @@ async function dashboardCommand() {
|
|
|
12373
12375
|
process.exit(1);
|
|
12374
12376
|
}
|
|
12375
12377
|
}
|
|
12378
|
+
ensureSkillGraph();
|
|
12376
12379
|
console.log(` \uD83C\uDF10 Starting HelixEvo Dashboard v${VERSION} at http://localhost:3847
|
|
12377
12380
|
`);
|
|
12378
12381
|
const child = spawn2("npx", ["next", "dev", "--port", "3847"], {
|
|
@@ -12489,6 +12492,43 @@ function copyToHelix(sourceDir) {
|
|
|
12489
12492
|
writeFileSync9(join15(HELIX_DASHBOARD_DIR, ".helixevo-version"), VERSION);
|
|
12490
12493
|
return HELIX_DASHBOARD_DIR;
|
|
12491
12494
|
}
|
|
12495
|
+
function ensureSkillGraph() {
|
|
12496
|
+
try {
|
|
12497
|
+
const existing = loadSkillGraph();
|
|
12498
|
+
const skills = loadAllGeneralSkills();
|
|
12499
|
+
if (skills.length === 0)
|
|
12500
|
+
return;
|
|
12501
|
+
const existingIds = new Set(existing.nodes.map((n) => n.id));
|
|
12502
|
+
const newSkills = skills.filter((s) => !existingIds.has(s.slug));
|
|
12503
|
+
if (existing.nodes.length > 0 && newSkills.length === 0)
|
|
12504
|
+
return;
|
|
12505
|
+
const existingNodes = existing.nodes;
|
|
12506
|
+
const newNodes = newSkills.map((s) => ({
|
|
12507
|
+
id: s.slug,
|
|
12508
|
+
name: s.slug,
|
|
12509
|
+
layer: "system",
|
|
12510
|
+
score: 0.7,
|
|
12511
|
+
generation: 0,
|
|
12512
|
+
status: "active",
|
|
12513
|
+
tags: [],
|
|
12514
|
+
failureCount: 0,
|
|
12515
|
+
lastEvolved: new Date().toISOString()
|
|
12516
|
+
}));
|
|
12517
|
+
const allNodes = [...existingNodes, ...newNodes];
|
|
12518
|
+
const graph = {
|
|
12519
|
+
updated: new Date().toISOString(),
|
|
12520
|
+
nodes: allNodes,
|
|
12521
|
+
edges: existing.edges,
|
|
12522
|
+
clusters: existing.clusters
|
|
12523
|
+
};
|
|
12524
|
+
saveSkillGraph(graph);
|
|
12525
|
+
if (existing.nodes.length === 0) {
|
|
12526
|
+
console.log(` ✓ Discovered ${allNodes.length} skills for dashboard`);
|
|
12527
|
+
} else if (newSkills.length > 0) {
|
|
12528
|
+
console.log(` ✓ Added ${newSkills.length} new skills to dashboard (${allNodes.length} total)`);
|
|
12529
|
+
}
|
|
12530
|
+
} catch {}
|
|
12531
|
+
}
|
|
12492
12532
|
|
|
12493
12533
|
// src/commands/watch.ts
|
|
12494
12534
|
import { join as join17 } from "node:path";
|
package/package.json
CHANGED