@wascartech/devbot 0.0.0-stage → 2.1.1

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 (376) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +638 -2
  3. package/dist/access-grant-store.js +99 -0
  4. package/dist/access-grant-store.js.map +1 -0
  5. package/dist/access-resolve.js +11 -0
  6. package/dist/access-resolve.js.map +1 -0
  7. package/dist/access-rule-store.js +99 -0
  8. package/dist/access-rule-store.js.map +1 -0
  9. package/dist/admin/access-routes.js +73 -0
  10. package/dist/admin/access-routes.js.map +1 -0
  11. package/dist/admin/access-rule-routes.js +107 -0
  12. package/dist/admin/access-rule-routes.js.map +1 -0
  13. package/dist/admin/custom-repos-routes.js +93 -0
  14. package/dist/admin/custom-repos-routes.js.map +1 -0
  15. package/dist/admin/env-routes.js +74 -0
  16. package/dist/admin/env-routes.js.map +1 -0
  17. package/dist/admin/global-settings-routes.js +254 -0
  18. package/dist/admin/global-settings-routes.js.map +1 -0
  19. package/dist/admin/mcp-cleanup.js +12 -0
  20. package/dist/admin/mcp-cleanup.js.map +1 -0
  21. package/dist/admin/mcp-routes.js +169 -0
  22. package/dist/admin/mcp-routes.js.map +1 -0
  23. package/dist/admin/query-params.js +80 -0
  24. package/dist/admin/query-params.js.map +1 -0
  25. package/dist/admin/routes.js +150 -0
  26. package/dist/admin/routes.js.map +1 -0
  27. package/dist/admin/run-history-routes.js +44 -0
  28. package/dist/admin/run-history-routes.js.map +1 -0
  29. package/dist/admin/run-routes.js +99 -0
  30. package/dist/admin/run-routes.js.map +1 -0
  31. package/dist/admin/settings-routes.js +119 -0
  32. package/dist/admin/settings-routes.js.map +1 -0
  33. package/dist/admin/stats-routes.js +51 -0
  34. package/dist/admin/stats-routes.js.map +1 -0
  35. package/dist/admin/task-routes.js +213 -0
  36. package/dist/admin/task-routes.js.map +1 -0
  37. package/dist/admin/test-routes.js +38 -0
  38. package/dist/admin/test-routes.js.map +1 -0
  39. package/dist/admin/volume-routes.js +82 -0
  40. package/dist/admin/volume-routes.js.map +1 -0
  41. package/dist/admin/webhook-routes.js +174 -0
  42. package/dist/admin/webhook-routes.js.map +1 -0
  43. package/dist/agents.js +48 -0
  44. package/dist/agents.js.map +1 -0
  45. package/dist/assignee-value.js +6 -0
  46. package/dist/assignee-value.js.map +1 -0
  47. package/dist/attachments.js +76 -0
  48. package/dist/attachments.js.map +1 -0
  49. package/dist/auto-commands.js +88 -0
  50. package/dist/auto-commands.js.map +1 -0
  51. package/dist/auto-features.js +32 -0
  52. package/dist/auto-features.js.map +1 -0
  53. package/dist/base-branch-store.js +40 -0
  54. package/dist/base-branch-store.js.map +1 -0
  55. package/dist/base-branches.js +9 -0
  56. package/dist/base-branches.js.map +1 -0
  57. package/dist/build-workflow.js +116 -0
  58. package/dist/build-workflow.js.map +1 -0
  59. package/dist/change-request-lifecycle.js +16 -0
  60. package/dist/change-request-lifecycle.js.map +1 -0
  61. package/dist/change-request-store.js +72 -0
  62. package/dist/change-request-store.js.map +1 -0
  63. package/dist/ci-failure.js +80 -0
  64. package/dist/ci-failure.js.map +1 -0
  65. package/dist/cli.js +3 -0
  66. package/dist/cli.js.map +1 -0
  67. package/dist/config.js +17 -0
  68. package/dist/config.js.map +1 -0
  69. package/dist/conflict-fix.js +178 -0
  70. package/dist/conflict-fix.js.map +1 -0
  71. package/dist/cookie.js +70 -0
  72. package/dist/cookie.js.map +1 -0
  73. package/dist/crypto-util.js +40 -0
  74. package/dist/crypto-util.js.map +1 -0
  75. package/dist/custom-repos-store.js +90 -0
  76. package/dist/custom-repos-store.js.map +1 -0
  77. package/dist/custom-repos-sync.js +118 -0
  78. package/dist/custom-repos-sync.js.map +1 -0
  79. package/dist/db.js +12 -0
  80. package/dist/db.js.map +1 -0
  81. package/dist/devbot-scripts.js +18 -0
  82. package/dist/devbot-scripts.js.map +1 -0
  83. package/dist/devcontainer.js +150 -0
  84. package/dist/devcontainer.js.map +1 -0
  85. package/dist/env-config-store.js +132 -0
  86. package/dist/env-config-store.js.map +1 -0
  87. package/dist/error-log-store.js +193 -0
  88. package/dist/error-log-store.js.map +1 -0
  89. package/dist/git.js +605 -0
  90. package/dist/git.js.map +1 -0
  91. package/dist/global-queue.js +127 -0
  92. package/dist/global-queue.js.map +1 -0
  93. package/dist/global-settings-store.js +307 -0
  94. package/dist/global-settings-store.js.map +1 -0
  95. package/dist/help.js +41 -0
  96. package/dist/help.js.map +1 -0
  97. package/dist/http-csp.js +15 -0
  98. package/dist/http-csp.js.map +1 -0
  99. package/dist/http-util.js +40 -0
  100. package/dist/http-util.js.map +1 -0
  101. package/dist/idempotency.js +50 -0
  102. package/dist/idempotency.js.map +1 -0
  103. package/dist/lifecycle.js +234 -0
  104. package/dist/lifecycle.js.map +1 -0
  105. package/dist/logger.js +33 -0
  106. package/dist/logger.js.map +1 -0
  107. package/dist/lsp-servers.js +81 -0
  108. package/dist/lsp-servers.js.map +1 -0
  109. package/dist/mcp/auth.js +62 -0
  110. package/dist/mcp/auth.js.map +1 -0
  111. package/dist/mcp/effective-mcp.js +42 -0
  112. package/dist/mcp/effective-mcp.js.map +1 -0
  113. package/dist/mcp/oauth-refresh.js +138 -0
  114. package/dist/mcp/oauth-refresh.js.map +1 -0
  115. package/dist/mcp/peer-sync.js +109 -0
  116. package/dist/mcp/peer-sync.js.map +1 -0
  117. package/dist/mcp/registry.js +54 -0
  118. package/dist/mcp/registry.js.map +1 -0
  119. package/dist/mcp/runner-bridge.js +93 -0
  120. package/dist/mcp/runner-bridge.js.map +1 -0
  121. package/dist/mcp/server.js +173 -0
  122. package/dist/mcp/server.js.map +1 -0
  123. package/dist/mcp/tools/_helpers.js +88 -0
  124. package/dist/mcp/tools/_helpers.js.map +1 -0
  125. package/dist/mcp/tools/attachments.js +117 -0
  126. package/dist/mcp/tools/attachments.js.map +1 -0
  127. package/dist/mcp/tools/cicd.js +105 -0
  128. package/dist/mcp/tools/cicd.js.map +1 -0
  129. package/dist/mcp/tools/discovery.js +34 -0
  130. package/dist/mcp/tools/discovery.js.map +1 -0
  131. package/dist/mcp/tools/issues.js +56 -0
  132. package/dist/mcp/tools/issues.js.map +1 -0
  133. package/dist/mcp/tools/merge_requests.js +67 -0
  134. package/dist/mcp/tools/merge_requests.js.map +1 -0
  135. package/dist/mcp/tools/metadata.js +127 -0
  136. package/dist/mcp/tools/metadata.js.map +1 -0
  137. package/dist/mcp/tools/peer_sync.js +43 -0
  138. package/dist/mcp/tools/peer_sync.js.map +1 -0
  139. package/dist/mcp/tools/repository.js +178 -0
  140. package/dist/mcp/tools/repository.js.map +1 -0
  141. package/dist/mcp/tools/work_tracking.js +125 -0
  142. package/dist/mcp/tools/work_tracking.js.map +1 -0
  143. package/dist/mcp/tools/workitems.js +30 -0
  144. package/dist/mcp/tools/workitems.js.map +1 -0
  145. package/dist/mcp/volume-files.js +187 -0
  146. package/dist/mcp/volume-files.js.map +1 -0
  147. package/dist/mcp-auth-store.js +134 -0
  148. package/dist/mcp-auth-store.js.map +1 -0
  149. package/dist/mcp-config-store.js +83 -0
  150. package/dist/mcp-config-store.js.map +1 -0
  151. package/dist/mention.js +512 -0
  152. package/dist/mention.js.map +1 -0
  153. package/dist/migrate.js +438 -0
  154. package/dist/migrate.js.map +1 -0
  155. package/dist/mr-assignee.js +71 -0
  156. package/dist/mr-assignee.js.map +1 -0
  157. package/dist/mr-content.js +90 -0
  158. package/dist/mr-content.js.map +1 -0
  159. package/dist/oauth-routes.js +54 -0
  160. package/dist/oauth-routes.js.map +1 -0
  161. package/dist/onboarding-allowlist.js +32 -0
  162. package/dist/onboarding-allowlist.js.map +1 -0
  163. package/dist/opencode.js +291 -0
  164. package/dist/opencode.js.map +1 -0
  165. package/dist/outbound-webhook-store.js +229 -0
  166. package/dist/outbound-webhook-store.js.map +1 -0
  167. package/dist/outbound-webhook.js +314 -0
  168. package/dist/outbound-webhook.js.map +1 -0
  169. package/dist/path-sandbox.js +200 -0
  170. package/dist/path-sandbox.js.map +1 -0
  171. package/dist/platform/git-auth.js +34 -0
  172. package/dist/platform/git-auth.js.map +1 -0
  173. package/dist/platform/gitealike/attachments.js +62 -0
  174. package/dist/platform/gitealike/attachments.js.map +1 -0
  175. package/dist/platform/gitealike/base.js +935 -0
  176. package/dist/platform/gitealike/base.js.map +1 -0
  177. package/dist/platform/gitealike/forgejo.js +16 -0
  178. package/dist/platform/gitealike/forgejo.js.map +1 -0
  179. package/dist/platform/gitealike/gitea.js +16 -0
  180. package/dist/platform/gitealike/gitea.js.map +1 -0
  181. package/dist/platform/gitealike/http.js +152 -0
  182. package/dist/platform/gitealike/http.js.map +1 -0
  183. package/dist/platform/gitealike/schemas.js +284 -0
  184. package/dist/platform/gitealike/schemas.js.map +1 -0
  185. package/dist/platform/github/attachments.js +52 -0
  186. package/dist/platform/github/attachments.js.map +1 -0
  187. package/dist/platform/github/http.js +153 -0
  188. package/dist/platform/github/http.js.map +1 -0
  189. package/dist/platform/github/index.js +936 -0
  190. package/dist/platform/github/index.js.map +1 -0
  191. package/dist/platform/github/schemas.js +308 -0
  192. package/dist/platform/github/schemas.js.map +1 -0
  193. package/dist/platform/github/zip.js +85 -0
  194. package/dist/platform/github/zip.js.map +1 -0
  195. package/dist/platform/gitlab/admin.js +105 -0
  196. package/dist/platform/gitlab/admin.js.map +1 -0
  197. package/dist/platform/gitlab/api.js +229 -0
  198. package/dist/platform/gitlab/api.js.map +1 -0
  199. package/dist/platform/gitlab/attachments.js +79 -0
  200. package/dist/platform/gitlab/attachments.js.map +1 -0
  201. package/dist/platform/gitlab/http.js +165 -0
  202. package/dist/platform/gitlab/http.js.map +1 -0
  203. package/dist/platform/gitlab/index.js +630 -0
  204. package/dist/platform/gitlab/index.js.map +1 -0
  205. package/dist/platform/gitlab/oauth.js +58 -0
  206. package/dist/platform/gitlab/oauth.js.map +1 -0
  207. package/dist/platform/gitlab/read.js +182 -0
  208. package/dist/platform/gitlab/read.js.map +1 -0
  209. package/dist/platform/gitlab/schemas-read.js +274 -0
  210. package/dist/platform/gitlab/schemas-read.js.map +1 -0
  211. package/dist/platform/gitlab/schemas.js +251 -0
  212. package/dist/platform/gitlab/schemas.js.map +1 -0
  213. package/dist/platform/gitlab/signature.js +52 -0
  214. package/dist/platform/gitlab/signature.js.map +1 -0
  215. package/dist/platform/index.js +38 -0
  216. package/dist/platform/index.js.map +1 -0
  217. package/dist/platform/port.js +26 -0
  218. package/dist/platform/port.js.map +1 -0
  219. package/dist/platform/types.js +80 -0
  220. package/dist/platform/types.js.map +1 -0
  221. package/dist/podman.js +119 -0
  222. package/dist/podman.js.map +1 -0
  223. package/dist/project-sync.js +69 -0
  224. package/dist/project-sync.js.map +1 -0
  225. package/dist/prompt.js +47 -0
  226. package/dist/prompt.js.map +1 -0
  227. package/dist/queue-store.js +236 -0
  228. package/dist/queue-store.js.map +1 -0
  229. package/dist/queue.js +129 -0
  230. package/dist/queue.js.map +1 -0
  231. package/dist/redact.js +23 -0
  232. package/dist/redact.js.map +1 -0
  233. package/dist/run-files.js +297 -0
  234. package/dist/run-files.js.map +1 -0
  235. package/dist/run-filters.js +54 -0
  236. package/dist/run-filters.js.map +1 -0
  237. package/dist/run-history-store.js +229 -0
  238. package/dist/run-history-store.js.map +1 -0
  239. package/dist/run-log-sink.js +270 -0
  240. package/dist/run-log-sink.js.map +1 -0
  241. package/dist/run-log.js +97 -0
  242. package/dist/run-log.js.map +1 -0
  243. package/dist/run-usage.js +29 -0
  244. package/dist/run-usage.js.map +1 -0
  245. package/dist/run-view.js +112 -0
  246. package/dist/run-view.js.map +1 -0
  247. package/dist/runner.js +1094 -0
  248. package/dist/runner.js.map +1 -0
  249. package/dist/runtime-apply.js +26 -0
  250. package/dist/runtime-apply.js.map +1 -0
  251. package/dist/runtime-settings.js +71 -0
  252. package/dist/runtime-settings.js.map +1 -0
  253. package/dist/runtime-state.js +19 -0
  254. package/dist/runtime-state.js.map +1 -0
  255. package/dist/server.js +119 -0
  256. package/dist/server.js.map +1 -0
  257. package/dist/settings-store.js +320 -0
  258. package/dist/settings-store.js.map +1 -0
  259. package/dist/status.js +121 -0
  260. package/dist/status.js.map +1 -0
  261. package/dist/stop.js +56 -0
  262. package/dist/stop.js.map +1 -0
  263. package/dist/sweeps.js +87 -0
  264. package/dist/sweeps.js.map +1 -0
  265. package/dist/task-log.js +279 -0
  266. package/dist/task-log.js.map +1 -0
  267. package/dist/task-view.js +62 -0
  268. package/dist/task-view.js.map +1 -0
  269. package/dist/token-store.js +68 -0
  270. package/dist/token-store.js.map +1 -0
  271. package/dist/types/access.js +74 -0
  272. package/dist/types/access.js.map +1 -0
  273. package/dist/types/custom-repos.js +28 -0
  274. package/dist/types/custom-repos.js.map +1 -0
  275. package/dist/types/env-config.js +25 -0
  276. package/dist/types/env-config.js.map +1 -0
  277. package/dist/types/env.js +190 -0
  278. package/dist/types/env.js.map +1 -0
  279. package/dist/types/global-settings.js +178 -0
  280. package/dist/types/global-settings.js.map +1 -0
  281. package/dist/types/lsp-servers.js +145 -0
  282. package/dist/types/lsp-servers.js.map +1 -0
  283. package/dist/types/mcp-config.js +77 -0
  284. package/dist/types/mcp-config.js.map +1 -0
  285. package/dist/types/opencode.js +59 -0
  286. package/dist/types/opencode.js.map +1 -0
  287. package/dist/types/outbound-webhook.js +56 -0
  288. package/dist/types/outbound-webhook.js.map +1 -0
  289. package/dist/types/project-settings.js +114 -0
  290. package/dist/types/project-settings.js.map +1 -0
  291. package/dist/types/provider-registry.js +37 -0
  292. package/dist/types/provider-registry.js.map +1 -0
  293. package/dist/types/runtime-settings.js +78 -0
  294. package/dist/types/runtime-settings.js.map +1 -0
  295. package/dist/usage-stats.js +159 -0
  296. package/dist/usage-stats.js.map +1 -0
  297. package/dist/version.js +19 -0
  298. package/dist/version.js.map +1 -0
  299. package/dist/volume-gc.js +193 -0
  300. package/dist/volume-gc.js.map +1 -0
  301. package/dist/volume-manage.js +191 -0
  302. package/dist/volume-manage.js.map +1 -0
  303. package/dist/volume-store.js +122 -0
  304. package/dist/volume-store.js.map +1 -0
  305. package/dist/webhook.js +1106 -0
  306. package/dist/webhook.js.map +1 -0
  307. package/dist/work-item-auto-store.js +75 -0
  308. package/dist/work-item-auto-store.js.map +1 -0
  309. package/dist/work-item-branch-store.js +37 -0
  310. package/dist/work-item-branch-store.js.map +1 -0
  311. package/migrations/0001_initial.sql +59 -0
  312. package/migrations/0002_queue_status.sql +15 -0
  313. package/migrations/0003_work_item_base_branch.sql +14 -0
  314. package/migrations/0004_project_settings.sql +15 -0
  315. package/migrations/0005_global_settings.sql +34 -0
  316. package/migrations/0006_model_variants.sql +8 -0
  317. package/migrations/0007_container_error_logs.sql +21 -0
  318. package/migrations/0008_project_access_grants.sql +16 -0
  319. package/migrations/0009_drop_imported_legacy.sql +4 -0
  320. package/migrations/0010_container_error_log_model.sql +6 -0
  321. package/migrations/0011_container_error_log_title.sql +5 -0
  322. package/migrations/0012_pending_task_cancel.sql +6 -0
  323. package/migrations/0013_builder_image.sql +6 -0
  324. package/migrations/0014_build_workflow.sql +7 -0
  325. package/migrations/0015_access_rules.sql +24 -0
  326. package/migrations/0016_trigger_policy.sql +17 -0
  327. package/migrations/0017_attachment_upload.sql +8 -0
  328. package/migrations/0018_work_item_branch.sql +11 -0
  329. package/migrations/0019_auto_push_mr.sql +18 -0
  330. package/migrations/0020_outbound_webhooks.sql +47 -0
  331. package/migrations/0021_runtime_settings.sql +12 -0
  332. package/migrations/0022_auto_resolve_conflicts.sql +17 -0
  333. package/migrations/0023_ci_fail_auto_report.sql +27 -0
  334. package/migrations/0024_work_item_auto_override.sql +22 -0
  335. package/migrations/0025_run_history.sql +37 -0
  336. package/migrations/0026_run_history_task_seq.sql +6 -0
  337. package/migrations/0027_custom_repos.sql +11 -0
  338. package/migrations/0028_run_usage.sql +14 -0
  339. package/migrations/0029_drop_ocr_langs.sql +6 -0
  340. package/migrations/0030_mr_assignee.sql +9 -0
  341. package/migrations/0031_lsp_servers.sql +14 -0
  342. package/opencode-config/managed/devbot-agent-policy.md +61 -0
  343. package/opencode-config/managed/opencode.json +77 -0
  344. package/opencode-config/opencode.json +211 -0
  345. package/opencode-config/plugins/devbot-path-guard.js +224 -0
  346. package/package.json +158 -4
  347. package/public/assets/DashboardSidebarCollapse-rIuzCmVm.js +1 -0
  348. package/public/assets/DashboardToolbar-DFCUqZ4Z.js +1 -0
  349. package/public/assets/ErrorLogModal.vue_vue_type_script_setup_true_lang-BCyNIf1E.js +1 -0
  350. package/public/assets/ErrorLogsPage-DyYK2Rop.js +1 -0
  351. package/public/assets/LogSlideover.vue_vue_type_script_setup_true_lang-33xie7TE.js +1 -0
  352. package/public/assets/OverviewPage-CFfa0uwg.js +1 -0
  353. package/public/assets/PageGrid-CBROIU87.js +1 -0
  354. package/public/assets/PeriodRangePicker.vue_vue_type_script_setup_true_lang-DiFUNem7.js +1 -0
  355. package/public/assets/ProjectDetailPage-CreyvUit.js +1 -0
  356. package/public/assets/ProjectsPage-CFx8-elJ.js +1 -0
  357. package/public/assets/RunDetailSlideover.vue_vue_type_script_setup_true_lang-DopYYmCT.js +1 -0
  358. package/public/assets/RunsPage-CRnnXU4P.js +1 -0
  359. package/public/assets/Select-Bvx32Diz.js +1 -0
  360. package/public/assets/SelectMenu-BjMxc4VB.js +1 -0
  361. package/public/assets/SettingsPage-Bb6LM3wO.js +60 -0
  362. package/public/assets/TimeCell.vue_vue_type_script_setup_true_lang-DW_UB0Fu.js +1 -0
  363. package/public/assets/UsagePage-BLMDEsv-.js +332 -0
  364. package/public/assets/UsagePage-DodEeAjB.css +1 -0
  365. package/public/assets/VolumesPage-CxcLc3L2.js +1 -0
  366. package/public/assets/WebhookConfigPanel.vue_vue_type_script_setup_true_lang-B2V2GF6b.js +1 -0
  367. package/public/assets/WorkItemCell.vue_vue_type_script_setup_true_lang-BQn6vTjd.js +1 -0
  368. package/public/assets/index-BMbMibyb.js +4 -0
  369. package/public/assets/index-C5RBBrPj.js +30 -0
  370. package/public/assets/index-CgX29UgC.css +1 -0
  371. package/public/assets/usage-format-jsb31_O8.js +1 -0
  372. package/public/assets/useManagedProjects-8rtjRqVJ.js +1 -0
  373. package/public/assets/useRouteTab-DfwrcMic.js +1 -0
  374. package/public/favicon.ico +0 -0
  375. package/public/favicon.png +0 -0
  376. package/public/index.html +15 -0
