nightytidy 0.2.6 → 0.2.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nightytidy",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Automated overnight codebase improvement through Claude Code",
5
5
  "license": "MIT",
6
6
  "author": "Dorian Spitz",
@@ -12,6 +12,7 @@ import { WebhookDispatcher } from './webhook-dispatcher.js';
12
12
  import { CliBridge } from './cli-bridge.js';
13
13
  import { AgentGit } from './git-integration.js';
14
14
  import { FirebaseAuth } from './firebase-auth.js';
15
+ import { acquireKeepAwake, releaseKeepAwake } from './keep-awake.js';
15
16
 
16
17
  const FIREBASE_WEBHOOK_URL = 'https://webhookingest-24h6taciuq-uc.a.run.app';
17
18
 
@@ -371,6 +372,7 @@ export async function startAgent() {
371
372
  run: { id: interrupted.id },
372
373
  }, []);
373
374
  reply({ type: 'interrupted-discarded', runId: interrupted.id });
375
+ if (!runQueue.getCurrent()) processQueue();
374
376
  break;
375
377
  }
376
378
 
@@ -479,7 +481,10 @@ export async function startAgent() {
479
481
 
480
482
  async function processQueue() {
481
483
  const run = runQueue.dequeue();
482
- if (!run) return;
484
+ if (!run) {
485
+ releaseKeepAwake();
486
+ return;
487
+ }
483
488
 
484
489
  const project = projectManager.getProject(run.projectId);
485
490
  if (!project) {
@@ -497,6 +502,7 @@ export async function startAgent() {
497
502
  runOutputBuffer = '';
498
503
  runProgress = { stepList: [], completedCount: 0, failedCount: 0, totalCost: 0, currentStepNum: null };
499
504
 
505
+ acquireKeepAwake();
500
506
  info(`\n━━━ Run started: ${project.name} ━━━`);
501
507
  info(` Steps: [${run.steps.join(', ')}] (${run.steps.length} total)`);
502
508
  info(` Project: ${project.path}`);
@@ -712,7 +718,8 @@ export async function startAgent() {
712
718
 
713
719
  stopHeartbeat();
714
720
  info(` Finishing run (report + merge)...`);
715
- await bridge.finishRun();
721
+ const finishResult = await bridge.finishRun();
722
+ const reportMarkdown = finishResult?.parsed?.reportContent || null;
716
723
  projectManager.updateProject(run.projectId, { lastRunAt: Date.now() });
717
724
 
718
725
  const elapsedMs = Date.now() - run.startedAt;
@@ -728,6 +735,7 @@ export async function startAgent() {
728
735
  project: project.name,
729
736
  projectId: project.id,
730
737
  run: { id: run.id, totalSteps, completedSteps: run.steps.length, elapsedMs: Date.now() - run.startedAt },
738
+ reportMarkdown,
731
739
  }, project.webhooks);
732
740
 
733
741
  activeBridge = null;
@@ -922,7 +930,8 @@ export async function startAgent() {
922
930
  // Finish the run
923
931
  stopHeartbeat();
924
932
  info(` Finishing resumed run (report + merge)...`);
925
- await bridge.finishRun();
933
+ const finishResult = await bridge.finishRun();
934
+ const reportMarkdown = finishResult?.parsed?.reportContent || null;
926
935
  projectManager.updateProject(interrupted.projectId, { lastRunAt: Date.now() });
927
936
 
928
937
  wsServer.broadcast({ type: 'run-completed', runId: interrupted.id, results: {} });
@@ -931,6 +940,7 @@ export async function startAgent() {
931
940
  dispatchWithQueue('run_completed', {
932
941
  project: project.name, projectId: project.id,
933
942
  run: { id: interrupted.id, totalSteps: interrupted.steps.length, completedSteps: runProgress.completedCount, elapsedMs: Date.now() - interrupted.startedAt },
943
+ reportMarkdown,
934
944
  }, project.webhooks);
935
945
 
936
946
  activeBridge = null;
@@ -951,11 +961,13 @@ export async function startAgent() {
951
961
  interrupted.status = 'running';
952
962
  runQueue._save();
953
963
 
964
+ let finishResult = null;
954
965
  try {
955
- await bridge.finishRun();
966
+ finishResult = await bridge.finishRun();
956
967
  } catch (err) {
957
968
  warn(` finishRun failed: ${err.message}`);
958
969
  }
970
+ const reportMarkdown = finishResult?.parsed?.reportContent || null;
959
971
 
960
972
  projectManager.updateProject(interrupted.projectId, { lastRunAt: Date.now() });
961
973
  wsServer.broadcast({ type: 'run-completed', runId: interrupted.id, status: 'completed', results: {} });
@@ -964,6 +976,7 @@ export async function startAgent() {
964
976
  dispatchWithQueue('run_completed', {
965
977
  project: project.name, projectId: project.id,
966
978
  run: { id: interrupted.id, totalSteps: interrupted.steps.length, completedSteps: interrupted.lastProgress?.completedCount || 0, elapsedMs: Date.now() - interrupted.startedAt },
979
+ reportMarkdown,
967
980
  }, project.webhooks);
968
981
 
969
982
  activeBridge = null;
@@ -1036,6 +1049,7 @@ export async function startAgent() {
1036
1049
  // Graceful shutdown
1037
1050
  const shutdown = async () => {
1038
1051
  info('Agent shutting down...');
1052
+ releaseKeepAwake();
1039
1053
  saveInterruptedState();
1040
1054
  scheduler.stopAll();
1041
1055
  await wsServer.stop();
@@ -1093,6 +1107,15 @@ export async function startAgent() {
1093
1107
  runQueue.markInterrupted({ completedCount: 0, failedCount: 0, totalCost: 0, stepList: [], currentStepNum: null });
1094
1108
  }
1095
1109
 
1110
+ // Process any queued runs left from a previous session
1111
+ if (!runQueue.getInterrupted() && !runQueue.getCurrent()) {
1112
+ const pending = runQueue.getQueue();
1113
+ if (pending.length > 0) {
1114
+ info(`Found ${pending.length} queued run(s) — starting queue processing`);
1115
+ processQueue();
1116
+ }
1117
+ }
1118
+
1096
1119
  // Print startup info
1097
1120
  console.log(`\nNightyTidy Agent v${AGENT_VERSION}`);
1098
1121
  console.log(`WebSocket: ws://127.0.0.1:${actualPort}`);
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Prevents the OS from sleeping while NightyTidy runs are active.
3
+ *
4
+ * Windows: Uses PowerShell to call SetThreadExecutionState with
5
+ * ES_CONTINUOUS | ES_SYSTEM_REQUIRED (0x80000001). This tells Windows
6
+ * "don't sleep, this process needs the system." The flag is automatically
7
+ * cleared when the process exits or when releaseKeepAwake() is called.
8
+ *
9
+ * macOS/Linux: Uses caffeinate / systemd-inhibit respectively.
10
+ *
11
+ * No admin privileges required on any platform.
12
+ */
13
+ import { execSync, spawn } from 'node:child_process';
14
+ import { debug, warn } from '../logger.js';
15
+
16
+ let keepAwakeProcess = null;
17
+
18
+ export function acquireKeepAwake() {
19
+ if (keepAwakeProcess) return; // already held
20
+
21
+ try {
22
+ if (process.platform === 'win32') {
23
+ // PowerShell script that sets ES_CONTINUOUS | ES_SYSTEM_REQUIRED
24
+ // and then sleeps forever. When we kill this process, the flag clears.
25
+ keepAwakeProcess = spawn('powershell', [
26
+ '-NoProfile', '-WindowStyle', 'Hidden', '-Command',
27
+ `Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public class SleepPreventer { [DllImport("kernel32.dll")] public static extern uint SetThreadExecutionState(uint esFlags); }'; [SleepPreventer]::SetThreadExecutionState(0x80000001); while($true) { Start-Sleep -Seconds 3600 }`,
28
+ ], { stdio: 'ignore', detached: false });
29
+ keepAwakeProcess.unref();
30
+ keepAwakeProcess.on('error', () => { keepAwakeProcess = null; });
31
+ debug('Sleep prevention acquired (Windows SetThreadExecutionState)');
32
+ } else if (process.platform === 'darwin') {
33
+ // macOS: caffeinate prevents sleep, -i = idle sleep, -s = system sleep
34
+ keepAwakeProcess = spawn('caffeinate', ['-is'], { stdio: 'ignore' });
35
+ keepAwakeProcess.unref();
36
+ keepAwakeProcess.on('error', () => { keepAwakeProcess = null; });
37
+ debug('Sleep prevention acquired (macOS caffeinate)');
38
+ } else {
39
+ // Linux: systemd-inhibit (may not exist on all distros)
40
+ keepAwakeProcess = spawn('systemd-inhibit', [
41
+ '--what=idle:sleep', '--who=NightyTidy', '--why=Running codebase improvement',
42
+ 'sleep', 'infinity',
43
+ ], { stdio: 'ignore' });
44
+ keepAwakeProcess.unref();
45
+ keepAwakeProcess.on('error', () => { keepAwakeProcess = null; });
46
+ debug('Sleep prevention acquired (Linux systemd-inhibit)');
47
+ }
48
+ } catch {
49
+ warn('Could not acquire sleep prevention — system may sleep during runs');
50
+ }
51
+ }
52
+
53
+ export function releaseKeepAwake() {
54
+ if (!keepAwakeProcess) return;
55
+ try {
56
+ keepAwakeProcess.kill();
57
+ } catch { /* already dead */ }
58
+ keepAwakeProcess = null;
59
+ debug('Sleep prevention released');
60
+ }