steroids-api 0.2.7

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.
Files changed (328) hide show
  1. package/dist/API/src/index.d.ts +10 -0
  2. package/dist/API/src/index.d.ts.map +1 -0
  3. package/dist/API/src/index.js +130 -0
  4. package/dist/API/src/index.js.map +1 -0
  5. package/dist/API/src/routes/activity.d.ts +7 -0
  6. package/dist/API/src/routes/activity.d.ts.map +1 -0
  7. package/dist/API/src/routes/activity.js +252 -0
  8. package/dist/API/src/routes/activity.js.map +1 -0
  9. package/dist/API/src/routes/config.d.ts +7 -0
  10. package/dist/API/src/routes/config.d.ts.map +1 -0
  11. package/dist/API/src/routes/config.js +521 -0
  12. package/dist/API/src/routes/config.js.map +1 -0
  13. package/dist/API/src/routes/health.d.ts +7 -0
  14. package/dist/API/src/routes/health.d.ts.map +1 -0
  15. package/dist/API/src/routes/health.js +172 -0
  16. package/dist/API/src/routes/health.js.map +1 -0
  17. package/dist/API/src/routes/incidents.d.ts +7 -0
  18. package/dist/API/src/routes/incidents.d.ts.map +1 -0
  19. package/dist/API/src/routes/incidents.js +117 -0
  20. package/dist/API/src/routes/incidents.js.map +1 -0
  21. package/dist/API/src/routes/projects.d.ts +7 -0
  22. package/dist/API/src/routes/projects.d.ts.map +1 -0
  23. package/dist/API/src/routes/projects.js +398 -0
  24. package/dist/API/src/routes/projects.js.map +1 -0
  25. package/dist/API/src/routes/runners.d.ts +7 -0
  26. package/dist/API/src/routes/runners.d.ts.map +1 -0
  27. package/dist/API/src/routes/runners.js +242 -0
  28. package/dist/API/src/routes/runners.js.map +1 -0
  29. package/dist/API/src/routes/tasks.d.ts +7 -0
  30. package/dist/API/src/routes/tasks.d.ts.map +1 -0
  31. package/dist/API/src/routes/tasks.js +1007 -0
  32. package/dist/API/src/routes/tasks.js.map +1 -0
  33. package/dist/API/src/utils/validation.d.ts +22 -0
  34. package/dist/API/src/utils/validation.d.ts.map +1 -0
  35. package/dist/API/src/utils/validation.js +50 -0
  36. package/dist/API/src/utils/validation.js.map +1 -0
  37. package/dist/index.d.ts +10 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +184 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/routes/activity.d.ts +7 -0
  42. package/dist/routes/activity.d.ts.map +1 -0
  43. package/dist/routes/activity.js +252 -0
  44. package/dist/routes/activity.js.map +1 -0
  45. package/dist/routes/config.d.ts +7 -0
  46. package/dist/routes/config.d.ts.map +1 -0
  47. package/dist/routes/config.js +647 -0
  48. package/dist/routes/config.js.map +1 -0
  49. package/dist/routes/credit-alerts.d.ts +2 -0
  50. package/dist/routes/credit-alerts.d.ts.map +1 -0
  51. package/dist/routes/credit-alerts.js +97 -0
  52. package/dist/routes/credit-alerts.js.map +1 -0
  53. package/dist/routes/health.d.ts +7 -0
  54. package/dist/routes/health.d.ts.map +1 -0
  55. package/dist/routes/health.js +200 -0
  56. package/dist/routes/health.js.map +1 -0
  57. package/dist/routes/incidents.d.ts +7 -0
  58. package/dist/routes/incidents.d.ts.map +1 -0
  59. package/dist/routes/incidents.js +117 -0
  60. package/dist/routes/incidents.js.map +1 -0
  61. package/dist/routes/projects.d.ts +7 -0
  62. package/dist/routes/projects.d.ts.map +1 -0
  63. package/dist/routes/projects.js +643 -0
  64. package/dist/routes/projects.js.map +1 -0
  65. package/dist/routes/runners.d.ts +7 -0
  66. package/dist/routes/runners.d.ts.map +1 -0
  67. package/dist/routes/runners.js +299 -0
  68. package/dist/routes/runners.js.map +1 -0
  69. package/dist/routes/skills.d.ts +3 -0
  70. package/dist/routes/skills.d.ts.map +1 -0
  71. package/dist/routes/skills.js +109 -0
  72. package/dist/routes/skills.js.map +1 -0
  73. package/dist/routes/storage.d.ts +7 -0
  74. package/dist/routes/storage.d.ts.map +1 -0
  75. package/dist/routes/storage.js +93 -0
  76. package/dist/routes/storage.js.map +1 -0
  77. package/dist/routes/tasks.d.ts +7 -0
  78. package/dist/routes/tasks.d.ts.map +1 -0
  79. package/dist/routes/tasks.js +1145 -0
  80. package/dist/routes/tasks.js.map +1 -0
  81. package/dist/src/cleanup/invocation-logs.d.ts +30 -0
  82. package/dist/src/cleanup/invocation-logs.d.ts.map +1 -0
  83. package/dist/src/cleanup/invocation-logs.js +66 -0
  84. package/dist/src/cleanup/invocation-logs.js.map +1 -0
  85. package/dist/src/commands/loop-phases.d.ts +11 -0
  86. package/dist/src/commands/loop-phases.d.ts.map +1 -0
  87. package/dist/src/commands/loop-phases.js +304 -0
  88. package/dist/src/commands/loop-phases.js.map +1 -0
  89. package/dist/src/config/loader.d.ts +160 -0
  90. package/dist/src/config/loader.d.ts.map +1 -0
  91. package/dist/src/config/loader.js +276 -0
  92. package/dist/src/config/loader.js.map +1 -0
  93. package/dist/src/database/connection.d.ts +35 -0
  94. package/dist/src/database/connection.d.ts.map +1 -0
  95. package/dist/src/database/connection.js +197 -0
  96. package/dist/src/database/connection.js.map +1 -0
  97. package/dist/src/database/queries.d.ts +220 -0
  98. package/dist/src/database/queries.d.ts.map +1 -0
  99. package/dist/src/database/queries.js +589 -0
  100. package/dist/src/database/queries.js.map +1 -0
  101. package/dist/src/database/schema.d.ts +8 -0
  102. package/dist/src/database/schema.d.ts.map +1 -0
  103. package/dist/src/database/schema.js +184 -0
  104. package/dist/src/database/schema.js.map +1 -0
  105. package/dist/src/git/push.d.ts +26 -0
  106. package/dist/src/git/push.d.ts.map +1 -0
  107. package/dist/src/git/push.js +91 -0
  108. package/dist/src/git/push.js.map +1 -0
  109. package/dist/src/git/status.d.ts +83 -0
  110. package/dist/src/git/status.d.ts.map +1 -0
  111. package/dist/src/git/status.js +315 -0
  112. package/dist/src/git/status.js.map +1 -0
  113. package/dist/src/health/stuck-task-detector.d.ts +131 -0
  114. package/dist/src/health/stuck-task-detector.d.ts.map +1 -0
  115. package/dist/src/health/stuck-task-detector.js +233 -0
  116. package/dist/src/health/stuck-task-detector.js.map +1 -0
  117. package/dist/src/health/stuck-task-recovery.d.ts +45 -0
  118. package/dist/src/health/stuck-task-recovery.d.ts.map +1 -0
  119. package/dist/src/health/stuck-task-recovery.js +309 -0
  120. package/dist/src/health/stuck-task-recovery.js.map +1 -0
  121. package/dist/src/index.d.ts +10 -0
  122. package/dist/src/index.d.ts.map +1 -0
  123. package/dist/src/index.js +130 -0
  124. package/dist/src/index.js.map +1 -0
  125. package/dist/src/locking/queries.d.ts +116 -0
  126. package/dist/src/locking/queries.d.ts.map +1 -0
  127. package/dist/src/locking/queries.js +232 -0
  128. package/dist/src/locking/queries.js.map +1 -0
  129. package/dist/src/locking/section-lock.d.ts +74 -0
  130. package/dist/src/locking/section-lock.d.ts.map +1 -0
  131. package/dist/src/locking/section-lock.js +196 -0
  132. package/dist/src/locking/section-lock.js.map +1 -0
  133. package/dist/src/locking/task-lock.d.ts +92 -0
  134. package/dist/src/locking/task-lock.d.ts.map +1 -0
  135. package/dist/src/locking/task-lock.js +233 -0
  136. package/dist/src/locking/task-lock.js.map +1 -0
  137. package/dist/src/migrations/index.d.ts +7 -0
  138. package/dist/src/migrations/index.d.ts.map +1 -0
  139. package/dist/src/migrations/index.js +9 -0
  140. package/dist/src/migrations/index.js.map +1 -0
  141. package/dist/src/migrations/manifest.d.ts +92 -0
  142. package/dist/src/migrations/manifest.d.ts.map +1 -0
  143. package/dist/src/migrations/manifest.js +255 -0
  144. package/dist/src/migrations/manifest.js.map +1 -0
  145. package/dist/src/migrations/runner.d.ts +84 -0
  146. package/dist/src/migrations/runner.d.ts.map +1 -0
  147. package/dist/src/migrations/runner.js +338 -0
  148. package/dist/src/migrations/runner.js.map +1 -0
  149. package/dist/src/orchestrator/coder.d.ts +32 -0
  150. package/dist/src/orchestrator/coder.d.ts.map +1 -0
  151. package/dist/src/orchestrator/coder.js +170 -0
  152. package/dist/src/orchestrator/coder.js.map +1 -0
  153. package/dist/src/orchestrator/coordinator.d.ts +28 -0
  154. package/dist/src/orchestrator/coordinator.d.ts.map +1 -0
  155. package/dist/src/orchestrator/coordinator.js +252 -0
  156. package/dist/src/orchestrator/coordinator.js.map +1 -0
  157. package/dist/src/orchestrator/fallback-handler.d.ts +24 -0
  158. package/dist/src/orchestrator/fallback-handler.d.ts.map +1 -0
  159. package/dist/src/orchestrator/fallback-handler.js +280 -0
  160. package/dist/src/orchestrator/fallback-handler.js.map +1 -0
  161. package/dist/src/orchestrator/invoke.d.ts +14 -0
  162. package/dist/src/orchestrator/invoke.d.ts.map +1 -0
  163. package/dist/src/orchestrator/invoke.js +76 -0
  164. package/dist/src/orchestrator/invoke.js.map +1 -0
  165. package/dist/src/orchestrator/post-coder.d.ts +10 -0
  166. package/dist/src/orchestrator/post-coder.d.ts.map +1 -0
  167. package/dist/src/orchestrator/post-coder.js +198 -0
  168. package/dist/src/orchestrator/post-coder.js.map +1 -0
  169. package/dist/src/orchestrator/post-reviewer.d.ts +10 -0
  170. package/dist/src/orchestrator/post-reviewer.d.ts.map +1 -0
  171. package/dist/src/orchestrator/post-reviewer.js +199 -0
  172. package/dist/src/orchestrator/post-reviewer.js.map +1 -0
  173. package/dist/src/orchestrator/reviewer.d.ts +35 -0
  174. package/dist/src/orchestrator/reviewer.d.ts.map +1 -0
  175. package/dist/src/orchestrator/reviewer.js +237 -0
  176. package/dist/src/orchestrator/reviewer.js.map +1 -0
  177. package/dist/src/orchestrator/schemas.d.ts +10 -0
  178. package/dist/src/orchestrator/schemas.d.ts.map +1 -0
  179. package/dist/src/orchestrator/schemas.js +81 -0
  180. package/dist/src/orchestrator/schemas.js.map +1 -0
  181. package/dist/src/orchestrator/task-selector.d.ts +102 -0
  182. package/dist/src/orchestrator/task-selector.d.ts.map +1 -0
  183. package/dist/src/orchestrator/task-selector.js +326 -0
  184. package/dist/src/orchestrator/task-selector.js.map +1 -0
  185. package/dist/src/orchestrator/types.d.ts +74 -0
  186. package/dist/src/orchestrator/types.d.ts.map +1 -0
  187. package/dist/src/orchestrator/types.js +5 -0
  188. package/dist/src/orchestrator/types.js.map +1 -0
  189. package/dist/src/prompts/coder.d.ts +36 -0
  190. package/dist/src/prompts/coder.d.ts.map +1 -0
  191. package/dist/src/prompts/coder.js +303 -0
  192. package/dist/src/prompts/coder.js.map +1 -0
  193. package/dist/src/prompts/prompt-helpers.d.ts +51 -0
  194. package/dist/src/prompts/prompt-helpers.d.ts.map +1 -0
  195. package/dist/src/prompts/prompt-helpers.js +299 -0
  196. package/dist/src/prompts/prompt-helpers.js.map +1 -0
  197. package/dist/src/prompts/reviewer.d.ts +40 -0
  198. package/dist/src/prompts/reviewer.d.ts.map +1 -0
  199. package/dist/src/prompts/reviewer.js +416 -0
  200. package/dist/src/prompts/reviewer.js.map +1 -0
  201. package/dist/src/providers/claude.d.ts +53 -0
  202. package/dist/src/providers/claude.d.ts.map +1 -0
  203. package/dist/src/providers/claude.js +227 -0
  204. package/dist/src/providers/claude.js.map +1 -0
  205. package/dist/src/providers/codex.d.ts +53 -0
  206. package/dist/src/providers/codex.d.ts.map +1 -0
  207. package/dist/src/providers/codex.js +253 -0
  208. package/dist/src/providers/codex.js.map +1 -0
  209. package/dist/src/providers/gemini.d.ts +58 -0
  210. package/dist/src/providers/gemini.d.ts.map +1 -0
  211. package/dist/src/providers/gemini.js +240 -0
  212. package/dist/src/providers/gemini.js.map +1 -0
  213. package/dist/src/providers/interface.d.ts +185 -0
  214. package/dist/src/providers/interface.d.ts.map +1 -0
  215. package/dist/src/providers/interface.js +92 -0
  216. package/dist/src/providers/interface.js.map +1 -0
  217. package/dist/src/providers/invocation-logger.d.ts +97 -0
  218. package/dist/src/providers/invocation-logger.d.ts.map +1 -0
  219. package/dist/src/providers/invocation-logger.js +378 -0
  220. package/dist/src/providers/invocation-logger.js.map +1 -0
  221. package/dist/src/providers/openai.d.ts +53 -0
  222. package/dist/src/providers/openai.d.ts.map +1 -0
  223. package/dist/src/providers/openai.js +230 -0
  224. package/dist/src/providers/openai.js.map +1 -0
  225. package/dist/src/providers/registry.d.ts +100 -0
  226. package/dist/src/providers/registry.d.ts.map +1 -0
  227. package/dist/src/providers/registry.js +170 -0
  228. package/dist/src/providers/registry.js.map +1 -0
  229. package/dist/src/routes/activity.d.ts +7 -0
  230. package/dist/src/routes/activity.d.ts.map +1 -0
  231. package/dist/src/routes/activity.js +252 -0
  232. package/dist/src/routes/activity.js.map +1 -0
  233. package/dist/src/routes/config.d.ts +7 -0
  234. package/dist/src/routes/config.d.ts.map +1 -0
  235. package/dist/src/routes/config.js +521 -0
  236. package/dist/src/routes/config.js.map +1 -0
  237. package/dist/src/routes/health.d.ts +7 -0
  238. package/dist/src/routes/health.d.ts.map +1 -0
  239. package/dist/src/routes/health.js +172 -0
  240. package/dist/src/routes/health.js.map +1 -0
  241. package/dist/src/routes/incidents.d.ts +7 -0
  242. package/dist/src/routes/incidents.d.ts.map +1 -0
  243. package/dist/src/routes/incidents.js +117 -0
  244. package/dist/src/routes/incidents.js.map +1 -0
  245. package/dist/src/routes/projects.d.ts +7 -0
  246. package/dist/src/routes/projects.d.ts.map +1 -0
  247. package/dist/src/routes/projects.js +398 -0
  248. package/dist/src/routes/projects.js.map +1 -0
  249. package/dist/src/routes/runners.d.ts +7 -0
  250. package/dist/src/routes/runners.d.ts.map +1 -0
  251. package/dist/src/routes/runners.js +242 -0
  252. package/dist/src/routes/runners.js.map +1 -0
  253. package/dist/src/routes/tasks.d.ts +7 -0
  254. package/dist/src/routes/tasks.d.ts.map +1 -0
  255. package/dist/src/routes/tasks.js +1007 -0
  256. package/dist/src/routes/tasks.js.map +1 -0
  257. package/dist/src/runners/activity-log.d.ts +65 -0
  258. package/dist/src/runners/activity-log.d.ts.map +1 -0
  259. package/dist/src/runners/activity-log.js +140 -0
  260. package/dist/src/runners/activity-log.js.map +1 -0
  261. package/dist/src/runners/cron.d.ts +30 -0
  262. package/dist/src/runners/cron.d.ts.map +1 -0
  263. package/dist/src/runners/cron.js +333 -0
  264. package/dist/src/runners/cron.js.map +1 -0
  265. package/dist/src/runners/daemon.d.ts +71 -0
  266. package/dist/src/runners/daemon.d.ts.map +1 -0
  267. package/dist/src/runners/daemon.js +233 -0
  268. package/dist/src/runners/daemon.js.map +1 -0
  269. package/dist/src/runners/global-db.d.ts +31 -0
  270. package/dist/src/runners/global-db.d.ts.map +1 -0
  271. package/dist/src/runners/global-db.js +220 -0
  272. package/dist/src/runners/global-db.js.map +1 -0
  273. package/dist/src/runners/hang-detector.d.ts +38 -0
  274. package/dist/src/runners/hang-detector.d.ts.map +1 -0
  275. package/dist/src/runners/hang-detector.js +130 -0
  276. package/dist/src/runners/hang-detector.js.map +1 -0
  277. package/dist/src/runners/heartbeat.d.ts +39 -0
  278. package/dist/src/runners/heartbeat.d.ts.map +1 -0
  279. package/dist/src/runners/heartbeat.js +71 -0
  280. package/dist/src/runners/heartbeat.js.map +1 -0
  281. package/dist/src/runners/lock.d.ts +47 -0
  282. package/dist/src/runners/lock.d.ts.map +1 -0
  283. package/dist/src/runners/lock.js +140 -0
  284. package/dist/src/runners/lock.js.map +1 -0
  285. package/dist/src/runners/orchestrator-loop.d.ts +20 -0
  286. package/dist/src/runners/orchestrator-loop.d.ts.map +1 -0
  287. package/dist/src/runners/orchestrator-loop.js +208 -0
  288. package/dist/src/runners/orchestrator-loop.js.map +1 -0
  289. package/dist/src/runners/projects.d.ts +96 -0
  290. package/dist/src/runners/projects.d.ts.map +1 -0
  291. package/dist/src/runners/projects.js +243 -0
  292. package/dist/src/runners/projects.js.map +1 -0
  293. package/dist/src/runners/wakeup.d.ts +37 -0
  294. package/dist/src/runners/wakeup.d.ts.map +1 -0
  295. package/dist/src/runners/wakeup.js +355 -0
  296. package/dist/src/runners/wakeup.js.map +1 -0
  297. package/dist/src/utils/validation.d.ts +22 -0
  298. package/dist/src/utils/validation.d.ts.map +1 -0
  299. package/dist/src/utils/validation.js +50 -0
  300. package/dist/src/utils/validation.js.map +1 -0
  301. package/dist/utils/sqlite.d.ts +17 -0
  302. package/dist/utils/sqlite.d.ts.map +1 -0
  303. package/dist/utils/sqlite.js +27 -0
  304. package/dist/utils/sqlite.js.map +1 -0
  305. package/dist/utils/storage-cache.d.ts +33 -0
  306. package/dist/utils/storage-cache.d.ts.map +1 -0
  307. package/dist/utils/storage-cache.js +81 -0
  308. package/dist/utils/storage-cache.js.map +1 -0
  309. package/dist/utils/validation.d.ts +22 -0
  310. package/dist/utils/validation.d.ts.map +1 -0
  311. package/dist/utils/validation.js +51 -0
  312. package/dist/utils/validation.js.map +1 -0
  313. package/package.json +39 -0
  314. package/src/index.ts +199 -0
  315. package/src/routes/activity.ts +302 -0
  316. package/src/routes/config.ts +723 -0
  317. package/src/routes/credit-alerts.ts +73 -0
  318. package/src/routes/health.ts +219 -0
  319. package/src/routes/incidents.ts +131 -0
  320. package/src/routes/projects.ts +854 -0
  321. package/src/routes/runners.ts +357 -0
  322. package/src/routes/skills.ts +127 -0
  323. package/src/routes/storage.ts +108 -0
  324. package/src/routes/tasks.ts +1372 -0
  325. package/src/utils/sqlite.ts +36 -0
  326. package/src/utils/storage-cache.ts +107 -0
  327. package/src/utils/validation.ts +61 -0
  328. package/tsconfig.json +20 -0
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Runners API routes
3
+ * Get runner status and current tasks
4
+ */
5
+
6
+ import { Router, Request, Response } from 'express';
7
+ import { openGlobalDatabase } from '../../../dist/runners/global-db.js';
8
+ import { getDaemonActiveStatus, setDaemonActiveStatus } from '../../../dist/runners/global-db.js';
9
+ import { cronStatus, cronInstall, cronUninstall } from '../../../dist/runners/cron.js';
10
+ import { getLastWakeupTime, wakeup } from '../../../dist/runners/wakeup.js';
11
+ import { listRunners, unregisterRunner } from '../../../dist/runners/daemon.js';
12
+
13
+ const router = Router();
14
+
15
+ interface RunnerInfo {
16
+ id: string;
17
+ status: string;
18
+ pid: number | null;
19
+ project_path: string | null;
20
+ project_name: string | null;
21
+ parallel_session_id: string | null;
22
+ current_task_id: string | null;
23
+ current_task_title: string | null;
24
+ started_at: string | null;
25
+ heartbeat_at: string;
26
+ section_id: string | null;
27
+ }
28
+
29
+ function stopAllRunnersNow(): number {
30
+ const runners = listRunners();
31
+ let stopped = 0;
32
+
33
+ for (const runner of runners) {
34
+ if (runner.pid) {
35
+ try {
36
+ process.kill(runner.pid, 'SIGTERM');
37
+ stopped += 1;
38
+ } catch {
39
+ // Runner already dead; still remove stale DB row.
40
+ }
41
+ }
42
+ unregisterRunner(runner.id);
43
+ }
44
+
45
+ return stopped;
46
+ }
47
+
48
+ /**
49
+ * GET /api/runners
50
+ * List all runners with their current tasks
51
+ */
52
+ router.get('/runners', (_req: Request, res: Response) => {
53
+ try {
54
+ const { db, close } = openGlobalDatabase();
55
+ try {
56
+ const runners = db
57
+ .prepare(
58
+ `SELECT
59
+ r.id,
60
+ r.status,
61
+ r.pid,
62
+ r.project_path as raw_project_path,
63
+ COALESCE(ps.project_path, r.project_path) as project_path,
64
+ r.parallel_session_id,
65
+ r.current_task_id,
66
+ r.started_at,
67
+ r.heartbeat_at,
68
+ r.section_id,
69
+ COALESCE(p2.name, p.name) as project_name
70
+ FROM runners r
71
+ LEFT JOIN projects p ON r.project_path = p.path
72
+ LEFT JOIN parallel_sessions ps ON r.parallel_session_id = ps.id
73
+ LEFT JOIN projects p2 ON ps.project_path = p2.path
74
+ ORDER BY r.heartbeat_at DESC`
75
+ )
76
+ .all() as Array<{
77
+ id: string;
78
+ status: string;
79
+ pid: number | null;
80
+ raw_project_path: string | null;
81
+ project_path: string | null;
82
+ parallel_session_id: string | null;
83
+ current_task_id: string | null;
84
+ started_at: string | null;
85
+ heartbeat_at: string;
86
+ section_id: string | null;
87
+ project_name: string | null;
88
+ }>;
89
+
90
+ const runnersWithTasks: RunnerInfo[] = runners.map((runner) => ({
91
+ id: runner.id,
92
+ status: runner.status,
93
+ pid: runner.pid,
94
+ project_path: runner.project_path,
95
+ project_name: runner.project_name,
96
+ parallel_session_id: runner.parallel_session_id,
97
+ current_task_id: runner.current_task_id,
98
+ current_task_title: null,
99
+ started_at: runner.started_at,
100
+ heartbeat_at: runner.heartbeat_at,
101
+ section_id: runner.section_id,
102
+ }));
103
+
104
+ const activeCount = runners.filter(
105
+ (r) => r.status === 'running' || r.status === 'active'
106
+ ).length;
107
+
108
+ res.json({
109
+ success: true,
110
+ runners: runnersWithTasks,
111
+ count: runners.length,
112
+ active_count: activeCount,
113
+ });
114
+ } finally {
115
+ close();
116
+ }
117
+ } catch (error) {
118
+ console.error('Error listing runners:', error);
119
+ res.status(500).json({
120
+ success: false,
121
+ error: 'Failed to list runners',
122
+ message: error instanceof Error ? error.message : 'Unknown error',
123
+ });
124
+ }
125
+ });
126
+
127
+ /**
128
+ * GET /api/runners/active-tasks
129
+ * List all tasks currently being worked on by runners
130
+ */
131
+ router.get('/runners/active-tasks', (_req: Request, res: Response) => {
132
+ try {
133
+ const { db, close } = openGlobalDatabase();
134
+ try {
135
+ const activeTasks = db
136
+ .prepare(
137
+ `SELECT
138
+ r.id as runner_id,
139
+ r.status,
140
+ COALESCE(ps.project_path, r.project_path) as project_path,
141
+ r.current_task_id,
142
+ r.started_at,
143
+ COALESCE(p2.name, p.name) as project_name
144
+ FROM runners r
145
+ LEFT JOIN projects p ON r.project_path = p.path
146
+ LEFT JOIN parallel_sessions ps ON r.parallel_session_id = ps.id
147
+ LEFT JOIN projects p2 ON ps.project_path = p2.path
148
+ WHERE r.current_task_id IS NOT NULL
149
+ ORDER BY r.started_at DESC`
150
+ )
151
+ .all() as Array<{
152
+ runner_id: string;
153
+ status: string;
154
+ project_path: string;
155
+ current_task_id: string;
156
+ started_at: string | null;
157
+ project_name: string | null;
158
+ }>;
159
+
160
+ res.json({
161
+ success: true,
162
+ tasks: activeTasks,
163
+ count: activeTasks.length,
164
+ });
165
+ } finally {
166
+ close();
167
+ }
168
+ } catch (error) {
169
+ console.error('Error listing active tasks:', error);
170
+ res.status(500).json({
171
+ success: false,
172
+ error: 'Failed to list active tasks',
173
+ message: error instanceof Error ? error.message : 'Unknown error',
174
+ });
175
+ }
176
+ });
177
+
178
+ /**
179
+ * GET /api/runners/cron
180
+ * Get cron status and last wakeup time
181
+ */
182
+ router.get('/runners/cron', (_req: Request, res: Response) => {
183
+ try {
184
+ const status = cronStatus();
185
+ const isActive = getDaemonActiveStatus();
186
+ const lastWakeup = getLastWakeupTime();
187
+
188
+ res.json({
189
+ success: true,
190
+ cron: {
191
+ installed: status.installed && isActive,
192
+ entry: status.entry,
193
+ error: status.error,
194
+ },
195
+ last_wakeup_at: lastWakeup,
196
+ });
197
+ } catch (error) {
198
+ console.error('Error getting cron status:', error);
199
+ res.status(500).json({
200
+ success: false,
201
+ error: 'Failed to get cron status',
202
+ message: error instanceof Error ? error.message : 'Unknown error',
203
+ });
204
+ }
205
+ });
206
+
207
+ /**
208
+ * POST /api/runners/cron/start
209
+ * Install cron job
210
+ */
211
+ router.post('/runners/cron/start', async (_req: Request, res: Response) => {
212
+ try {
213
+ const status = cronStatus();
214
+ if (!status.installed) {
215
+ const installResult = cronInstall();
216
+ if (!installResult.success) {
217
+ res.status(400).json({
218
+ success: false,
219
+ error: installResult.message,
220
+ message: installResult.error,
221
+ });
222
+ return;
223
+ }
224
+ }
225
+
226
+ setDaemonActiveStatus(true);
227
+
228
+ // Make "Start Steroids" feel immediate: run one wakeup cycle now.
229
+ const wakeupResults = await wakeup({ quiet: true });
230
+ const activated = wakeupResults.filter(
231
+ (item) => item.action === 'started' || item.action === 'restarted'
232
+ ).length;
233
+
234
+ res.json({
235
+ success: true,
236
+ message: `Daemon resumed. Immediate wakeup started/restarted ${activated} runner(s).`,
237
+ wakeup: wakeupResults,
238
+ });
239
+ } catch (error) {
240
+ console.error('Error installing cron:', error);
241
+ res.status(500).json({
242
+ success: false,
243
+ error: 'Failed to install cron',
244
+ message: error instanceof Error ? error.message : 'Unknown error',
245
+ });
246
+ }
247
+ });
248
+
249
+ /**
250
+ * POST /api/runners/cron/stop
251
+ * Uninstall cron job
252
+ */
253
+ router.post('/runners/cron/stop', (_req: Request, res: Response) => {
254
+ try {
255
+ setDaemonActiveStatus(false);
256
+ const stopped = stopAllRunnersNow();
257
+ res.json({
258
+ success: true,
259
+ message: `Daemon paused. Stopped ${stopped} active runner(s).`,
260
+ stopped,
261
+ });
262
+ } catch (error) {
263
+ console.error('Error uninstalling cron:', error);
264
+ res.status(500).json({
265
+ success: false,
266
+ error: 'Failed to uninstall cron',
267
+ message: error instanceof Error ? error.message : 'Unknown error',
268
+ });
269
+ }
270
+ });
271
+
272
+ /**
273
+ * POST /api/runners/:runnerId/kill
274
+ * Kill a specific runner by ID
275
+ */
276
+ router.post('/runners/:runnerId/kill', (req: Request, res: Response) => {
277
+ try {
278
+ const { runnerId } = req.params;
279
+ const runners = listRunners();
280
+ const runner = runners.find((r) => r.id === runnerId || r.id.startsWith(runnerId));
281
+
282
+ if (!runner) {
283
+ return res.status(404).json({
284
+ success: false,
285
+ error: 'Runner not found',
286
+ });
287
+ }
288
+
289
+ if (!runner.pid) {
290
+ return res.status(400).json({
291
+ success: false,
292
+ error: 'Runner has no PID (not running)',
293
+ });
294
+ }
295
+
296
+ try {
297
+ process.kill(runner.pid, 'SIGTERM');
298
+ res.json({
299
+ success: true,
300
+ message: `Runner ${runner.id.slice(0, 8)} killed`,
301
+ runner_id: runner.id,
302
+ pid: runner.pid,
303
+ });
304
+ } catch (killError) {
305
+ res.status(500).json({
306
+ success: false,
307
+ error: 'Failed to kill runner process',
308
+ message: killError instanceof Error ? killError.message : 'Unknown error',
309
+ });
310
+ }
311
+ } catch (error) {
312
+ console.error('Error killing runner:', error);
313
+ res.status(500).json({
314
+ success: false,
315
+ error: 'Failed to kill runner',
316
+ message: error instanceof Error ? error.message : 'Unknown error',
317
+ });
318
+ }
319
+ });
320
+
321
+ /**
322
+ * POST /api/runners/wakeup/now
323
+ * Force immediate execution of the wakeup cycle
324
+ */
325
+ router.post('/runners/wakeup/now', async (_req: Request, res: Response) => {
326
+ try {
327
+ const wakeupResults = await wakeup({ quiet: true });
328
+
329
+ if (wakeupResults.length > 0 && wakeupResults[0].action === 'skipped' && wakeupResults[0].reason === 'Wakeup cycle already running') {
330
+ res.status(429).json({
331
+ success: false,
332
+ error: 'Too Many Requests',
333
+ message: 'A wakeup cycle is already running.',
334
+ });
335
+ return;
336
+ }
337
+
338
+ const activated = wakeupResults.filter(
339
+ (item) => item.action === 'started' || item.action === 'restarted'
340
+ ).length;
341
+
342
+ res.json({
343
+ success: true,
344
+ message: `Immediate wakeup completed. Started/restarted ${activated} runner(s).`,
345
+ wakeup: wakeupResults,
346
+ });
347
+ } catch (error) {
348
+ console.error('Error forcing wakeup:', error);
349
+ res.status(500).json({
350
+ success: false,
351
+ error: 'Failed to force wakeup',
352
+ message: error instanceof Error ? error.message : 'Unknown error',
353
+ });
354
+ }
355
+ });
356
+
357
+ export default router;
@@ -0,0 +1,127 @@
1
+ import { Router } from 'express';
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { homedir } from 'node:os';
5
+
6
+ const router = Router();
7
+ interface SkillListing {
8
+ name: string;
9
+ type: 'pre-installed' | 'custom';
10
+ characterCount: number;
11
+ }
12
+
13
+ function readSkillLength(filePath: string): number {
14
+ if (!existsSync(filePath)) {
15
+ return 0;
16
+ }
17
+
18
+ const content = readFileSync(filePath, 'utf-8');
19
+ return content.length;
20
+ }
21
+
22
+ function getCustomSkillsDir(): string {
23
+ const dir = join(homedir(), '.steroids', 'skills');
24
+ if (!existsSync(dir)) {
25
+ mkdirSync(dir, { recursive: true });
26
+ }
27
+ return dir;
28
+ }
29
+
30
+ function getPreinstalledSkillsDir(): string {
31
+ // Relative to API running from either root or dist
32
+ let currentDir = process.cwd();
33
+ let rootDir = currentDir;
34
+ while (currentDir !== '/') {
35
+ if (existsSync(join(currentDir, 'WebUI', 'src', 'assets', 'skills'))) {
36
+ rootDir = currentDir;
37
+ break;
38
+ }
39
+ currentDir = join(currentDir, '..');
40
+ }
41
+ return join(rootDir, 'WebUI', 'src', 'assets', 'skills');
42
+ }
43
+
44
+ router.get('/skills', (req, res) => {
45
+ const skills: SkillListing[] = [];
46
+
47
+ try {
48
+ const customDir = getCustomSkillsDir();
49
+ if (existsSync(customDir)) {
50
+ for (const file of readdirSync(customDir)) {
51
+ if (file.endsWith('.md')) {
52
+ const name = file.replace('.md', '');
53
+ skills.push({ name, type: 'custom', characterCount: readSkillLength(join(customDir, file)) });
54
+ }
55
+ }
56
+ }
57
+
58
+ const preinstalledDir = getPreinstalledSkillsDir();
59
+ if (existsSync(preinstalledDir)) {
60
+ for (const file of readdirSync(preinstalledDir)) {
61
+ if (file.endsWith('.md')) {
62
+ const name = file.replace('.md', '');
63
+ if (!skills.find(s => s.name === name)) {
64
+ skills.push({ name, type: 'pre-installed', characterCount: readSkillLength(join(preinstalledDir, file)) });
65
+ }
66
+ }
67
+ }
68
+ }
69
+ res.json({ success: true, data: skills });
70
+ } catch (err: any) {
71
+ res.status(500).json({ success: false, error: err.message });
72
+ }
73
+ });
74
+
75
+ router.get('/skills/:name', (req, res) => {
76
+ const name = req.params.name;
77
+
78
+ try {
79
+ const customFile = join(getCustomSkillsDir(), `${name}.md`);
80
+ if (existsSync(customFile)) {
81
+ return res.json({ success: true, data: { name, content: readFileSync(customFile, 'utf-8'), type: 'custom' } });
82
+ }
83
+
84
+ const preinstalledFile = join(getPreinstalledSkillsDir(), `${name}.md`);
85
+ if (existsSync(preinstalledFile)) {
86
+ return res.json({ success: true, data: { name, content: readFileSync(preinstalledFile, 'utf-8'), type: 'pre-installed' } });
87
+ }
88
+
89
+ res.status(404).json({ success: false, error: 'Skill not found' });
90
+ } catch (err: any) {
91
+ res.status(500).json({ success: false, error: err.message });
92
+ }
93
+ });
94
+
95
+ router.delete('/skills/:name', (req, res) => {
96
+ const name = req.params.name;
97
+
98
+ try {
99
+ const filePath = join(getCustomSkillsDir(), `${name.replace('.md', '')}.md`);
100
+ if (!existsSync(filePath)) {
101
+ return res.status(404).json({ success: false, error: 'Custom skill not found' });
102
+ }
103
+ rmSync(filePath);
104
+ res.json({ success: true, message: 'Skill deleted successfully' });
105
+ } catch (err: any) {
106
+ res.status(500).json({ success: false, error: err.message });
107
+ }
108
+ });
109
+
110
+ router.post('/skills/:name', (req, res) => {
111
+ const name = req.params.name;
112
+ const { content } = req.body;
113
+
114
+ if (!content) {
115
+ return res.status(400).json({ success: false, error: 'Content is required' });
116
+ }
117
+
118
+ try {
119
+ const filePath = join(getCustomSkillsDir(), `${name.replace('.md', '')}.md`);
120
+ writeFileSync(filePath, content, 'utf-8');
121
+ res.json({ success: true, message: 'Skill created/updated successfully' });
122
+ } catch (err: any) {
123
+ res.status(500).json({ success: false, error: err.message });
124
+ }
125
+ });
126
+
127
+ export default router;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Storage API routes
3
+ * Project storage breakdown and cleanup
4
+ */
5
+
6
+ import { Router, Request, Response } from 'express';
7
+ import {
8
+ validateProjectPath,
9
+ getCachedStorageBreakdown,
10
+ bustStorageCache,
11
+ } from '../utils/storage-cache.js';
12
+ import { cleanupInvocationLogs } from '../../../dist/cleanup/invocation-logs.js';
13
+ import { cleanupTextLogs } from '../../../dist/cleanup/text-logs.js';
14
+ import { cleanupBackups } from '../../../dist/cleanup/backups.js';
15
+ import { formatBytes } from '../../../dist/cleanup/directory-size.js';
16
+
17
+ const router = Router();
18
+
19
+ /**
20
+ * GET /api/projects/storage
21
+ * Get storage breakdown for a project's .steroids/ directory
22
+ * Query: path (required) — absolute path to the project
23
+ */
24
+ router.get('/projects/storage', async (req: Request, res: Response) => {
25
+ try {
26
+ const result = await validateProjectPath(req.query.path as string);
27
+ if (!result.valid) {
28
+ res.status(result.status).json({ success: false, error: result.error });
29
+ return;
30
+ }
31
+
32
+ const retention = parseRetentionDays(req.query.retention_days);
33
+ if (!retention.valid) {
34
+ res.status(400).json({ success: false, error: retention.error });
35
+ return;
36
+ }
37
+
38
+ const breakdown = await getCachedStorageBreakdown(
39
+ result.realPath,
40
+ retention.days,
41
+ Math.max(retention.days, 30) // Match cleanupBackups floor
42
+ );
43
+ res.json(breakdown);
44
+ } catch (error) {
45
+ console.error('Error getting project storage:', error);
46
+ res.status(500).json({
47
+ success: false,
48
+ error: 'Failed to get storage breakdown',
49
+ message: error instanceof Error ? error.message : 'Unknown error',
50
+ });
51
+ }
52
+ });
53
+
54
+ function parseRetentionDays(raw: unknown): { valid: true; days: number } | { valid: false; error: string } {
55
+ if (raw === undefined) return { valid: true, days: 7 };
56
+ if (typeof raw !== 'number' || !Number.isInteger(raw) || raw < 1 || raw > 365) {
57
+ return { valid: false, error: 'retention_days must be a positive integer (1-365)' };
58
+ }
59
+ return { valid: true, days: raw };
60
+ }
61
+
62
+ function runCleanup(projectPath: string, retentionDays: number) {
63
+ const invResult = cleanupInvocationLogs(projectPath, { retentionDays });
64
+ const textResult = cleanupTextLogs(projectPath, { retentionDays });
65
+ // Respect user retention but keep backups for at least 30 days by default
66
+ const backupResult = cleanupBackups(projectPath, { retentionDays: Math.max(retentionDays, 30) });
67
+ bustStorageCache(projectPath);
68
+ return {
69
+ ok: true as const,
70
+ deleted_files: invResult.deletedFiles + textResult.deletedFiles + backupResult.deletedFiles,
71
+ freed_bytes: invResult.freedBytes + textResult.freedBytes + backupResult.freedBytes,
72
+ freed_human: formatBytes(invResult.freedBytes + textResult.freedBytes + backupResult.freedBytes),
73
+ };
74
+ }
75
+
76
+ /**
77
+ * POST /api/projects/clear-logs
78
+ * Delete old invocation and text logs for a project
79
+ * Body: { path: string, retention_days?: number }
80
+ */
81
+ router.post('/projects/clear-logs', async (req: Request, res: Response) => {
82
+ try {
83
+ const { path: rawPath, retention_days } = req.body;
84
+
85
+ const retention = parseRetentionDays(retention_days);
86
+ if (!retention.valid) {
87
+ res.status(400).json({ ok: false, error: retention.error });
88
+ return;
89
+ }
90
+
91
+ const validation = await validateProjectPath(rawPath);
92
+ if (!validation.valid) {
93
+ res.status(validation.status).json({ ok: false, error: validation.error });
94
+ return;
95
+ }
96
+
97
+ res.json(runCleanup(validation.realPath, retention.days));
98
+ } catch (error) {
99
+ console.error('Error clearing project logs:', error);
100
+ res.status(500).json({
101
+ ok: false,
102
+ error: 'Failed to clear logs',
103
+ message: error instanceof Error ? error.message : 'Unknown error',
104
+ });
105
+ }
106
+ });
107
+
108
+ export default router;