helixevo 0.2.14 → 0.2.16
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/run/route.ts +110 -39
- package/dashboard/components/quick-actions.tsx +88 -31
- package/dist/postinstall.js +20 -4
- package/package.json +2 -2
|
@@ -1,55 +1,126 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import {
|
|
2
|
+
import { spawn, type ChildProcess } from 'child_process'
|
|
3
3
|
|
|
4
4
|
export const dynamic = 'force-dynamic'
|
|
5
5
|
|
|
6
6
|
// Allowed commands — whitelist to prevent arbitrary execution
|
|
7
|
-
const ALLOWED_COMMANDS: Record<string, { cmd: string; timeout: number }> = {
|
|
8
|
-
'status': { cmd: 'helixevo status',
|
|
9
|
-
'health': { cmd: 'helixevo health --verbose',
|
|
10
|
-
'metrics': { cmd: 'helixevo metrics --verbose',
|
|
11
|
-
'evolve': { cmd: 'helixevo evolve --verbose',
|
|
12
|
-
'evolve-dry': { cmd: 'helixevo evolve --dry-run --verbose', timeout: 300000 },
|
|
13
|
-
'generalize': { cmd: 'helixevo generalize --verbose',
|
|
14
|
-
'generalize-dry': { cmd: 'helixevo generalize --dry-run --verbose', timeout: 300000 },
|
|
15
|
-
'graph-rebuild': { cmd: 'helixevo graph --rebuild --verbose',
|
|
16
|
-
'graph-optimize': { cmd: 'helixevo graph --optimize --verbose', timeout: 300000 },
|
|
17
|
-
'research': { cmd: 'helixevo research --verbose',
|
|
18
|
-
'research-dry': { cmd: 'helixevo research --dry-run --verbose', timeout: 300000 },
|
|
19
|
-
'report': { cmd: 'helixevo report --days 7',
|
|
7
|
+
const ALLOWED_COMMANDS: Record<string, { cmd: string; args: string[]; timeout: number }> = {
|
|
8
|
+
'status': { cmd: 'helixevo', args: ['status'], timeout: 15000 },
|
|
9
|
+
'health': { cmd: 'helixevo', args: ['health', '--verbose'], timeout: 120000 },
|
|
10
|
+
'metrics': { cmd: 'helixevo', args: ['metrics', '--verbose'], timeout: 15000 },
|
|
11
|
+
'evolve': { cmd: 'helixevo', args: ['evolve', '--verbose'], timeout: 300000 },
|
|
12
|
+
'evolve-dry': { cmd: 'helixevo', args: ['evolve', '--dry-run', '--verbose'], timeout: 300000 },
|
|
13
|
+
'generalize': { cmd: 'helixevo', args: ['generalize', '--verbose'], timeout: 300000 },
|
|
14
|
+
'generalize-dry': { cmd: 'helixevo', args: ['generalize', '--dry-run', '--verbose'], timeout: 300000 },
|
|
15
|
+
'graph-rebuild': { cmd: 'helixevo', args: ['graph', '--rebuild', '--verbose'], timeout: 300000 },
|
|
16
|
+
'graph-optimize': { cmd: 'helixevo', args: ['graph', '--optimize', '--verbose'], timeout: 300000 },
|
|
17
|
+
'research': { cmd: 'helixevo', args: ['research', '--verbose'], timeout: 300000 },
|
|
18
|
+
'research-dry': { cmd: 'helixevo', args: ['research', '--dry-run', '--verbose'], timeout: 300000 },
|
|
19
|
+
'report': { cmd: 'helixevo', args: ['report', '--days', '7'], timeout: 30000 },
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// Track the currently running process so it can be stopped
|
|
23
|
+
let activeProcess: ChildProcess | null = null
|
|
24
|
+
let activeCommand: string | null = null
|
|
25
|
+
|
|
22
26
|
export async function POST(request: Request) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const { command } = body as { command: string }
|
|
27
|
+
const body = await request.json()
|
|
28
|
+
const { command } = body as { command: string }
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
const entry = ALLOWED_COMMANDS[command]
|
|
31
|
+
if (!entry) {
|
|
32
|
+
return NextResponse.json({
|
|
33
|
+
success: false,
|
|
34
|
+
error: `Unknown command: ${command}`,
|
|
35
|
+
}, { status: 400 })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Kill any existing process before starting a new one
|
|
39
|
+
if (activeProcess && !activeProcess.killed) {
|
|
40
|
+
activeProcess.kill('SIGTERM')
|
|
41
|
+
activeProcess = null
|
|
42
|
+
activeCommand = null
|
|
43
|
+
}
|
|
34
44
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
return new Promise<Response>((resolve) => {
|
|
46
|
+
let output = ''
|
|
47
|
+
let timedOut = false
|
|
48
|
+
|
|
49
|
+
const child = spawn(entry.cmd, entry.args, {
|
|
38
50
|
env: { ...process.env },
|
|
51
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
39
52
|
})
|
|
40
53
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
activeProcess = child
|
|
55
|
+
activeCommand = command
|
|
56
|
+
|
|
57
|
+
const timeout = setTimeout(() => {
|
|
58
|
+
timedOut = true
|
|
59
|
+
child.kill('SIGTERM')
|
|
60
|
+
}, entry.timeout)
|
|
61
|
+
|
|
62
|
+
child.stdout?.on('data', (data: Buffer) => {
|
|
63
|
+
output += data.toString()
|
|
45
64
|
})
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
|
|
66
|
+
child.stderr?.on('data', (data: Buffer) => {
|
|
67
|
+
output += data.toString()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
child.on('close', (code, signal) => {
|
|
71
|
+
clearTimeout(timeout)
|
|
72
|
+
activeProcess = null
|
|
73
|
+
activeCommand = null
|
|
74
|
+
|
|
75
|
+
if (signal === 'SIGTERM' && !timedOut) {
|
|
76
|
+
// Stopped by user
|
|
77
|
+
resolve(NextResponse.json({
|
|
78
|
+
success: false,
|
|
79
|
+
stopped: true,
|
|
80
|
+
output: output + '\n\n[Stopped by user]',
|
|
81
|
+
}))
|
|
82
|
+
} else if (timedOut) {
|
|
83
|
+
resolve(NextResponse.json({
|
|
84
|
+
success: false,
|
|
85
|
+
output: output + '\n\n[Timed out]',
|
|
86
|
+
}, { status: 500 }))
|
|
87
|
+
} else {
|
|
88
|
+
resolve(NextResponse.json({
|
|
89
|
+
success: code === 0,
|
|
90
|
+
command: `${entry.cmd} ${entry.args.join(' ')}`,
|
|
91
|
+
output: output || 'No output',
|
|
92
|
+
}, code === 0 ? {} : { status: 500 }))
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
child.on('error', (err) => {
|
|
97
|
+
clearTimeout(timeout)
|
|
98
|
+
activeProcess = null
|
|
99
|
+
activeCommand = null
|
|
100
|
+
resolve(NextResponse.json({
|
|
101
|
+
success: false,
|
|
102
|
+
output: err.message,
|
|
103
|
+
}, { status: 500 }))
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// DELETE = stop the running command
|
|
109
|
+
export async function DELETE() {
|
|
110
|
+
if (activeProcess && !activeProcess.killed) {
|
|
111
|
+
const cmd = activeCommand
|
|
112
|
+
activeProcess.kill('SIGTERM')
|
|
113
|
+
activeProcess = null
|
|
114
|
+
activeCommand = null
|
|
115
|
+
return NextResponse.json({ stopped: true, command: cmd })
|
|
54
116
|
}
|
|
117
|
+
return NextResponse.json({ stopped: false, message: 'No running command' })
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// GET = check if something is running
|
|
121
|
+
export async function GET() {
|
|
122
|
+
return NextResponse.json({
|
|
123
|
+
running: activeProcess !== null && !activeProcess.killed,
|
|
124
|
+
command: activeCommand,
|
|
125
|
+
})
|
|
55
126
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useState } from 'react'
|
|
3
|
+
import { useState, useRef } from 'react'
|
|
4
4
|
|
|
5
5
|
interface Action {
|
|
6
6
|
id: string
|
|
@@ -17,12 +17,13 @@ interface QuickActionsProps {
|
|
|
17
17
|
actions: Action[]
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
type RunState = 'idle' | 'running' | 'success' | 'error'
|
|
20
|
+
type RunState = 'idle' | 'running' | 'success' | 'error' | 'stopped'
|
|
21
21
|
|
|
22
22
|
export function QuickActions({ actions }: QuickActionsProps) {
|
|
23
23
|
const [running, setRunning] = useState<string | null>(null)
|
|
24
24
|
const [state, setState] = useState<RunState>('idle')
|
|
25
25
|
const [output, setOutput] = useState<string | null>(null)
|
|
26
|
+
const abortRef = useRef<AbortController | null>(null)
|
|
26
27
|
|
|
27
28
|
const handleRun = async (action: Action) => {
|
|
28
29
|
if (action.disabled) return
|
|
@@ -30,19 +31,48 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
30
31
|
setState('running')
|
|
31
32
|
setOutput(null)
|
|
32
33
|
|
|
34
|
+
const controller = new AbortController()
|
|
35
|
+
abortRef.current = controller
|
|
36
|
+
|
|
33
37
|
try {
|
|
34
38
|
const res = await fetch('/api/run', {
|
|
35
39
|
method: 'POST',
|
|
36
40
|
headers: { 'Content-Type': 'application/json' },
|
|
37
41
|
body: JSON.stringify({ command: action.command }),
|
|
42
|
+
signal: controller.signal,
|
|
38
43
|
})
|
|
39
44
|
const data = await res.json()
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
if (data.stopped) {
|
|
46
|
+
setOutput(data.output ?? 'Stopped')
|
|
47
|
+
setState('stopped')
|
|
48
|
+
} else {
|
|
49
|
+
setOutput(data.output ?? data.error ?? 'No output')
|
|
50
|
+
setState(data.success ? 'success' : 'error')
|
|
51
|
+
}
|
|
52
|
+
} catch (err: unknown) {
|
|
53
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
54
|
+
// Client-side abort — also kill server-side process
|
|
55
|
+
try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
|
|
56
|
+
setOutput('Stopped by user')
|
|
57
|
+
setState('stopped')
|
|
58
|
+
} else {
|
|
59
|
+
setOutput('Network error')
|
|
60
|
+
setState('error')
|
|
61
|
+
}
|
|
62
|
+
} finally {
|
|
63
|
+
abortRef.current = null
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const handleStop = async () => {
|
|
68
|
+
// Abort the fetch request
|
|
69
|
+
if (abortRef.current) {
|
|
70
|
+
abortRef.current.abort()
|
|
45
71
|
}
|
|
72
|
+
// Also kill server-side process
|
|
73
|
+
try {
|
|
74
|
+
await fetch('/api/run', { method: 'DELETE' })
|
|
75
|
+
} catch {}
|
|
46
76
|
}
|
|
47
77
|
|
|
48
78
|
const handleClose = () => {
|
|
@@ -51,6 +81,21 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
51
81
|
setOutput(null)
|
|
52
82
|
}
|
|
53
83
|
|
|
84
|
+
const stateColor = state === 'success' ? 'var(--green)'
|
|
85
|
+
: state === 'error' ? 'var(--red)'
|
|
86
|
+
: state === 'stopped' ? 'var(--yellow)'
|
|
87
|
+
: 'var(--text-secondary)'
|
|
88
|
+
|
|
89
|
+
const stateBorderColor = state === 'success' ? 'var(--green-border)'
|
|
90
|
+
: state === 'error' ? 'var(--red-border)'
|
|
91
|
+
: state === 'stopped' ? 'var(--yellow-border)'
|
|
92
|
+
: 'var(--border)'
|
|
93
|
+
|
|
94
|
+
const stateBgColor = state === 'success' ? 'var(--green-light)'
|
|
95
|
+
: state === 'error' ? 'var(--red-light)'
|
|
96
|
+
: state === 'stopped' ? 'var(--yellow-light)'
|
|
97
|
+
: 'var(--bg-section)'
|
|
98
|
+
|
|
54
99
|
return (
|
|
55
100
|
<>
|
|
56
101
|
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
@@ -93,12 +138,12 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
93
138
|
))}
|
|
94
139
|
</div>
|
|
95
140
|
|
|
96
|
-
{/* Output
|
|
141
|
+
{/* Output panel */}
|
|
97
142
|
{running && state !== 'idle' && (
|
|
98
143
|
<div style={{
|
|
99
144
|
marginTop: 12,
|
|
100
145
|
background: 'var(--bg-card)',
|
|
101
|
-
border: `1px solid ${
|
|
146
|
+
border: `1px solid ${stateBorderColor}`,
|
|
102
147
|
borderRadius: 'var(--radius-lg)',
|
|
103
148
|
overflow: 'hidden',
|
|
104
149
|
}}>
|
|
@@ -106,9 +151,7 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
106
151
|
<div style={{
|
|
107
152
|
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
108
153
|
padding: '10px 14px',
|
|
109
|
-
background:
|
|
110
|
-
: state === 'success' ? 'var(--green-light)'
|
|
111
|
-
: 'var(--red-light)',
|
|
154
|
+
background: stateBgColor,
|
|
112
155
|
borderBottom: '1px solid var(--border)',
|
|
113
156
|
}}>
|
|
114
157
|
<div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, fontWeight: 600 }}>
|
|
@@ -121,32 +164,46 @@ export function QuickActions({ actions }: QuickActionsProps) {
|
|
|
121
164
|
)}
|
|
122
165
|
{state === 'success' && <span style={{ color: 'var(--green)' }}>✓</span>}
|
|
123
166
|
{state === 'error' && <span style={{ color: 'var(--red)' }}>✗</span>}
|
|
124
|
-
<span style={{
|
|
125
|
-
|
|
126
|
-
}}>
|
|
167
|
+
{state === 'stopped' && <span style={{ color: 'var(--yellow)' }}>■</span>}
|
|
168
|
+
<span style={{ color: stateColor }}>
|
|
127
169
|
{state === 'running' ? `Running ${actions.find(a => a.id === running)?.label}...`
|
|
128
170
|
: state === 'success' ? 'Completed'
|
|
171
|
+
: state === 'stopped' ? 'Stopped'
|
|
129
172
|
: 'Failed'}
|
|
130
173
|
</span>
|
|
131
174
|
</div>
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
<button onClick={
|
|
135
|
-
background: '
|
|
136
|
-
padding: '3px
|
|
137
|
-
|
|
138
|
-
}}>
|
|
139
|
-
Refresh
|
|
140
|
-
</button>
|
|
141
|
-
<button onClick={handleClose} style={{
|
|
142
|
-
background: 'none', border: '1px solid var(--border)', borderRadius: 'var(--radius)',
|
|
143
|
-
padding: '3px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer',
|
|
144
|
-
color: 'var(--text-secondary)',
|
|
175
|
+
<div style={{ display: 'flex', gap: 6 }}>
|
|
176
|
+
{state === 'running' && (
|
|
177
|
+
<button onClick={handleStop} style={{
|
|
178
|
+
background: 'var(--red)', color: '#fff', border: 'none', borderRadius: 'var(--radius)',
|
|
179
|
+
padding: '3px 12px', fontSize: 11, fontWeight: 600, cursor: 'pointer',
|
|
180
|
+
display: 'flex', alignItems: 'center', gap: 4,
|
|
145
181
|
}}>
|
|
146
|
-
|
|
182
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
|
183
|
+
<rect x="4" y="4" width="16" height="16" rx="2" />
|
|
184
|
+
</svg>
|
|
185
|
+
Stop
|
|
147
186
|
</button>
|
|
148
|
-
|
|
149
|
-
|
|
187
|
+
)}
|
|
188
|
+
{state !== 'running' && (
|
|
189
|
+
<>
|
|
190
|
+
<button onClick={() => { window.location.reload() }} style={{
|
|
191
|
+
background: 'none', border: '1px solid var(--border)', borderRadius: 'var(--radius)',
|
|
192
|
+
padding: '3px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer',
|
|
193
|
+
color: 'var(--text-secondary)',
|
|
194
|
+
}}>
|
|
195
|
+
Refresh
|
|
196
|
+
</button>
|
|
197
|
+
<button onClick={handleClose} style={{
|
|
198
|
+
background: 'none', border: '1px solid var(--border)', borderRadius: 'var(--radius)',
|
|
199
|
+
padding: '3px 10px', fontSize: 11, fontWeight: 600, cursor: 'pointer',
|
|
200
|
+
color: 'var(--text-secondary)',
|
|
201
|
+
}}>
|
|
202
|
+
Close
|
|
203
|
+
</button>
|
|
204
|
+
</>
|
|
205
|
+
)}
|
|
206
|
+
</div>
|
|
150
207
|
</div>
|
|
151
208
|
|
|
152
209
|
{/* Output */}
|
package/dist/postinstall.js
CHANGED
|
@@ -1,18 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/postinstall.ts
|
|
4
|
-
import {
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
5
|
import { join, dirname } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
var
|
|
8
|
-
var
|
|
7
|
+
var __dirname = "/Users/tianchichen/Documents/GitHub/helixevo/src";
|
|
8
|
+
var version = "unknown";
|
|
9
|
+
try {
|
|
10
|
+
const candidates = [
|
|
11
|
+
join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"),
|
|
12
|
+
join(process.cwd(), "package.json"),
|
|
13
|
+
join(__dirname, "..", "package.json")
|
|
14
|
+
];
|
|
15
|
+
for (const p of candidates) {
|
|
16
|
+
try {
|
|
17
|
+
const pkg = JSON.parse(readFileSync(p, "utf-8"));
|
|
18
|
+
if (pkg.name === "helixevo" && pkg.version) {
|
|
19
|
+
version = pkg.version;
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
} catch {}
|
|
23
|
+
}
|
|
24
|
+
} catch {}
|
|
9
25
|
var green = "\x1B[32m";
|
|
10
26
|
var cyan = "\x1B[36m";
|
|
11
27
|
var bold = "\x1B[1m";
|
|
12
28
|
var dim = "\x1B[2m";
|
|
13
29
|
var reset = "\x1B[0m";
|
|
14
30
|
console.log();
|
|
15
|
-
console.log(` ${green}✓${reset} ${bold}HelixEvo v${
|
|
31
|
+
console.log(` ${green}✓${reset} ${bold}HelixEvo v${version}${reset} installed successfully`);
|
|
16
32
|
console.log();
|
|
17
33
|
console.log(` ${dim}Get started:${reset}`);
|
|
18
34
|
console.log(` ${cyan}helixevo init${reset} Import skills + generate skill tests`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helixevo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
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": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"typecheck": "tsc --noEmit",
|
|
25
25
|
"prepare": "npm run build",
|
|
26
26
|
"prepublishOnly": "npm run typecheck && npm run build",
|
|
27
|
-
"postinstall": "node dist/postinstall.js
|
|
27
|
+
"postinstall": "node dist/postinstall.js || true"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"commander": "^13.1.0",
|