@eugene218/noxdev 1.0.0 → 1.0.2
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/README.md +30 -0
- package/dist/dashboard/api/server.js +2 -2
- package/dist/dashboard/assets/index-CiAjd0j2.js +102 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/index.js +975 -350
- package/docker/Dockerfile +26 -0
- package/package.json +5 -2
- package/templates/demo-tasks.md +168 -0
- package/dist/dashboard/assets/index-DH8xJMbY.js +0 -102
package/dist/index.js
CHANGED
|
@@ -1,38 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/) )\\ ship code while you sleep
|
|
12
|
-
" \\|/ "
|
|
13
|
-
---m-m---`;
|
|
14
|
-
|
|
15
|
-
// src/commands/init.ts
|
|
16
|
-
import { execSync } from "child_process";
|
|
17
|
-
import {
|
|
18
|
-
existsSync,
|
|
19
|
-
readFileSync,
|
|
20
|
-
mkdirSync as mkdirSync2,
|
|
21
|
-
writeFileSync
|
|
22
|
-
} from "fs";
|
|
23
|
-
import { join as join2, resolve } from "path";
|
|
24
|
-
import { homedir as homedir2 } from "os";
|
|
25
|
-
import chalk from "chalk";
|
|
26
|
-
import ora from "ora";
|
|
27
|
-
|
|
28
|
-
// src/db/index.ts
|
|
29
|
-
import Database from "better-sqlite3";
|
|
30
|
-
import { mkdirSync } from "fs";
|
|
31
|
-
import { join } from "path";
|
|
32
|
-
import { homedir } from "os";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
33
11
|
|
|
34
12
|
// src/db/migrate.ts
|
|
35
|
-
|
|
13
|
+
function migrate(db) {
|
|
14
|
+
db.exec(SCHEMA);
|
|
15
|
+
}
|
|
16
|
+
var SCHEMA;
|
|
17
|
+
var init_migrate = __esm({
|
|
18
|
+
"src/db/migrate.ts"() {
|
|
19
|
+
"use strict";
|
|
20
|
+
SCHEMA = `
|
|
36
21
|
CREATE TABLE IF NOT EXISTS projects (
|
|
37
22
|
id TEXT PRIMARY KEY,
|
|
38
23
|
display_name TEXT NOT NULL,
|
|
@@ -105,14 +90,14 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
105
90
|
UNIQUE(run_id, task_id)
|
|
106
91
|
);
|
|
107
92
|
`;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
111
95
|
|
|
112
96
|
// src/db/index.ts
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
97
|
+
import Database from "better-sqlite3";
|
|
98
|
+
import { mkdirSync } from "fs";
|
|
99
|
+
import { join } from "path";
|
|
100
|
+
import { homedir } from "os";
|
|
116
101
|
function getDb() {
|
|
117
102
|
if (_db) return _db;
|
|
118
103
|
mkdirSync(DB_DIR, { recursive: true });
|
|
@@ -122,231 +107,15 @@ function getDb() {
|
|
|
122
107
|
migrate(_db);
|
|
123
108
|
return _db;
|
|
124
109
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
};
|
|
133
|
-
const pkgPath = join2(repoPath, "package.json");
|
|
134
|
-
if (!existsSync(pkgPath)) return defaults;
|
|
135
|
-
try {
|
|
136
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
137
|
-
const scripts = pkg.scripts ?? {};
|
|
138
|
-
return {
|
|
139
|
-
test_command: scripts.test ? `pnpm test` : defaults.test_command,
|
|
140
|
-
build_command: scripts.build ? `pnpm build` : defaults.build_command,
|
|
141
|
-
lint_command: scripts.lint ? `pnpm lint` : defaults.lint_command
|
|
142
|
-
};
|
|
143
|
-
} catch {
|
|
144
|
-
return defaults;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
function registerInit(program2) {
|
|
148
|
-
program2.command("init").description("Initialize a new project").argument("<project>", "project name").requiredOption("--repo <path>", "path to git repository").action(async (project, opts) => {
|
|
149
|
-
try {
|
|
150
|
-
await runInit(project, opts.repo);
|
|
151
|
-
} catch (err) {
|
|
152
|
-
console.error(
|
|
153
|
-
chalk.red(
|
|
154
|
-
`Error: ${err instanceof Error ? err.message : String(err)}`
|
|
155
|
-
)
|
|
156
|
-
);
|
|
157
|
-
process.exitCode = 1;
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
async function runInit(project, repoPath) {
|
|
162
|
-
const resolvedRepo = resolve(repoPath);
|
|
163
|
-
const branch = `noxdev/${project}`;
|
|
164
|
-
const worktreePath = join2(homedir2(), "worktrees", project);
|
|
165
|
-
if (!existsSync(resolvedRepo)) {
|
|
166
|
-
throw new Error(`Repository path does not exist: ${resolvedRepo}`);
|
|
167
|
-
}
|
|
168
|
-
const gitDir = join2(resolvedRepo, ".git");
|
|
169
|
-
if (!existsSync(gitDir)) {
|
|
170
|
-
throw new Error(
|
|
171
|
-
`Not a git repository (missing .git): ${resolvedRepo}`
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
console.log(chalk.green("\u2713") + " Repository validated: " + resolvedRepo);
|
|
175
|
-
try {
|
|
176
|
-
execSync("git rev-parse HEAD", { cwd: resolvedRepo, stdio: "pipe" });
|
|
177
|
-
} catch {
|
|
178
|
-
console.log(chalk.yellow(" \u26A0 Empty repository detected. Creating initial commit..."));
|
|
179
|
-
const readmePath = join2(resolvedRepo, "README.md");
|
|
180
|
-
if (!existsSync(readmePath)) {
|
|
181
|
-
writeFileSync(readmePath, `# ${project}
|
|
182
|
-
`);
|
|
183
|
-
}
|
|
184
|
-
execSync("git add .", { cwd: resolvedRepo, stdio: "pipe" });
|
|
185
|
-
try {
|
|
186
|
-
execSync("git config user.name", { cwd: resolvedRepo, stdio: "pipe" });
|
|
187
|
-
} catch {
|
|
188
|
-
execSync('git config user.name "noxdev"', { cwd: resolvedRepo, stdio: "pipe" });
|
|
189
|
-
}
|
|
190
|
-
try {
|
|
191
|
-
execSync("git config user.email", { cwd: resolvedRepo, stdio: "pipe" });
|
|
192
|
-
} catch {
|
|
193
|
-
execSync('git config user.email "noxdev@local"', { cwd: resolvedRepo, stdio: "pipe" });
|
|
194
|
-
}
|
|
195
|
-
execSync('git commit -m "init"', { cwd: resolvedRepo, stdio: "pipe" });
|
|
196
|
-
console.log(chalk.green(" \u2713 Initial commit created"));
|
|
197
|
-
}
|
|
198
|
-
const spinnerWt = ora("Creating git worktree\u2026").start();
|
|
199
|
-
try {
|
|
200
|
-
const defaultBranch = execSync("git symbolic-ref --short HEAD", {
|
|
201
|
-
cwd: resolvedRepo,
|
|
202
|
-
encoding: "utf-8"
|
|
203
|
-
}).trim();
|
|
204
|
-
let branchExists = false;
|
|
205
|
-
try {
|
|
206
|
-
const result = execSync(`git branch --list ${branch}`, {
|
|
207
|
-
cwd: resolvedRepo,
|
|
208
|
-
encoding: "utf-8"
|
|
209
|
-
}).trim();
|
|
210
|
-
branchExists = result.length > 0;
|
|
211
|
-
} catch {
|
|
212
|
-
}
|
|
213
|
-
let worktreeExisted = false;
|
|
214
|
-
if (branchExists) {
|
|
215
|
-
try {
|
|
216
|
-
execSync(`git worktree add ${worktreePath} ${branch}`, {
|
|
217
|
-
cwd: resolvedRepo,
|
|
218
|
-
stdio: "pipe"
|
|
219
|
-
});
|
|
220
|
-
} catch (err) {
|
|
221
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
222
|
-
if (msg.includes("already exists")) {
|
|
223
|
-
worktreeExisted = true;
|
|
224
|
-
} else {
|
|
225
|
-
throw err;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
} else {
|
|
229
|
-
try {
|
|
230
|
-
execSync(
|
|
231
|
-
`git worktree add -b ${branch} ${worktreePath} ${defaultBranch}`,
|
|
232
|
-
{ cwd: resolvedRepo, stdio: "pipe" }
|
|
233
|
-
);
|
|
234
|
-
} catch (err) {
|
|
235
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
236
|
-
if (msg.includes("already exists")) {
|
|
237
|
-
worktreeExisted = true;
|
|
238
|
-
} else {
|
|
239
|
-
throw err;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (worktreeExisted) {
|
|
244
|
-
spinnerWt.warn(`Worktree already exists at ${worktreePath}`);
|
|
245
|
-
} else {
|
|
246
|
-
spinnerWt.succeed(
|
|
247
|
-
`Worktree created at ${chalk.cyan(worktreePath)}`
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
} catch (err) {
|
|
251
|
-
spinnerWt.fail("Failed to create worktree");
|
|
252
|
-
throw err;
|
|
110
|
+
var DB_DIR, DB_PATH, _db;
|
|
111
|
+
var init_db = __esm({
|
|
112
|
+
"src/db/index.ts"() {
|
|
113
|
+
"use strict";
|
|
114
|
+
init_migrate();
|
|
115
|
+
DB_DIR = join(homedir(), ".noxdev");
|
|
116
|
+
DB_PATH = join(DB_DIR, "ledger.db");
|
|
253
117
|
}
|
|
254
|
-
|
|
255
|
-
const configDir = join2(resolvedRepo, ".noxdev");
|
|
256
|
-
const configPath = join2(configDir, "config.json");
|
|
257
|
-
const projectConfig = {
|
|
258
|
-
project,
|
|
259
|
-
display_name: project,
|
|
260
|
-
test_command: detected.test_command,
|
|
261
|
-
build_command: detected.build_command,
|
|
262
|
-
lint_command: detected.lint_command,
|
|
263
|
-
docker: {
|
|
264
|
-
memory: "4g",
|
|
265
|
-
cpus: 2,
|
|
266
|
-
timeout_minutes: 30
|
|
267
|
-
},
|
|
268
|
-
secrets: "",
|
|
269
|
-
tasks_file: "TASKS.md",
|
|
270
|
-
critic_default: "strict",
|
|
271
|
-
push_default: "never"
|
|
272
|
-
};
|
|
273
|
-
mkdirSync2(configDir, { recursive: true });
|
|
274
|
-
writeFileSync(configPath, JSON.stringify(projectConfig, null, 2) + "\n");
|
|
275
|
-
console.log(chalk.green("\u2713") + " Config written: " + configPath);
|
|
276
|
-
const db = getDb();
|
|
277
|
-
const existing = db.prepare("SELECT id FROM projects WHERE id = ?").get(project);
|
|
278
|
-
if (existing) {
|
|
279
|
-
console.log(
|
|
280
|
-
chalk.yellow("\u26A0") + ` Project "${project}" already registered \u2014 updating`
|
|
281
|
-
);
|
|
282
|
-
db.prepare(
|
|
283
|
-
`UPDATE projects
|
|
284
|
-
SET repo_path = ?, worktree_path = ?, branch = ?,
|
|
285
|
-
test_command = ?, build_command = ?, lint_command = ?,
|
|
286
|
-
updated_at = datetime('now')
|
|
287
|
-
WHERE id = ?`
|
|
288
|
-
).run(
|
|
289
|
-
resolvedRepo,
|
|
290
|
-
worktreePath,
|
|
291
|
-
branch,
|
|
292
|
-
detected.test_command,
|
|
293
|
-
detected.build_command,
|
|
294
|
-
detected.lint_command,
|
|
295
|
-
project
|
|
296
|
-
);
|
|
297
|
-
} else {
|
|
298
|
-
db.prepare(
|
|
299
|
-
`INSERT INTO projects (id, display_name, repo_path, worktree_path, branch,
|
|
300
|
-
test_command, build_command, lint_command)
|
|
301
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
302
|
-
).run(
|
|
303
|
-
project,
|
|
304
|
-
project,
|
|
305
|
-
resolvedRepo,
|
|
306
|
-
worktreePath,
|
|
307
|
-
branch,
|
|
308
|
-
detected.test_command,
|
|
309
|
-
detected.build_command,
|
|
310
|
-
detected.lint_command
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
console.log(chalk.green("\u2713") + ` Project "${project}" registered in database`);
|
|
314
|
-
let dockerOk = false;
|
|
315
|
-
try {
|
|
316
|
-
const result = execSync("docker images -q noxdev-runner:latest", {
|
|
317
|
-
encoding: "utf-8",
|
|
318
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
319
|
-
}).trim();
|
|
320
|
-
dockerOk = result.length > 0;
|
|
321
|
-
} catch {
|
|
322
|
-
}
|
|
323
|
-
if (!dockerOk) {
|
|
324
|
-
console.log(
|
|
325
|
-
chalk.yellow("\u26A0") + " Docker image noxdev-runner:latest not found. Build it before running tasks."
|
|
326
|
-
);
|
|
327
|
-
} else {
|
|
328
|
-
console.log(chalk.green("\u2713") + " Docker image noxdev-runner:latest found");
|
|
329
|
-
}
|
|
330
|
-
console.log("");
|
|
331
|
-
console.log(chalk.bold("Project initialized:"));
|
|
332
|
-
console.log(` Worktree: ${chalk.cyan(worktreePath)}`);
|
|
333
|
-
console.log(` Branch: ${chalk.cyan(branch)}`);
|
|
334
|
-
console.log(` Test: ${detected.test_command}`);
|
|
335
|
-
console.log(` Build: ${detected.build_command}`);
|
|
336
|
-
console.log(` Lint: ${detected.lint_command}`);
|
|
337
|
-
console.log(` Config: ${configPath}`);
|
|
338
|
-
console.log("");
|
|
339
|
-
console.log(
|
|
340
|
-
chalk.blue("\u2192") + ` Write tasks in ${worktreePath}/TASKS.md then run: ${chalk.bold(`noxdev run ${project}`)}`
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// src/commands/run.ts
|
|
345
|
-
import { join as join7 } from "path";
|
|
346
|
-
import { homedir as homedir6 } from "os";
|
|
347
|
-
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
348
|
-
import { spawn, execSync as execSync4 } from "child_process";
|
|
349
|
-
import chalk3 from "chalk";
|
|
118
|
+
});
|
|
350
119
|
|
|
351
120
|
// src/db/queries.ts
|
|
352
121
|
function insertRun(db, run) {
|
|
@@ -391,12 +160,14 @@ function insertTaskCache(db, runId, tasks) {
|
|
|
391
160
|
insertMany(tasks);
|
|
392
161
|
}
|
|
393
162
|
function insertTaskResult(db, result) {
|
|
163
|
+
const mergeDecision = result.pushMode === "auto" && (result.status === "COMPLETED" || result.status === "COMPLETED_RETRY") ? "approved" : "pending";
|
|
164
|
+
const mergedAt = mergeDecision === "approved" ? (/* @__PURE__ */ new Date()).toISOString() : null;
|
|
394
165
|
db.prepare(
|
|
395
166
|
`INSERT INTO task_results
|
|
396
167
|
(run_id, task_id, title, status, exit_code, auth_mode, critic_mode, push_mode,
|
|
397
168
|
attempt, commit_sha, started_at, finished_at, duration_seconds,
|
|
398
|
-
dev_log_file, critic_log_file, diff_file)
|
|
399
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
169
|
+
dev_log_file, critic_log_file, diff_file, merge_decision, merged_at)
|
|
170
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
400
171
|
).run(
|
|
401
172
|
result.runId,
|
|
402
173
|
result.taskId,
|
|
@@ -413,12 +184,14 @@ function insertTaskResult(db, result) {
|
|
|
413
184
|
result.durationSeconds,
|
|
414
185
|
result.devLogFile,
|
|
415
186
|
result.criticLogFile,
|
|
416
|
-
result.diffFile
|
|
187
|
+
result.diffFile,
|
|
188
|
+
mergeDecision,
|
|
189
|
+
mergedAt
|
|
417
190
|
);
|
|
418
191
|
}
|
|
419
192
|
function updateMergeDecision(db, taskResultId, decision, mergedAt) {
|
|
420
193
|
db.prepare(
|
|
421
|
-
`UPDATE task_results SET merge_decision =
|
|
194
|
+
`UPDATE task_results SET merge_decision = LOWER(?), merged_at = ? WHERE id = ?`
|
|
422
195
|
).run(decision, mergedAt ?? null, taskResultId);
|
|
423
196
|
}
|
|
424
197
|
function getLatestRun(db, projectId) {
|
|
@@ -428,7 +201,7 @@ function getTaskResults(db, runId) {
|
|
|
428
201
|
return db.prepare(`SELECT * FROM task_results WHERE run_id = ?`).all(runId);
|
|
429
202
|
}
|
|
430
203
|
function getPendingMerge(db, runId) {
|
|
431
|
-
return db.prepare(`SELECT * FROM task_results WHERE run_id = ? AND merge_decision = 'pending'`).all(runId);
|
|
204
|
+
return db.prepare(`SELECT * FROM task_results WHERE run_id = ? AND LOWER(merge_decision) = 'pending'`).all(runId);
|
|
432
205
|
}
|
|
433
206
|
function getProject(db, projectId) {
|
|
434
207
|
return db.prepare(`SELECT * FROM projects WHERE id = ?`).get(projectId) ?? null;
|
|
@@ -451,50 +224,21 @@ function getAllProjects(db) {
|
|
|
451
224
|
)`
|
|
452
225
|
).all();
|
|
453
226
|
}
|
|
227
|
+
var init_queries = __esm({
|
|
228
|
+
"src/db/queries.ts"() {
|
|
229
|
+
"use strict";
|
|
230
|
+
}
|
|
231
|
+
});
|
|
454
232
|
|
|
455
233
|
// src/config/index.ts
|
|
234
|
+
var config_exports = {};
|
|
235
|
+
__export(config_exports, {
|
|
236
|
+
loadGlobalConfig: () => loadGlobalConfig,
|
|
237
|
+
loadProjectConfig: () => loadProjectConfig
|
|
238
|
+
});
|
|
456
239
|
import { readFileSync as readFileSync2 } from "fs";
|
|
457
240
|
import { join as join3 } from "path";
|
|
458
241
|
import { homedir as homedir3 } from "os";
|
|
459
|
-
var DEFAULT_GLOBAL_CONFIG = {
|
|
460
|
-
accounts: {
|
|
461
|
-
max: {
|
|
462
|
-
preferred: true,
|
|
463
|
-
rate_limit_ceiling: 80
|
|
464
|
-
},
|
|
465
|
-
api: {
|
|
466
|
-
fallback: true,
|
|
467
|
-
daily_cap_usd: 5,
|
|
468
|
-
model: "claude-sonnet-4-6"
|
|
469
|
-
}
|
|
470
|
-
},
|
|
471
|
-
safety: {
|
|
472
|
-
auto_push: false,
|
|
473
|
-
max_retries_per_task: 3,
|
|
474
|
-
circuit_breaker_threshold: 5
|
|
475
|
-
},
|
|
476
|
-
secrets: {
|
|
477
|
-
provider: "age",
|
|
478
|
-
global: "",
|
|
479
|
-
age_key: ""
|
|
480
|
-
}
|
|
481
|
-
};
|
|
482
|
-
var DEFAULT_PROJECT_CONFIG = {
|
|
483
|
-
project: "",
|
|
484
|
-
display_name: "",
|
|
485
|
-
test_command: "pnpm test",
|
|
486
|
-
build_command: "pnpm build",
|
|
487
|
-
lint_command: "pnpm lint",
|
|
488
|
-
docker: {
|
|
489
|
-
memory: "4g",
|
|
490
|
-
cpus: 2,
|
|
491
|
-
timeout_minutes: 30
|
|
492
|
-
},
|
|
493
|
-
secrets: "",
|
|
494
|
-
tasks_file: "TASKS.md",
|
|
495
|
-
critic_default: "strict",
|
|
496
|
-
push_default: "never"
|
|
497
|
-
};
|
|
498
242
|
function deepMerge(defaults, overrides) {
|
|
499
243
|
const result = { ...defaults };
|
|
500
244
|
for (const key of Object.keys(overrides)) {
|
|
@@ -540,8 +284,59 @@ function loadProjectConfig(projectPath) {
|
|
|
540
284
|
}
|
|
541
285
|
return deepMerge(DEFAULT_PROJECT_CONFIG, overrides);
|
|
542
286
|
}
|
|
287
|
+
var DEFAULT_GLOBAL_CONFIG, DEFAULT_PROJECT_CONFIG;
|
|
288
|
+
var init_config = __esm({
|
|
289
|
+
"src/config/index.ts"() {
|
|
290
|
+
"use strict";
|
|
291
|
+
DEFAULT_GLOBAL_CONFIG = {
|
|
292
|
+
accounts: {
|
|
293
|
+
max: {
|
|
294
|
+
preferred: true,
|
|
295
|
+
rate_limit_ceiling: 80
|
|
296
|
+
},
|
|
297
|
+
api: {
|
|
298
|
+
fallback: true,
|
|
299
|
+
daily_cap_usd: 5,
|
|
300
|
+
model: "claude-sonnet-4-6"
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
safety: {
|
|
304
|
+
auto_push: false,
|
|
305
|
+
max_retries_per_task: 3,
|
|
306
|
+
circuit_breaker_threshold: 5
|
|
307
|
+
},
|
|
308
|
+
secrets: {
|
|
309
|
+
provider: "age",
|
|
310
|
+
global: "",
|
|
311
|
+
age_key: ""
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
DEFAULT_PROJECT_CONFIG = {
|
|
315
|
+
project: "",
|
|
316
|
+
display_name: "",
|
|
317
|
+
test_command: "pnpm test",
|
|
318
|
+
build_command: "pnpm build",
|
|
319
|
+
lint_command: "pnpm lint",
|
|
320
|
+
docker: {
|
|
321
|
+
memory: "4g",
|
|
322
|
+
cpus: 2,
|
|
323
|
+
timeout_minutes: 30
|
|
324
|
+
},
|
|
325
|
+
secrets: "",
|
|
326
|
+
tasks_file: "TASKS.md",
|
|
327
|
+
critic_default: "strict",
|
|
328
|
+
push_default: "never"
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
});
|
|
543
332
|
|
|
544
333
|
// src/auth/index.ts
|
|
334
|
+
var auth_exports = {};
|
|
335
|
+
__export(auth_exports, {
|
|
336
|
+
getMaxCredentialPath: () => getMaxCredentialPath,
|
|
337
|
+
isMaxAvailable: () => isMaxAvailable,
|
|
338
|
+
resolveAuth: () => resolveAuth
|
|
339
|
+
});
|
|
545
340
|
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
546
341
|
import { execSync as execSync2 } from "child_process";
|
|
547
342
|
import { homedir as homedir4 } from "os";
|
|
@@ -573,18 +368,14 @@ function resolveAuth(config) {
|
|
|
573
368
|
"No auth available. Max credentials not found at ~/.claude.json and API fallback is disabled or decryption failed."
|
|
574
369
|
);
|
|
575
370
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
import { tmpdir, homedir as homedir5 } from "os";
|
|
582
|
-
import chalk2 from "chalk";
|
|
371
|
+
var init_auth = __esm({
|
|
372
|
+
"src/auth/index.ts"() {
|
|
373
|
+
"use strict";
|
|
374
|
+
}
|
|
375
|
+
});
|
|
583
376
|
|
|
584
377
|
// src/parser/tasks.ts
|
|
585
378
|
import { readFileSync as readFileSync4 } from "fs";
|
|
586
|
-
var TASK_HEADER_RE = /^## (T\d+): (.+)$/;
|
|
587
|
-
var FIELD_RE = /^- (STATUS|FILES|VERIFY|CRITIC|PUSH|SPEC): (.+)$/i;
|
|
588
379
|
function parseTasks(content, includeDone = false) {
|
|
589
380
|
const lines = content.split("\n");
|
|
590
381
|
const tasks = [];
|
|
@@ -651,11 +442,17 @@ function parseTasksFromFile(filePath, includeDone = false) {
|
|
|
651
442
|
const content = readFileSync4(filePath, "utf-8");
|
|
652
443
|
return parseTasks(content, includeDone);
|
|
653
444
|
}
|
|
445
|
+
var TASK_HEADER_RE, FIELD_RE;
|
|
446
|
+
var init_tasks = __esm({
|
|
447
|
+
"src/parser/tasks.ts"() {
|
|
448
|
+
"use strict";
|
|
449
|
+
TASK_HEADER_RE = /^## (T\d+): (.+)$/;
|
|
450
|
+
FIELD_RE = /^- (STATUS|FILES|VERIFY|CRITIC|PUSH|SPEC): (.+)$/i;
|
|
451
|
+
}
|
|
452
|
+
});
|
|
654
453
|
|
|
655
454
|
// src/parser/status-update.ts
|
|
656
455
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
657
|
-
var TASK_HEADER_RE2 = /^## (T\d+): /;
|
|
658
|
-
var STATUS_LINE_RE = /^(- STATUS: )\w+/;
|
|
659
456
|
function replaceTaskStatus(content, taskId, newStatus) {
|
|
660
457
|
const lines = content.split("\n");
|
|
661
458
|
let inTargetTask = false;
|
|
@@ -682,6 +479,14 @@ function updateTaskStatus(filePath, taskId, newStatus) {
|
|
|
682
479
|
}
|
|
683
480
|
writeFileSync2(filePath, result.content, "utf-8");
|
|
684
481
|
}
|
|
482
|
+
var TASK_HEADER_RE2, STATUS_LINE_RE;
|
|
483
|
+
var init_status_update = __esm({
|
|
484
|
+
"src/parser/status-update.ts"() {
|
|
485
|
+
"use strict";
|
|
486
|
+
TASK_HEADER_RE2 = /^## (T\d+): /;
|
|
487
|
+
STATUS_LINE_RE = /^(- STATUS: )\w+/;
|
|
488
|
+
}
|
|
489
|
+
});
|
|
685
490
|
|
|
686
491
|
// src/prompts/builder.ts
|
|
687
492
|
function buildTaskPrompt(ctx) {
|
|
@@ -738,14 +543,17 @@ Respond with APPROVED or REJECTED followed by a brief explanation.
|
|
|
738
543
|
If REJECTED, explain what needs to change.
|
|
739
544
|
`;
|
|
740
545
|
}
|
|
546
|
+
var init_builder = __esm({
|
|
547
|
+
"src/prompts/builder.ts"() {
|
|
548
|
+
"use strict";
|
|
549
|
+
}
|
|
550
|
+
});
|
|
741
551
|
|
|
742
552
|
// src/docker/runner.ts
|
|
743
553
|
import { execFileSync } from "child_process";
|
|
744
554
|
import { statSync } from "fs";
|
|
745
555
|
import { dirname, join as join5 } from "path";
|
|
746
556
|
import { fileURLToPath } from "url";
|
|
747
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
748
|
-
var __dirname = dirname(__filename);
|
|
749
557
|
function resolveScriptsDir() {
|
|
750
558
|
return join5(__dirname, "scripts");
|
|
751
559
|
}
|
|
@@ -792,8 +600,25 @@ function captureDiff(worktreeDir, outputFile) {
|
|
|
792
600
|
return false;
|
|
793
601
|
}
|
|
794
602
|
}
|
|
603
|
+
var __filename, __dirname;
|
|
604
|
+
var init_runner = __esm({
|
|
605
|
+
"src/docker/runner.ts"() {
|
|
606
|
+
"use strict";
|
|
607
|
+
__filename = fileURLToPath(import.meta.url);
|
|
608
|
+
__dirname = dirname(__filename);
|
|
609
|
+
}
|
|
610
|
+
});
|
|
795
611
|
|
|
796
612
|
// src/engine/orchestrator.ts
|
|
613
|
+
var orchestrator_exports = {};
|
|
614
|
+
__export(orchestrator_exports, {
|
|
615
|
+
executeRun: () => executeRun
|
|
616
|
+
});
|
|
617
|
+
import { execSync as execSync3 } from "child_process";
|
|
618
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync, readFileSync as readFileSync6, copyFileSync, existsSync as existsSync3 } from "fs";
|
|
619
|
+
import { join as join6 } from "path";
|
|
620
|
+
import { tmpdir, homedir as homedir5 } from "os";
|
|
621
|
+
import chalk2 from "chalk";
|
|
797
622
|
function getCurrentSha(cwd) {
|
|
798
623
|
return execSync3("git rev-parse HEAD", { cwd, encoding: "utf-8" }).trim();
|
|
799
624
|
}
|
|
@@ -1106,8 +931,28 @@ async function runCritic(ctx, task, logDir, attempt, claudeJsonSrc, claudeSnapsh
|
|
|
1106
931
|
diffFile: diffOutputFile
|
|
1107
932
|
};
|
|
1108
933
|
}
|
|
934
|
+
var init_orchestrator = __esm({
|
|
935
|
+
"src/engine/orchestrator.ts"() {
|
|
936
|
+
"use strict";
|
|
937
|
+
init_tasks();
|
|
938
|
+
init_status_update();
|
|
939
|
+
init_builder();
|
|
940
|
+
init_runner();
|
|
941
|
+
init_queries();
|
|
942
|
+
}
|
|
943
|
+
});
|
|
1109
944
|
|
|
1110
945
|
// src/commands/run.ts
|
|
946
|
+
var run_exports = {};
|
|
947
|
+
__export(run_exports, {
|
|
948
|
+
registerRun: () => registerRun,
|
|
949
|
+
runAllProjects: () => runAllProjects
|
|
950
|
+
});
|
|
951
|
+
import { join as join7 } from "path";
|
|
952
|
+
import { homedir as homedir6 } from "os";
|
|
953
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
954
|
+
import { spawn, execSync as execSync4 } from "child_process";
|
|
955
|
+
import chalk3 from "chalk";
|
|
1111
956
|
function generateRunId() {
|
|
1112
957
|
const now = /* @__PURE__ */ new Date();
|
|
1113
958
|
const pad3 = (n) => String(n).padStart(2, "0");
|
|
@@ -1404,11 +1249,271 @@ function registerRun(program2) {
|
|
|
1404
1249
|
}
|
|
1405
1250
|
);
|
|
1406
1251
|
}
|
|
1252
|
+
var init_run = __esm({
|
|
1253
|
+
"src/commands/run.ts"() {
|
|
1254
|
+
"use strict";
|
|
1255
|
+
init_db();
|
|
1256
|
+
init_queries();
|
|
1257
|
+
init_config();
|
|
1258
|
+
init_config();
|
|
1259
|
+
init_auth();
|
|
1260
|
+
init_orchestrator();
|
|
1261
|
+
init_tasks();
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
// src/index.ts
|
|
1266
|
+
import { createRequire } from "module";
|
|
1267
|
+
import { Command } from "commander";
|
|
1268
|
+
import chalk14 from "chalk";
|
|
1269
|
+
|
|
1270
|
+
// src/brand.ts
|
|
1271
|
+
var BANNER = (version2) => ` ,___,
|
|
1272
|
+
[O.O] noxdev v${version2}
|
|
1273
|
+
/) )\\ ship code while you sleep
|
|
1274
|
+
" \\|/ "
|
|
1275
|
+
---m-m---`;
|
|
1276
|
+
|
|
1277
|
+
// src/commands/init.ts
|
|
1278
|
+
init_db();
|
|
1279
|
+
import { execSync } from "child_process";
|
|
1280
|
+
import {
|
|
1281
|
+
existsSync,
|
|
1282
|
+
readFileSync,
|
|
1283
|
+
mkdirSync as mkdirSync2,
|
|
1284
|
+
writeFileSync
|
|
1285
|
+
} from "fs";
|
|
1286
|
+
import { join as join2, resolve } from "path";
|
|
1287
|
+
import { homedir as homedir2 } from "os";
|
|
1288
|
+
import chalk from "chalk";
|
|
1289
|
+
import ora from "ora";
|
|
1290
|
+
function detectCommands(repoPath) {
|
|
1291
|
+
const defaults = {
|
|
1292
|
+
test_command: "pnpm test",
|
|
1293
|
+
build_command: "pnpm build",
|
|
1294
|
+
lint_command: "pnpm lint"
|
|
1295
|
+
};
|
|
1296
|
+
const pkgPath = join2(repoPath, "package.json");
|
|
1297
|
+
if (!existsSync(pkgPath)) return defaults;
|
|
1298
|
+
try {
|
|
1299
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1300
|
+
const scripts = pkg.scripts ?? {};
|
|
1301
|
+
return {
|
|
1302
|
+
test_command: scripts.test ? `pnpm test` : defaults.test_command,
|
|
1303
|
+
build_command: scripts.build ? `pnpm build` : defaults.build_command,
|
|
1304
|
+
lint_command: scripts.lint ? `pnpm lint` : defaults.lint_command
|
|
1305
|
+
};
|
|
1306
|
+
} catch {
|
|
1307
|
+
return defaults;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
function registerInit(program2) {
|
|
1311
|
+
program2.command("init").description("Initialize a new project").argument("<project>", "project name").requiredOption("--repo <path>", "path to git repository").action(async (project, opts) => {
|
|
1312
|
+
try {
|
|
1313
|
+
await runInit(project, opts.repo);
|
|
1314
|
+
} catch (err) {
|
|
1315
|
+
console.error(
|
|
1316
|
+
chalk.red(
|
|
1317
|
+
`Error: ${err instanceof Error ? err.message : String(err)}`
|
|
1318
|
+
)
|
|
1319
|
+
);
|
|
1320
|
+
process.exitCode = 1;
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
async function runInit(project, repoPath) {
|
|
1325
|
+
const resolvedRepo = resolve(repoPath);
|
|
1326
|
+
const branch = `noxdev/${project}`;
|
|
1327
|
+
const worktreePath = join2(homedir2(), "worktrees", project);
|
|
1328
|
+
if (!existsSync(resolvedRepo)) {
|
|
1329
|
+
throw new Error(`Repository path does not exist: ${resolvedRepo}`);
|
|
1330
|
+
}
|
|
1331
|
+
const gitDir = join2(resolvedRepo, ".git");
|
|
1332
|
+
if (!existsSync(gitDir)) {
|
|
1333
|
+
throw new Error(
|
|
1334
|
+
`Not a git repository (missing .git): ${resolvedRepo}`
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
console.log(chalk.green("\u2713") + " Repository validated: " + resolvedRepo);
|
|
1338
|
+
try {
|
|
1339
|
+
execSync("git rev-parse HEAD", { cwd: resolvedRepo, stdio: "pipe" });
|
|
1340
|
+
} catch {
|
|
1341
|
+
console.log(chalk.yellow(" \u26A0 Empty repository detected. Creating initial commit..."));
|
|
1342
|
+
const readmePath = join2(resolvedRepo, "README.md");
|
|
1343
|
+
if (!existsSync(readmePath)) {
|
|
1344
|
+
writeFileSync(readmePath, `# ${project}
|
|
1345
|
+
`);
|
|
1346
|
+
}
|
|
1347
|
+
execSync("git add .", { cwd: resolvedRepo, stdio: "pipe" });
|
|
1348
|
+
try {
|
|
1349
|
+
execSync("git config user.name", { cwd: resolvedRepo, stdio: "pipe" });
|
|
1350
|
+
} catch {
|
|
1351
|
+
execSync('git config user.name "noxdev"', { cwd: resolvedRepo, stdio: "pipe" });
|
|
1352
|
+
}
|
|
1353
|
+
try {
|
|
1354
|
+
execSync("git config user.email", { cwd: resolvedRepo, stdio: "pipe" });
|
|
1355
|
+
} catch {
|
|
1356
|
+
execSync('git config user.email "noxdev@local"', { cwd: resolvedRepo, stdio: "pipe" });
|
|
1357
|
+
}
|
|
1358
|
+
execSync('git commit -m "init"', { cwd: resolvedRepo, stdio: "pipe" });
|
|
1359
|
+
console.log(chalk.green(" \u2713 Initial commit created"));
|
|
1360
|
+
}
|
|
1361
|
+
const spinnerWt = ora("Creating git worktree\u2026").start();
|
|
1362
|
+
try {
|
|
1363
|
+
const defaultBranch = execSync("git symbolic-ref --short HEAD", {
|
|
1364
|
+
cwd: resolvedRepo,
|
|
1365
|
+
encoding: "utf-8"
|
|
1366
|
+
}).trim();
|
|
1367
|
+
let branchExists = false;
|
|
1368
|
+
try {
|
|
1369
|
+
const result = execSync(`git branch --list ${branch}`, {
|
|
1370
|
+
cwd: resolvedRepo,
|
|
1371
|
+
encoding: "utf-8"
|
|
1372
|
+
}).trim();
|
|
1373
|
+
branchExists = result.length > 0;
|
|
1374
|
+
} catch {
|
|
1375
|
+
}
|
|
1376
|
+
let worktreeExisted = false;
|
|
1377
|
+
if (branchExists) {
|
|
1378
|
+
try {
|
|
1379
|
+
execSync(`git worktree add ${worktreePath} ${branch}`, {
|
|
1380
|
+
cwd: resolvedRepo,
|
|
1381
|
+
stdio: "pipe"
|
|
1382
|
+
});
|
|
1383
|
+
} catch (err) {
|
|
1384
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1385
|
+
if (msg.includes("already exists")) {
|
|
1386
|
+
worktreeExisted = true;
|
|
1387
|
+
} else {
|
|
1388
|
+
throw err;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
} else {
|
|
1392
|
+
try {
|
|
1393
|
+
execSync(
|
|
1394
|
+
`git worktree add -b ${branch} ${worktreePath} ${defaultBranch}`,
|
|
1395
|
+
{ cwd: resolvedRepo, stdio: "pipe" }
|
|
1396
|
+
);
|
|
1397
|
+
} catch (err) {
|
|
1398
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1399
|
+
if (msg.includes("already exists")) {
|
|
1400
|
+
worktreeExisted = true;
|
|
1401
|
+
} else {
|
|
1402
|
+
throw err;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
if (worktreeExisted) {
|
|
1407
|
+
spinnerWt.warn(`Worktree already exists at ${worktreePath}`);
|
|
1408
|
+
} else {
|
|
1409
|
+
spinnerWt.succeed(
|
|
1410
|
+
`Worktree created at ${chalk.cyan(worktreePath)}`
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
} catch (err) {
|
|
1414
|
+
spinnerWt.fail("Failed to create worktree");
|
|
1415
|
+
throw err;
|
|
1416
|
+
}
|
|
1417
|
+
const detected = detectCommands(resolvedRepo);
|
|
1418
|
+
const configDir = join2(resolvedRepo, ".noxdev");
|
|
1419
|
+
const configPath = join2(configDir, "config.json");
|
|
1420
|
+
const projectConfig = {
|
|
1421
|
+
project,
|
|
1422
|
+
display_name: project,
|
|
1423
|
+
test_command: detected.test_command,
|
|
1424
|
+
build_command: detected.build_command,
|
|
1425
|
+
lint_command: detected.lint_command,
|
|
1426
|
+
docker: {
|
|
1427
|
+
memory: "4g",
|
|
1428
|
+
cpus: 2,
|
|
1429
|
+
timeout_minutes: 30
|
|
1430
|
+
},
|
|
1431
|
+
secrets: "",
|
|
1432
|
+
tasks_file: "TASKS.md",
|
|
1433
|
+
critic_default: "strict",
|
|
1434
|
+
push_default: "never"
|
|
1435
|
+
};
|
|
1436
|
+
mkdirSync2(configDir, { recursive: true });
|
|
1437
|
+
writeFileSync(configPath, JSON.stringify(projectConfig, null, 2) + "\n");
|
|
1438
|
+
console.log(chalk.green("\u2713") + " Config written: " + configPath);
|
|
1439
|
+
const db = getDb();
|
|
1440
|
+
const existing = db.prepare("SELECT id FROM projects WHERE id = ?").get(project);
|
|
1441
|
+
if (existing) {
|
|
1442
|
+
console.log(
|
|
1443
|
+
chalk.yellow("\u26A0") + ` Project "${project}" already registered \u2014 updating`
|
|
1444
|
+
);
|
|
1445
|
+
db.prepare(
|
|
1446
|
+
`UPDATE projects
|
|
1447
|
+
SET repo_path = ?, worktree_path = ?, branch = ?,
|
|
1448
|
+
test_command = ?, build_command = ?, lint_command = ?,
|
|
1449
|
+
updated_at = datetime('now')
|
|
1450
|
+
WHERE id = ?`
|
|
1451
|
+
).run(
|
|
1452
|
+
resolvedRepo,
|
|
1453
|
+
worktreePath,
|
|
1454
|
+
branch,
|
|
1455
|
+
detected.test_command,
|
|
1456
|
+
detected.build_command,
|
|
1457
|
+
detected.lint_command,
|
|
1458
|
+
project
|
|
1459
|
+
);
|
|
1460
|
+
} else {
|
|
1461
|
+
db.prepare(
|
|
1462
|
+
`INSERT INTO projects (id, display_name, repo_path, worktree_path, branch,
|
|
1463
|
+
test_command, build_command, lint_command)
|
|
1464
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1465
|
+
).run(
|
|
1466
|
+
project,
|
|
1467
|
+
project,
|
|
1468
|
+
resolvedRepo,
|
|
1469
|
+
worktreePath,
|
|
1470
|
+
branch,
|
|
1471
|
+
detected.test_command,
|
|
1472
|
+
detected.build_command,
|
|
1473
|
+
detected.lint_command
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
console.log(chalk.green("\u2713") + ` Project "${project}" registered in database`);
|
|
1477
|
+
let dockerOk = false;
|
|
1478
|
+
try {
|
|
1479
|
+
const result = execSync("docker images -q noxdev-runner:latest", {
|
|
1480
|
+
encoding: "utf-8",
|
|
1481
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1482
|
+
}).trim();
|
|
1483
|
+
dockerOk = result.length > 0;
|
|
1484
|
+
} catch {
|
|
1485
|
+
}
|
|
1486
|
+
if (!dockerOk) {
|
|
1487
|
+
console.log(
|
|
1488
|
+
chalk.yellow("\u26A0") + " Docker image noxdev-runner:latest not found. Build it before running tasks."
|
|
1489
|
+
);
|
|
1490
|
+
} else {
|
|
1491
|
+
console.log(chalk.green("\u2713") + " Docker image noxdev-runner:latest found");
|
|
1492
|
+
}
|
|
1493
|
+
console.log("");
|
|
1494
|
+
console.log(chalk.bold("Project initialized:"));
|
|
1495
|
+
console.log(` Worktree: ${chalk.cyan(worktreePath)}`);
|
|
1496
|
+
console.log(` Branch: ${chalk.cyan(branch)}`);
|
|
1497
|
+
console.log(` Test: ${detected.test_command}`);
|
|
1498
|
+
console.log(` Build: ${detected.build_command}`);
|
|
1499
|
+
console.log(` Lint: ${detected.lint_command}`);
|
|
1500
|
+
console.log(` Config: ${configPath}`);
|
|
1501
|
+
console.log("");
|
|
1502
|
+
console.log(
|
|
1503
|
+
chalk.blue("\u2192") + ` Write tasks in ${worktreePath}/TASKS.md then run: ${chalk.bold(`noxdev run ${project}`)}`
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// src/index.ts
|
|
1508
|
+
init_run();
|
|
1407
1509
|
|
|
1408
1510
|
// src/commands/status.ts
|
|
1511
|
+
init_db();
|
|
1512
|
+
init_queries();
|
|
1409
1513
|
import chalk5 from "chalk";
|
|
1410
1514
|
|
|
1411
1515
|
// src/engine/summary.ts
|
|
1516
|
+
init_queries();
|
|
1412
1517
|
import chalk4 from "chalk";
|
|
1413
1518
|
function getAllProjectSummaries(db) {
|
|
1414
1519
|
const projects = getAllProjects(db);
|
|
@@ -1617,6 +1722,7 @@ function registerStatus(program2) {
|
|
|
1617
1722
|
}
|
|
1618
1723
|
|
|
1619
1724
|
// src/commands/log.ts
|
|
1725
|
+
init_db();
|
|
1620
1726
|
import chalk6 from "chalk";
|
|
1621
1727
|
function statusBadge2(status) {
|
|
1622
1728
|
switch (status) {
|
|
@@ -1709,10 +1815,13 @@ function registerLog(program2) {
|
|
|
1709
1815
|
}
|
|
1710
1816
|
|
|
1711
1817
|
// src/commands/merge.ts
|
|
1818
|
+
init_db();
|
|
1819
|
+
init_queries();
|
|
1712
1820
|
import chalk7 from "chalk";
|
|
1713
1821
|
import { createInterface } from "readline";
|
|
1714
1822
|
|
|
1715
1823
|
// src/merge/interactive.ts
|
|
1824
|
+
init_queries();
|
|
1716
1825
|
import { execSync as execSync5 } from "child_process";
|
|
1717
1826
|
function getMergeCandidates(db, projectId) {
|
|
1718
1827
|
const run = getLatestRun(db, projectId);
|
|
@@ -1721,8 +1830,28 @@ function getMergeCandidates(db, projectId) {
|
|
|
1721
1830
|
`SELECT id, task_id, title, status, commit_sha, diff_file
|
|
1722
1831
|
FROM task_results
|
|
1723
1832
|
WHERE run_id = ?
|
|
1724
|
-
AND status IN ('
|
|
1725
|
-
AND merge_decision = 'pending'
|
|
1833
|
+
AND UPPER(status) IN ('COMPLETED', 'COMPLETED_RETRY')
|
|
1834
|
+
AND LOWER(merge_decision) = 'pending'
|
|
1835
|
+
AND commit_sha IS NOT NULL`
|
|
1836
|
+
).all(run.id);
|
|
1837
|
+
return rows.map((r) => ({
|
|
1838
|
+
taskResultId: r.id,
|
|
1839
|
+
taskId: r.task_id,
|
|
1840
|
+
title: r.title,
|
|
1841
|
+
status: r.status,
|
|
1842
|
+
commitSha: r.commit_sha,
|
|
1843
|
+
diffFile: r.diff_file
|
|
1844
|
+
}));
|
|
1845
|
+
}
|
|
1846
|
+
function getAutoApprovedTasks(db, projectId) {
|
|
1847
|
+
const run = getLatestRun(db, projectId);
|
|
1848
|
+
if (!run) return [];
|
|
1849
|
+
const rows = db.prepare(
|
|
1850
|
+
`SELECT id, task_id, title, status, commit_sha, diff_file
|
|
1851
|
+
FROM task_results
|
|
1852
|
+
WHERE run_id = ?
|
|
1853
|
+
AND UPPER(status) IN ('COMPLETED', 'COMPLETED_RETRY')
|
|
1854
|
+
AND LOWER(merge_decision) = 'approved'
|
|
1726
1855
|
AND commit_sha IS NOT NULL`
|
|
1727
1856
|
).all(run.id);
|
|
1728
1857
|
return rows.map((r) => ({
|
|
@@ -1815,8 +1944,8 @@ function getBranchFromWorktree(worktreeDir) {
|
|
|
1815
1944
|
|
|
1816
1945
|
// src/commands/merge.ts
|
|
1817
1946
|
function askQuestion(rl, prompt) {
|
|
1818
|
-
return new Promise((
|
|
1819
|
-
rl.question(prompt, (answer) =>
|
|
1947
|
+
return new Promise((resolve3) => {
|
|
1948
|
+
rl.question(prompt, (answer) => resolve3(answer.trim().toLowerCase()));
|
|
1820
1949
|
});
|
|
1821
1950
|
}
|
|
1822
1951
|
async function reviewCandidate(rl, candidate, worktreeDir) {
|
|
@@ -1894,36 +2023,45 @@ function registerMerge(program2) {
|
|
|
1894
2023
|
console.log(chalk7.red(`Project not found: ${projectId}`));
|
|
1895
2024
|
return;
|
|
1896
2025
|
}
|
|
1897
|
-
const
|
|
1898
|
-
|
|
1899
|
-
|
|
2026
|
+
const autoApproved = getAutoApprovedTasks(db, projectId);
|
|
2027
|
+
const pendingCandidates = getMergeCandidates(db, projectId);
|
|
2028
|
+
if (autoApproved.length === 0 && pendingCandidates.length === 0) {
|
|
2029
|
+
console.log("No merge tasks.");
|
|
1900
2030
|
return;
|
|
1901
2031
|
}
|
|
1902
2032
|
const run = getLatestRun(db, projectId);
|
|
1903
2033
|
const runId = run?.id ?? "unknown";
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
2034
|
+
if (autoApproved.length > 0) {
|
|
2035
|
+
console.log(
|
|
2036
|
+
chalk7.green(` \u2713 ${autoApproved.length} auto-approved tasks (PUSH: auto)`)
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
1907
2039
|
const decisions = [];
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
decision
|
|
1914
|
-
|
|
2040
|
+
if (pendingCandidates.length > 0) {
|
|
2041
|
+
console.log(
|
|
2042
|
+
`Run ${runId}: ${chalk7.bold(String(pendingCandidates.length))} tasks pending review`
|
|
2043
|
+
);
|
|
2044
|
+
for (const candidate of pendingCandidates) {
|
|
2045
|
+
const decision = await reviewCandidate(rl, candidate, proj.worktree_path);
|
|
2046
|
+
decisions.push({
|
|
2047
|
+
taskResultId: candidate.taskResultId,
|
|
2048
|
+
taskId: candidate.taskId,
|
|
2049
|
+
decision
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
1915
2052
|
}
|
|
1916
|
-
const
|
|
2053
|
+
const interactiveApproved = decisions.filter((d) => d.decision === "approved").length;
|
|
1917
2054
|
const rejected = decisions.filter((d) => d.decision === "rejected").length;
|
|
1918
2055
|
const skipped = decisions.filter((d) => d.decision === "skipped").length;
|
|
2056
|
+
const totalApproved = autoApproved.length + interactiveApproved;
|
|
1919
2057
|
console.log("");
|
|
1920
2058
|
console.log(
|
|
1921
|
-
`Summary: ${chalk7.green(String(
|
|
2059
|
+
`Summary: ${autoApproved.length} auto-approved, ${chalk7.green(String(interactiveApproved))} approved, ${chalk7.red(String(rejected))} rejected, ${chalk7.gray(String(skipped))} skipped`
|
|
1922
2060
|
);
|
|
1923
|
-
if (
|
|
2061
|
+
if (totalApproved > 0) {
|
|
1924
2062
|
const confirm = await askQuestion(
|
|
1925
2063
|
rl,
|
|
1926
|
-
`Merge ${
|
|
2064
|
+
`Merge ${totalApproved} approved commits to main? [y/n] `
|
|
1927
2065
|
);
|
|
1928
2066
|
if (confirm === "y") {
|
|
1929
2067
|
const result = applyMergeDecisions(
|
|
@@ -1954,6 +2092,7 @@ function registerMerge(program2) {
|
|
|
1954
2092
|
}
|
|
1955
2093
|
|
|
1956
2094
|
// src/commands/projects.ts
|
|
2095
|
+
init_db();
|
|
1957
2096
|
import { existsSync as existsSync4, readFileSync as readFileSync7 } from "fs";
|
|
1958
2097
|
import { join as join8 } from "path";
|
|
1959
2098
|
import chalk8 from "chalk";
|
|
@@ -2143,8 +2282,8 @@ function registerDashboard(program2) {
|
|
|
2143
2282
|
process.on("SIGINT", cleanup);
|
|
2144
2283
|
process.on("SIGTERM", cleanup);
|
|
2145
2284
|
await Promise.race([
|
|
2146
|
-
new Promise((
|
|
2147
|
-
new Promise((
|
|
2285
|
+
new Promise((resolve3) => apiProcess.on("exit", () => resolve3())),
|
|
2286
|
+
new Promise((resolve3) => uiProcess.on("exit", () => resolve3()))
|
|
2148
2287
|
]);
|
|
2149
2288
|
cleanup();
|
|
2150
2289
|
}
|
|
@@ -2211,10 +2350,10 @@ function registerDoctor(program2) {
|
|
|
2211
2350
|
if (output) {
|
|
2212
2351
|
return { passed: true };
|
|
2213
2352
|
} else {
|
|
2214
|
-
return { passed: false, message: "noxdev-runner image not found.
|
|
2353
|
+
return { passed: false, message: "noxdev-runner image not found. Run: noxdev setup" };
|
|
2215
2354
|
}
|
|
2216
2355
|
} catch {
|
|
2217
|
-
return { passed: false, message: "noxdev-runner image not found.
|
|
2356
|
+
return { passed: false, message: "noxdev-runner image not found. Run: noxdev setup" };
|
|
2218
2357
|
}
|
|
2219
2358
|
}));
|
|
2220
2359
|
checks.push(runCheck("noxdev config directory", false, () => {
|
|
@@ -2222,13 +2361,13 @@ function registerDoctor(program2) {
|
|
|
2222
2361
|
if (existsSync6(configDir)) {
|
|
2223
2362
|
return { passed: true };
|
|
2224
2363
|
} else {
|
|
2225
|
-
return { passed: false, message: "No config directory. Run: noxdev
|
|
2364
|
+
return { passed: false, message: "No config directory. Run: noxdev setup" };
|
|
2226
2365
|
}
|
|
2227
2366
|
}));
|
|
2228
2367
|
checks.push(runCheck("SQLite database", false, () => {
|
|
2229
2368
|
const dbPath = join9(homedir7(), ".noxdev", "ledger.db");
|
|
2230
2369
|
if (!existsSync6(dbPath)) {
|
|
2231
|
-
return { passed: false, message: "No database. Run: noxdev
|
|
2370
|
+
return { passed: false, message: "No database. Run: noxdev setup" };
|
|
2232
2371
|
}
|
|
2233
2372
|
try {
|
|
2234
2373
|
const db = new Database2(dbPath, { readonly: true });
|
|
@@ -2236,7 +2375,7 @@ function registerDoctor(program2) {
|
|
|
2236
2375
|
db.close();
|
|
2237
2376
|
return { passed: true, message: `${result.count} projects registered` };
|
|
2238
2377
|
} catch {
|
|
2239
|
-
return { passed: false, message: "No database. Run: noxdev
|
|
2378
|
+
return { passed: false, message: "No database. Run: noxdev setup" };
|
|
2240
2379
|
}
|
|
2241
2380
|
}));
|
|
2242
2381
|
checks.push(runCheck("Git installed", true, () => {
|
|
@@ -2278,6 +2417,7 @@ ${passed}/${total} checks passed. ${criticalFailed ? "Issues found" : "Ready"}`)
|
|
|
2278
2417
|
}
|
|
2279
2418
|
|
|
2280
2419
|
// src/commands/remove.ts
|
|
2420
|
+
init_db();
|
|
2281
2421
|
import { execSync as execSync7 } from "child_process";
|
|
2282
2422
|
import { rmSync, existsSync as existsSync7 } from "fs";
|
|
2283
2423
|
import readline from "readline";
|
|
@@ -2287,10 +2427,10 @@ function askConfirmation(message) {
|
|
|
2287
2427
|
input: process.stdin,
|
|
2288
2428
|
output: process.stdout
|
|
2289
2429
|
});
|
|
2290
|
-
return new Promise((
|
|
2430
|
+
return new Promise((resolve3) => {
|
|
2291
2431
|
rl.question(message, (answer) => {
|
|
2292
2432
|
rl.close();
|
|
2293
|
-
|
|
2433
|
+
resolve3(answer.trim().toLowerCase() === "y");
|
|
2294
2434
|
});
|
|
2295
2435
|
});
|
|
2296
2436
|
}
|
|
@@ -2357,6 +2497,489 @@ async function runRemove(projectId, force) {
|
|
|
2357
2497
|
console.log(chalk11.green(`\u2713 Project "${projectId}" removed from noxdev.`));
|
|
2358
2498
|
}
|
|
2359
2499
|
|
|
2500
|
+
// src/commands/setup.ts
|
|
2501
|
+
import chalk12 from "chalk";
|
|
2502
|
+
import { execSync as execSync8, spawnSync } from "child_process";
|
|
2503
|
+
import fs2 from "fs";
|
|
2504
|
+
import os from "os";
|
|
2505
|
+
import path3 from "path";
|
|
2506
|
+
import { createInterface as createInterface2 } from "readline";
|
|
2507
|
+
|
|
2508
|
+
// src/lib/paths.ts
|
|
2509
|
+
import fs from "fs";
|
|
2510
|
+
import path2 from "path";
|
|
2511
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2512
|
+
var cachedRoot = null;
|
|
2513
|
+
function findCliRoot() {
|
|
2514
|
+
if (cachedRoot) return cachedRoot;
|
|
2515
|
+
const here = path2.dirname(fileURLToPath2(import.meta.url));
|
|
2516
|
+
let dir = here;
|
|
2517
|
+
while (dir !== "/" && dir !== ".") {
|
|
2518
|
+
const pkgPath = path2.join(dir, "package.json");
|
|
2519
|
+
if (fs.existsSync(pkgPath)) {
|
|
2520
|
+
try {
|
|
2521
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
2522
|
+
if (pkg.name === "@eugene218/noxdev" || pkg.name === "noxdev") {
|
|
2523
|
+
cachedRoot = dir;
|
|
2524
|
+
return dir;
|
|
2525
|
+
}
|
|
2526
|
+
} catch {
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
dir = path2.dirname(dir);
|
|
2530
|
+
}
|
|
2531
|
+
throw new Error(`Could not find noxdev CLI package root walking up from ${here}`);
|
|
2532
|
+
}
|
|
2533
|
+
function dockerfilePath() {
|
|
2534
|
+
return path2.join(findCliRoot(), "docker", "Dockerfile");
|
|
2535
|
+
}
|
|
2536
|
+
function demoTasksPath() {
|
|
2537
|
+
return path2.join(findCliRoot(), "templates", "demo-tasks.md");
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
// src/commands/setup.ts
|
|
2541
|
+
function registerSetup(program2) {
|
|
2542
|
+
program2.command("setup").description("Build Docker image and prepare noxdev for first use").option("--rebuild", "Force rebuild of Docker image even if it exists").option("--yes", "Skip confirmation prompts (for scripting)").action(async (opts) => {
|
|
2543
|
+
try {
|
|
2544
|
+
await runSetup(opts);
|
|
2545
|
+
} catch (err) {
|
|
2546
|
+
console.error(
|
|
2547
|
+
chalk12.red(
|
|
2548
|
+
`Error: ${err instanceof Error ? err.message : String(err)}`
|
|
2549
|
+
)
|
|
2550
|
+
);
|
|
2551
|
+
process.exitCode = 1;
|
|
2552
|
+
}
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
async function runSetup(opts = {}) {
|
|
2556
|
+
console.log(chalk12.bold("\n\u{1F989} noxdev setup\n"));
|
|
2557
|
+
console.log(chalk12.cyan("Checking prerequisites...\n"));
|
|
2558
|
+
const nodeVersion = process.versions.node;
|
|
2559
|
+
const major = parseInt(nodeVersion.split(".")[0]);
|
|
2560
|
+
if (major < 20) {
|
|
2561
|
+
console.error(chalk12.red(`\u2716 Node ${process.versions.node} is too old. noxdev requires Node 20 or newer.`));
|
|
2562
|
+
process.exit(1);
|
|
2563
|
+
}
|
|
2564
|
+
if (major >= 25) {
|
|
2565
|
+
console.error(chalk12.red(`\u2716 Node ${process.versions.node} is not supported.`));
|
|
2566
|
+
console.error(chalk12.gray(" noxdev depends on better-sqlite3 which lacks prebuilt binaries for Node 25+."));
|
|
2567
|
+
console.error(chalk12.gray(" Install Node 22 LTS: brew install node@22 (macOS) or nvm install 22"));
|
|
2568
|
+
process.exit(1);
|
|
2569
|
+
}
|
|
2570
|
+
if (major > 22) {
|
|
2571
|
+
console.warn(chalk12.yellow(`\u26A0 Node ${process.versions.node} is untested (supported: 20.x, 22.x LTS).`));
|
|
2572
|
+
console.warn(chalk12.gray(" Continuing anyway. Report issues at github.com/eugeneorlov/noxdev/issues"));
|
|
2573
|
+
}
|
|
2574
|
+
console.log(chalk12.green(`\u2713 Node ${nodeVersion} (supported)`));
|
|
2575
|
+
try {
|
|
2576
|
+
execSync8("docker --version", { stdio: "pipe" });
|
|
2577
|
+
console.log(chalk12.green("\u2713 Docker installed"));
|
|
2578
|
+
} catch {
|
|
2579
|
+
console.error(chalk12.red("\u2716 Docker not found. Install Docker Desktop: https://www.docker.com/products/docker-desktop/"));
|
|
2580
|
+
process.exit(1);
|
|
2581
|
+
}
|
|
2582
|
+
try {
|
|
2583
|
+
execSync8("docker ps", { stdio: "pipe" });
|
|
2584
|
+
console.log(chalk12.green("\u2713 Docker daemon running"));
|
|
2585
|
+
} catch {
|
|
2586
|
+
console.error(chalk12.red("\u2716 Docker daemon not running. Start Docker Desktop and try again."));
|
|
2587
|
+
process.exit(1);
|
|
2588
|
+
}
|
|
2589
|
+
try {
|
|
2590
|
+
execSync8("git --version", { stdio: "pipe" });
|
|
2591
|
+
console.log(chalk12.green("\u2713 Git installed"));
|
|
2592
|
+
} catch {
|
|
2593
|
+
console.error(chalk12.red("\u2716 Git not found. Install with: brew install git (macOS) or apt install git (Linux)"));
|
|
2594
|
+
process.exit(1);
|
|
2595
|
+
}
|
|
2596
|
+
try {
|
|
2597
|
+
execSync8("claude --version", { stdio: "pipe" });
|
|
2598
|
+
console.log(chalk12.green("\u2713 Claude Code CLI installed"));
|
|
2599
|
+
} catch {
|
|
2600
|
+
console.error(chalk12.red("\u2716 Claude Code CLI not found. Install with: npm install -g @anthropic-ai/claude-code"));
|
|
2601
|
+
process.exit(1);
|
|
2602
|
+
}
|
|
2603
|
+
if (!opts.yes) {
|
|
2604
|
+
console.log("\nThis will:");
|
|
2605
|
+
console.log(" \u2022 Build the noxdev-runner Docker image (~3-5 min)");
|
|
2606
|
+
console.log(" \u2022 Verify SOPS + age are installed");
|
|
2607
|
+
console.log(" \u2022 Create ~/.noxdev/ config directory");
|
|
2608
|
+
console.log(" \u2022 Run noxdev doctor\n");
|
|
2609
|
+
const rl = createInterface2({
|
|
2610
|
+
input: process.stdin,
|
|
2611
|
+
output: process.stdout
|
|
2612
|
+
});
|
|
2613
|
+
const answer = await new Promise((resolve3) => {
|
|
2614
|
+
rl.question("Continue? [Y/n] ", (answer2) => {
|
|
2615
|
+
rl.close();
|
|
2616
|
+
resolve3(answer2);
|
|
2617
|
+
});
|
|
2618
|
+
});
|
|
2619
|
+
if (answer.toLowerCase() === "n") {
|
|
2620
|
+
console.log("\nSetup cancelled.");
|
|
2621
|
+
process.exit(0);
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
const dockerfileLocation = dockerfilePath();
|
|
2625
|
+
if (!fs2.existsSync(dockerfileLocation)) {
|
|
2626
|
+
console.error(chalk12.red("\u2716 Dockerfile not found at " + dockerfileLocation));
|
|
2627
|
+
console.error(chalk12.gray(" This is a noxdev install bug. Reinstall with:"));
|
|
2628
|
+
console.error(chalk12.gray(" npm install -g @eugene218/noxdev"));
|
|
2629
|
+
process.exit(1);
|
|
2630
|
+
}
|
|
2631
|
+
let imageExists = false;
|
|
2632
|
+
try {
|
|
2633
|
+
execSync8("docker image inspect noxdev-runner:latest", { stdio: "pipe" });
|
|
2634
|
+
imageExists = true;
|
|
2635
|
+
} catch {
|
|
2636
|
+
imageExists = false;
|
|
2637
|
+
}
|
|
2638
|
+
if (imageExists && !opts.rebuild) {
|
|
2639
|
+
console.log(chalk12.green("\n\u2713 Docker image already exists (use --rebuild to force)"));
|
|
2640
|
+
} else {
|
|
2641
|
+
const dockerfileDir = path3.dirname(dockerfileLocation);
|
|
2642
|
+
console.log(chalk12.cyan("\nBuilding noxdev-runner image (this takes 3-5 minutes)...\n"));
|
|
2643
|
+
const result = spawnSync("docker", ["build", "-t", "noxdev-runner:latest", dockerfileDir], {
|
|
2644
|
+
stdio: "inherit"
|
|
2645
|
+
});
|
|
2646
|
+
if (result.status !== 0) {
|
|
2647
|
+
console.error(chalk12.red("\n\u2716 Docker build failed."));
|
|
2648
|
+
process.exit(1);
|
|
2649
|
+
}
|
|
2650
|
+
console.log(chalk12.green("\n\u2713 Docker image built successfully"));
|
|
2651
|
+
}
|
|
2652
|
+
let sopsInstalled = false;
|
|
2653
|
+
let ageInstalled = false;
|
|
2654
|
+
try {
|
|
2655
|
+
execSync8("sops --version", { stdio: "pipe" });
|
|
2656
|
+
sopsInstalled = true;
|
|
2657
|
+
} catch {
|
|
2658
|
+
}
|
|
2659
|
+
try {
|
|
2660
|
+
execSync8("age --version", { stdio: "pipe" });
|
|
2661
|
+
ageInstalled = true;
|
|
2662
|
+
} catch {
|
|
2663
|
+
}
|
|
2664
|
+
if (sopsInstalled && ageInstalled) {
|
|
2665
|
+
console.log(chalk12.green("\n\u2713 SOPS and age installed"));
|
|
2666
|
+
} else if (!sopsInstalled && !ageInstalled) {
|
|
2667
|
+
console.log(chalk12.yellow("\n\u26A0 SOPS and age not installed (optional, for encrypted secrets)"));
|
|
2668
|
+
if (process.platform === "darwin") {
|
|
2669
|
+
console.log(chalk12.gray(" Install with: brew install sops age"));
|
|
2670
|
+
} else if (process.platform === "linux") {
|
|
2671
|
+
console.log(chalk12.gray(" Install with: apt install sops age (or download from GitHub)"));
|
|
2672
|
+
} else {
|
|
2673
|
+
console.log(chalk12.gray(" Install with: choco install sops age (or download from GitHub)"));
|
|
2674
|
+
}
|
|
2675
|
+
} else if (!sopsInstalled) {
|
|
2676
|
+
console.log(chalk12.yellow("\n\u26A0 SOPS not installed (optional, for encrypted secrets)"));
|
|
2677
|
+
if (process.platform === "darwin") {
|
|
2678
|
+
console.log(chalk12.gray(" Install with: brew install sops"));
|
|
2679
|
+
}
|
|
2680
|
+
} else if (!ageInstalled) {
|
|
2681
|
+
console.log(chalk12.yellow("\n\u26A0 age not installed (optional, for encrypted secrets)"));
|
|
2682
|
+
if (process.platform === "darwin") {
|
|
2683
|
+
console.log(chalk12.gray(" Install with: brew install age"));
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
const noxdevDir = path3.join(os.homedir(), ".noxdev");
|
|
2687
|
+
if (!fs2.existsSync(noxdevDir)) {
|
|
2688
|
+
fs2.mkdirSync(noxdevDir, { recursive: true });
|
|
2689
|
+
console.log(chalk12.green("\n\u2713 Created ~/.noxdev/"));
|
|
2690
|
+
} else {
|
|
2691
|
+
console.log(chalk12.green("\n\u2713 ~/.noxdev/ already exists"));
|
|
2692
|
+
}
|
|
2693
|
+
console.log(chalk12.bold.green("\n\u2705 Setup complete.\n"));
|
|
2694
|
+
console.log("Next steps:");
|
|
2695
|
+
console.log(" noxdev demo # See noxdev build a project autonomously");
|
|
2696
|
+
console.log(" noxdev init <name> --repo . # Register an existing project\n");
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
// src/commands/demo.ts
|
|
2700
|
+
init_db();
|
|
2701
|
+
import { execSync as execSync9 } from "child_process";
|
|
2702
|
+
import {
|
|
2703
|
+
existsSync as existsSync8,
|
|
2704
|
+
mkdirSync as mkdirSync5,
|
|
2705
|
+
writeFileSync as writeFileSync5,
|
|
2706
|
+
copyFileSync as copyFileSync2,
|
|
2707
|
+
rmSync as rmSync2
|
|
2708
|
+
} from "fs";
|
|
2709
|
+
import { join as join10 } from "path";
|
|
2710
|
+
import { homedir as homedir8, tmpdir as tmpdir2 } from "os";
|
|
2711
|
+
import chalk13 from "chalk";
|
|
2712
|
+
import ora2 from "ora";
|
|
2713
|
+
function dumpErr(err) {
|
|
2714
|
+
if (err && typeof err === "object") {
|
|
2715
|
+
const e = err;
|
|
2716
|
+
if (e.stderr?.length) {
|
|
2717
|
+
console.error(chalk13.gray(" \u2500 stderr \u2500"));
|
|
2718
|
+
console.error(chalk13.gray(" " + e.stderr.toString().trim().replace(/\n/g, "\n ")));
|
|
2719
|
+
}
|
|
2720
|
+
if (e.stdout?.length) {
|
|
2721
|
+
console.error(chalk13.gray(" \u2500 stdout \u2500"));
|
|
2722
|
+
console.error(chalk13.gray(" " + e.stdout.toString().trim().replace(/\n/g, "\n ")));
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
function registerDemo(program2) {
|
|
2727
|
+
program2.command("demo").description("Scaffold a fresh Vite + React + TypeScript project and run noxdev demo tasks").option("--fresh", "Clean up any existing noxdev-demo project first").action(async (opts) => {
|
|
2728
|
+
try {
|
|
2729
|
+
await runDemo(opts);
|
|
2730
|
+
} catch (err) {
|
|
2731
|
+
console.error(
|
|
2732
|
+
chalk13.red(
|
|
2733
|
+
`Error: ${err instanceof Error ? err.message : String(err)}`
|
|
2734
|
+
)
|
|
2735
|
+
);
|
|
2736
|
+
process.exitCode = 1;
|
|
2737
|
+
}
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
async function runDemo(opts = {}) {
|
|
2741
|
+
console.log(chalk13.bold("\n\u{1F989} noxdev demo\n"));
|
|
2742
|
+
const projectName = "noxdev-demo";
|
|
2743
|
+
const tempDir = join10(tmpdir2(), projectName);
|
|
2744
|
+
const worktreePath = join10(homedir8(), "worktrees", projectName);
|
|
2745
|
+
const branch = `noxdev/${projectName}`;
|
|
2746
|
+
if (opts.fresh) {
|
|
2747
|
+
console.log(chalk13.yellow("\u{1F9F9} Cleaning up existing demo project..."));
|
|
2748
|
+
const db2 = getDb();
|
|
2749
|
+
const existing2 = db2.prepare("SELECT id FROM projects WHERE id = ?").get(projectName);
|
|
2750
|
+
if (existing2) {
|
|
2751
|
+
db2.prepare("DELETE FROM projects WHERE id = ?").run(projectName);
|
|
2752
|
+
console.log(chalk13.gray(" \u2713 Removed project from database"));
|
|
2753
|
+
}
|
|
2754
|
+
if (existsSync8(worktreePath)) {
|
|
2755
|
+
rmSync2(worktreePath, { recursive: true, force: true });
|
|
2756
|
+
console.log(chalk13.gray(" \u2713 Removed worktree directory"));
|
|
2757
|
+
}
|
|
2758
|
+
if (existsSync8(tempDir)) {
|
|
2759
|
+
rmSync2(tempDir, { recursive: true, force: true });
|
|
2760
|
+
console.log(chalk13.gray(" \u2713 Removed temporary directory"));
|
|
2761
|
+
}
|
|
2762
|
+
console.log(chalk13.green("\u2713 Cleanup complete\n"));
|
|
2763
|
+
}
|
|
2764
|
+
console.log(chalk13.cyan("This demo will:"));
|
|
2765
|
+
console.log(chalk13.gray(" \u2022 Scaffold a fresh Vite + React + TypeScript project"));
|
|
2766
|
+
console.log(chalk13.gray(" \u2022 Register it with noxdev"));
|
|
2767
|
+
console.log(chalk13.gray(" \u2022 Copy demo tasks and run them autonomously"));
|
|
2768
|
+
console.log(chalk13.gray(" \u2022 Show you the completed result\n"));
|
|
2769
|
+
console.log(chalk13.bold("Step 1: Checking prerequisites"));
|
|
2770
|
+
let dockerOk = false;
|
|
2771
|
+
try {
|
|
2772
|
+
const result = execSync9("docker images -q noxdev-runner:latest", {
|
|
2773
|
+
encoding: "utf-8",
|
|
2774
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2775
|
+
}).trim();
|
|
2776
|
+
dockerOk = result.length > 0;
|
|
2777
|
+
} catch {
|
|
2778
|
+
}
|
|
2779
|
+
if (!dockerOk) {
|
|
2780
|
+
console.error(chalk13.red("\u2716 Docker image noxdev-runner:latest not found."));
|
|
2781
|
+
console.error(chalk13.yellow(" Run `noxdev setup` first to build the Docker image."));
|
|
2782
|
+
process.exit(1);
|
|
2783
|
+
}
|
|
2784
|
+
console.log(chalk13.green("\u2713 Docker image noxdev-runner:latest found"));
|
|
2785
|
+
const db = getDb();
|
|
2786
|
+
const existing = db.prepare("SELECT id FROM projects WHERE id = ?").get(projectName);
|
|
2787
|
+
if (existing) {
|
|
2788
|
+
console.log(chalk13.yellow(`\u26A0 Project "${projectName}" already exists.`));
|
|
2789
|
+
console.log(chalk13.gray(" Use --fresh to clean up and start over."));
|
|
2790
|
+
console.log(chalk13.gray(` Or run: noxdev run ${projectName}`));
|
|
2791
|
+
return;
|
|
2792
|
+
}
|
|
2793
|
+
console.log(chalk13.bold("\nStep 2: Scaffolding Vite + React + TypeScript project"));
|
|
2794
|
+
const spinner = ora2("Creating Vite project...").start();
|
|
2795
|
+
try {
|
|
2796
|
+
execSync9(`npm create vite@latest ${projectName} -- --template react-ts`, {
|
|
2797
|
+
cwd: tmpdir2(),
|
|
2798
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2799
|
+
});
|
|
2800
|
+
spinner.succeed("Vite project scaffolded");
|
|
2801
|
+
} catch (err) {
|
|
2802
|
+
spinner.fail("Failed to scaffold Vite project");
|
|
2803
|
+
dumpErr(err);
|
|
2804
|
+
throw err;
|
|
2805
|
+
}
|
|
2806
|
+
console.log(chalk13.bold("\nStep 3: Initializing git repository"));
|
|
2807
|
+
const gitSpinner = ora2("Setting up git...").start();
|
|
2808
|
+
try {
|
|
2809
|
+
execSync9("git init", { cwd: tempDir, stdio: ["pipe", "pipe", "pipe"] });
|
|
2810
|
+
execSync9('git config user.name "noxdev"', { cwd: tempDir, stdio: ["pipe", "pipe", "pipe"] });
|
|
2811
|
+
execSync9('git config user.email "noxdev@demo"', { cwd: tempDir, stdio: ["pipe", "pipe", "pipe"] });
|
|
2812
|
+
execSync9("git add .", { cwd: tempDir, stdio: ["pipe", "pipe", "pipe"] });
|
|
2813
|
+
execSync9('git commit -m "Initial Vite scaffold"', { cwd: tempDir, stdio: ["pipe", "pipe", "pipe"] });
|
|
2814
|
+
gitSpinner.succeed("Git repository initialized");
|
|
2815
|
+
} catch (err) {
|
|
2816
|
+
gitSpinner.fail("Failed to initialize git repository");
|
|
2817
|
+
dumpErr(err);
|
|
2818
|
+
throw err;
|
|
2819
|
+
}
|
|
2820
|
+
console.log(chalk13.bold("\nStep 4: Registering project with noxdev"));
|
|
2821
|
+
const registerSpinner = ora2("Creating worktree and registering project...").start();
|
|
2822
|
+
try {
|
|
2823
|
+
const defaultBranch = execSync9("git symbolic-ref --short HEAD", {
|
|
2824
|
+
cwd: tempDir,
|
|
2825
|
+
encoding: "utf-8"
|
|
2826
|
+
}).trim();
|
|
2827
|
+
execSync9(`git worktree add -b ${branch} ${worktreePath} ${defaultBranch}`, {
|
|
2828
|
+
cwd: tempDir,
|
|
2829
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2830
|
+
});
|
|
2831
|
+
const projectConfig = {
|
|
2832
|
+
project: projectName,
|
|
2833
|
+
display_name: projectName,
|
|
2834
|
+
test_command: "pnpm test",
|
|
2835
|
+
build_command: "pnpm build",
|
|
2836
|
+
lint_command: "pnpm lint",
|
|
2837
|
+
docker: {
|
|
2838
|
+
memory: "4g",
|
|
2839
|
+
cpus: 2,
|
|
2840
|
+
timeout_minutes: 30
|
|
2841
|
+
},
|
|
2842
|
+
secrets: "",
|
|
2843
|
+
tasks_file: "TASKS.md",
|
|
2844
|
+
critic_default: "strict",
|
|
2845
|
+
push_default: "never"
|
|
2846
|
+
};
|
|
2847
|
+
const configDir = join10(tempDir, ".noxdev");
|
|
2848
|
+
const configPath = join10(configDir, "config.json");
|
|
2849
|
+
mkdirSync5(configDir, { recursive: true });
|
|
2850
|
+
writeFileSync5(configPath, JSON.stringify(projectConfig, null, 2) + "\n");
|
|
2851
|
+
db.prepare(
|
|
2852
|
+
`INSERT INTO projects (id, display_name, repo_path, worktree_path, branch,
|
|
2853
|
+
test_command, build_command, lint_command)
|
|
2854
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
2855
|
+
).run(
|
|
2856
|
+
projectName,
|
|
2857
|
+
projectName,
|
|
2858
|
+
tempDir,
|
|
2859
|
+
worktreePath,
|
|
2860
|
+
branch,
|
|
2861
|
+
"pnpm test",
|
|
2862
|
+
"pnpm build",
|
|
2863
|
+
"pnpm lint"
|
|
2864
|
+
);
|
|
2865
|
+
registerSpinner.succeed("Project registered with noxdev");
|
|
2866
|
+
} catch (err) {
|
|
2867
|
+
registerSpinner.fail("Failed to register project");
|
|
2868
|
+
dumpErr(err);
|
|
2869
|
+
throw err;
|
|
2870
|
+
}
|
|
2871
|
+
console.log(chalk13.bold("\nStep 5: Setting up demo tasks"));
|
|
2872
|
+
const tasksSpinner = ora2("Copying demo tasks template...").start();
|
|
2873
|
+
try {
|
|
2874
|
+
const targetTasksPath = join10(worktreePath, "TASKS.md");
|
|
2875
|
+
copyFileSync2(demoTasksPath(), targetTasksPath);
|
|
2876
|
+
tasksSpinner.succeed("Demo tasks copied");
|
|
2877
|
+
} catch (err) {
|
|
2878
|
+
tasksSpinner.fail("Failed to copy demo tasks");
|
|
2879
|
+
dumpErr(err);
|
|
2880
|
+
throw err;
|
|
2881
|
+
}
|
|
2882
|
+
console.log(chalk13.bold("\nStep 6: Installing dependencies"));
|
|
2883
|
+
const depsSpinner = ora2("Installing dependencies with pnpm...").start();
|
|
2884
|
+
try {
|
|
2885
|
+
execSync9("pnpm install", { cwd: worktreePath, stdio: ["pipe", "pipe", "pipe"] });
|
|
2886
|
+
depsSpinner.succeed("Dependencies installed");
|
|
2887
|
+
} catch (err) {
|
|
2888
|
+
depsSpinner.fail("Failed to install dependencies");
|
|
2889
|
+
dumpErr(err);
|
|
2890
|
+
throw err;
|
|
2891
|
+
}
|
|
2892
|
+
console.log(chalk13.bold("\nStep 7: Running noxdev demo tasks autonomously"));
|
|
2893
|
+
console.log(chalk13.cyan("\u{1F989} Launching autonomous agent...\n"));
|
|
2894
|
+
try {
|
|
2895
|
+
const { runAllProjects: runAllProjects2 } = await Promise.resolve().then(() => (init_run(), run_exports));
|
|
2896
|
+
const projectRow = {
|
|
2897
|
+
id: projectName,
|
|
2898
|
+
display_name: projectName,
|
|
2899
|
+
repo_path: tempDir,
|
|
2900
|
+
worktree_path: worktreePath,
|
|
2901
|
+
branch
|
|
2902
|
+
};
|
|
2903
|
+
await runSingleProject(projectRow);
|
|
2904
|
+
} catch (err) {
|
|
2905
|
+
console.error(chalk13.red("Failed to run noxdev tasks:"), err instanceof Error ? err.message : String(err));
|
|
2906
|
+
dumpErr(err);
|
|
2907
|
+
throw err;
|
|
2908
|
+
}
|
|
2909
|
+
console.log(chalk13.bold("\n\u{1F389} Demo complete!"));
|
|
2910
|
+
console.log("");
|
|
2911
|
+
console.log(chalk13.green("\u2713 Vite + React + TypeScript project scaffolded"));
|
|
2912
|
+
console.log(chalk13.green("\u2713 Git repository initialized"));
|
|
2913
|
+
console.log(chalk13.green("\u2713 Project registered with noxdev"));
|
|
2914
|
+
console.log(chalk13.green("\u2713 Demo tasks executed autonomously"));
|
|
2915
|
+
console.log("");
|
|
2916
|
+
console.log(chalk13.bold("What happened:"));
|
|
2917
|
+
console.log(chalk13.gray(" \u2022 noxdev read the task specifications in TASKS.md"));
|
|
2918
|
+
console.log(chalk13.gray(" \u2022 Claude Code built the welcome page according to specs"));
|
|
2919
|
+
console.log(chalk13.gray(" \u2022 All changes were committed automatically"));
|
|
2920
|
+
console.log("");
|
|
2921
|
+
console.log(chalk13.bold("Next steps:"));
|
|
2922
|
+
console.log(chalk13.gray(` \u2022 View the result: cd ${worktreePath} && pnpm dev`));
|
|
2923
|
+
console.log(chalk13.gray(` \u2022 See the tasks: cat ${worktreePath}/TASKS.md`));
|
|
2924
|
+
console.log(chalk13.gray(` \u2022 Run more tasks: noxdev run ${projectName}`));
|
|
2925
|
+
console.log(chalk13.gray(" \u2022 Review changes: noxdev dashboard"));
|
|
2926
|
+
console.log("");
|
|
2927
|
+
console.log(chalk13.cyan("\u{1F989} Welcome to autonomous development with noxdev!"));
|
|
2928
|
+
}
|
|
2929
|
+
async function runSingleProject(project) {
|
|
2930
|
+
const { loadGlobalConfig: loadGlobalConfig2, loadProjectConfig: loadProjectConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
2931
|
+
const { resolveAuth: resolveAuth2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
2932
|
+
const { executeRun: executeRun2 } = await Promise.resolve().then(() => (init_orchestrator(), orchestrator_exports));
|
|
2933
|
+
const db = getDb();
|
|
2934
|
+
const globalConfig = loadGlobalConfig2();
|
|
2935
|
+
const projectConfig = loadProjectConfig2(project.repo_path);
|
|
2936
|
+
const auth = resolveAuth2({
|
|
2937
|
+
max: { preferred: globalConfig.accounts.max.preferred },
|
|
2938
|
+
api: {
|
|
2939
|
+
fallback: globalConfig.accounts.api.fallback,
|
|
2940
|
+
dailyCapUsd: globalConfig.accounts.api.daily_cap_usd,
|
|
2941
|
+
model: globalConfig.accounts.api.model
|
|
2942
|
+
},
|
|
2943
|
+
secrets: {
|
|
2944
|
+
provider: globalConfig.secrets.provider,
|
|
2945
|
+
globalSecretsFile: globalConfig.secrets.global,
|
|
2946
|
+
ageKeyFile: globalConfig.secrets.age_key
|
|
2947
|
+
}
|
|
2948
|
+
});
|
|
2949
|
+
const runId = generateRunId2();
|
|
2950
|
+
const gitDir = join10(project.repo_path, ".git");
|
|
2951
|
+
const ctx = {
|
|
2952
|
+
projectId: project.id,
|
|
2953
|
+
projectConfig,
|
|
2954
|
+
worktreeDir: project.worktree_path,
|
|
2955
|
+
projectGitDir: gitDir,
|
|
2956
|
+
gitTargetPath: gitDir,
|
|
2957
|
+
runId,
|
|
2958
|
+
db,
|
|
2959
|
+
auth
|
|
2960
|
+
};
|
|
2961
|
+
await executeRun2(ctx);
|
|
2962
|
+
try {
|
|
2963
|
+
execSync9("git add TASKS.md", { cwd: project.worktree_path, stdio: ["pipe", "pipe", "pipe"] });
|
|
2964
|
+
execSync9('git commit -m "noxdev: update task statuses"', { cwd: project.worktree_path, stdio: ["pipe", "pipe", "pipe"] });
|
|
2965
|
+
console.log(chalk13.gray(" \u2713 TASKS.md status updates committed"));
|
|
2966
|
+
} catch {
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
function generateRunId2() {
|
|
2970
|
+
const now = /* @__PURE__ */ new Date();
|
|
2971
|
+
const pad3 = (n) => String(n).padStart(2, "0");
|
|
2972
|
+
return [
|
|
2973
|
+
now.getFullYear(),
|
|
2974
|
+
pad3(now.getMonth() + 1),
|
|
2975
|
+
pad3(now.getDate()),
|
|
2976
|
+
"_",
|
|
2977
|
+
pad3(now.getHours()),
|
|
2978
|
+
pad3(now.getMinutes()),
|
|
2979
|
+
pad3(now.getSeconds())
|
|
2980
|
+
].join("");
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2360
2983
|
// src/index.ts
|
|
2361
2984
|
var require2 = createRequire(import.meta.url);
|
|
2362
2985
|
var { version } = require2("../package.json");
|
|
@@ -2371,10 +2994,12 @@ registerProjects(program);
|
|
|
2371
2994
|
registerDashboard(program);
|
|
2372
2995
|
registerDoctor(program);
|
|
2373
2996
|
registerRemove(program);
|
|
2997
|
+
registerSetup(program);
|
|
2998
|
+
registerDemo(program);
|
|
2374
2999
|
var args = process.argv.slice(2);
|
|
2375
3000
|
var hasNoSubcommand = args.length === 0 || args.length === 1 && args[0].startsWith("-");
|
|
2376
3001
|
if (hasNoSubcommand && !args.includes("--version") && !args.includes("-V")) {
|
|
2377
|
-
console.log(
|
|
3002
|
+
console.log(chalk14.hex("#C9A84C")(BANNER(version)));
|
|
2378
3003
|
console.log();
|
|
2379
3004
|
}
|
|
2380
3005
|
program.parse();
|