@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.
@@ -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
+ };