@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/dist/index.js CHANGED
@@ -1,38 +1,23 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/index.ts
4
- import { createRequire } from "module";
5
- import { Command } from "commander";
6
- import chalk12 from "chalk";
7
-
8
- // src/brand.ts
9
- var BANNER = (version2) => ` ,___,
10
- [O.O] noxdev v${version2}
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
- var SCHEMA = `
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
- function migrate(db) {
109
- db.exec(SCHEMA);
110
- }
93
+ }
94
+ });
111
95
 
112
96
  // src/db/index.ts
113
- var DB_DIR = join(homedir(), ".noxdev");
114
- var DB_PATH = join(DB_DIR, "ledger.db");
115
- var _db;
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
- // src/commands/init.ts
127
- function detectCommands(repoPath) {
128
- const defaults = {
129
- test_command: "pnpm test",
130
- build_command: "pnpm build",
131
- lint_command: "pnpm lint"
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
- const detected = detectCommands(resolvedRepo);
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 = ?, merged_at = ? WHERE id = ?`
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
- // src/engine/orchestrator.ts
578
- import { execSync as execSync3 } from "child_process";
579
- import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, unlinkSync, readFileSync as readFileSync6, copyFileSync, existsSync as existsSync3 } from "fs";
580
- import { join as join6 } from "path";
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 ('completed', 'completed_retry')
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((resolve2) => {
1819
- rl.question(prompt, (answer) => resolve2(answer.trim().toLowerCase()));
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 candidates = getMergeCandidates(db, projectId);
1898
- if (candidates.length === 0) {
1899
- console.log("No pending merge tasks.");
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
- console.log(
1905
- `Run ${runId}: ${chalk7.bold(String(candidates.length))} tasks pending review`
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
- for (const candidate of candidates) {
1909
- const decision = await reviewCandidate(rl, candidate, proj.worktree_path);
1910
- decisions.push({
1911
- taskResultId: candidate.taskResultId,
1912
- taskId: candidate.taskId,
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 approved = decisions.filter((d) => d.decision === "approved").length;
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(approved))} approved, ${chalk7.red(String(rejected))} rejected, ${chalk7.gray(String(skipped))} skipped`
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 (approved > 0) {
2061
+ if (totalApproved > 0) {
1924
2062
  const confirm = await askQuestion(
1925
2063
  rl,
1926
- `Merge ${approved} approved commits to main? [y/n] `
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((resolve2) => apiProcess.on("exit", () => resolve2())),
2147
- new Promise((resolve2) => uiProcess.on("exit", () => resolve2()))
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. Build it with: docker build -t noxdev-runner ." };
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. Build it with: docker build -t noxdev-runner ." };
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 init <project>" };
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 init <project>" };
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 init <project>" };
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((resolve2) => {
2430
+ return new Promise((resolve3) => {
2291
2431
  rl.question(message, (answer) => {
2292
2432
  rl.close();
2293
- resolve2(answer.trim().toLowerCase() === "y");
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(chalk12.hex("#C9A84C")(BANNER(version)));
3002
+ console.log(chalk14.hex("#C9A84C")(BANNER(version)));
2378
3003
  console.log();
2379
3004
  }
2380
3005
  program.parse();