helixevo 0.2.28 → 0.2.29

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.
@@ -1,6 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { execSync } from 'child_process'
3
- import { existsSync, cpSync, readdirSync, rmSync, writeFileSync, mkdirSync } from 'fs'
3
+ import { existsSync, cpSync, readdirSync, rmSync, writeFileSync, readFileSync, mkdirSync } from 'fs'
4
4
  import { join } from 'path'
5
5
  import { homedir } from 'os'
6
6
 
@@ -45,8 +45,15 @@ function copyDashboardFiles(sourceDir: string, newVersion: string): void {
45
45
  // Write version marker
46
46
  writeFileSync(join(HELIX_DASHBOARD_DIR, '.helixevo-version'), newVersion)
47
47
 
48
- // Also write .env.local — survives copyToHelix overwrites from old CLIs
48
+ // Write .env.local — survives copyToHelix overwrites from old CLIs
49
49
  writeFileSync(join(HELIX_DASHBOARD_DIR, '.env.local'), `HELIXEVO_VERSION=${newVersion}\n`)
50
+
51
+ // Touch next.config.mjs to trigger Next.js dev mode auto-restart
52
+ const configPath = join(HELIX_DASHBOARD_DIR, 'next.config.mjs')
53
+ if (existsSync(configPath)) {
54
+ const content = readFileSync(configPath, 'utf-8')
55
+ writeFileSync(configPath, content) // rewrite same content = new mtime
56
+ }
50
57
  }
51
58
 
