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,130 @@
1
+ /**
2
+ * Steroids API Server
3
+ * REST API for multi-project monitoring and management
4
+ */
5
+ import express from 'express';
6
+ import cors from 'cors';
7
+ import { realpathSync } from 'node:fs';
8
+ import { resolve } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import projectsRouter from './routes/projects.js';
11
+ import activityRouter from './routes/activity.js';
12
+ import runnersRouter from './routes/runners.js';
13
+ import tasksRouter from './routes/tasks.js';
14
+ import configRouter from './routes/config.js';
15
+ import healthRouter from './routes/health.js';
16
+ import incidentsRouter from './routes/incidents.js';
17
+ const PORT = process.env.PORT || 3501;
18
+ const HOST = process.env.HOST || '0.0.0.0';
19
+ export function createApp() {
20
+ const app = express();
21
+ // Middleware - CORS allowing all origins for local network access
22
+ app.use(cors({
23
+ origin: true, // Allow all origins
24
+ credentials: true,
25
+ }));
26
+ app.use(express.json());
27
+ // Request logging (keep test output clean)
28
+ if (process.env.NODE_ENV !== 'test') {
29
+ app.use((req, res, next) => {
30
+ console.log(`${new Date().toISOString()} ${req.method} ${req.path}`);
31
+ next();
32
+ });
33
+ }
34
+ // Routes
35
+ app.use('/api', projectsRouter);
36
+ app.use('/api', activityRouter);
37
+ app.use('/api', runnersRouter);
38
+ app.use('/api', tasksRouter);
39
+ app.use('/api', configRouter);
40
+ app.use('/api', healthRouter);
41
+ app.use('/api', incidentsRouter);
42
+ // Health check
43
+ app.get('/health', (req, res) => {
44
+ res.json({
45
+ status: 'ok',
46
+ timestamp: new Date().toISOString(),
47
+ version: '0.4.2',
48
+ });
49
+ });
50
+ // Root endpoint
51
+ app.get('/', (req, res) => {
52
+ res.json({
53
+ name: 'Steroids API',
54
+ version: '0.4.2',
55
+ endpoints: [
56
+ 'GET /health',
57
+ 'GET /api/projects',
58
+ 'GET /api/projects/status?path=<path>',
59
+ 'GET /api/projects/<path>/tasks',
60
+ 'GET /api/projects/<path>/sections',
61
+ 'POST /api/projects',
62
+ 'POST /api/projects/remove',
63
+ 'POST /api/projects/enable',
64
+ 'POST /api/projects/disable',
65
+ 'POST /api/projects/prune',
66
+ 'GET /api/activity?hours=<hours>&project=<path>',
67
+ 'GET /api/runners',
68
+ 'GET /api/runners/active-tasks',
69
+ 'GET /api/tasks/<taskId>?project=<path>',
70
+ 'GET /api/tasks/<taskId>/logs?project=<path>',
71
+ 'GET /api/config/schema',
72
+ 'GET /api/config/schema/<category>',
73
+ 'GET /api/config?scope=global|project|merged&project=<path>',
74
+ 'PUT /api/config',
75
+ 'GET /api/health?project=<path>',
76
+ 'GET /api/incidents?project=<path>&limit=<n>&task=<prefix>&unresolved=<true|false>',
77
+ 'GET /api/ai/providers',
78
+ 'GET /api/ai/models/<provider>',
79
+ ],
80
+ });
81
+ });
82
+ // 404 handler
83
+ app.use((req, res) => {
84
+ res.status(404).json({
85
+ success: false,
86
+ error: 'Not found',
87
+ path: req.path,
88
+ });
89
+ });
90
+ // Error handler
91
+ app.use((err, req, res, next) => {
92
+ console.error('Unhandled error:', err);
93
+ res.status(500).json({
94
+ success: false,
95
+ error: 'Internal server error',
96
+ message: err.message,
97
+ });
98
+ });
99
+ return app;
100
+ }
101
+ const app = createApp();
102
+ export function startServer() {
103
+ // Start server on all interfaces
104
+ app.listen(Number(PORT), HOST, () => {
105
+ console.log(`Steroids API server listening on ${HOST}:${PORT}`);
106
+ console.log(`Health check: http://localhost:${PORT}/health`);
107
+ console.log(`API docs: http://localhost:${PORT}/`);
108
+ });
109
+ }
110
+ function normalizePath(p) {
111
+ try {
112
+ return realpathSync(p);
113
+ }
114
+ catch {
115
+ return resolve(p);
116
+ }
117
+ }
118
+ // Only start listening when executed as the entrypoint (safe to import in tests).
119
+ // This works for `node dist/.../index.js` and `tsx watch src/index.ts` (tsx keeps the entry file in argv).
120
+ const shouldAutoStart = (() => {
121
+ if (process.env.NODE_ENV === 'test')
122
+ return false;
123
+ const thisPath = normalizePath(fileURLToPath(import.meta.url));
124
+ const argvPaths = process.argv.slice(1).map((a) => normalizePath(a));
125
+ return argvPaths.includes(thisPath);
126
+ })();
127
+ if (shouldAutoStart)
128
+ startServer();
129
+ export default app;
130
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Database queries for task and section lock management
3
+ * All lock operations are atomic using SQLite transactions
4
+ */
5
+ import type Database from 'better-sqlite3';
6
+ export interface TaskLock {
7
+ task_id: string;
8
+ runner_id: string;
9
+ acquired_at: string;
10
+ expires_at: string;
11
+ heartbeat_at: string;
12
+ }
13
+ export interface SectionLock {
14
+ section_id: string;
15
+ runner_id: string;
16
+ acquired_at: string;
17
+ expires_at: string;
18
+ }
19
+ export interface LockInfo {
20
+ isLocked: boolean;
21
+ lock: TaskLock | null;
22
+ isExpired: boolean;
23
+ isOwnedByUs: boolean;
24
+ }
25
+ /**
26
+ * Get task lock by task ID
27
+ */
28
+ export declare function getTaskLock(db: Database.Database, taskId: string): TaskLock | null;
29
+ /**
30
+ * Check if a task lock is expired
31
+ */
32
+ export declare function isTaskLockExpired(lock: TaskLock): boolean;
33
+ /**
34
+ * Get task lock info with ownership and expiry status
35
+ */
36
+ export declare function getTaskLockInfo(db: Database.Database, taskId: string, runnerId: string): LockInfo;
37
+ /**
38
+ * Try to insert a new task lock (atomic)
39
+ * Returns true if lock was acquired, false if already exists
40
+ */
41
+ export declare function tryInsertTaskLock(db: Database.Database, taskId: string, runnerId: string, timeoutMinutes: number): boolean;
42
+ /**
43
+ * Claim an expired lock atomically
44
+ * Only succeeds if the lock is actually expired at execution time
45
+ */
46
+ export declare function claimExpiredTaskLock(db: Database.Database, taskId: string, runnerId: string, timeoutMinutes: number): boolean;
47
+ /**
48
+ * Release a task lock (only if owned by the specified runner)
49
+ * Returns true if lock was released, false if not owned
50
+ */
51
+ export declare function releaseTaskLock(db: Database.Database, taskId: string, runnerId: string): boolean;
52
+ /**
53
+ * Force release a task lock (admin operation)
54
+ * Returns true if lock was released
55
+ */
56
+ export declare function forceReleaseTaskLock(db: Database.Database, taskId: string): boolean;
57
+ /**
58
+ * Update heartbeat for a task lock
59
+ * Returns true if heartbeat was updated, false if lock not found or not owned
60
+ */
61
+ export declare function updateTaskLockHeartbeat(db: Database.Database, taskId: string, runnerId: string): boolean;
62
+ /**
63
+ * Extend a task lock's expiry time
64
+ * Returns true if extended, false if lock not found or not owned
65
+ */
66
+ export declare function extendTaskLock(db: Database.Database, taskId: string, runnerId: string, additionalMinutes: number): boolean;
67
+ /**
68
+ * List all task locks
69
+ */
70
+ export declare function listTaskLocks(db: Database.Database): TaskLock[];
71
+ /**
72
+ * Find all expired task locks
73
+ */
74
+ export declare function findExpiredTaskLocks(db: Database.Database): TaskLock[];
75
+ /**
76
+ * Delete all expired task locks
77
+ * Returns the number of locks deleted
78
+ */
79
+ export declare function cleanupExpiredTaskLocks(db: Database.Database): number;
80
+ /**
81
+ * Get section lock by section ID
82
+ */
83
+ export declare function getSectionLock(db: Database.Database, sectionId: string): SectionLock | null;
84
+ /**
85
+ * Check if a section lock is expired
86
+ */
87
+ export declare function isSectionLockExpired(lock: SectionLock): boolean;
88
+ /**
89
+ * Try to insert a new section lock (atomic)
90
+ */
91
+ export declare function tryInsertSectionLock(db: Database.Database, sectionId: string, runnerId: string, timeoutMinutes: number): boolean;
92
+ /**
93
+ * Claim an expired section lock atomically
94
+ */
95
+ export declare function claimExpiredSectionLock(db: Database.Database, sectionId: string, runnerId: string, timeoutMinutes: number): boolean;
96
+ /**
97
+ * Release a section lock
98
+ */
99
+ export declare function releaseSectionLock(db: Database.Database, sectionId: string, runnerId: string): boolean;
100
+ /**
101
+ * Force release a section lock
102
+ */
103
+ export declare function forceReleaseSectionLock(db: Database.Database, sectionId: string): boolean;
104
+ /**
105
+ * List all section locks
106
+ */
107
+ export declare function listSectionLocks(db: Database.Database): SectionLock[];
108
+ /**
109
+ * Find all expired section locks
110
+ */
111
+ export declare function findExpiredSectionLocks(db: Database.Database): SectionLock[];
112
+ /**
113
+ * Delete all expired section locks
114
+ */
115
+ export declare function cleanupExpiredSectionLocks(db: Database.Database): number;
116
+ //# sourceMappingURL=queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../../../src/locking/queries.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Database queries for task and section lock management
3
+ * All lock operations are atomic using SQLite transactions
4
+ */
5
+ // ============ Task Lock Queries ============
6
+ /**
7
+ * Get task lock by task ID
8
+ */
9
+ export function getTaskLock(db, taskId) {
10
+ return db
11
+ .prepare('SELECT * FROM task_locks WHERE task_id = ?')
12
+ .get(taskId);
13
+ }
14
+ /**
15
+ * Check if a task lock is expired
16
+ */
17
+ export function isTaskLockExpired(lock) {
18
+ const expiresAt = new Date(lock.expires_at).getTime();
19
+ return Date.now() > expiresAt;
20
+ }
21
+ /**
22
+ * Get task lock info with ownership and expiry status
23
+ */
24
+ export function getTaskLockInfo(db, taskId, runnerId) {
25
+ const lock = getTaskLock(db, taskId);
26
+ if (!lock) {
27
+ return {
28
+ isLocked: false,
29
+ lock: null,
30
+ isExpired: false,
31
+ isOwnedByUs: false,
32
+ };
33
+ }
34
+ const isExpired = isTaskLockExpired(lock);
35
+ const isOwnedByUs = lock.runner_id === runnerId;
36
+ return {
37
+ isLocked: !isExpired,
38
+ lock,
39
+ isExpired,
40
+ isOwnedByUs,
41
+ };
42
+ }
43
+ /**
44
+ * Try to insert a new task lock (atomic)
45
+ * Returns true if lock was acquired, false if already exists
46
+ */
47
+ export function tryInsertTaskLock(db, taskId, runnerId, timeoutMinutes) {
48
+ const expiresAt = new Date(Date.now() + timeoutMinutes * 60 * 1000).toISOString();
49
+ try {
50
+ db.prepare(`INSERT INTO task_locks (task_id, runner_id, expires_at)
51
+ VALUES (?, ?, ?)`).run(taskId, runnerId, expiresAt);
52
+ return true;
53
+ }
54
+ catch (err) {
55
+ // IntegrityError means lock already exists
56
+ if (err instanceof Error && err.message.includes('UNIQUE constraint')) {
57
+ return false;
58
+ }
59
+ throw err;
60
+ }
61
+ }
62
+ /**
63
+ * Claim an expired lock atomically
64
+ * Only succeeds if the lock is actually expired at execution time
65
+ */
66
+ export function claimExpiredTaskLock(db, taskId, runnerId, timeoutMinutes) {
67
+ const expiresAt = new Date(Date.now() + timeoutMinutes * 60 * 1000).toISOString();
68
+ const result = db.prepare(`UPDATE task_locks
69
+ SET runner_id = ?,
70
+ acquired_at = datetime('now'),
71
+ expires_at = ?,
72
+ heartbeat_at = datetime('now')
73
+ WHERE task_id = ?
74
+ AND expires_at < datetime('now')`).run(runnerId, expiresAt, taskId);
75
+ return result.changes > 0;
76
+ }
77
+ /**
78
+ * Release a task lock (only if owned by the specified runner)
79
+ * Returns true if lock was released, false if not owned
80
+ */
81
+ export function releaseTaskLock(db, taskId, runnerId) {
82
+ const result = db.prepare(`DELETE FROM task_locks
83
+ WHERE task_id = ?
84
+ AND runner_id = ?`).run(taskId, runnerId);
85
+ return result.changes > 0;
86
+ }
87
+ /**
88
+ * Force release a task lock (admin operation)
89
+ * Returns true if lock was released
90
+ */
91
+ export function forceReleaseTaskLock(db, taskId) {
92
+ const result = db.prepare(`DELETE FROM task_locks WHERE task_id = ?`).run(taskId);
93
+ return result.changes > 0;
94
+ }
95
+ /**
96
+ * Update heartbeat for a task lock
97
+ * Returns true if heartbeat was updated, false if lock not found or not owned
98
+ */
99
+ export function updateTaskLockHeartbeat(db, taskId, runnerId) {
100
+ const result = db.prepare(`UPDATE task_locks
101
+ SET heartbeat_at = datetime('now')
102
+ WHERE task_id = ?
103
+ AND runner_id = ?`).run(taskId, runnerId);
104
+ return result.changes > 0;
105
+ }
106
+ /**
107
+ * Extend a task lock's expiry time
108
+ * Returns true if extended, false if lock not found or not owned
109
+ */
110
+ export function extendTaskLock(db, taskId, runnerId, additionalMinutes) {
111
+ const newExpiresAt = new Date(Date.now() + additionalMinutes * 60 * 1000).toISOString();
112
+ const result = db.prepare(`UPDATE task_locks
113
+ SET expires_at = ?,
114
+ heartbeat_at = datetime('now')
115
+ WHERE task_id = ?
116
+ AND runner_id = ?`).run(newExpiresAt, taskId, runnerId);
117
+ return result.changes > 0;
118
+ }
119
+ /**
120
+ * List all task locks
121
+ */
122
+ export function listTaskLocks(db) {
123
+ return db
124
+ .prepare('SELECT * FROM task_locks ORDER BY acquired_at DESC')
125
+ .all();
126
+ }
127
+ /**
128
+ * Find all expired task locks
129
+ */
130
+ export function findExpiredTaskLocks(db) {
131
+ return db
132
+ .prepare(`SELECT * FROM task_locks
133
+ WHERE expires_at < datetime('now')
134
+ ORDER BY expires_at ASC`)
135
+ .all();
136
+ }
137
+ /**
138
+ * Delete all expired task locks
139
+ * Returns the number of locks deleted
140
+ */
141
+ export function cleanupExpiredTaskLocks(db) {
142
+ const result = db.prepare(`DELETE FROM task_locks WHERE expires_at < datetime('now')`).run();
143
+ return result.changes;
144
+ }
145
+ // ============ Section Lock Queries ============
146
+ /**
147
+ * Get section lock by section ID
148
+ */
149
+ export function getSectionLock(db, sectionId) {
150
+ return db
151
+ .prepare('SELECT * FROM section_locks WHERE section_id = ?')
152
+ .get(sectionId);
153
+ }
154
+ /**
155
+ * Check if a section lock is expired
156
+ */
157
+ export function isSectionLockExpired(lock) {
158
+ const expiresAt = new Date(lock.expires_at).getTime();
159
+ return Date.now() > expiresAt;
160
+ }
161
+ /**
162
+ * Try to insert a new section lock (atomic)
163
+ */
164
+ export function tryInsertSectionLock(db, sectionId, runnerId, timeoutMinutes) {
165
+ const expiresAt = new Date(Date.now() + timeoutMinutes * 60 * 1000).toISOString();
166
+ try {
167
+ db.prepare(`INSERT INTO section_locks (section_id, runner_id, expires_at)
168
+ VALUES (?, ?, ?)`).run(sectionId, runnerId, expiresAt);
169
+ return true;
170
+ }
171
+ catch (err) {
172
+ if (err instanceof Error && err.message.includes('UNIQUE constraint')) {
173
+ return false;
174
+ }
175
+ throw err;
176
+ }
177
+ }
178
+ /**
179
+ * Claim an expired section lock atomically
180
+ */
181
+ export function claimExpiredSectionLock(db, sectionId, runnerId, timeoutMinutes) {
182
+ const expiresAt = new Date(Date.now() + timeoutMinutes * 60 * 1000).toISOString();
183
+ const result = db.prepare(`UPDATE section_locks
184
+ SET runner_id = ?,
185
+ acquired_at = datetime('now'),
186
+ expires_at = ?
187
+ WHERE section_id = ?
188
+ AND expires_at < datetime('now')`).run(runnerId, expiresAt, sectionId);
189
+ return result.changes > 0;
190
+ }
191
+ /**
192
+ * Release a section lock
193
+ */
194
+ export function releaseSectionLock(db, sectionId, runnerId) {
195
+ const result = db.prepare(`DELETE FROM section_locks
196
+ WHERE section_id = ?
197
+ AND runner_id = ?`).run(sectionId, runnerId);
198
+ return result.changes > 0;
199
+ }
200
+ /**
201
+ * Force release a section lock
202
+ */
203
+ export function forceReleaseSectionLock(db, sectionId) {
204
+ const result = db.prepare(`DELETE FROM section_locks WHERE section_id = ?`).run(sectionId);
205
+ return result.changes > 0;
206
+ }
207
+ /**
208
+ * List all section locks
209
+ */
210
+ export function listSectionLocks(db) {
211
+ return db
212
+ .prepare('SELECT * FROM section_locks ORDER BY acquired_at DESC')
213
+ .all();
214
+ }
215
+ /**
216
+ * Find all expired section locks
217
+ */
218
+ export function findExpiredSectionLocks(db) {
219
+ return db
220
+ .prepare(`SELECT * FROM section_locks
221
+ WHERE expires_at < datetime('now')
222
+ ORDER BY expires_at ASC`)
223
+ .all();
224
+ }
225
+ /**
226
+ * Delete all expired section locks
227
+ */
228
+ export function cleanupExpiredSectionLocks(db) {
229
+ const result = db.prepare(`DELETE FROM section_locks WHERE expires_at < datetime('now')`).run();
230
+ return result.changes;
231
+ }
232
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../../../src/locking/queries.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Section locking for parallel work prevention
3
+ * Prevents multiple runners from working on tasks in the same section
4
+ */
5
+ import type Database from 'better-sqlite3';
6
+ import { type SectionLock } from './queries.js';
7
+ export interface SectionLockAcquisitionResult {
8
+ acquired: boolean;
9
+ lock?: SectionLock;
10
+ reason?: 'already_owned' | 'acquired_new' | 'claimed_expired';
11
+ error?: SectionLockError;
12
+ }
13
+ export interface SectionLockError {
14
+ code: 'SECTION_LOCKED' | 'LOCK_NOT_FOUND' | 'PERMISSION_DENIED';
15
+ message: string;
16
+ details?: {
17
+ sectionId: string;
18
+ runnerId?: string;
19
+ expiresAt?: string;
20
+ };
21
+ }
22
+ export interface SectionLockReleaseResult {
23
+ released: boolean;
24
+ error?: SectionLockError;
25
+ }
26
+ export interface SectionLockManager {
27
+ acquire: () => SectionLockAcquisitionResult;
28
+ release: () => SectionLockReleaseResult;
29
+ isHeld: () => boolean;
30
+ }
31
+ /**
32
+ * Acquire a section lock with atomic operations
33
+ *
34
+ * Flow:
35
+ * 1. Try INSERT (fails if lock exists)
36
+ * 2. If exists, check if we own it
37
+ * 3. If owned by another, check if expired
38
+ * 4. If expired, claim atomically
39
+ * 5. If not expired, return failure
40
+ */
41
+ export declare function acquireSectionLock(db: Database.Database, sectionId: string, runnerId: string, timeoutMinutes?: number): SectionLockAcquisitionResult;
42
+ /**
43
+ * Release a section lock (only by owner)
44
+ */
45
+ export declare function releaseSectionLock(db: Database.Database, sectionId: string, runnerId: string): SectionLockReleaseResult;
46
+ /**
47
+ * Force release a section lock (admin operation)
48
+ */
49
+ export declare function forceRelease(db: Database.Database, sectionId: string): SectionLockReleaseResult;
50
+ /**
51
+ * Create a section lock manager for a specific section
52
+ */
53
+ export declare function createSectionLockManager(db: Database.Database, sectionId: string, runnerId: string, timeoutMinutes?: number): SectionLockManager;
54
+ /**
55
+ * Check if a section is locked
56
+ */
57
+ export declare function isSectionLocked(db: Database.Database, sectionId: string): boolean;
58
+ /**
59
+ * Check if a section is locked by a specific runner
60
+ */
61
+ export declare function isSectionLockedBy(db: Database.Database, sectionId: string, runnerId: string): boolean;
62
+ /**
63
+ * Get time until section lock expires (in milliseconds)
64
+ */
65
+ export declare function getTimeUntilExpiry(db: Database.Database, sectionId: string): number | null;
66
+ /**
67
+ * Check if any tasks in the section are in progress by another runner
68
+ * This can be used before acquiring a section lock
69
+ */
70
+ export declare function getSectionLockHolder(db: Database.Database, sectionId: string): SectionLock | null;
71
+ export declare const SECTION_LOCK_CONSTANTS: {
72
+ readonly DEFAULT_TIMEOUT_MINUTES: 120;
73
+ };
74
+ //# sourceMappingURL=section-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"section-lock.d.ts","sourceRoot":"","sources":["../../../../src/locking/section-lock.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAOL,KAAK,WAAW,EACjB,MAAM,cAAc,CAAC;AAOtB,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,MAAM,CAAC,EAAE,eAAe,GAAG,cAAc,GAAG,iBAAiB,CAAC;IAC9D,KAAK,CAAC,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,gBAAgB,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,4BAA4B,CAAC;IAC5C,OAAO,EAAE,MAAM,wBAAwB,CAAC;IACxC,MAAM,EAAE,MAAM,OAAO,CAAC;CACvB;AAID;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,MAAwC,GACvD,4BAA4B,CAqD9B;AAyBD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,wBAAwB,CA+B1B;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,GAChB,wBAAwB,CAe1B;AAID;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,MAAwC,GACvD,kBAAkB,CASpB;AAID;;GAEG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAIT;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAIT;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAOf;AAID;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,SAAS,EAAE,MAAM,GAChB,WAAW,GAAG,IAAI,CAMpB;AAID,eAAO,MAAM,sBAAsB;;CAEzB,CAAC"}