@@ -0,0 +1,99 @@
1
+ import { db } from "./db.js";
2
+ import { getProject, listTokens } from "./token-store.js";
3
+ // Directed cross-project access grants for the internal devbot-forge MCP.
4
+ // Same DB file as project_tokens / project_mcp_servers (devbot.db, 0600).
5
+ // A row (source, target) lets source's runs read target's forge data and
6
+ // clone target's code. Direction is explicit: A->B does not imply B->A.
7
+ //
8
+ // Enrollment invariant: both endpoints must be enrolled (a project_tokens row).
9
+ // Enforced in the admin route on write, and re-checked here on read — a grant
10
+ // whose target was later revoked is ignored (stale edge), never honored.
11
+ export function initAccessGrantTables() {
12
+ db.exec(`
13
+ CREATE TABLE IF NOT EXISTS project_access_grants (
14
+ source_project_id INTEGER NOT NULL,
15
+ target_project_id INTEGER NOT NULL,
16
+ created_at INTEGER NOT NULL,
17
+ updated_at INTEGER NOT NULL,
18
+ PRIMARY KEY (source_project_id, target_project_id)
19
+ )
20
+ `);
21
+ db.exec("CREATE INDEX IF NOT EXISTS idx_access_grants_target ON project_access_grants(target_project_id)");
22
+ }
23
+ // Raw outbound target ids (may include stale/unenrolled targets).
24
+ function rawOutboundTargets(sourceProjectId) {
25
+ const rows = db
26
+ .prepare("SELECT target_project_id FROM project_access_grants WHERE source_project_id = ? ORDER BY target_project_id")
27
+ .all(sourceProjectId);
28
+ return rows.map((r) => r.target_project_id);
29
+ }
30
+ // Raw inbound source ids (may include stale/unenrolled sources).
31
+ function rawInboundSources(targetProjectId) {
32
+ const rows = db
33
+ .prepare("SELECT source_project_id FROM project_access_grants WHERE target_project_id = ? ORDER BY source_project_id")
34
+ .all(targetProjectId);
35
+ return rows.map((r) => r.source_project_id);
36
+ }
37
+ // Outbound grants resolved to enrolled target projects only. Stale edges
38
+ // (target no longer enrolled) are filtered out.
39
+ export function listAccessTargets(sourceProjectId) {
40
+ const out = [];
41
+ for (const id of rawOutboundTargets(sourceProjectId)) {
42
+ const p = getProject(id);
43
+ if (p)
44
+ out.push({ projectId: p.projectId, projectPath: p.projectPath });
45
+ }
46
+ return out;
47
+ }
48
+ // Inbound grants resolved to enrolled source projects only (for the panel's
49
+ // read-only "who can access this project" list).
50
+ export function listAccessSources(targetProjectId) {
51
+ const out = [];
52
+ for (const id of rawInboundSources(targetProjectId)) {
53
+ const p = getProject(id);
54
+ if (p)
55
+ out.push({ projectId: p.projectId, projectPath: p.projectPath });
56
+ }
57
+ return out;
58
+ }
59
+ // Live authorization check for a cross-project read/clone. Own project is
60
+ // always allowed. Otherwise a grant row must exist AND the target must still
61
+ // be enrolled. Read on every MCP tool call, so revoking a grant is immediate.
62
+ export function isAccessAllowed(sourceProjectId, targetProjectId) {
63
+ if (sourceProjectId === targetProjectId)
64
+ return true;
65
+ const row = db
66
+ .prepare("SELECT 1 AS ok FROM project_access_grants WHERE source_project_id = ? AND target_project_id = ?")
67
+ .get(sourceProjectId, targetProjectId);
68
+ if (!row)
69
+ return false;
70
+ return getProject(targetProjectId) !== null;
71
+ }
72
+ export function upsertAccessGrant(sourceProjectId, targetProjectId) {
73
+ const now = Math.floor(Date.now() / 1000);
74
+ db.prepare(`INSERT OR REPLACE INTO project_access_grants
75
+ (source_project_id, target_project_id, created_at, updated_at)
76
+ VALUES (?, ?,
77
+ COALESCE((SELECT created_at FROM project_access_grants WHERE source_project_id = ? AND target_project_id = ?), ?),
78
+ ?)`).run(sourceProjectId, targetProjectId, sourceProjectId, targetProjectId, now, now);
79
+ }
80
+ export function deleteAccessGrant(sourceProjectId, targetProjectId) {
81
+ const r = db
82
+ .prepare("DELETE FROM project_access_grants WHERE source_project_id = ? AND target_project_id = ?")
83
+ .run(sourceProjectId, targetProjectId);
84
+ return r.changes > 0;
85
+ }
86
+ // Remove every grant touching this project, in both directions. Called from
87
+ // /api/revoke's finally block alongside the other per-project cleanup.
88
+ export function deleteAllAccessGrantsForProject(projectId) {
89
+ db.prepare("DELETE FROM project_access_grants WHERE source_project_id = ? OR target_project_id = ?").run(projectId, projectId);
90
+ }
91
+ // Enrolled projects that are not the source and not already granted — the
92
+ // add-picker candidates for the Access modal.
93
+ export function listGrantCandidates(sourceProjectId) {
94
+ const granted = new Set(rawOutboundTargets(sourceProjectId));
95
+ return listTokens()
96
+ .filter((p) => p.projectId !== sourceProjectId && !granted.has(p.projectId))
97
+ .map((p) => ({ projectId: p.projectId, projectPath: p.projectPath }));
98
+ }
99
+ //# sourceMappingURL=access-grant-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-grant-store.js","sourceRoot":"","sources":["../src/access-grant-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE1D,0EAA0E;AAC1E,0EAA0E;AAC1E,yEAAyE;AACzE,wEAAwE;AACxE,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,yEAAyE;AAEzE,MAAM,UAAU,qBAAqB;IACnC,EAAE,CAAC,IAAI,CAAC;;;;;;;;GAQP,CAAC,CAAC;IACH,EAAE,CAAC,IAAI,CAAC,iGAAiG,CAAC,CAAC;AAC7G,CAAC;AAOD,kEAAkE;AAClE,SAAS,kBAAkB,CAAC,eAAuB;IACjD,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CAAC,4GAA4G,CAAC;SACrH,GAAG,CAAC,eAAe,CAAyC,CAAC;IAChE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;AAC9C,CAAC;AAED,iEAAiE;AACjE,SAAS,iBAAiB,CAAC,eAAuB;IAChD,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CAAC,4GAA4G,CAAC;SACrH,GAAG,CAAC,eAAe,CAAyC,CAAC;IAChE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;AAC9C,CAAC;AAED,yEAAyE;AACzE,gDAAgD;AAChD,MAAM,UAAU,iBAAiB,CAAC,eAAuB;IACvD,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,KAAK,MAAM,EAAE,IAAI,kBAAkB,CAAC,eAAe,CAAC,EAAE,CAAC;QACrD,MAAM,CAAC,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC;QACzB,IAAI,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,4EAA4E;AAC5E,iDAAiD;AACjD,MAAM,UAAU,iBAAiB,CAAC,eAAuB;IACvD,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,KAAK,MAAM,EAAE,IAAI,iBAAiB,CAAC,eAAe,CAAC,EAAE,CAAC;QACpD,MAAM,CAAC,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC;QACzB,IAAI,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,0EAA0E;AAC1E,6EAA6E;AAC7E,8EAA8E;AAC9E,MAAM,UAAU,eAAe,CAAC,eAAuB,EAAE,eAAuB;IAC9E,IAAI,eAAe,KAAK,eAAe;QAAE,OAAO,IAAI,CAAC;IACrD,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CACN,iGAAiG,CAClG;SACA,GAAG,CAAC,eAAe,EAAE,eAAe,CAA+B,CAAC;IACvE,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,OAAO,UAAU,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,eAAuB,EAAE,eAAuB;IAChF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,EAAE,CAAC,OAAO,CACR;;;;UAIM,CACP,CAAC,GAAG,CAAC,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,eAAuB,EAAE,eAAuB;IAChF,MAAM,CAAC,GAAG,EAAE;SACT,OAAO,CAAC,yFAAyF,CAAC;SAClG,GAAG,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;IACzC,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,4EAA4E;AAC5E,uEAAuE;AACvE,MAAM,UAAU,+BAA+B,CAAC,SAAiB;IAC/D,EAAE,CAAC,OAAO,CAAC,wFAAwF,CAAC,CAAC,GAAG,CACtG,SAAS,EACT,SAAS,CACV,CAAC;AACJ,CAAC;AAED,0EAA0E;AAC1E,8CAA8C;AAC9C,MAAM,UAAU,mBAAmB,CAAC,eAAuB;IACzD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAC,CAAC;IAC7D,OAAO,UAAU,EAAE;SAChB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,eAAe,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;SAC3E,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { getPlatform } from "./platform/index.js";
2
+ // Resolve a username to the forge's stable numeric identity for access rules.
3
+ // Forge-neutral: GitLab, ForgeJo, Gitea and GitHub each implement
4
+ // `workItems.resolveUser`. Returns null when no exact user matches.
5
+ export async function resolveUserByUsername(username) {
6
+ const user = await getPlatform().workItems.resolveUser(username);
7
+ if (!user)
8
+ return null;
9
+ return { userId: user.id, username: user.username };
10
+ }
11
+ //# sourceMappingURL=access-resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-resolve.js","sourceRoot":"","sources":["../src/access-resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,8EAA8E;AAC9E,kEAAkE;AAClE,oEAAoE;AACpE,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,QAAgB;IAC1D,MAAM,IAAI,GAAG,MAAM,WAAW,EAAE,CAAC,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACjE,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;AACtD,CAAC"}
@@ -0,0 +1,99 @@
1
+ import { db } from "./db.js";
2
+ import { log } from "./logger.js";
3
+ // Per-project trigger access rules. Same DB file as the other stores
4
+ // (devbot.db, 0600). A rule maps a subject (a normalized project role, or a
5
+ // specific forge user by numeric id) to an access level. Rules refine the
6
+ // role-based baseline for that subject; they do not affect other subjects.
7
+ //
8
+ // Precedence is user rule > role rule > baseline. Rules are read on every
9
+ // mention, so add/remove takes effect immediately.
10
+ export function initAccessRuleTables() {
11
+ db.exec(`
12
+ CREATE TABLE IF NOT EXISTS project_access_rules (
13
+ project_id INTEGER NOT NULL,
14
+ subject_type TEXT NOT NULL,
15
+ subject_id TEXT NOT NULL,
16
+ username TEXT NOT NULL DEFAULT '',
17
+ role TEXT NOT NULL DEFAULT '',
18
+ level TEXT NOT NULL,
19
+ created_at INTEGER NOT NULL,
20
+ updated_at INTEGER NOT NULL,
21
+ PRIMARY KEY (project_id, subject_type, subject_id)
22
+ )
23
+ `);
24
+ db.exec("CREATE INDEX IF NOT EXISTS idx_project_access_rules_project ON project_access_rules(project_id)");
25
+ }
26
+ export function listAccessRules(projectId) {
27
+ let rows;
28
+ try {
29
+ rows = db
30
+ .prepare(`SELECT subject_type, subject_id, username, role, level
31
+ FROM project_access_rules WHERE project_id = ?
32
+ ORDER BY subject_type, subject_id COLLATE NOCASE`)
33
+ .all(projectId);
34
+ }
35
+ catch {
36
+ return [];
37
+ }
38
+ const out = [];
39
+ for (const r of rows) {
40
+ if (r.subject_type !== "role" && r.subject_type !== "user")
41
+ continue;
42
+ if (r.level !== "full" && r.level !== "plan" && r.level !== "denied")
43
+ continue;
44
+ out.push({
45
+ subjectType: r.subject_type,
46
+ subjectId: r.subject_id,
47
+ username: r.username ?? "",
48
+ role: r.role ?? "",
49
+ level: r.level,
50
+ });
51
+ }
52
+ return out;
53
+ }
54
+ // The level for a single subject, or null when no rule matches.
55
+ function ruleLevel(projectId, subjectType, subjectId) {
56
+ try {
57
+ const row = db
58
+ .prepare("SELECT level FROM project_access_rules WHERE project_id = ? AND subject_type = ? AND subject_id = ?")
59
+ .get(projectId, subjectType, subjectId);
60
+ if (!row)
61
+ return null;
62
+ return row.level === "full" || row.level === "plan" || row.level === "denied" ? row.level : null;
63
+ }
64
+ catch (e) {
65
+ log.warn("access rule read failed", { projectId, subjectType, subjectId, err: e });
66
+ return null;
67
+ }
68
+ }
69
+ // The matched rule for an actor: an exact user rule wins over a role rule.
70
+ // Returns the subject it matched on (for logging) plus the level, or null when
71
+ // no rule applies (caller falls back to the role-based baseline).
72
+ export function resolveAccessRule(projectId, actor) {
73
+ const userLevel = ruleLevel(projectId, "user", String(actor.userId));
74
+ if (userLevel)
75
+ return { level: userLevel, subjectType: "user", subjectId: String(actor.userId) };
76
+ const roleLevel = ruleLevel(projectId, "role", actor.permission);
77
+ if (roleLevel)
78
+ return { level: roleLevel, subjectType: "role", subjectId: actor.permission };
79
+ return null;
80
+ }
81
+ export function upsertAccessRule(projectId, rule) {
82
+ const now = Math.floor(Date.now() / 1000);
83
+ db.prepare(`INSERT OR REPLACE INTO project_access_rules
84
+ (project_id, subject_type, subject_id, username, role, level, created_at, updated_at)
85
+ VALUES (?, ?, ?, ?, ?, ?,
86
+ COALESCE((SELECT created_at FROM project_access_rules WHERE project_id = ? AND subject_type = ? AND subject_id = ?), ?),
87
+ ?)`).run(projectId, rule.subjectType, rule.subjectId, rule.username, rule.role, rule.level, projectId, rule.subjectType, rule.subjectId, now, now);
88
+ }
89
+ export function deleteAccessRule(projectId, subjectType, subjectId) {
90
+ const r = db
91
+ .prepare("DELETE FROM project_access_rules WHERE project_id = ? AND subject_type = ? AND subject_id = ?")
92
+ .run(projectId, subjectType, subjectId);
93
+ return r.changes > 0;
94
+ }
95
+ // Remove every rule for a project. Called from /api/revoke's cleanup.
96
+ export function deleteAllAccessRulesForProject(projectId) {
97
+ db.prepare("DELETE FROM project_access_rules WHERE project_id = ?").run(projectId);
98
+ }
99
+ //# sourceMappingURL=access-rule-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-rule-store.js","sourceRoot":"","sources":["../src/access-rule-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { logReq, requireSession } from "../http-util.js";
2
+ import { getProject } from "../token-store.js";
3
+ import { listAccessTargets, listAccessSources, listGrantCandidates, upsertAccessGrant, deleteAccessGrant, } from "../access-grant-store.js";
4
+ function parseProjectIdParam(raw) {
5
+ const id = Number(Array.isArray(raw) ? raw[0] : raw);
6
+ return Number.isFinite(id) && Number.isInteger(id) && id > 0 ? id : null;
7
+ }
8
+ // Directed cross-project access grants for the internal devbot-forge MCP.
9
+ // GET returns the project's outbound grants (editable), inbound grants (read-
10
+ // only), and the add-picker candidates (enrolled projects not already granted).
11
+ // PUT/DELETE manage one directed edge. Both endpoints must be enrolled; a
12
+ // self-grant is rejected. All routes are admin-session gated like the rest of
13
+ // /api/*.
14
+ export function mountAccessRoutes(app) {
15
+ app.get("/api/projects/:id/access", requireSession((req, res) => {
16
+ const projectId = parseProjectIdParam(req.params.id ?? "");
17
+ if (!projectId) {
18
+ logReq(req, "warn", "access route bad id", { id: req.params.id });
19
+ return res.status(400).json({ error: "invalid project id" });
20
+ }
21
+ if (!getProject(projectId)) {
22
+ logReq(req, "warn", "access route not managed", { projectId });
23
+ return res.status(404).json({ error: "not managed" });
24
+ }
25
+ const outbound = listAccessTargets(projectId);
26
+ const inbound = listAccessSources(projectId);
27
+ const candidates = listGrantCandidates(projectId);
28
+ logReq(req, "debug", "get access", { projectId, outbound: outbound.length, inbound: inbound.length });
29
+ return res.json({ outbound, inbound, candidates });
30
+ }));
31
+ // PUT /api/projects/:id/access/:targetId — create a directed grant id -> target.
32
+ app.put("/api/projects/:id/access/:targetId", requireSession((req, res) => {
33
+ const sourceId = parseProjectIdParam(req.params.id ?? "");
34
+ const targetId = parseProjectIdParam(req.params.targetId ?? "");
35
+ if (!sourceId || !targetId) {
36
+ logReq(req, "warn", "access put bad id", { source: req.params.id, target: req.params.targetId });
37
+ return res.status(400).json({ error: "invalid project id" });
38
+ }
39
+ if (sourceId === targetId) {
40
+ logReq(req, "warn", "access put self", { projectId: sourceId });
41
+ return res.status(400).json({ error: "cannot grant access to self" });
42
+ }
43
+ if (!getProject(sourceId)) {
44
+ logReq(req, "warn", "access put source not managed", { projectId: sourceId });
45
+ return res.status(404).json({ error: "source project not managed" });
46
+ }
47
+ if (!getProject(targetId)) {
48
+ logReq(req, "warn", "access put target not enrolled", { sourceId, targetId });
49
+ return res.status(404).json({ error: "target project not enrolled" });
50
+ }
51
+ upsertAccessGrant(sourceId, targetId);
52
+ logReq(req, "info", "upsert access grant", { sourceId, targetId });
53
+ return res.json({ ok: true, sourceId, targetId });
54
+ }));
55
+ // DELETE /api/projects/:id/access/:targetId — remove a directed grant.
56
+ // Idempotent: 204 whether or not the edge existed.
57
+ app.delete("/api/projects/:id/access/:targetId", requireSession((req, res) => {
58
+ const sourceId = parseProjectIdParam(req.params.id ?? "");
59
+ const targetId = parseProjectIdParam(req.params.targetId ?? "");
60
+ if (!sourceId || !targetId) {
61
+ logReq(req, "warn", "access delete bad id", { source: req.params.id, target: req.params.targetId });
62
+ return res.status(400).json({ error: "invalid project id" });
63
+ }
64
+ if (!getProject(sourceId)) {
65
+ logReq(req, "warn", "access delete source not managed", { projectId: sourceId });
66
+ return res.status(404).json({ error: "source project not managed" });
67
+ }
68
+ deleteAccessGrant(sourceId, targetId);
69
+ logReq(req, "info", "delete access grant", { sourceId, targetId });
70
+ return res.status(204).end();
71
+ }));
72
+ }
73
+ //# sourceMappingURL=access-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-routes.js","sourceRoot":"","sources":["../../src/admin/access-routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ import { logReq, requireSession } from "../http-util.js";
2
+ import { getProject } from "../token-store.js";
3
+ import { listAccessRules, upsertAccessRule, deleteAccessRule, } from "../access-rule-store.js";
4
+ import { resolveUserByUsername } from "../access-resolve.js";
5
+ import { AccessRuleAddSchema } from "../types/access.js";
6
+ function parseProjectIdParam(raw) {
7
+ const id = Number(Array.isArray(raw) ? raw[0] : raw);
8
+ return Number.isFinite(id) && Number.isInteger(id) && id > 0 ? id : null;
9
+ }
10
+ // Per-project trigger access rules. The bot-wide policy is role based; these
11
+ // routes manage per-subject refinements (a role or a specific user). Adding a
12
+ // user rule resolves the username to a stable numeric id via the active forge.
13
+ // All routes are admin-session gated.
14
+ export function mountAccessRuleRoutes(app) {
15
+ // GET /api/projects/:id/access-rules — the project's rules.
16
+ app.get("/api/projects/:id/access-rules", requireSession((req, res) => {
17
+ const projectId = parseProjectIdParam(req.params.id ?? "");
18
+ if (!projectId) {
19
+ logReq(req, "warn", "access-rule route bad id", { id: req.params.id });
20
+ return res.status(400).json({ error: "invalid project id" });
21
+ }
22
+ if (!getProject(projectId)) {
23
+ logReq(req, "warn", "access-rule route not managed", { projectId });
24
+ return res.status(404).json({ error: "not managed" });
25
+ }
26
+ const rules = listAccessRules(projectId);
27
+ logReq(req, "debug", "list access rules", { projectId, count: rules.length });
28
+ return res.json({ rules });
29
+ }));
30
+ // POST /api/projects/:id/access-rules — add or update a rule. A role rule is
31
+ // keyed by the role name; a user rule resolves the username to an id.
32
+ app.post("/api/projects/:id/access-rules", requireSession(async (req, res) => {
33
+ const projectId = parseProjectIdParam(req.params.id ?? "");
34
+ if (!projectId) {
35
+ logReq(req, "warn", "access-rule post bad id", { id: req.params.id });
36
+ return res.status(400).json({ error: "invalid project id" });
37
+ }
38
+ if (!getProject(projectId)) {
39
+ logReq(req, "warn", "access-rule post not managed", { projectId });
40
+ return res.status(404).json({ error: "not managed" });
41
+ }
42
+ const parsed = AccessRuleAddSchema.safeParse(req.body);
43
+ if (!parsed.success) {
44
+ logReq(req, "warn", "access-rule post bad body", { projectId, issues: parsed.error.issues });
45
+ return res.status(400).json({ error: "bad request", issues: parsed.error.issues });
46
+ }
47
+ const body = parsed.data;
48
+ if (body.subjectType === "role") {
49
+ upsertAccessRule(projectId, {
50
+ subjectType: "role",
51
+ subjectId: body.role,
52
+ username: "",
53
+ role: body.role,
54
+ level: body.level,
55
+ });
56
+ logReq(req, "info", "access-rule role set", { projectId, role: body.role, level: body.level });
57
+ return res.json({ ok: true, rule: { subjectType: "role", subjectId: body.role, username: "", role: body.role, level: body.level } });
58
+ }
59
+ let user;
60
+ try {
61
+ user = await resolveUserByUsername(body.username);
62
+ }
63
+ catch (e) {
64
+ logReq(req, "warn", "access-rule resolve failed", { projectId, username: body.username, err: e });
65
+ return res.status(502).json({ error: "failed to resolve user" });
66
+ }
67
+ if (!user) {
68
+ logReq(req, "warn", "access-rule user not found", { projectId, username: body.username });
69
+ return res.status(404).json({ error: "user not found" });
70
+ }
71
+ upsertAccessRule(projectId, {
72
+ subjectType: "user",
73
+ subjectId: String(user.userId),
74
+ username: user.username,
75
+ role: "",
76
+ level: body.level,
77
+ });
78
+ logReq(req, "info", "access-rule user set", { projectId, userId: user.userId, level: body.level });
79
+ return res.json({
80
+ ok: true,
81
+ rule: { subjectType: "user", subjectId: String(user.userId), username: user.username, role: "", level: body.level },
82
+ });
83
+ }));
84
+ // DELETE /api/projects/:id/access-rules/:subjectType/:subjectId — remove a
85
+ // rule. Idempotent: 204 whether or not it existed.
86
+ app.delete("/api/projects/:id/access-rules/:subjectType/:subjectId", requireSession((req, res) => {
87
+ const projectId = parseProjectIdParam(req.params.id ?? "");
88
+ const subjectType = req.params.subjectType;
89
+ const subjectId = String(req.params.subjectId ?? "").trim();
90
+ if (!projectId || (subjectType !== "role" && subjectType !== "user") || subjectId === "") {
91
+ logReq(req, "warn", "access-rule delete bad params", {
92
+ project: req.params.id,
93
+ subjectType: req.params.subjectType,
94
+ subjectId: req.params.subjectId,
95
+ });
96
+ return res.status(400).json({ error: "invalid params" });
97
+ }
98
+ if (!getProject(projectId)) {
99
+ logReq(req, "warn", "access-rule delete not managed", { projectId });
100
+ return res.status(404).json({ error: "not managed" });
101
+ }
102
+ deleteAccessRule(projectId, subjectType, subjectId);
103
+ logReq(req, "info", "access-rule delete", { projectId, subjectType, subjectId });
104
+ return res.status(204).end();
105
+ }));
106
+ }
107
+ //# sourceMappingURL=access-rule-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-rule-routes.js","sourceRoot":"","sources":["../../src/admin/access-rule-routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,SAAS,mBAAmB,CAAC,GAAY;IACvC,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3E,CAAC;AAED,6EAA6E;AAC7E,8EAA8E;AAC9E,+EAA+E;AAC/E,sCAAsC;AACtC,MAAM,UAAU,qBAAqB,CAAC,GAAoB;IACxD,4DAA4D;IAC5D,GAAG,CAAC,GAAG,CAAC,gCAAgC,EAAE,cAAc,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACpE,MAAM,SAAS,GAAG,mBAAmB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,0BAA0B,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;YACvE,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,+BAA+B,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;YACpE,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,MAAM,KAAK,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,mBAAmB,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9E,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC,CAAC;IAEJ,6EAA6E;IAC7E,sEAAsE;IACtE,GAAG,CAAC,IAAI,CAAC,gCAAgC,EAAE,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC3E,MAAM,SAAS,GAAG,mBAAmB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,yBAAyB,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;YACtE,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,8BAA8B,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;YACnE,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,2BAA2B,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;YAC7F,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACrF,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QACzB,IAAI,IAAI,CAAC,WAAW,KAAK,MAAM,EAAE,CAAC;YAChC,gBAAgB,CAAC,SAAS,EAAE;gBAC1B,WAAW,EAAE,MAAM;gBACnB,SAAS,EAAE,IAAI,CAAC,IAAI;gBACpB,QAAQ,EAAE,EAAE;gBACZ,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;aAClB,CAAC,CAAC;YACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,sBAAsB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;YAC/F,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACvI,CAAC;QACD,IAAI,IAAI,CAAC;QACT,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,4BAA4B,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAU,EAAE,CAAC,CAAC;YAC3G,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,4BAA4B,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC1F,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,gBAAgB,CAAC,SAAS,EAAE;YAC1B,WAAW,EAAE,MAAM;YACnB,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;YAC9B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,IAAI,EAAE,EAAE;YACR,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,sBAAsB,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACnG,OAAO,GAAG,CAAC,IAAI,CAAC;YACd,EAAE,EAAE,IAAI;YACR,IAAI,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE;SACpH,CAAC,CAAC;IACL,CAAC,CAAC,CAAC,CAAC;IAEJ,2EAA2E;IAC3E,mDAAmD;IACnD,GAAG,CAAC,MAAM,CAAC,wDAAwD,EAAE,cAAc,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC/F,MAAM,SAAS,GAAG,mBAAmB,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC;QAC3C,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5D,IAAI,CAAC,SAAS,IAAI,CAAC,WAAW,KAAK,MAAM,IAAI,WAAW,KAAK,MAAM,CAAC,IAAI,SAAS,KAAK,EAAE,EAAE,CAAC;YACzF,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,+BAA+B,EAAE;gBACnD,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE;gBACtB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,WAAW;gBACnC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS;aAChC,CAAC,CAAC;YACH,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,gCAAgC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;YACrE,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,gBAAgB,CAAC,SAAS,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;QACpD,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC;QACjF,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,93 @@
1
+ import { logReq, requireSession } from "../http-util.js";
2
+ import { getProject } from "../token-store.js";
3
+ import { listCustomRepoSummaries, upsertCustomRepo, deleteCustomRepo, } from "../custom-repos-store.js";
4
+ import { CustomRepoBodySchema, isCustomRepoKind, isValidCustomRepoName, } from "../types/custom-repos.js";
5
+ // Admin routes for custom skills/subagent repositories (work item #63). One
6
+ // route family serves both global (project_id = 0) and per-project scopes, and
7
+ // both `skills` / `agents` kinds. Tokens are never echoed back.
8
+ function parseProjectIdParam(raw) {
9
+ const id = Number(raw);
10
+ return Number.isFinite(id) && Number.isInteger(id) && id > 0 ? id : null;
11
+ }
12
+ function parseKind(raw) {
13
+ return isCustomRepoKind(raw) ? raw : null;
14
+ }
15
+ export function mountCustomReposRoutes(app) {
16
+ // ---- Global scope --------------------------------------------------------
17
+ app.get("/api/settings/custom-repos/:kind", requireSession((req, res) => {
18
+ const kind = parseKind(String(req.params.kind ?? ""));
19
+ if (!kind)
20
+ return res.status(400).json({ error: "invalid kind" });
21
+ logReq(req, "debug", "list global custom repos", { kind });
22
+ return res.json({ repos: listCustomRepoSummaries(0, kind) });
23
+ }));
24
+ app.put("/api/settings/custom-repos/:kind/:name", requireSession((req, res) => {
25
+ const kind = parseKind(String(req.params.kind ?? ""));
26
+ const name = String(req.params.name ?? "");
27
+ if (!kind)
28
+ return res.status(400).json({ error: "invalid kind" });
29
+ if (!isValidCustomRepoName(name))
30
+ return res.status(400).json({ error: "invalid name" });
31
+ const parsed = CustomRepoBodySchema.safeParse(req.body);
32
+ if (!parsed.success) {
33
+ logReq(req, "warn", "global custom repo put bad body", { kind, name, issues: parsed.error.issues });
34
+ return res.status(400).json({ error: "bad request", issues: parsed.error.issues });
35
+ }
36
+ upsertCustomRepo(0, kind, name, parsed.data);
37
+ logReq(req, "info", "upsert global custom repo", { kind, name });
38
+ return res.json({ ok: true, name });
39
+ }));
40
+ app.delete("/api/settings/custom-repos/:kind/:name", requireSession((req, res) => {
41
+ const kind = parseKind(String(req.params.kind ?? ""));
42
+ const name = String(req.params.name ?? "");
43
+ if (!kind)
44
+ return res.status(400).json({ error: "invalid kind" });
45
+ if (!isValidCustomRepoName(name))
46
+ return res.status(400).json({ error: "invalid name" });
47
+ const existed = deleteCustomRepo(0, kind, name);
48
+ if (!existed)
49
+ return res.status(404).json({ error: "not found" });
50
+ logReq(req, "info", "delete global custom repo", { kind, name });
51
+ return res.status(204).end();
52
+ }));
53
+ const projectRoute = (handler) => requireSession((req, res) => {
54
+ const projectId = parseProjectIdParam(String(req.params.id ?? ""));
55
+ const kind = parseKind(String(req.params.kind ?? ""));
56
+ if (!projectId)
57
+ return res.status(400).json({ error: "invalid project id" });
58
+ if (!kind)
59
+ return res.status(400).json({ error: "invalid kind" });
60
+ if (!getProject(projectId))
61
+ return res.status(404).json({ error: "not managed" });
62
+ req.repoProjectId = projectId;
63
+ req.repoKind = kind;
64
+ return handler(req, res);
65
+ });
66
+ app.get("/api/projects/:id/custom-repos/:kind", projectRoute((req, res) => {
67
+ return res.json({ repos: listCustomRepoSummaries(req.repoProjectId, req.repoKind) });
68
+ }));
69
+ app.put("/api/projects/:id/custom-repos/:kind/:name", projectRoute((req, res) => {
70
+ const name = String(req.params.name ?? "");
71
+ if (!isValidCustomRepoName(name))
72
+ return res.status(400).json({ error: "invalid name" });
73
+ const parsed = CustomRepoBodySchema.safeParse(req.body);
74
+ if (!parsed.success) {
75
+ logReq(req, "warn", "project custom repo put bad body", { name, issues: parsed.error.issues });
76
+ return res.status(400).json({ error: "bad request", issues: parsed.error.issues });
77
+ }
78
+ upsertCustomRepo(req.repoProjectId, req.repoKind, name, parsed.data);
79
+ logReq(req, "info", "upsert project custom repo", { projectId: req.repoProjectId, kind: req.repoKind, name });
80
+ return res.json({ ok: true, name });
81
+ }));
82
+ app.delete("/api/projects/:id/custom-repos/:kind/:name", projectRoute((req, res) => {
83
+ const name = String(req.params.name ?? "");
84
+ if (!isValidCustomRepoName(name))
85
+ return res.status(400).json({ error: "invalid name" });
86
+ const existed = deleteCustomRepo(req.repoProjectId, req.repoKind, name);
87
+ if (!existed)
88
+ return res.status(404).json({ error: "not found" });
89
+ logReq(req, "info", "delete project custom repo", { projectId: req.repoProjectId, kind: req.repoKind, name });
90
+ return res.status(204).end();
91
+ }));
92
+ }
93
+ //# sourceMappingURL=custom-repos-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-repos-routes.js","sourceRoot":"","sources":["../../src/admin/custom-repos-routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import { logReq, requireSession } from "../http-util.js";
2
+ import { getProject } from "../token-store.js";
3
+ import { listEnvVarSummaries, upsertEnvVar, deleteEnvVar, } from "../env-config-store.js";
4
+ import { EnvVarBodySchema, ENV_NAME_RE, ENV_NAME_MAX, } from "../types/env-config.js";
5
+ function parseProjectIdParam(raw) {
6
+ const id = Number(Array.isArray(raw) ? raw[0] : raw);
7
+ return Number.isFinite(id) && Number.isInteger(id) && id > 0 ? id : null;
8
+ }
9
+ function parseEnvNameParam(raw) {
10
+ const value = Array.isArray(raw) ? raw[0] : raw;
11
+ return typeof value === "string" && ENV_NAME_RE.test(value) && value.length <= ENV_NAME_MAX ? value : null;
12
+ }
13
+ function envRoute(needsName, handler) {
14
+ return requireSession((req, res) => {
15
+ const projectId = parseProjectIdParam(req.params.id ?? "");
16
+ if (!projectId) {
17
+ logReq(req, "warn", "env route bad id", { id: req.params.id });
18
+ return res.status(400).json({ error: "invalid project id" });
19
+ }
20
+ let name;
21
+ if (needsName) {
22
+ const parsedName = parseEnvNameParam(req.params.name ?? "");
23
+ if (!parsedName) {
24
+ logReq(req, "warn", "env route bad name", { projectId, name: req.params.name });
25
+ return res.status(400).json({ error: "invalid name" });
26
+ }
27
+ name = parsedName;
28
+ }
29
+ if (!getProject(projectId)) {
30
+ logReq(req, "warn", "env route not managed", { projectId });
31
+ return res.status(404).json({ error: "not managed" });
32
+ }
33
+ req.envProjectId = projectId;
34
+ if (name)
35
+ req.envName = name;
36
+ return handler(req, res);
37
+ });
38
+ }
39
+ export function mountEnvRoutes(app) {
40
+ // GET /api/projects/:id/env — list env var summaries for a project.
41
+ // Never returns values — only names, enabled flags, and hasValue.
42
+ app.get("/api/projects/:id/env", envRoute(false, (req, res) => {
43
+ const vars = listEnvVarSummaries(req.envProjectId);
44
+ logReq(req, "debug", "list env", { projectId: req.envProjectId, count: vars.length });
45
+ return res.json({ vars });
46
+ }));
47
+ // PUT /api/projects/:id/env/:name — upsert an env var.
48
+ // Body: { value: string, enabled: boolean }. An empty value on edit means
49
+ // "leave unchanged" (keep the existing encrypted value).
50
+ app.put("/api/projects/:id/env/:name", envRoute(true, (req, res) => {
51
+ const name = req.envName;
52
+ const parsed = EnvVarBodySchema.safeParse(req.body);
53
+ if (!parsed.success) {
54
+ logReq(req, "warn", "env put bad body", { projectId: req.envProjectId, name, issues: parsed.error.issues });
55
+ return res.status(400).json({ error: "bad request", issues: parsed.error.issues });
56
+ }
57
+ upsertEnvVar(req.envProjectId, name, parsed.data.value, parsed.data.enabled);
58
+ // Never log the value — it's a secret.
59
+ logReq(req, "info", "upsert env", { projectId: req.envProjectId, name, enabled: parsed.data.enabled });
60
+ return res.json({ ok: true, name, enabled: parsed.data.enabled });
61
+ }));
62
+ // DELETE /api/projects/:id/env/:name — remove an env var.
63
+ app.delete("/api/projects/:id/env/:name", envRoute(true, (req, res) => {
64
+ const name = req.envName;
65
+ const existed = deleteEnvVar(req.envProjectId, name);
66
+ if (!existed) {
67
+ logReq(req, "debug", "env delete not found", { projectId: req.envProjectId, name });
68
+ return res.status(404).json({ error: "not found" });
69
+ }
70
+ logReq(req, "info", "delete env", { projectId: req.envProjectId, name });
71
+ return res.status(204).end();
72
+ }));
73
+ }
74
+ //# sourceMappingURL=env-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-routes.js","sourceRoot":"","sources":["../../src/admin/env-routes.ts"],"names":[],"mappings":"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"}