52
59
  export async function POST() {
@@ -15,7 +15,7 @@ function compareVersions(current: string, latest: string): boolean {
15
15
  return false
16
16
  }
17
17
 
18
- type UpdateState = 'idle' | 'updating' | 'installed' | 'error'
18
+ type UpdateState = 'idle' | 'updating' | 'reloading' | 'error'
19
19
 
20
20
  export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
21
21
  const [latestVersion, setLatestVersion] = useState<string | null>(null)
@@ -23,7 +23,7 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
23
23
  const [state, setState] = useState<UpdateState>('idle')
24
24
  const [newVersion, setNewVersion] = useState<string | null>(null)
25
25
  const [errorMsg, setErrorMsg] = useState<string | null>(null)
26
- const [copied, setCopied] = useState(false)
26
+ const [countdown, setCountdown] = useState(0)
27
27
 
28
28
  useEffect(() => {
29
29
  let mounted = true
@@ -53,8 +53,22 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
53
53
  const data = await res.json()
54
54
 
55
55
  if (data.success) {
56
- setState('installed')
57
56
  setNewVersion(data.version)
57
+ setState('reloading')
58
+
59
+ // Next.js dev mode detects config change and auto-restarts.
60
+ // Wait for it to recompile, then reload.
61
+ let secs = 8
62
+ setCountdown(secs)
63
+ const timer = setInterval(() => {
64
+ secs--
65
+ setCountdown(secs)
66
+ if (secs <= 0) {
67
+ clearInterval(timer)
68
+ // Hard reload with cache busting
69
+ window.location.href = window.location.pathname + '?v=' + Date.now()
70
+ }
71
+ }, 1000)
58
72
  } else {
59
73
  setState('error')
60
74
  setErrorMsg(data.error ?? 'Update failed')
@@ -65,65 +79,47 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
65
79
  }
66
80
  }
67
81
 
68
- const restartCmd = 'helixevo dashboard'
69
- const handleCopy = async () => {
70
- try {
71
- await navigator.clipboard.writeText(restartCmd)
72
- setCopied(true)
73
- setTimeout(() => setCopied(false), 2000)
74
- } catch {}
75
- }
76
-
77
82
  return (
78
83
  <div style={{
79
84
  position: 'fixed',
80
85
  bottom: 20,
81
86
  right: 20,
82
- width: 340,
87
+ width: 320,
83
88
  background: 'var(--bg-card)',
84
- border: `1px solid ${state === 'installed' ? 'var(--green-border)' : state === 'error' ? 'var(--red-border)' : 'var(--purple-border)'}`,
89
+ border: `1px solid ${state === 'reloading' ? 'var(--green-border)' : state === 'error' ? 'var(--red-border)' : 'var(--purple-border)'}`,
85
90
  borderRadius: 'var(--radius-lg)',
86
91
  boxShadow: 'var(--shadow-xl)',
87
92
  padding: '16px 18px',
88
93
  zIndex: 9999,
89
94
  animation: 'updateSlideIn 0.4s ease-out',
90
95
  }}>
91
- {/* Close button */}
92
- {state !== 'updating' && (
96
+ {/* Close */}
97
+ {(state === 'idle' || state === 'error') && (
93
98
  <button
94
99
  onClick={() => setDismissed(true)}
95
100
  style={{
96
101
  position: 'absolute', top: 8, right: 10,
97
102
  background: 'none', border: 'none', cursor: 'pointer',
98
- color: 'var(--text-dim)', fontSize: 18, lineHeight: 1,
99
- padding: '2px 4px',
103
+ color: 'var(--text-dim)', fontSize: 18, lineHeight: 1, padding: '2px 4px',
100
104
  }}
101
- aria-label="Dismiss"
102
- >
103
- &times;
104
- </button>
105
+ >&times;</button>
105
106
  )}
106
107
 
107
108
  {/* Header */}
108
109
  <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
109
110
  <div style={{
110
111
  width: 28, height: 28, borderRadius: '50%',
111
- background: state === 'installed' ? 'var(--green-light)'
112
- : state === 'error' ? 'var(--red-light)'
113
- : 'var(--purple-light)',
114
- display: 'flex',
115
- alignItems: 'center', justifyContent: 'center',
112
+ background: state === 'reloading' ? 'var(--green-light)' : state === 'error' ? 'var(--red-light)' : 'var(--purple-light)',
113
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
116
114
  }}>
117
- {state === 'updating' ? (
115
+ {(state === 'updating' || state === 'reloading') ? (
118
116
  <div style={{
119
- width: 14, height: 14, border: '2px solid var(--purple-border)',
120
- borderTopColor: 'var(--purple)', borderRadius: '50%',
117
+ width: 14, height: 14,
118
+ border: `2px solid ${state === 'reloading' ? 'var(--green-border)' : 'var(--purple-border)'}`,
119
+ borderTopColor: state === 'reloading' ? 'var(--green)' : 'var(--purple)',
120
+ borderRadius: '50%',
121
121
  animation: 'updateSpin 0.8s linear infinite',
122
122
  }} />
123
- ) : state === 'installed' ? (
124
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--green)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
125
- <path d="M20 6L9 17l-5-5" />
126
- </svg>
127
123
  ) : state === 'error' ? (
128
124
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="var(--red)" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
129
125
  <path d="M18 6L6 18M6 6l12 12" />
@@ -135,41 +131,29 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
135
131
  )}
136
132
  </div>
137
133
  <div>
138
- <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)', letterSpacing: -0.2 }}>
134
+ <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>
139
135
  {state === 'updating' ? 'Installing...'
140
- : state === 'installed' ? 'Installed!'
136
+ : state === 'reloading' ? 'Reloading...'
141
137
  : state === 'error' ? 'Update Failed'
142
138
  : 'Update Available'}
143
139
  </div>
144
140
  <div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
145
- {state === 'installed'
146
- ? <>v{newVersion} is ready</>
141
+ {state === 'reloading'
142
+ ? <>v{newVersion} installed — refreshing in {countdown}s</>
147
143
  : <>v{currentVersion} &rarr; <span style={{ color: 'var(--green)', fontWeight: 600 }}>v{latestVersion}</span></>
148
144
  }
149
145
  </div>
150
146
  </div>
151
147
  </div>
152
148
 
153
- {/* Idle — Update Now button */}
149
+ {/* Idle */}
154
150
  {state === 'idle' && (
155
- <button
156
- onClick={handleUpdate}
157
- style={{
158
- width: '100%',
159
- padding: '9px 16px',
160
- background: 'var(--purple)',
161
- color: '#fff',
162
- border: 'none',
163
- borderRadius: 'var(--radius)',
164
- fontSize: 13,
165
- fontWeight: 600,
166
- cursor: 'pointer',
167
- display: 'flex',
168
- alignItems: 'center',
169
- justifyContent: 'center',
170
- gap: 6,
171
- }}
172
- >
151
+ <button onClick={handleUpdate} style={{
152
+ width: '100%', padding: '9px 16px',
153
+ background: 'var(--purple)', color: '#fff', border: 'none',
154
+ borderRadius: 'var(--radius)', fontSize: 13, fontWeight: 600, cursor: 'pointer',
155
+ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
156
+ }}>
173
157
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
174
158
  <path d="M12 19V5m-7 7l7-7 7 7" />
175
159
  </svg>
@@ -177,62 +161,36 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
177
161
  </button>
178
162
  )}
179
163
 
180
- {/* Updating spinner */}
164
+ {/* Updating */}
181
165
  {state === 'updating' && (
182
166
  <div style={{
183
- width: '100%',
184
- padding: '9px 16px',
185
- background: 'var(--bg-section)',
186
- borderRadius: 'var(--radius)',
187
- fontSize: 12,
188
- color: 'var(--text-secondary)',
189
- textAlign: 'center',
167
+ padding: '9px 16px', background: 'var(--bg-section)',
168
+ borderRadius: 'var(--radius)', fontSize: 12,
169
+ color: 'var(--text-secondary)', textAlign: 'center',
190
170
  }}>
191
171
  Installing helixevo@latest...
192
172
  </div>
193
173
  )}
194
174
 
195
- {/* Installed restart instructions */}
196
- {state === 'installed' && (
175
+ {/* Reloading with countdown */}
176
+ {state === 'reloading' && (
197
177
  <div>
198
178
  <div style={{
199
- padding: '10px 14px',
200
- background: 'var(--green-light)',
201
- borderRadius: 'var(--radius)',
202
- fontSize: 12,
203
- color: 'var(--green)',
204
- fontWeight: 600,
205
- marginBottom: 10,
206
- textAlign: 'center',
179
+ padding: '9px 16px', background: 'var(--green-light)',
180
+ borderRadius: 'var(--radius)', fontSize: 12,
181
+ color: 'var(--green)', textAlign: 'center', fontWeight: 600,
182
+ marginBottom: 8,
207
183
  }}>
208
- v{newVersion} installed successfully
184
+ Dashboard will refresh automatically
209
185
  </div>
210
- <div style={{ fontSize: 11, color: 'var(--text-dim)', marginBottom: 8, lineHeight: 1.5 }}>
211
- Restart the dashboard to load the new version:
212
- </div>
213
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
214
- <div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
215
- 1. Press <kbd style={{ padding: '1px 5px', background: 'var(--bg-section)', borderRadius: 3, fontFamily: 'var(--font-mono)', fontSize: 10, border: '1px solid var(--border)' }}>Ctrl+C</kbd> in the terminal running the dashboard
216
- </div>
217
- <div style={{ fontSize: 11, color: 'var(--text-dim)' }}>
218
- 2. Run:
219
- </div>
220
- <div
221
- onClick={handleCopy}
222
- style={{
223
- display: 'flex', justifyContent: 'space-between', alignItems: 'center',
224
- padding: '7px 12px',
225
- background: 'var(--bg-section)', border: '1px solid var(--border)',
226
- borderRadius: 'var(--radius)',
227
- fontFamily: 'var(--font-mono)', fontSize: 11,
228
- color: 'var(--text)', cursor: 'pointer',
229
- }}
230
- >
231
- <span>$ {restartCmd}</span>
232
- <span style={{ fontSize: 10, color: copied ? 'var(--green)' : 'var(--purple)', fontWeight: 600, fontFamily: 'var(--font)' }}>
233
- {copied ? 'Copied!' : 'Copy'}
234
- </span>
235
- </div>
186
+ {/* Progress bar */}
187
+ <div style={{ height: 3, background: 'var(--bg-section)', borderRadius: 2, overflow: 'hidden' }}>
188
+ <div style={{
189
+ height: '100%', background: 'var(--green)',
190
+ borderRadius: 2,
191
+ width: `${((8 - countdown) / 8) * 100}%`,
192
+ transition: 'width 1s linear',
193
+ }} />
236
194
  </div>
237
195
  </div>
238
196
  )}
@@ -241,32 +199,18 @@ export function UpdateBanner({ currentVersion }: { currentVersion: string }) {
241
199
  {state === 'error' && (
242
200
  <>
243
201
  <div style={{
244
- width: '100%',
245
- padding: '8px 12px',
246
- background: 'var(--red-light)',
247
- borderRadius: 'var(--radius)',
248
- fontSize: 11,
249
- color: 'var(--red)',
250
- marginBottom: 8,
251
- maxHeight: 60,
252
- overflow: 'auto',
202
+ padding: '8px 12px', background: 'var(--red-light)',
203
+ borderRadius: 'var(--radius)', fontSize: 11, color: 'var(--red)',
204
+ marginBottom: 8, maxHeight: 60, overflow: 'auto',
253
205
  }}>
254
206
  {errorMsg}
255
207
  </div>
256
- <button
257
- onClick={handleUpdate}
258
- style={{
259
- width: '100%',
260
- padding: '7px 12px',
261
- background: 'var(--bg-section)',
262
- color: 'var(--text-secondary)',
263
- border: '1px solid var(--border)',
264
- borderRadius: 'var(--radius)',
265
- fontSize: 12,
266
- fontWeight: 600,
267
- cursor: 'pointer',
268
- }}
269
- >
208
+ <button onClick={handleUpdate} style={{
209
+ width: '100%', padding: '7px 12px',
210
+ background: 'var(--bg-section)', color: 'var(--text-secondary)',
211
+ border: '1px solid var(--border)', borderRadius: 'var(--radius)',
212
+ fontSize: 12, fontWeight: 600, cursor: 'pointer',
213
+ }}>
270
214
  Retry
271
215
  </button>
272
216
  </>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helixevo",
3
- "version": "0.2.28",
3
+ "version": "0.2.29",
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": {