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,51 @@
1
+ /**
2
+ * Path validation utilities for API security
3
+ */
4
+ import { existsSync, realpathSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ /**
7
+ * Validate that a path is safe and points to a valid Steroids project
8
+ *
9
+ * @param path - Path to validate
10
+ * @returns True if path is valid and safe
11
+ */
12
+ export function isValidProjectPath(path) {
13
+ try {
14
+ const realPath = realpathSync(path);
15
+ // Must contain .steroids directory with database
16
+ const steroidsDb = join(realPath, '.steroids', 'steroids.db');
17
+ if (!existsSync(steroidsDb)) {
18
+ return false;
19
+ }
20
+ // Must not be system directories
21
+ const forbidden = ['/etc', '/var', '/usr', '/bin', '/sbin'];
22
+ if (forbidden.some((f) => realPath.startsWith(f)) ||
23
+ (realPath.startsWith('/System') && !realPath.startsWith('/System/Volumes/Data'))) {
24
+ return false;
25
+ }
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ /**
33
+ * Validate request body for path-based operations
34
+ *
35
+ * @param body - Request body
36
+ * @returns Validation result with error message if invalid
37
+ */
38
+ export function validatePathRequest(body) {
39
+ if (!body || typeof body !== 'object') {
40
+ return { valid: false, error: 'Request body must be an object' };
41
+ }
42
+ const { path } = body;
43
+ if (!path || typeof path !== 'string') {
44
+ return { valid: false, error: 'Request body must contain a "path" string field' };
45
+ }
46
+ if (path.trim().length === 0) {
47
+ return { valid: false, error: 'Path cannot be empty' };
48
+ }
49
+ return { valid: true, path };
50
+ }
51
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAEpC,iDAAiD;QACjD,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,aAAa,CAAC,CAAC;QAC9D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,iCAAiC;QACjC,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QAC5D,IACE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YAC7C,CAAC,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,EAChF,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAa;IAC/C,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC;IACnE,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,GAAG,IAA0B,CAAC;IAE5C,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,iDAAiD,EAAE,CAAC;IACpF,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC;IACzD,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAC/B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "steroids-api",
3
+ "version": "0.2.7",
4
+ "description": "REST API for Steroids multi-project monitoring",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "start": "node dist/API/src/index.js",
10
+ "dev": "tsx watch src/index.ts",
11
+ "test": "jest"
12
+ },
13
+ "keywords": [
14
+ "api",
15
+ "rest",
16
+ "steroids",
17
+ "monitoring"
18
+ ],
19
+ "author": "",
20
+ "license": "MIT",
21
+ "dependencies": {
22
+ "@types/tail": "^2.2.3",
23
+ "better-sqlite3": "^11.0.0",
24
+ "cors": "^2.8.5",
25
+ "express": "^4.18.2",
26
+ "tail": "^2.2.6"
27
+ },
28
+ "devDependencies": {
29
+ "@types/better-sqlite3": "^7.6.13",
30
+ "@types/cors": "^2.8.17",
31
+ "@types/express": "^4.17.21",
32
+ "@types/node": "^20.10.0",
33
+ "tsx": "^4.7.0",
34
+ "typescript": "^5.3.0"
35
+ },
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ }
39
+ }
package/src/index.ts ADDED
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Steroids API Server
3
+ * REST API for multi-project monitoring and management
4
+ */
5
+
6
+ import express from 'express';
7
+ import cors from 'cors';
8
+ import { execSync } from 'node:child_process';
9
+ import { realpathSync, readFileSync, existsSync } from 'node:fs';
10
+ import { resolve, join, dirname } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ // Load API keys from .zshrc if not already set in the environment.
14
+ // The API server is a daemon and may not inherit interactive shell env vars.
15
+ (function loadEnvFromZshrc() {
16
+ const vars = ['STEROIDS_ANTHROPIC', 'STEROIDS_GOOGLE', 'STEROIDS_MISTRAL', 'STEROIDS_OPENAI'];
17
+ const missing = vars.filter((v) => !process.env[v]);
18
+ if (missing.length === 0) return;
19
+ try {
20
+ const exports = missing.map((v) => `printf "${v}=%s\\n" "$${v}"`).join('; ');
21
+ const out = execSync(
22
+ `zsh -c 'source ~/.zshrc 2>/dev/null; ${exports}'`,
23
+ { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }
24
+ ).trim();
25
+ for (const line of out.split('\n')) {
26
+ const eq = line.indexOf('=');
27
+ if (eq === -1) continue;
28
+ const key = line.slice(0, eq);
29
+ const val = line.slice(eq + 1).trim();
30
+ if (val && !process.env[key]) process.env[key] = val;
31
+ }
32
+ } catch {
33
+ // .zshrc not accessible or vars not defined there — continue without them
34
+ }
35
+ }());
36
+ import projectsRouter from './routes/projects.js';
37
+ import storageRouter from './routes/storage.js';
38
+ import activityRouter from './routes/activity.js';
39
+ import runnersRouter from './routes/runners.js';
40
+ import tasksRouter from './routes/tasks.js';
41
+ import configRouter from './routes/config.js';
42
+ import healthRouter from './routes/health.js';
43
+ import incidentsRouter from './routes/incidents.js';
44
+ import skillsRouter from './routes/skills.js';
45
+ import { creditAlertRoutes } from './routes/credit-alerts.js';
46
+
47
+ const PORT = process.env.PORT || 3501;
48
+ const HOST = process.env.HOST || '0.0.0.0';
49
+
50
+ // Get version from package.json
51
+ const __filename = fileURLToPath(import.meta.url);
52
+ const __dirname = dirname(__filename);
53
+
54
+ function getVersion(): string {
55
+ const paths = [
56
+ join(__dirname, '..', 'package.json'), // From src/
57
+ join(__dirname, '..', '..', 'package.json'), // From dist/
58
+ ];
59
+ for (const p of paths) {
60
+ try {
61
+ if (existsSync(p)) {
62
+ return JSON.parse(readFileSync(p, 'utf-8')).version;
63
+ }
64
+ } catch { /* continue */ }
65
+ }
66
+ return 'unknown';
67
+ }
68
+
69
+ const version = getVersion();
70
+
71
+ export function createApp(): express.Express {
72
+ const app = express();
73
+
74
+ // Middleware - CORS allowing all origins for local network access
75
+ app.use(cors({
76
+ origin: true, // Allow all origins
77
+ credentials: true,
78
+ }));
79
+ app.use(express.json());
80
+
81
+ // Request logging (keep test output clean)
82
+ if (process.env.NODE_ENV !== 'test') {
83
+ app.use((req, res, next) => {
84
+ console.log(`${new Date().toISOString()} ${req.method} ${req.path}`);
85
+ next();
86
+ });
87
+ }
88
+
89
+ // Routes
90
+ app.use('/api', projectsRouter);
91
+ app.use('/api', storageRouter);
92
+ app.use('/api', activityRouter);
93
+ app.use('/api', runnersRouter);
94
+ app.use('/api', tasksRouter);
95
+ app.use('/api', configRouter);
96
+ app.use('/api', healthRouter);
97
+ app.use('/api', incidentsRouter);
98
+ app.use('/api', skillsRouter);
99
+ app.use('/api/credit-alerts', creditAlertRoutes);
100
+
101
+ // Health check
102
+ app.get('/health', (req, res) => {
103
+ res.json({
104
+ status: 'ok',
105
+ timestamp: new Date().toISOString(),
106
+ version,
107
+ });
108
+ });
109
+
110
+ // Root endpoint
111
+ app.get('/', (req, res) => {
112
+ res.json({
113
+ name: 'Steroids API',
114
+ version,
115
+ endpoints: [
116
+ 'GET /health',
117
+ 'GET /api/projects',
118
+ 'GET /api/projects/status?path=<path>',
119
+ 'GET /api/projects/storage?path=<path>',
120
+ 'GET /api/projects/<path>/tasks?status=<status>&section=<id>&issue=<failed_retries|stale>&hours=<hours>',
121
+ 'GET /api/projects/<path>/sections',
122
+ 'POST /api/projects',
123
+ 'POST /api/projects/remove',
124
+ 'POST /api/projects/enable',
125
+ 'POST /api/projects/disable',
126
+ 'POST /api/projects/prune',
127
+ 'GET /api/activity?hours=<hours>&project=<path>',
128
+ 'GET /api/runners',
129
+ 'GET /api/runners/active-tasks',
130
+ 'GET /api/tasks/<taskId>?project=<path>',
131
+ 'GET /api/tasks/<taskId>/logs?project=<path>',
132
+ 'GET /api/config/schema',
133
+ 'GET /api/config/schema/<category>',
134
+ 'GET /api/config?scope=global|project|merged&project=<path>',
135
+ 'PUT /api/config',
136
+ 'GET /api/health?project=<path>',
137
+ 'GET /api/incidents?project=<path>&limit=<n>&task=<prefix>&unresolved=<true|false>',
138
+ 'GET /api/ai/providers',
139
+ 'GET /api/ai/models/<provider>',
140
+ 'GET /api/credit-alerts?project=<path>',
141
+ 'POST /api/credit-alerts/<id>/dismiss',
142
+ 'POST /api/credit-alerts/<id>/retry',
143
+ ],
144
+ });
145
+ });
146
+
147
+ // 404 handler
148
+ app.use((req, res) => {
149
+ res.status(404).json({
150
+ success: false,
151
+ error: 'Not found',
152
+ path: req.path,
153
+ });
154
+ });
155
+
156
+ // Error handler
157
+ app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
158
+ console.error('Unhandled error:', err);
159
+ res.status(500).json({
160
+ success: false,
161
+ error: 'Internal server error',
162
+ message: err.message,
163
+ });
164
+ });
165
+
166
+ return app;
167
+ }
168
+
169
+ const app = createApp();
170
+
171
+ export function startServer(): void {
172
+ // Start server on all interfaces
173
+ app.listen(Number(PORT), HOST, () => {
174
+ console.log(`Steroids API server listening on ${HOST}:${PORT}`);
175
+ console.log(`Health check: http://localhost:${PORT}/health`);
176
+ console.log(`API docs: http://localhost:${PORT}/`);
177
+ });
178
+ }
179
+
180
+ function normalizePath(p: string): string {
181
+ try {
182
+ return realpathSync(p);
183
+ } catch {
184
+ return resolve(p);
185
+ }
186
+ }
187
+
188
+ // Only start listening when executed as the entrypoint (safe to import in tests).
189
+ // This works for `node dist/.../index.js` and `tsx watch src/index.ts` (tsx keeps the entry file in argv).
190
+ const shouldAutoStart = (() => {
191
+ if (process.env.NODE_ENV === 'test') return false;
192
+ const thisPath = normalizePath(fileURLToPath(import.meta.url));
193
+ const argvPaths = process.argv.slice(1).map((a) => normalizePath(a));
194
+ return argvPaths.includes(thisPath);
195
+ })();
196
+
197
+ if (shouldAutoStart) startServer();
198
+
199
+ export default app;
@@ -0,0 +1,302 @@
1
+ /**
2
+ * Activity API routes
3
+ * Exposes activity log statistics for the dashboard
4
+ */
5
+
6
+ import { Router, Request, Response } from 'express';
7
+ import { execSync } from 'node:child_process';
8
+ import {
9
+ getActivityStatsByProject,
10
+ getActivityFiltered,
11
+ ProjectActivityStats,
12
+ ActivityStatus,
13
+ ActivityLogEntry,
14
+ } from '../../../dist/runners/activity-log.js';
15
+
16
+ /**
17
+ * Get GitHub URL from git remote for a project
18
+ */
19
+ function getGitHubUrl(projectPath: string): string | null {
20
+ try {
21
+ const remoteUrl = execSync('git remote get-url origin', {
22
+ cwd: projectPath,
23
+ encoding: 'utf-8',
24
+ }).trim();
25
+
26
+ // Convert SSH or HTTPS URL to web URL
27
+ if (remoteUrl.startsWith('git@github.com:')) {
28
+ const path = remoteUrl.replace('git@github.com:', '').replace(/\.git$/, '');
29
+ return `https://github.com/${path}`;
30
+ } else if (remoteUrl.includes('github.com')) {
31
+ return remoteUrl.replace(/\.git$/, '');
32
+ }
33
+ return null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ interface ActivityListEntry extends ActivityLogEntry {
40
+ github_url: string | null;
41
+ }
42
+
43
+ const router = Router();
44
+
45
+ interface ActivityStatsResponse {
46
+ project_path: string;
47
+ project_name: string | null;
48
+ completed: number;
49
+ failed: number;
50
+ skipped: number;
51
+ partial: number;
52
+ disputed: number;
53
+ total: number;
54
+ first_activity: string | null;
55
+ last_activity: string | null;
56
+ tasks_per_hour: number;
57
+ success_rate: number;
58
+ }
59
+
60
+ /**
61
+ * GET /api/activity
62
+ * Get activity statistics for a time range
63
+ * Query params:
64
+ * - hours: number (default: 24) - hours to look back
65
+ * - project: string (optional) - filter by project path
66
+ */
67
+ router.get('/activity', (req: Request, res: Response) => {
68
+ try {
69
+ const hoursParam = req.query.hours;
70
+ const projectPath = req.query.project as string | undefined;
71
+
72
+ // Parse hours parameter (default: 24)
73
+ let hours = 24;
74
+ if (hoursParam !== undefined) {
75
+ const parsed = parseInt(hoursParam as string, 10);
76
+ if (isNaN(parsed) || parsed <= 0) {
77
+ res.status(400).json({
78
+ success: false,
79
+ error: 'Invalid hours parameter - must be a positive integer',
80
+ });
81
+ return;
82
+ }
83
+ hours = parsed;
84
+ }
85
+
86
+ // Get stats from the activity log
87
+ const stats: ProjectActivityStats[] = getActivityStatsByProject(hours);
88
+
89
+ // Filter by project if specified
90
+ const filteredStats = projectPath
91
+ ? stats.filter((s) => s.project_path === projectPath)
92
+ : stats;
93
+
94
+ // Calculate derived metrics for each project
95
+ const enrichedStats: ActivityStatsResponse[] = filteredStats.map((s) => {
96
+ // Calculate hours between first and last activity for rate
97
+ let tasksPerHour = 0;
98
+ if (s.first_activity && s.last_activity && s.total > 0) {
99
+ const firstTime = new Date(s.first_activity).getTime();
100
+ const lastTime = new Date(s.last_activity).getTime();
101
+ const hoursDiff = Math.max((lastTime - firstTime) / (1000 * 60 * 60), 1);
102
+ tasksPerHour = Math.round((s.total / hoursDiff) * 100) / 100;
103
+ }
104
+
105
+ // Calculate success rate (completed / total)
106
+ const successRate =
107
+ s.total > 0 ? Math.round((s.completed / s.total) * 1000) / 10 : 0;
108
+
109
+ return {
110
+ project_path: s.project_path,
111
+ project_name: s.project_name,
112
+ completed: s.completed,
113
+ failed: s.failed,
114
+ skipped: s.skipped,
115
+ partial: s.partial,
116
+ disputed: s.disputed,
117
+ total: s.total,
118
+ first_activity: s.first_activity,
119
+ last_activity: s.last_activity,
120
+ tasks_per_hour: tasksPerHour,
121
+ success_rate: successRate,
122
+ };
123
+ });
124
+
125
+ // If filtering by a single project, return just that project's stats
126
+ // Otherwise return all projects
127
+ if (projectPath) {
128
+ const projectStats = enrichedStats[0] || {
129
+ project_path: projectPath,
130
+ project_name: null,
131
+ completed: 0,
132
+ failed: 0,
133
+ skipped: 0,
134
+ partial: 0,
135
+ disputed: 0,
136
+ total: 0,
137
+ first_activity: null,
138
+ last_activity: null,
139
+ tasks_per_hour: 0,
140
+ success_rate: 0,
141
+ };
142
+
143
+ res.json({
144
+ success: true,
145
+ hours,
146
+ stats: projectStats,
147
+ });
148
+ } else {
149
+ // Aggregate all projects for global stats
150
+ const totals = enrichedStats.reduce(
151
+ (acc, s) => ({
152
+ completed: acc.completed + s.completed,
153
+ failed: acc.failed + s.failed,
154
+ skipped: acc.skipped + s.skipped,
155
+ partial: acc.partial + s.partial,
156
+ disputed: acc.disputed + s.disputed,
157
+ total: acc.total + s.total,
158
+ }),
159
+ { completed: 0, failed: 0, skipped: 0, partial: 0, disputed: 0, total: 0 }
160
+ );
161
+
162
+ // Find the earliest first_activity and latest last_activity across all projects
163
+ let globalFirstActivity: string | null = null;
164
+ let globalLastActivity: string | null = null;
165
+ for (const s of enrichedStats) {
166
+ if (s.first_activity) {
167
+ if (!globalFirstActivity || s.first_activity < globalFirstActivity) {
168
+ globalFirstActivity = s.first_activity;
169
+ }
170
+ }
171
+ if (s.last_activity) {
172
+ if (!globalLastActivity || s.last_activity > globalLastActivity) {
173
+ globalLastActivity = s.last_activity;
174
+ }
175
+ }
176
+ }
177
+
178
+ // Calculate global metrics based on actual activity timespan
179
+ const globalSuccessRate =
180
+ totals.total > 0
181
+ ? Math.round((totals.completed / totals.total) * 1000) / 10
182
+ : 0;
183
+
184
+ // Calculate tasks per hour based on actual time between first and last activity
185
+ let globalTasksPerHour = 0;
186
+ if (globalFirstActivity && globalLastActivity && totals.total > 0) {
187
+ const firstTime = new Date(globalFirstActivity).getTime();
188
+ const lastTime = new Date(globalLastActivity).getTime();
189
+ const hoursDiff = Math.max((lastTime - firstTime) / (1000 * 60 * 60), 1);
190
+ globalTasksPerHour = Math.round((totals.total / hoursDiff) * 100) / 100;
191
+ }
192
+
193
+ res.json({
194
+ success: true,
195
+ hours,
196
+ stats: {
197
+ ...totals,
198
+ first_activity: globalFirstActivity,
199
+ last_activity: globalLastActivity,
200
+ tasks_per_hour: globalTasksPerHour,
201
+ success_rate: globalSuccessRate,
202
+ },
203
+ by_project: enrichedStats,
204
+ });
205
+ }
206
+ } catch (error) {
207
+ console.error('Error getting activity stats:', error);
208
+ res.status(500).json({
209
+ success: false,
210
+ error: 'Failed to get activity stats',
211
+ message: error instanceof Error ? error.message : 'Unknown error',
212
+ });
213
+ }
214
+ });
215
+
216
+ /**
217
+ * GET /api/activity/list
218
+ * Get activity log entries with optional filters
219
+ * Query params:
220
+ * - hours: number (default: 24) - hours to look back
221
+ * - status: string (optional) - filter by status (completed, failed, skipped, partial, disputed)
222
+ * - project: string (optional) - filter by project path
223
+ * - limit: number (optional) - max entries to return
224
+ */
225
+ router.get('/activity/list', (req: Request, res: Response) => {
226
+ try {
227
+ const hoursParam = req.query.hours;
228
+ const statusParam = req.query.status as string | undefined;
229
+ const projectPath = req.query.project as string | undefined;
230
+ const limitParam = req.query.limit;
231
+
232
+ // Parse hours parameter (default: 24)
233
+ let hours = 24;
234
+ if (hoursParam !== undefined) {
235
+ const parsed = parseInt(hoursParam as string, 10);
236
+ if (isNaN(parsed) || parsed <= 0) {
237
+ res.status(400).json({
238
+ success: false,
239
+ error: 'Invalid hours parameter - must be a positive integer',
240
+ });
241
+ return;
242
+ }
243
+ hours = parsed;
244
+ }
245
+
246
+ // Validate status if provided
247
+ const validStatuses: ActivityStatus[] = ['completed', 'failed', 'skipped', 'partial', 'disputed'];
248
+ if (statusParam && !validStatuses.includes(statusParam as ActivityStatus)) {
249
+ res.status(400).json({
250
+ success: false,
251
+ error: `Invalid status - must be one of: ${validStatuses.join(', ')}`,
252
+ });
253
+ return;
254
+ }
255
+
256
+ // Parse limit
257
+ let limit: number | undefined;
258
+ if (limitParam !== undefined) {
259
+ const parsed = parseInt(limitParam as string, 10);
260
+ if (!isNaN(parsed) && parsed > 0) {
261
+ limit = parsed;
262
+ }
263
+ }
264
+
265
+ const rawEntries = getActivityFiltered({
266
+ hoursAgo: hours,
267
+ status: statusParam as ActivityStatus | undefined,
268
+ projectPath,
269
+ limit,
270
+ });
271
+
272
+ // Cache GitHub URLs by project path to avoid repeated git calls
273
+ const githubUrlCache = new Map<string, string | null>();
274
+
275
+ const entries: ActivityListEntry[] = rawEntries.map((entry) => {
276
+ if (!githubUrlCache.has(entry.project_path)) {
277
+ githubUrlCache.set(entry.project_path, getGitHubUrl(entry.project_path));
278
+ }
279
+ return {
280
+ ...entry,
281
+ github_url: githubUrlCache.get(entry.project_path) ?? null,
282
+ };
283
+ });
284
+
285
+ res.json({
286
+ success: true,
287
+ hours,
288
+ status: statusParam || 'all',
289
+ entries,
290
+ count: entries.length,
291
+ });
292
+ } catch (error) {
293
+ console.error('Error getting activity list:', error);
294
+ res.status(500).json({
295
+ success: false,
296
+ error: 'Failed to get activity list',
297
+ message: error instanceof Error ? error.message : 'Unknown error',
298
+ });
299
+ }
300
+ });
301
+
302
+ export default router;