@eugene218/noxdev 1.0.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/dist/dashboard/api/server.js +296 -0
- package/dist/dashboard/assets/index-DH8xJMbY.js +102 -0
- package/dist/dashboard/assets/index-sORHn6si.css +1 -0
- package/dist/dashboard/index.html +14 -0
- package/dist/dashboard/owl-logo.svg +25 -0
- package/dist/index.js +2380 -0
- package/dist/scripts/docker-capture-diff.sh +50 -0
- package/dist/scripts/docker-run-api.sh +59 -0
- package/dist/scripts/docker-run-max.sh +57 -0
- package/package.json +58 -0
- package/scripts/docker-capture-diff.sh +50 -0
- package/scripts/docker-run-api.sh +59 -0
- package/scripts/docker-run-max.sh +57 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// src/api/server.ts
|
|
2
|
+
import express from "express";
|
|
3
|
+
import cors from "cors";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import { access, constants } from "fs/promises";
|
|
6
|
+
import path2 from "path";
|
|
7
|
+
import os2 from "os";
|
|
8
|
+
|
|
9
|
+
// src/api/routes/index.ts
|
|
10
|
+
import { Router as Router4 } from "express";
|
|
11
|
+
|
|
12
|
+
// src/api/routes/projects.ts
|
|
13
|
+
import { Router } from "express";
|
|
14
|
+
|
|
15
|
+
// src/api/db.ts
|
|
16
|
+
import path from "path";
|
|
17
|
+
import os from "os";
|
|
18
|
+
import Database from "better-sqlite3";
|
|
19
|
+
function getDb() {
|
|
20
|
+
const dbPath = path.join(os.homedir(), ".noxdev", "ledger.db");
|
|
21
|
+
return new Database(dbPath, {
|
|
22
|
+
readonly: false,
|
|
23
|
+
fileMustExist: false
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/api/routes/projects.ts
|
|
28
|
+
var router = Router();
|
|
29
|
+
router.get("/", (req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
const db = getDb();
|
|
32
|
+
const query = `
|
|
33
|
+
SELECT p.*, r.id as last_run_id, r.status as last_run_status,
|
|
34
|
+
r.completed, r.failed, r.total_tasks, r.started_at as last_run_at
|
|
35
|
+
FROM projects p
|
|
36
|
+
LEFT JOIN runs r ON p.id = r.project_id
|
|
37
|
+
AND r.started_at = (SELECT MAX(started_at) FROM runs WHERE project_id = p.id)
|
|
38
|
+
`;
|
|
39
|
+
const projects = db.prepare(query).all();
|
|
40
|
+
res.json(projects);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error("Error fetching projects:", error);
|
|
43
|
+
res.status(500).json({ error: "Failed to fetch projects" });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
router.get("/:id", (req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
const db = getDb();
|
|
49
|
+
const projectId = req.params.id;
|
|
50
|
+
const projectQuery = `
|
|
51
|
+
SELECT p.*, r.id as last_run_id
|
|
52
|
+
FROM projects p
|
|
53
|
+
LEFT JOIN runs r ON p.id = r.project_id
|
|
54
|
+
AND r.started_at = (SELECT MAX(started_at) FROM runs WHERE project_id = p.id)
|
|
55
|
+
WHERE p.id = ?
|
|
56
|
+
`;
|
|
57
|
+
const project = db.prepare(projectQuery).get(projectId);
|
|
58
|
+
if (!project) {
|
|
59
|
+
return res.status(404).json({ error: "Project not found" });
|
|
60
|
+
}
|
|
61
|
+
const runsQuery = `
|
|
62
|
+
SELECT * FROM runs
|
|
63
|
+
WHERE project_id = ?
|
|
64
|
+
ORDER BY started_at DESC
|
|
65
|
+
LIMIT 10
|
|
66
|
+
`;
|
|
67
|
+
const runs = db.prepare(runsQuery).all(projectId);
|
|
68
|
+
res.json({
|
|
69
|
+
...project,
|
|
70
|
+
runs
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error("Error fetching project:", error);
|
|
74
|
+
res.status(500).json({ error: "Failed to fetch project" });
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
var projects_default = router;
|
|
78
|
+
|
|
79
|
+
// src/api/routes/runs.ts
|
|
80
|
+
import { Router as Router2 } from "express";
|
|
81
|
+
import { readFileSync } from "fs";
|
|
82
|
+
var router2 = Router2();
|
|
83
|
+
router2.get("/", (req, res) => {
|
|
84
|
+
try {
|
|
85
|
+
const db = getDb();
|
|
86
|
+
const query = `
|
|
87
|
+
SELECT r.*, p.display_name as project_display_name
|
|
88
|
+
FROM runs r
|
|
89
|
+
JOIN projects p ON r.project_id = p.id
|
|
90
|
+
ORDER BY r.started_at DESC
|
|
91
|
+
LIMIT 50
|
|
92
|
+
`;
|
|
93
|
+
const runs = db.prepare(query).all();
|
|
94
|
+
res.json(runs);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error("Error fetching runs:", error);
|
|
97
|
+
res.status(500).json({ error: "Failed to fetch runs" });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
router2.get("/:id", (req, res) => {
|
|
101
|
+
try {
|
|
102
|
+
const db = getDb();
|
|
103
|
+
const runId = req.params.id;
|
|
104
|
+
const runQuery = `
|
|
105
|
+
SELECT r.*, p.display_name as project_display_name
|
|
106
|
+
FROM runs r
|
|
107
|
+
JOIN projects p ON r.project_id = p.id
|
|
108
|
+
WHERE r.id = ?
|
|
109
|
+
`;
|
|
110
|
+
const run = db.prepare(runQuery).get(runId);
|
|
111
|
+
if (!run) {
|
|
112
|
+
return res.status(404).json({ error: "Run not found" });
|
|
113
|
+
}
|
|
114
|
+
const taskResultsQuery = `
|
|
115
|
+
SELECT * FROM task_results
|
|
116
|
+
WHERE run_id = ?
|
|
117
|
+
ORDER BY id ASC
|
|
118
|
+
`;
|
|
119
|
+
const taskResults = db.prepare(taskResultsQuery).all(runId);
|
|
120
|
+
res.json({
|
|
121
|
+
...run,
|
|
122
|
+
task_results: taskResults
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error("Error fetching run:", error);
|
|
126
|
+
res.status(500).json({ error: "Failed to fetch run" });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
router2.get("/:id/tasks/:taskId", (req, res) => {
|
|
130
|
+
try {
|
|
131
|
+
const db = getDb();
|
|
132
|
+
const runId = req.params.id;
|
|
133
|
+
const taskId = req.params.taskId;
|
|
134
|
+
const query = `
|
|
135
|
+
SELECT tr.*, t.spec as task_spec
|
|
136
|
+
FROM task_results tr
|
|
137
|
+
JOIN tasks t ON t.run_id = tr.run_id AND t.task_id = tr.task_id
|
|
138
|
+
WHERE tr.run_id = ? AND tr.task_id = ?
|
|
139
|
+
`;
|
|
140
|
+
const taskResult = db.prepare(query).get(runId, taskId);
|
|
141
|
+
if (!taskResult) {
|
|
142
|
+
return res.status(404).json({ error: "Task not found" });
|
|
143
|
+
}
|
|
144
|
+
let diff = null;
|
|
145
|
+
if (taskResult.diff_file) {
|
|
146
|
+
try {
|
|
147
|
+
diff = readFileSync(taskResult.diff_file, "utf-8");
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.warn(`Could not read diff file: ${taskResult.diff_file}`, error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
res.json({
|
|
153
|
+
...taskResult,
|
|
154
|
+
diff
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error("Error fetching task:", error);
|
|
158
|
+
res.status(500).json({ error: "Failed to fetch task" });
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
router2.get("/:id/tasks/:taskId/diff", (req, res) => {
|
|
162
|
+
try {
|
|
163
|
+
const db = getDb();
|
|
164
|
+
const runId = req.params.id;
|
|
165
|
+
const taskId = req.params.taskId;
|
|
166
|
+
const query = `
|
|
167
|
+
SELECT diff_file FROM task_results
|
|
168
|
+
WHERE run_id = ? AND task_id = ?
|
|
169
|
+
`;
|
|
170
|
+
const result = db.prepare(query).get(runId, taskId);
|
|
171
|
+
if (!result || !result.diff_file) {
|
|
172
|
+
return res.status(404).json({ error: "No diff available for this task" });
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
const diffContent = readFileSync(result.diff_file, "utf-8");
|
|
176
|
+
res.setHeader("Content-Type", "text/plain");
|
|
177
|
+
res.send(diffContent);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error("Error reading diff file:", error);
|
|
180
|
+
res.status(404).json({ error: "Diff file not found" });
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error("Error fetching diff:", error);
|
|
184
|
+
res.status(500).json({ error: "Failed to fetch diff" });
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
router2.post("/:id/tasks/:taskId/merge", (req, res) => {
|
|
188
|
+
try {
|
|
189
|
+
const db = getDb();
|
|
190
|
+
const runId = req.params.id;
|
|
191
|
+
const taskId = req.params.taskId;
|
|
192
|
+
const { decision } = req.body;
|
|
193
|
+
if (!decision || !["approved", "rejected"].includes(decision)) {
|
|
194
|
+
return res.status(400).json({ error: 'Decision must be either "approved" or "rejected"' });
|
|
195
|
+
}
|
|
196
|
+
const checkQuery = `
|
|
197
|
+
SELECT id FROM task_results
|
|
198
|
+
WHERE run_id = ? AND task_id = ?
|
|
199
|
+
`;
|
|
200
|
+
const existingTask = db.prepare(checkQuery).get(runId, taskId);
|
|
201
|
+
if (!existingTask) {
|
|
202
|
+
return res.status(404).json({ error: "Task not found" });
|
|
203
|
+
}
|
|
204
|
+
const updateQuery = `
|
|
205
|
+
UPDATE task_results
|
|
206
|
+
SET merge_decision = ?, merged_at = datetime('now')
|
|
207
|
+
WHERE run_id = ? AND task_id = ?
|
|
208
|
+
`;
|
|
209
|
+
const result = db.prepare(updateQuery).run(decision, runId, taskId);
|
|
210
|
+
if (result.changes === 0) {
|
|
211
|
+
return res.status(404).json({ error: "Task not found or no changes made" });
|
|
212
|
+
}
|
|
213
|
+
res.json({
|
|
214
|
+
success: true,
|
|
215
|
+
taskId,
|
|
216
|
+
decision
|
|
217
|
+
});
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error("Error updating merge decision:", error);
|
|
220
|
+
res.status(500).json({ error: "Failed to update merge decision" });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
var runs_default = router2;
|
|
224
|
+
|
|
225
|
+
// src/api/routes/merge.ts
|
|
226
|
+
import { Router as Router3 } from "express";
|
|
227
|
+
var router3 = Router3();
|
|
228
|
+
router3.post("/:projectId", (req, res) => {
|
|
229
|
+
try {
|
|
230
|
+
const db = getDb();
|
|
231
|
+
const projectId = req.params.projectId;
|
|
232
|
+
const approvedTasksQuery = `
|
|
233
|
+
SELECT tr.run_id, tr.task_id, tr.diff_file
|
|
234
|
+
FROM task_results tr
|
|
235
|
+
JOIN runs r ON tr.run_id = r.id
|
|
236
|
+
WHERE r.project_id = ? AND tr.merge_decision = 'approved' AND tr.merged_at IS NOT NULL
|
|
237
|
+
`;
|
|
238
|
+
const approvedTasks = db.prepare(approvedTasksQuery).all(projectId);
|
|
239
|
+
const count = approvedTasks.length;
|
|
240
|
+
res.json({
|
|
241
|
+
success: true,
|
|
242
|
+
merged: count
|
|
243
|
+
});
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error("Error executing merge:", error);
|
|
246
|
+
res.status(500).json({ error: "Failed to execute merge" });
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
var merge_default = router3;
|
|
250
|
+
|
|
251
|
+
// src/api/routes/index.ts
|
|
252
|
+
var router4 = Router4();
|
|
253
|
+
router4.use("/projects", projects_default);
|
|
254
|
+
router4.use("/runs", runs_default);
|
|
255
|
+
router4.use("/merge", merge_default);
|
|
256
|
+
var routes_default = router4;
|
|
257
|
+
|
|
258
|
+
// src/api/server.ts
|
|
259
|
+
var app = express();
|
|
260
|
+
var PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 4400;
|
|
261
|
+
app.use(cors({
|
|
262
|
+
origin: [
|
|
263
|
+
"http://localhost:3000",
|
|
264
|
+
"http://localhost:5173",
|
|
265
|
+
/^http:\/\/localhost:\d+$/
|
|
266
|
+
]
|
|
267
|
+
}));
|
|
268
|
+
app.use(express.json());
|
|
269
|
+
app.get("/api/health", async (req, res) => {
|
|
270
|
+
const dbPath = path2.join(os2.homedir(), ".noxdev", "ledger.db");
|
|
271
|
+
let dbOk = false;
|
|
272
|
+
try {
|
|
273
|
+
if (existsSync(dbPath)) {
|
|
274
|
+
await access(dbPath, constants.R_OK);
|
|
275
|
+
dbOk = true;
|
|
276
|
+
}
|
|
277
|
+
} catch (error) {
|
|
278
|
+
}
|
|
279
|
+
res.json({
|
|
280
|
+
status: "ok",
|
|
281
|
+
db: dbOk
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
app.use("/api", routes_default);
|
|
285
|
+
function start() {
|
|
286
|
+
app.listen(PORT, () => {
|
|
287
|
+
console.log(`noxdev dashboard API running at http://localhost:${PORT}`);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
291
|
+
start();
|
|
292
|
+
}
|
|
293
|
+
export {
|
|
294
|
+
app,
|
|
295
|
+
start
|
|
296
|
+
};
|