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.
@@ -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 [copied, setCopied] = useState(false)
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 command = 'npm install -g helixevo@latest'
48
+ const handleUpdate = async () => {
49
+ setState('updating')
50
+ setErrorMsg(null)
45
51
 
46
- const handleCopy = async () => {
47
52
  try {
48
- await navigator.clipboard.writeText(command)
49
- setCopied(true)
50
- setTimeout(() => setCopied(false), 2000)
51
- } catch {}
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: '1px solid var(--purple-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
- <button
70
- onClick={() => setDismissed(true)}
71
- style={{
72
- position: 'absolute', top: 8, right: 10,
73
- background: 'none', border: 'none', cursor: 'pointer',
74
- color: 'var(--text-dim)', fontSize: 18, lineHeight: 1,
75
- padding: '2px 4px',
76
- }}
77
- aria-label="Dismiss"
78
- >
79
- &times;
80
- </button>
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
+ &times;
100
+ </button>
101
+ )}
81
102
 
82
103
  {/* Header */}
83
- <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
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(--purple-light)', display: 'flex',
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
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--purple)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
90
- <path d="M12 19V5m-7 7l7-7 7 7" />
91
- </svg>
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
- Update Available
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
- v{currentVersion} &rarr; <span style={{ color: 'var(--green)', fontWeight: 600 }}>v{latestVersion}</span>
141
+ {state === 'success'
142
+ ? <>Now on <span style={{ color: 'var(--green)', fontWeight: 600 }}>v{newVersion}</span> — restarting...</>
143
+ : <>v{currentVersion} &rarr; <span style={{ color: 'var(--green)', fontWeight: 600 }}>v{latestVersion}</span></>
144
+ }
99
145
  </div>
100
146
  </div>
101
147
  </div>
102
148
 
103
- {/* Command */}
104
- <div
105
- onClick={handleCopy}
106
- style={{
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
- padding: '8px 12px',
111
- fontFamily: 'var(--font-mono)',
112
- fontSize: 11,
185
+ fontSize: 12,
113
186
  color: 'var(--text-secondary)',
114
- cursor: 'pointer',
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
- {copied ? 'Copied!' : 'Copy'}
133
- </span>
134
- </div>
189
+ Installing helixevo@latest...
190
+ </div>
191
+ )}
135
192
 
136
- {/* Subtle hint */}
137
- <div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 8, textAlign: 'center' }}>
138
- Run in terminal, then refresh dashboard
139
- </div>
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helixevo",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "Self-evolving skill ecosystem for AI agents. Skills and projects co-evolve through multi-judge evaluation and a Pareto frontier.",
5
5
  "type": "module",
6
6
  "bin": {