@wardby/cli 0.2.0 → 0.3.0

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 (491) hide show
  1. package/.env.example +34 -4
  2. package/README.md +183 -71
  3. package/dist/claude-coding-worker/driver.d.ts +7 -1
  4. package/dist/claude-coding-worker/driver.js +31 -2
  5. package/dist/claude-coding-worker/main.js +5 -2
  6. package/dist/claude-coding-worker/tool-socket.d.ts +9 -0
  7. package/dist/claude-coding-worker/tool-socket.js +26 -0
  8. package/dist/cli-help.d.ts +1 -1
  9. package/dist/cli-help.js +13 -4
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.js +219 -29
  12. package/dist/coding/collect-exclude.d.ts +25 -0
  13. package/dist/coding/collect-exclude.js +76 -0
  14. package/dist/coding/profile.d.ts +67 -22
  15. package/dist/coding/profile.js +58 -25
  16. package/dist/coding/protected-path-wording.d.ts +24 -0
  17. package/dist/coding/protected-path-wording.js +55 -0
  18. package/dist/coding/protected-paths.d.ts +31 -0
  19. package/dist/coding/protected-paths.js +48 -0
  20. package/dist/coding/protocol.d.ts +99 -3
  21. package/dist/coding/protocol.js +155 -4
  22. package/dist/coding/registry/adapters.d.ts +2 -0
  23. package/dist/coding/registry/adapters.js +9 -0
  24. package/dist/coding/registry/allowlist.d.ts +9 -0
  25. package/dist/coding/registry/allowlist.js +28 -0
  26. package/dist/coding/registry/json-scan.d.ts +55 -0
  27. package/dist/coding/registry/json-scan.js +191 -0
  28. package/dist/coding/registry/lockfiles.d.ts +6 -0
  29. package/dist/coding/registry/lockfiles.js +89 -0
  30. package/dist/coding/registry/npm-lockfile.d.ts +9 -0
  31. package/dist/coding/registry/npm-lockfile.js +73 -0
  32. package/dist/coding/registry/npm-plan.d.ts +29 -0
  33. package/dist/coding/registry/npm-plan.js +289 -0
  34. package/dist/coding/registry/npm.d.ts +7 -0
  35. package/dist/coding/registry/npm.js +252 -0
  36. package/dist/coding/registry/pypi.d.ts +4 -0
  37. package/dist/coding/registry/pypi.js +210 -0
  38. package/dist/coding/registry/report.d.ts +37 -0
  39. package/dist/coding/registry/report.js +31 -0
  40. package/dist/coding/registry/token.d.ts +4 -0
  41. package/dist/coding/registry/token.js +7 -0
  42. package/dist/coding/registry/types.d.ts +286 -0
  43. package/dist/coding/registry/types.js +19 -0
  44. package/dist/coding/registry/worker-config.d.ts +14 -0
  45. package/dist/coding/registry/worker-config.js +31 -0
  46. package/dist/coding/services/builtins.d.ts +22 -0
  47. package/dist/coding/services/builtins.js +85 -0
  48. package/dist/coding/services/catalog.d.ts +443 -0
  49. package/dist/coding/services/catalog.js +159 -0
  50. package/dist/coding/services/declaration.d.ts +11 -0
  51. package/dist/coding/services/declaration.js +114 -0
  52. package/dist/coding/services/note.d.ts +8 -0
  53. package/dist/coding/services/note.js +15 -0
  54. package/dist/coding/services/resolve.d.ts +25 -0
  55. package/dist/coding/services/resolve.js +50 -0
  56. package/dist/coding/services/wording.d.ts +25 -0
  57. package/dist/coding/services/wording.js +70 -0
  58. package/dist/coding-proxy/main.js +24 -4
  59. package/dist/coding-worker/artifact.d.ts +18 -0
  60. package/dist/coding-worker/artifact.js +48 -6
  61. package/dist/coding-worker/debug-trace.d.ts +37 -0
  62. package/dist/coding-worker/debug-trace.js +117 -0
  63. package/dist/coding-worker/driver.d.ts +14 -1
  64. package/dist/coding-worker/driver.js +106 -14
  65. package/dist/coding-worker/errors.d.ts +7 -0
  66. package/dist/coding-worker/errors.js +26 -0
  67. package/dist/coding-worker/main.js +13 -4
  68. package/dist/coding-worker/sdk.d.ts +2 -2
  69. package/dist/coding-worker/sdk.js +7 -2
  70. package/dist/coding-worker/types.d.ts +3 -0
  71. package/dist/config/providers.d.ts +41 -0
  72. package/dist/config/providers.js +66 -0
  73. package/dist/core/budget-groups.d.ts +109 -6
  74. package/dist/core/budget-groups.js +125 -19
  75. package/dist/core/budget-wording.d.ts +22 -0
  76. package/dist/core/budget-wording.js +55 -0
  77. package/dist/core/coding-queue.d.ts +1 -1
  78. package/dist/core/datastores.d.ts +1 -1
  79. package/dist/core/datastores.js +18 -2
  80. package/dist/core/db.d.ts +40 -2
  81. package/dist/core/db.js +75 -2
  82. package/dist/core/dispatch.d.ts +49 -7
  83. package/dist/core/dispatch.js +290 -45
  84. package/dist/core/engine-native.js +27 -10
  85. package/dist/core/grants.d.ts +86 -0
  86. package/dist/core/grants.js +126 -0
  87. package/dist/core/host-events.d.ts +49 -0
  88. package/dist/core/host-events.js +257 -0
  89. package/dist/core/host-identity-links.d.ts +54 -0
  90. package/dist/core/host-identity-links.js +189 -0
  91. package/dist/core/host-status.d.ts +61 -0
  92. package/dist/core/host-status.js +211 -0
  93. package/dist/core/http-runtime.js +3 -4
  94. package/dist/core/in-flight-runs.d.ts +8 -0
  95. package/dist/core/in-flight-runs.js +56 -0
  96. package/dist/core/lease.d.ts +1 -1
  97. package/dist/core/private-directory.d.ts +20 -0
  98. package/dist/core/private-directory.js +36 -0
  99. package/dist/core/provider-wording.d.ts +12 -0
  100. package/dist/core/provider-wording.js +44 -0
  101. package/dist/core/reconciler.d.ts +39 -3
  102. package/dist/core/reconciler.js +86 -2
  103. package/dist/core/repo-access.d.ts +99 -0
  104. package/dist/core/repo-access.js +136 -0
  105. package/dist/core/review-host-checks.d.ts +11 -0
  106. package/dist/core/review-host-checks.js +41 -0
  107. package/dist/core/review-host-tools.d.ts +47 -0
  108. package/dist/core/review-host-tools.js +347 -0
  109. package/dist/core/run-heartbeat.d.ts +27 -0
  110. package/dist/core/run-heartbeat.js +54 -0
  111. package/dist/core/runner.d.ts +31 -9
  112. package/dist/core/runner.js +237 -40
  113. package/dist/core/scheduler.d.ts +1 -1
  114. package/dist/core/secrets.d.ts +12 -3
  115. package/dist/core/secrets.js +25 -6
  116. package/dist/core/subagent-memory-tools.d.ts +2 -2
  117. package/dist/core/subagent-memory-tools.js +16 -3
  118. package/dist/core/tool-admin.d.ts +81 -0
  119. package/dist/core/tool-admin.js +129 -0
  120. package/dist/core/tool-names.d.ts +42 -0
  121. package/dist/core/tool-names.js +64 -0
  122. package/dist/core/untrusted-content.d.ts +32 -0
  123. package/dist/core/untrusted-content.js +72 -0
  124. package/dist/core/webhooks.d.ts +9 -2
  125. package/dist/core/webhooks.js +23 -2
  126. package/dist/generated/prisma/browser.d.ts +328 -0
  127. package/dist/generated/prisma/browser.js +17 -0
  128. package/dist/generated/prisma/client.d.ts +347 -0
  129. package/dist/generated/prisma/client.js +34 -0
  130. package/dist/generated/prisma/commonInputTypes.d.ts +839 -0
  131. package/dist/generated/prisma/commonInputTypes.js +10 -0
  132. package/dist/generated/prisma/enums.d.ts +59 -0
  133. package/dist/generated/prisma/enums.js +59 -0
  134. package/dist/generated/prisma/internal/class.d.ts +605 -0
  135. package/dist/generated/prisma/internal/class.js +49 -0
  136. package/dist/generated/prisma/internal/prismaNamespace.d.ts +4494 -0
  137. package/dist/generated/prisma/internal/prismaNamespace.js +628 -0
  138. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +635 -0
  139. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +599 -0
  140. package/dist/generated/prisma/models/Agent.d.ts +3560 -0
  141. package/dist/generated/prisma/models/Agent.js +1 -0
  142. package/dist/generated/prisma/models/AgentDatastore.d.ts +1210 -0
  143. package/dist/generated/prisma/models/AgentDatastore.js +1 -0
  144. package/dist/generated/prisma/models/AgentMemory.d.ts +986 -0
  145. package/dist/generated/prisma/models/AgentMemory.js +1 -0
  146. package/dist/generated/prisma/models/AgentRepository.d.ts +1425 -0
  147. package/dist/generated/prisma/models/AgentRepository.js +1 -0
  148. package/dist/generated/prisma/models/AgentSecret.d.ts +1212 -0
  149. package/dist/generated/prisma/models/AgentSecret.js +1 -0
  150. package/dist/generated/prisma/models/AgentSubAgent.d.ts +1217 -0
  151. package/dist/generated/prisma/models/AgentSubAgent.js +1 -0
  152. package/dist/generated/prisma/models/AgentTool.d.ts +1403 -0
  153. package/dist/generated/prisma/models/AgentTool.js +1 -0
  154. package/dist/generated/prisma/models/AuthFormChallenge.d.ts +1298 -0
  155. package/dist/generated/prisma/models/AuthFormChallenge.js +1 -0
  156. package/dist/generated/prisma/models/AuthLoginKey.d.ts +1245 -0
  157. package/dist/generated/prisma/models/AuthLoginKey.js +1 -0
  158. package/dist/generated/prisma/models/AuthRateLimit.d.ts +979 -0
  159. package/dist/generated/prisma/models/AuthRateLimit.js +1 -0
  160. package/dist/generated/prisma/models/AuthSession.d.ts +1376 -0
  161. package/dist/generated/prisma/models/AuthSession.js +1 -0
  162. package/dist/generated/prisma/models/AuthUser.d.ts +1700 -0
  163. package/dist/generated/prisma/models/AuthUser.js +1 -0
  164. package/dist/generated/prisma/models/BudgetGroup.d.ts +1548 -0
  165. package/dist/generated/prisma/models/BudgetGroup.js +1 -0
  166. package/dist/generated/prisma/models/CodingAgentProfile.d.ts +1681 -0
  167. package/dist/generated/prisma/models/CodingAgentProfile.js +1 -0
  168. package/dist/generated/prisma/models/CodingProxyRequest.d.ts +1749 -0
  169. package/dist/generated/prisma/models/CodingProxyRequest.js +1 -0
  170. package/dist/generated/prisma/models/CodingProxySession.d.ts +1626 -0
  171. package/dist/generated/prisma/models/CodingProxySession.js +1 -0
  172. package/dist/generated/prisma/models/CodingRun.d.ts +3583 -0
  173. package/dist/generated/prisma/models/CodingRun.js +1 -0
  174. package/dist/generated/prisma/models/CodingService.d.ts +1348 -0
  175. package/dist/generated/prisma/models/CodingService.js +1 -0
  176. package/dist/generated/prisma/models/Datastore.d.ts +1434 -0
  177. package/dist/generated/prisma/models/Datastore.js +1 -0
  178. package/dist/generated/prisma/models/DatastoreEntry.d.ts +1314 -0
  179. package/dist/generated/prisma/models/DatastoreEntry.js +1 -0
  180. package/dist/generated/prisma/models/HostEventDelivery.d.ts +946 -0
  181. package/dist/generated/prisma/models/HostEventDelivery.js +1 -0
  182. package/dist/generated/prisma/models/HostIdentity.d.ts +1232 -0
  183. package/dist/generated/prisma/models/HostIdentity.js +1 -0
  184. package/dist/generated/prisma/models/HostIdentityLinkRequest.d.ts +1473 -0
  185. package/dist/generated/prisma/models/HostIdentityLinkRequest.js +1 -0
  186. package/dist/generated/prisma/models/OAuthAuthorizationCode.d.ts +1526 -0
  187. package/dist/generated/prisma/models/OAuthAuthorizationCode.js +1 -0
  188. package/dist/generated/prisma/models/OAuthAuthorizationRequest.d.ts +1350 -0
  189. package/dist/generated/prisma/models/OAuthAuthorizationRequest.js +1 -0
  190. package/dist/generated/prisma/models/OAuthClient.d.ts +1289 -0
  191. package/dist/generated/prisma/models/OAuthClient.js +1 -0
  192. package/dist/generated/prisma/models/OAuthFamily.d.ts +1540 -0
  193. package/dist/generated/prisma/models/OAuthFamily.js +1 -0
  194. package/dist/generated/prisma/models/OAuthGrant.d.ts +1280 -0
  195. package/dist/generated/prisma/models/OAuthGrant.js +1 -0
  196. package/dist/generated/prisma/models/Principal.d.ts +2245 -0
  197. package/dist/generated/prisma/models/Principal.js +1 -0
  198. package/dist/generated/prisma/models/RegistryAllowance.d.ts +1148 -0
  199. package/dist/generated/prisma/models/RegistryAllowance.js +1 -0
  200. package/dist/generated/prisma/models/RegistryApprovedVersion.d.ts +1219 -0
  201. package/dist/generated/prisma/models/RegistryApprovedVersion.js +1 -0
  202. package/dist/generated/prisma/models/RegistryFetch.d.ts +1428 -0
  203. package/dist/generated/prisma/models/RegistryFetch.js +1 -0
  204. package/dist/generated/prisma/models/RegistryPlanRefusal.d.ts +1294 -0
  205. package/dist/generated/prisma/models/RegistryPlanRefusal.js +1 -0
  206. package/dist/generated/prisma/models/RegistryVersionFact.d.ts +1085 -0
  207. package/dist/generated/prisma/models/RegistryVersionFact.js +1 -0
  208. package/dist/generated/prisma/models/ResourceGrant.d.ts +1437 -0
  209. package/dist/generated/prisma/models/ResourceGrant.js +1 -0
  210. package/dist/generated/prisma/models/Run.d.ts +2669 -0
  211. package/dist/generated/prisma/models/Run.js +1 -0
  212. package/dist/generated/prisma/models/RunHostCheck.d.ts +1239 -0
  213. package/dist/generated/prisma/models/RunHostCheck.js +1 -0
  214. package/dist/generated/prisma/models/RunHostStatus.d.ts +1315 -0
  215. package/dist/generated/prisma/models/RunHostStatus.js +1 -0
  216. package/dist/generated/prisma/models/SchedulerLease.d.ts +970 -0
  217. package/dist/generated/prisma/models/SchedulerLease.js +1 -0
  218. package/dist/generated/prisma/models/Secret.d.ts +1399 -0
  219. package/dist/generated/prisma/models/Secret.js +1 -0
  220. package/dist/generated/prisma/models/SecretElicitationOutcome.d.ts +973 -0
  221. package/dist/generated/prisma/models/SecretElicitationOutcome.js +1 -0
  222. package/dist/generated/prisma/models/Task.d.ts +1176 -0
  223. package/dist/generated/prisma/models/Task.js +1 -0
  224. package/dist/generated/prisma/models/Tool.d.ts +1490 -0
  225. package/dist/generated/prisma/models/Tool.js +1 -0
  226. package/dist/generated/prisma/models/Webhook.d.ts +1386 -0
  227. package/dist/generated/prisma/models/Webhook.js +1 -0
  228. package/dist/generated/prisma/models.d.ts +45 -0
  229. package/dist/generated/prisma/models.js +1 -0
  230. package/dist/help/build.d.ts +1 -0
  231. package/dist/help/build.js +9 -0
  232. package/dist/help/catalog.d.ts +24 -0
  233. package/dist/help/catalog.js +160 -0
  234. package/dist/help/cli.d.ts +2 -0
  235. package/dist/help/cli.js +65 -0
  236. package/dist/help/runtime.d.ts +3 -0
  237. package/dist/help/runtime.js +44 -0
  238. package/dist/help/search.d.ts +9 -0
  239. package/dist/help/search.js +104 -0
  240. package/dist/help-index.json +660 -0
  241. package/dist/import/cli-args.js +3 -2
  242. package/dist/import/create.d.ts +6 -1
  243. package/dist/import/create.js +68 -14
  244. package/dist/import/index.d.ts +1 -1
  245. package/dist/import/index.js +19 -9
  246. package/dist/import/neutral-schema.d.ts +11 -11
  247. package/dist/mcp/auth/access.d.ts +70 -0
  248. package/dist/mcp/auth/access.js +58 -0
  249. package/dist/mcp/auth/grants-cli.d.ts +149 -0
  250. package/dist/mcp/auth/grants-cli.js +518 -0
  251. package/dist/mcp/auth/host-account-cli.d.ts +2 -0
  252. package/dist/mcp/auth/host-account-cli.js +47 -0
  253. package/dist/mcp/auth/ownership.d.ts +37 -63
  254. package/dist/mcp/auth/ownership.js +19 -14
  255. package/dist/mcp/auth/principal.d.ts +1 -1
  256. package/dist/mcp/auth/repo-authorization.d.ts +22 -0
  257. package/dist/mcp/auth/repo-authorization.js +51 -0
  258. package/dist/mcp/auth/resource-server.d.ts +32 -3
  259. package/dist/mcp/auth/resource-server.js +84 -3
  260. package/dist/mcp/auth/self-hosted/browser.js +22 -8
  261. package/dist/mcp/auth/self-hosted/cli.d.ts +1 -1
  262. package/dist/mcp/auth/self-hosted/cli.js +47 -13
  263. package/dist/mcp/auth/self-hosted/credentials.d.ts +21 -3
  264. package/dist/mcp/auth/self-hosted/credentials.js +62 -4
  265. package/dist/mcp/auth/self-hosted/rate-limit.d.ts +1 -1
  266. package/dist/mcp/auth/self-hosted/session.d.ts +3 -2
  267. package/dist/mcp/context.d.ts +28 -2
  268. package/dist/mcp/errors.d.ts +25 -6
  269. package/dist/mcp/errors.js +98 -0
  270. package/dist/mcp/host-events/github-ingress.d.ts +36 -0
  271. package/dist/mcp/host-events/github-ingress.js +92 -0
  272. package/dist/mcp/host-events/github-user-callback.d.ts +18 -0
  273. package/dist/mcp/host-events/github-user-callback.js +50 -0
  274. package/dist/mcp/index.d.ts +8 -1
  275. package/dist/mcp/index.js +106 -16
  276. package/dist/mcp/server.d.ts +1 -1
  277. package/dist/mcp/server.js +27 -9
  278. package/dist/mcp/shared/page-style.d.ts +16 -0
  279. package/dist/mcp/shared/page-style.js +129 -0
  280. package/dist/mcp/tasks/manager.d.ts +1 -1
  281. package/dist/mcp/tools/agents.js +447 -49
  282. package/dist/mcp/tools/budget-groups.js +3 -3
  283. package/dist/mcp/tools/datastore.js +23 -16
  284. package/dist/mcp/tools/grants.d.ts +2 -0
  285. package/dist/mcp/tools/grants.js +239 -0
  286. package/dist/mcp/tools/help.d.ts +5 -0
  287. package/dist/mcp/tools/help.js +67 -0
  288. package/dist/mcp/tools/host-accounts.d.ts +2 -0
  289. package/dist/mcp/tools/host-accounts.js +114 -0
  290. package/dist/mcp/tools/memory.d.ts +7 -1
  291. package/dist/mcp/tools/memory.js +5 -5
  292. package/dist/mcp/tools/repositories.d.ts +2 -0
  293. package/dist/mcp/tools/repositories.js +181 -0
  294. package/dist/mcp/tools/runs.d.ts +6 -0
  295. package/dist/mcp/tools/runs.js +60 -6
  296. package/dist/mcp/tools/scheduling.js +4 -7
  297. package/dist/mcp/tools/secret-elicitation-form.d.ts +1 -1
  298. package/dist/mcp/tools/secret-elicitation-form.js +10 -28
  299. package/dist/mcp/tools/secret-elicitation.d.ts +1 -1
  300. package/dist/mcp/tools/secrets.js +18 -6
  301. package/dist/mcp/tools/services.d.ts +2 -0
  302. package/dist/mcp/tools/services.js +222 -0
  303. package/dist/mcp/tools/subagents.js +53 -17
  304. package/dist/mcp/tools/tools.d.ts +41 -0
  305. package/dist/mcp/tools/tools.js +202 -41
  306. package/dist/mcp/tools/trigger.js +36 -6
  307. package/dist/mcp/tools/webhooks.js +15 -4
  308. package/dist/mcp/transport/streamable-http.d.ts +11 -1
  309. package/dist/mcp/transport/streamable-http.js +32 -2
  310. package/dist/mcp/unattended-schedules.d.ts +1 -1
  311. package/dist/mcp/webhooks/ingress.d.ts +1 -1
  312. package/dist/providers/auth/delegating.d.ts +19 -0
  313. package/dist/providers/auth/delegating.js +71 -0
  314. package/dist/providers/auth/index.d.ts +3 -1
  315. package/dist/providers/auth/index.js +11 -0
  316. package/dist/providers/auth/self-hosted.d.ts +4 -3
  317. package/dist/providers/auth/self-hosted.js +16 -4
  318. package/dist/providers/auth/types.d.ts +6 -0
  319. package/dist/providers/coding-proxy/memory-ledger.d.ts +3 -0
  320. package/dist/providers/coding-proxy/memory-ledger.js +21 -2
  321. package/dist/providers/coding-proxy/metering.d.ts +4 -0
  322. package/dist/providers/coding-proxy/metering.js +28 -2
  323. package/dist/providers/coding-proxy/prisma-ledger.d.ts +18 -1
  324. package/dist/providers/coding-proxy/prisma-ledger.js +50 -5
  325. package/dist/providers/coding-proxy/proxy.d.ts +13 -0
  326. package/dist/providers/coding-proxy/proxy.js +479 -18
  327. package/dist/providers/coding-proxy/registry/audit.d.ts +131 -0
  328. package/dist/providers/coding-proxy/registry/audit.js +380 -0
  329. package/dist/providers/coding-proxy/registry/bounded-fetch.d.ts +16 -0
  330. package/dist/providers/coding-proxy/registry/bounded-fetch.js +77 -0
  331. package/dist/providers/coding-proxy/registry/plan.d.ts +58 -0
  332. package/dist/providers/coding-proxy/registry/plan.js +304 -0
  333. package/dist/providers/coding-proxy/registry/prisma-store.d.ts +34 -0
  334. package/dist/providers/coding-proxy/registry/prisma-store.js +151 -0
  335. package/dist/providers/coding-proxy/registry/service.d.ts +213 -0
  336. package/dist/providers/coding-proxy/registry/service.js +1137 -0
  337. package/dist/providers/coding-proxy/registry/store.d.ts +127 -0
  338. package/dist/providers/coding-proxy/registry/store.js +70 -0
  339. package/dist/providers/coding-proxy/runtime.d.ts +18 -1
  340. package/dist/providers/coding-proxy/runtime.js +63 -0
  341. package/dist/providers/coding-proxy/secure-fetch.js +0 -1
  342. package/dist/providers/coding-proxy/server.d.ts +11 -0
  343. package/dist/providers/coding-proxy/server.js +163 -0
  344. package/dist/providers/coding-proxy/types.d.ts +15 -0
  345. package/dist/providers/datastore/postgres.d.ts +1 -1
  346. package/dist/providers/engine/types.d.ts +10 -0
  347. package/dist/providers/executor/build.d.ts +3 -3
  348. package/dist/providers/executor/composition.d.ts +4 -1
  349. package/dist/providers/executor/composition.js +19 -0
  350. package/dist/providers/executor/container.d.ts +106 -4
  351. package/dist/providers/executor/container.js +305 -22
  352. package/dist/providers/executor/dbos.d.ts +3 -4
  353. package/dist/providers/executor/in-process.d.ts +2 -3
  354. package/dist/providers/executor/routing.d.ts +7 -0
  355. package/dist/providers/executor/routing.js +13 -0
  356. package/dist/providers/executor/types.d.ts +22 -0
  357. package/dist/providers/jobs/claude-tool-setup.d.ts +23 -0
  358. package/dist/providers/jobs/claude-tool-setup.js +50 -0
  359. package/dist/providers/jobs/collect-prune.d.ts +7 -0
  360. package/dist/providers/jobs/collect-prune.js +27 -0
  361. package/dist/providers/jobs/docker-isolation.d.ts +48 -3
  362. package/dist/providers/jobs/docker-isolation.js +213 -22
  363. package/dist/providers/jobs/docker-services.d.ts +35 -0
  364. package/dist/providers/jobs/docker-services.js +191 -0
  365. package/dist/providers/jobs/docker.d.ts +60 -2
  366. package/dist/providers/jobs/docker.js +313 -50
  367. package/dist/providers/jobs/fake-kubernetes-api.d.ts +1 -0
  368. package/dist/providers/jobs/fake-kubernetes-api.js +12 -3
  369. package/dist/providers/jobs/kubernetes-isolation.d.ts +17 -2
  370. package/dist/providers/jobs/kubernetes-isolation.js +238 -57
  371. package/dist/providers/jobs/kubernetes-platform.d.ts +10 -4
  372. package/dist/providers/jobs/kubernetes-platform.js +11 -5
  373. package/dist/providers/jobs/kubernetes-preflight.js +3 -0
  374. package/dist/providers/jobs/kubernetes.d.ts +21 -2
  375. package/dist/providers/jobs/kubernetes.js +117 -31
  376. package/dist/providers/jobs/types.d.ts +20 -0
  377. package/dist/providers/llm/anthropic.js +6 -2
  378. package/dist/providers/llm/bedrock.js +2 -1
  379. package/dist/providers/llm/claude-messages.d.ts +5 -1
  380. package/dist/providers/llm/claude-messages.js +1 -0
  381. package/dist/providers/llm/claude-provider.d.ts +3 -1
  382. package/dist/providers/llm/claude-provider.js +5 -1
  383. package/dist/providers/llm/index.d.ts +1 -1
  384. package/dist/providers/llm/index.js +1 -1
  385. package/dist/providers/llm/pricing-anthropic.d.ts +10 -0
  386. package/dist/providers/llm/pricing-anthropic.js +32 -14
  387. package/dist/providers/llm/pricing-bedrock-claude.d.ts +8 -0
  388. package/dist/providers/llm/pricing-bedrock-claude.js +9 -0
  389. package/dist/providers/llm/routing.d.ts +10 -1
  390. package/dist/providers/llm/routing.js +18 -0
  391. package/dist/providers/llm/types.d.ts +12 -0
  392. package/dist/providers/llm/types.js +8 -1
  393. package/dist/providers/memory/postgres.d.ts +1 -1
  394. package/dist/providers/review-host/diff-lines.d.ts +16 -0
  395. package/dist/providers/review-host/diff-lines.js +59 -0
  396. package/dist/providers/review-host/github-events.d.ts +6 -0
  397. package/dist/providers/review-host/github-events.js +180 -0
  398. package/dist/providers/review-host/github-user-auth.d.ts +38 -0
  399. package/dist/providers/review-host/github-user-auth.js +128 -0
  400. package/dist/providers/review-host/github.d.ts +57 -0
  401. package/dist/providers/review-host/github.js +567 -0
  402. package/dist/providers/review-host/index.d.ts +12 -0
  403. package/dist/providers/review-host/index.js +30 -0
  404. package/dist/providers/review-host/review-format.d.ts +19 -0
  405. package/dist/providers/review-host/review-format.js +59 -0
  406. package/dist/providers/review-host/types.d.ts +284 -0
  407. package/dist/providers/review-host/types.js +24 -0
  408. package/dist/providers/vcs/git.d.ts +16 -6
  409. package/dist/providers/vcs/git.js +79 -14
  410. package/dist/providers/vcs/github.d.ts +74 -3
  411. package/dist/providers/vcs/github.js +195 -15
  412. package/dist/providers/vcs/types.d.ts +42 -4
  413. package/dist/quickstart/index.js +34 -32
  414. package/dist/quickstart/migrate.d.ts +23 -0
  415. package/dist/quickstart/migrate.js +125 -0
  416. package/dist/sandbox/fetch-policy.d.ts +20 -2
  417. package/dist/sandbox/fetch-policy.js +64 -3
  418. package/dist/sandbox/host-functions.d.ts +9 -1
  419. package/dist/sandbox/host-functions.js +12 -5
  420. package/dist/serve.js +1 -1
  421. package/dist/wardby-bin.js +6 -0
  422. package/docs/README.md +30 -0
  423. package/docs/architecture-runtime.md +90 -0
  424. package/docs/assets/brand/wardby-icon-512.png +0 -0
  425. package/docs/assets/brand/wardby-icon.svg +16 -0
  426. package/docs/assets/brand/wardby-mascot-profile-512.png +0 -0
  427. package/docs/assets/brand/wardby-mascot.png +0 -0
  428. package/docs/assets/brand/wardby-mascot.svg +5 -0
  429. package/docs/assets/wardby-workflow.svg +106 -0
  430. package/docs/code-review-agents.md +481 -0
  431. package/docs/coding-agent-setup.md +172 -0
  432. package/docs/coding-packages.md +455 -0
  433. package/docs/coding-services.md +300 -0
  434. package/docs/coding-worker-byo-images.md +98 -0
  435. package/docs/coding-worker-isolation.md +1061 -0
  436. package/docs/getting-started-gke.md +615 -0
  437. package/docs/getting-started-identity-provider.md +308 -0
  438. package/docs/getting-started.md +133 -0
  439. package/docs/observability.md +53 -0
  440. package/docs/release-verification.md +66 -0
  441. package/docs/security-deployment.md +657 -0
  442. package/help/code-review-agents.md +31 -0
  443. package/help/coding-packages.md +31 -0
  444. package/help/coding-services.md +71 -0
  445. package/help/creating-agents.md +68 -0
  446. package/help/deploy-gke.md +39 -0
  447. package/help/deployment-targets.md +39 -0
  448. package/help/errors/budget-group-exhausted.md +25 -0
  449. package/help/errors/docker-isolation-unsupported.md +26 -0
  450. package/help/errors/protected-path.md +45 -0
  451. package/help/errors/repo-access.md +27 -0
  452. package/help/errors/service-declaration-invalid.md +27 -0
  453. package/help/errors/service-declaration-unavailable.md +25 -0
  454. package/help/errors/service-launcher-unsupported.md +30 -0
  455. package/help/errors/service-not-allowed.md +27 -0
  456. package/help/errors/service-unknown.md +23 -0
  457. package/help/errors/service-unready.md +49 -0
  458. package/help/getting-started.md +31 -0
  459. package/help/github.md +30 -0
  460. package/help/identity-and-access.md +39 -0
  461. package/help/mcp.md +30 -0
  462. package/help/native-capabilities.md +30 -0
  463. package/help/observability.md +41 -0
  464. package/help/operating-agents.md +30 -0
  465. package/help/security.md +27 -0
  466. package/help/troubleshooting/budgets.md +26 -0
  467. package/help/troubleshooting/coding-workers.md +47 -0
  468. package/help/troubleshooting/repository-access.md +27 -0
  469. package/package.json +30 -11
  470. package/prisma/migrate.config.mjs +20 -0
  471. package/prisma/migrations/20260925010000_coding_collect_exclude/migration.sql +8 -0
  472. package/prisma/migrations/20260925015000_allowed_egress_default/migration.sql +8 -0
  473. package/prisma/migrations/20260925020000_coding_package_registry/migration.sql +55 -0
  474. package/prisma/migrations/20260925030000_tool_name_per_owner/migration.sql +11 -0
  475. package/prisma/migrations/20260926010000_run_trigger_host_event/migration.sql +7 -0
  476. package/prisma/migrations/20260926020000_code_review_hosts/migration.sql +53 -0
  477. package/prisma/migrations/20260926030000_auth_user_roles/migration.sql +9 -0
  478. package/prisma/migrations/20260926040000_agent_effort/migration.sql +6 -0
  479. package/prisma/migrations/20260926050000_registry_lockfile_plan/migration.sql +32 -0
  480. package/prisma/migrations/20260926050000_repo_access_authorization/migration.sql +82 -0
  481. package/prisma/migrations/20260926060000_registry_plan_refusal/migration.sql +19 -0
  482. package/prisma/migrations/20260926100000_registry_plan_refusal_published_at/migration.sql +5 -0
  483. package/prisma/migrations/20260926190000_run_host_status/migration.sql +18 -0
  484. package/prisma/migrations/20260926210000_run_host_status_at_dispatch/migration.sql +5 -0
  485. package/prisma/migrations/20260927010000_resource_grants/migration.sql +72 -0
  486. package/prisma/migrations/20260927020000_proxy_session_budget_exhausted/migration.sql +3 -0
  487. package/prisma/migrations/20260927030000_coding_debug_trace/migration.sql +4 -0
  488. package/prisma/migrations/20260927040000_proxy_session_upstream_failure/migration.sql +3 -0
  489. package/prisma/migrations/20260927050000_coding_run_services/migration.sql +74 -0
  490. package/prisma/migrations/20260928000000_coding_run_tool_image/migration.sql +5 -0
  491. package/prisma/schema.prisma +417 -23
@@ -1,16 +1,16 @@
1
1
  /**
2
- * Single seam for ownership decisions across Agent/BudgetGroup/Tool/Secret/
3
- * Webhook/Task (and anything else that gains a nullable owner column
4
- * later). A null owner means "public": readable AND mutable by anyone —
5
- * there is no gatekeeper, so no principal is turned away. `isOwner` stays
2
+ * Ownership decisions for the resource types that don't have grants yet:
3
+ * BudgetGroup/Tool/Secret/Datastore/Webhook/Task. A null owner still means
4
+ * "public" here: readable AND mutable by anyone. Tool and budget-group
5
+ * grants replace that in the resource-sharing grants Phases 2 and 3.
6
+ *
7
+ * Agents are NOT decided here any more: they use explicit grants (owner, or
8
+ * read < execute < write), and an owner-less agent is reachable only
9
+ * through its grants -- see ./access.ts and core/grants.ts. `isOwner` stays
6
10
  * available (strict, no null-passthrough) for checks that must stay
7
- * owner-only regardless of publicness — e.g. a future claim-on-edit
8
- * feature would use it to tell "already owned" apart from "public" before
9
- * deciding whether to stamp a new owner. Every tool file routes through
10
- * this module instead of re-deriving the null-handling itself, so changing
11
- * the policy again later is a change here, not an N-file sweep.
11
+ * owner-only regardless of publicness.
12
12
  */
13
- import type { PrismaClient, Tool } from "@prisma/client";
13
+ import type { PrismaClient, Tool } from "#prisma";
14
14
  export declare function canRead(ownerId: string | null, principalId: string): boolean;
15
15
  export declare function isOwner(ownerId: string | null, principalId: string): boolean;
16
16
  /** Same rule as canRead: a null owner is a mutation target for anyone, same as it's a read target for anyone. */
@@ -24,90 +24,64 @@ export declare function visibleToPrincipal(principalId: string): {
24
24
  ownerId: null;
25
25
  })[];
26
26
  };
27
- export declare function requireOwnedAgent(db: PrismaClient, id: string, principalId: string): Promise<{
27
+ export declare function requireOwnedBudgetGroup(db: PrismaClient, id: string, principalId: string): Promise<{
28
28
  name: string;
29
- model: string;
30
- budgetUsd: import("@prisma/client/runtime/library").Decimal;
31
29
  id: string;
32
- systemPrompt: string;
33
- createdAt: Date;
34
- updatedAt: Date;
35
- kind: import("@prisma/client").$Enums.AgentKind;
36
- schedule: string | null;
37
- timezone: string;
38
- scheduleEnabled: boolean;
39
- lastScheduledAt: Date | null;
40
- maxTurns: number;
41
30
  ownerId: string | null;
42
- budgetGroupId: string | null;
43
- memoryEnabled: boolean;
44
- }>;
45
- export declare function requireReadableAgent(db: PrismaClient, id: string, principalId: string): Promise<{
46
- name: string;
47
- model: string;
48
- budgetUsd: import("@prisma/client/runtime/library").Decimal;
49
- id: string;
50
- systemPrompt: string;
31
+ dailyBudgetUsd: import("@prisma/client-runtime-utils").Decimal | null;
32
+ weeklyBudgetUsd: import("@prisma/client-runtime-utils").Decimal | null;
33
+ monthlyBudgetUsd: import("@prisma/client-runtime-utils").Decimal | null;
34
+ warnThresholdRatio: import("@prisma/client-runtime-utils").Decimal;
51
35
  createdAt: Date;
52
36
  updatedAt: Date;
53
- kind: import("@prisma/client").$Enums.AgentKind;
54
- schedule: string | null;
55
- timezone: string;
56
- scheduleEnabled: boolean;
57
- lastScheduledAt: Date | null;
58
- maxTurns: number;
59
- ownerId: string | null;
60
- budgetGroupId: string | null;
61
- memoryEnabled: boolean;
62
37
  }>;
63
- export declare function requireOwnedBudgetGroup(db: PrismaClient, id: string, principalId: string): Promise<{
38
+ export declare function requireReadableBudgetGroup(db: Pick<PrismaClient, "budgetGroup">, id: string, principalId: string): Promise<{
64
39
  name: string;
65
40
  id: string;
66
- createdAt: Date;
67
- updatedAt: Date;
68
41
  ownerId: string | null;
69
- dailyBudgetUsd: import("@prisma/client/runtime/library").Decimal | null;
70
- weeklyBudgetUsd: import("@prisma/client/runtime/library").Decimal | null;
71
- monthlyBudgetUsd: import("@prisma/client/runtime/library").Decimal | null;
72
- warnThresholdRatio: import("@prisma/client/runtime/library").Decimal;
73
- }>;
74
- export declare function requireReadableBudgetGroup(db: PrismaClient, id: string, principalId: string): Promise<{
75
- name: string;
76
- id: string;
42
+ dailyBudgetUsd: import("@prisma/client-runtime-utils").Decimal | null;
43
+ weeklyBudgetUsd: import("@prisma/client-runtime-utils").Decimal | null;
44
+ monthlyBudgetUsd: import("@prisma/client-runtime-utils").Decimal | null;
45
+ warnThresholdRatio: import("@prisma/client-runtime-utils").Decimal;
77
46
  createdAt: Date;
78
47
  updatedAt: Date;
79
- ownerId: string | null;
80
- dailyBudgetUsd: import("@prisma/client/runtime/library").Decimal | null;
81
- weeklyBudgetUsd: import("@prisma/client/runtime/library").Decimal | null;
82
- monthlyBudgetUsd: import("@prisma/client/runtime/library").Decimal | null;
83
- warnThresholdRatio: import("@prisma/client/runtime/library").Decimal;
84
48
  }>;
85
49
  export declare function requireOwnedSecret(db: PrismaClient, id: string, principalId: string): Promise<void>;
86
50
  export declare function requireOwnedWebhook(db: PrismaClient, id: string, principalId: string): Promise<void>;
87
51
  export declare function requireOwnedDatastore(db: PrismaClient, id: string, principalId: string): Promise<{
88
52
  name: string;
89
53
  id: string;
54
+ ownerId: string | null;
90
55
  createdAt: Date;
91
56
  updatedAt: Date;
92
- ownerId: string | null;
93
57
  }>;
94
58
  export declare function requireOwnedTool(db: Pick<PrismaClient, "tool">, id: string, principalId: string): Promise<Tool>;
59
+ /**
60
+ * Owner-only, with no null-is-public passthrough: for changing or removing
61
+ * a tool's code (update_tool/delete_tool), where requireOwnedTool's rule
62
+ * would let anyone rewrite a public tool. A public tool is readable but
63
+ * immutable over MCP (403) -- only an operator can change one, via the CLI.
64
+ * A tool the caller can't read is 404, the same as a missing one.
65
+ */
66
+ export declare function requireStrictlyOwnedTool(db: Pick<PrismaClient, "tool">, id: string, principalId: string): Promise<Tool>;
67
+ /** requireStrictlyOwnedTool's rule for a row already read (e.g. inside core/tool-admin.ts's transaction). */
68
+ export declare function assertStrictlyOwnedTool(tool: Tool | null, id: string, principalId: string): Tool;
95
69
  /**
96
70
  * A Task's principalId is the caller who triggered it, not the underlying
97
71
  * agent's owner — a public agent's runs are still private to whoever
98
72
  * triggered them. No canRead/null=public path exists here on purpose.
99
73
  */
100
74
  export declare function requireOwnedTask(db: PrismaClient, taskId: string, principalId: string): Promise<{
101
- status: import("@prisma/client").$Enums.TaskStatus;
75
+ status: import("#prisma").TaskStatus;
102
76
  runId: string | null;
103
- error: import("@prisma/client/runtime/library").JsonValue | null;
104
- result: import("@prisma/client/runtime/library").JsonValue | null;
77
+ kind: import("#prisma").TaskKind;
78
+ error: import("@prisma/client/runtime/client").JsonValue | null;
79
+ result: import("@prisma/client/runtime/client").JsonValue | null;
105
80
  id: string;
106
81
  createdAt: Date;
107
82
  updatedAt: Date;
108
- kind: import("@prisma/client").$Enums.TaskKind;
109
83
  principalId: string | null;
110
- inputRequests: import("@prisma/client/runtime/library").JsonValue | null;
111
- inputResponses: import("@prisma/client/runtime/library").JsonValue | null;
84
+ inputRequests: import("@prisma/client/runtime/client").JsonValue | null;
85
+ inputResponses: import("@prisma/client/runtime/client").JsonValue | null;
112
86
  ttlAt: Date;
113
87
  }>;
@@ -17,20 +17,6 @@ export function assertCanMutate(ownerId, principalId, notFoundMessage) {
17
17
  export function visibleToPrincipal(principalId) {
18
18
  return { OR: [{ ownerId: principalId }, { ownerId: null }] };
19
19
  }
20
- export async function requireOwnedAgent(db, id, principalId) {
21
- const agent = await db.agent.findUnique({ where: { id } });
22
- if (!agent)
23
- throw new McpError(404, `Agent "${id}" not found.`);
24
- assertCanMutate(agent.ownerId, principalId, `Agent "${id}" is not owned by the caller.`);
25
- return agent;
26
- }
27
- export async function requireReadableAgent(db, id, principalId) {
28
- const agent = await db.agent.findUnique({ where: { id } });
29
- if (!agent || !canRead(agent.ownerId, principalId)) {
30
- throw new McpError(404, `Agent "${id}" not found.`);
31
- }
32
- return agent;
33
- }
34
20
  export async function requireOwnedBudgetGroup(db, id, principalId) {
35
21
  const group = await db.budgetGroup.findUnique({ where: { id } });
36
22
  if (!group)
@@ -71,6 +57,25 @@ export async function requireOwnedTool(db, id, principalId) {
71
57
  assertCanMutate(tool.ownerId, principalId, `Tool "${id}" is not owned by the caller.`);
72
58
  return tool;
73
59
  }
60
+ /**
61
+ * Owner-only, with no null-is-public passthrough: for changing or removing
62
+ * a tool's code (update_tool/delete_tool), where requireOwnedTool's rule
63
+ * would let anyone rewrite a public tool. A public tool is readable but
64
+ * immutable over MCP (403) -- only an operator can change one, via the CLI.
65
+ * A tool the caller can't read is 404, the same as a missing one.
66
+ */
67
+ export async function requireStrictlyOwnedTool(db, id, principalId) {
68
+ return assertStrictlyOwnedTool(await db.tool.findUnique({ where: { id } }), id, principalId);
69
+ }
70
+ /** requireStrictlyOwnedTool's rule for a row already read (e.g. inside core/tool-admin.ts's transaction). */
71
+ export function assertStrictlyOwnedTool(tool, id, principalId) {
72
+ if (!tool || !canRead(tool.ownerId, principalId))
73
+ throw new McpError(404, `Tool "${id}" not found.`);
74
+ if (!isOwner(tool.ownerId, principalId)) {
75
+ throw new McpError(403, `Tool "${id}" is public; public tools can't be changed or deleted over MCP.`);
76
+ }
77
+ return tool;
78
+ }
74
79
  /**
75
80
  * A Task's principalId is the caller who triggered it, not the underlying
76
81
  * agent's owner — a public agent's runs are still private to whoever
@@ -5,5 +5,5 @@
5
5
  * always carries an owner, so a deployment that later moves from stdio to
6
6
  * HTTP+OAuth doesn't orphan locally-authored agents.
7
7
  */
8
- import type { PrismaClient, Principal } from "@prisma/client";
8
+ import type { PrismaClient, Principal } from "#prisma";
9
9
  export declare function resolvePrincipal(subject: string, db: PrismaClient): Promise<Principal>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Set-time repository authorization for the MCP tools that give an agent
3
+ * authority over a repository (link_repository; create_agent/update_agent's
4
+ * codingProfile.repository). Returns the stamp to store on the granting row,
5
+ * or throws an actionable McpError. See core/repo-access.ts for the use-time
6
+ * side and the rules; the host call happens here, before any transaction.
7
+ */
8
+ import { type AuthorizedVia, type RepoAccessKind } from "../../core/repo-access.js";
9
+ import type { McpRequestContext } from "../context.js";
10
+ export interface RepositoryAuthorization {
11
+ authorizedVia: AuthorizedVia;
12
+ authorizedById: string;
13
+ authorizedAt: Date;
14
+ }
15
+ export declare function authorizeRepositoryForSet(ctx: McpRequestContext, input: {
16
+ /** The agent's owner. The caller is always the owner here (or creating the agent). */
17
+ ownerId: string | null;
18
+ provider: string;
19
+ repository: string;
20
+ kind: RepoAccessKind;
21
+ adminOverride?: boolean;
22
+ }): Promise<RepositoryAuthorization>;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Set-time repository authorization for the MCP tools that give an agent
3
+ * authority over a repository (link_repository; create_agent/update_agent's
4
+ * codingProfile.repository). Returns the stamp to store on the granting row,
5
+ * or throws an actionable McpError. See core/repo-access.ts for the use-time
6
+ * side and the rules; the host call happens here, before any transaction.
7
+ */
8
+ import { requiredLevel } from "../../core/repo-access.js";
9
+ import { McpError } from "../errors.js";
10
+ import { requireScope } from "./resource-server.js";
11
+ export async function authorizeRepositoryForSet(ctx, input) {
12
+ if (!input.ownerId) {
13
+ throw new McpError(400, "owner_required: agents without an owner can't be given a repository, because an owner-less agent has no owner " +
14
+ "whose GitHub access can be checked. An admin can assign an owner with make_owner first.");
15
+ }
16
+ const stamp = (authorizedVia) => ({
17
+ authorizedVia,
18
+ authorizedById: ctx.principal.id,
19
+ authorizedAt: new Date(),
20
+ });
21
+ if (input.adminOverride) {
22
+ // Role-gated: the scope alone never suffices (resource-server.ts).
23
+ requireScope(ctx, ctx.canonicalUri, "agents:admin");
24
+ return stamp("admin");
25
+ }
26
+ const gate = ctx.providers.repoAccess;
27
+ if (!gate) {
28
+ throw new McpError(503, "Repository access can't be checked on this deployment (no GitHub App is configured). " +
29
+ "An admin can approve it explicitly with adminOverride.");
30
+ }
31
+ const required = requiredLevel(input.kind);
32
+ const decision = await gate.authorizePrincipal({
33
+ principalId: input.ownerId,
34
+ provider: input.provider,
35
+ repository: input.repository,
36
+ required,
37
+ fresh: true,
38
+ });
39
+ if (decision.ok)
40
+ return stamp("host_permission");
41
+ switch (decision.reason) {
42
+ case "identity_not_linked":
43
+ throw new McpError(403, "Link your GitHub account first (link_host_account): wardby checks that your own GitHub account can " +
44
+ `access ${input.repository} before an agent you own may use it.`);
45
+ case "insufficient_permission":
46
+ throw new McpError(403, `Your linked GitHub account has ${decision.level ?? "no"} access to ${input.repository}; ` +
47
+ `this needs ${required}. Ask a repository admin for access, or a wardby admin to approve it (adminOverride).`);
48
+ default:
49
+ throw new McpError(503, `Could not verify your GitHub access to ${input.repository} right now (is the GitHub App installed on it?). Try again.`);
50
+ }
51
+ }
@@ -4,12 +4,35 @@
4
4
  * AuthProvider (delegating | self-hosted) is active — both implement
5
5
  * verifyBearer, so this file never branches on auth mode.
6
6
  */
7
- import type { PrismaClient } from "@prisma/client";
7
+ import type { PrismaClient } from "#prisma";
8
8
  import type { AuthProvider } from "../../providers/auth/types.js";
9
- import { McpError } from "../errors.js";
9
+ import { McpError, insufficientScope } from "../errors.js";
10
10
  import type { McpRequestContext, McpProviders } from "../context.js";
11
11
  /** The initial capability tiers (§Scopes → capability tiers in the design doc). */
12
12
  export declare const SCOPES_SUPPORTED: string[];
13
+ /**
14
+ * Scopes whose operations reach beyond the caller's own (or public)
15
+ * resources — they double as the permission names roles grant:
16
+ * agents:admin reassigns ANY agent's owner (make_owner) and sets a BYO
17
+ * workerImageRef; packages:approve widens coding agents' package allowlists;
18
+ * services:manage changes the coding-run service catalog, which decides what
19
+ * runs next to every coding run that names an entry.
20
+ * A token scope only DELEGATES — it never authorizes on its own:
21
+ * requireScope/requireAnyScope honour one only when one of the caller's
22
+ * roles (McpRequestContext.roles, resolved live per request) grants it.
23
+ */
24
+ export declare const PRIVILEGED_SCOPES: readonly string[];
25
+ /**
26
+ * The built-in roles and the permissions (privileged scope names) each
27
+ * grants. No roles = member: every non-privileged scope, nothing privileged.
28
+ */
29
+ export declare const ROLE_PERMISSIONS: Readonly<Record<string, readonly string[]>>;
30
+ export declare const ROLE_NAMES: readonly string[];
31
+ export declare function isRoleName(name: string): boolean;
32
+ /** The permissions granted by `roles`; unknown role names grant nothing. */
33
+ export declare function permissionsOf(roles: readonly string[] | null | undefined): Set<string>;
34
+ /** Intersects a space-delimited scope string with `allowed`, deduplicated, keeping request order. */
35
+ export declare function limitScope(scope: string, allowed: readonly string[]): string;
13
36
  export interface ProtectedResourceMetadata {
14
37
  resource: string;
15
38
  authorization_servers: string[];
@@ -44,4 +67,10 @@ export declare function authenticate(headers: AuthenticateHeaders, deps: Authent
44
67
  * per missing scope.
45
68
  */
46
69
  export declare function requireScope(ctx: McpRequestContext, canonicalUri: string, ...scopes: string[]): void;
47
- export { McpError };
70
+ /**
71
+ * Like requireScope, but any ONE of `alternatives` suffices (e.g. package
72
+ * approval: packages:approve or agents:admin). The first alternative is the
73
+ * one named in the insufficient_scope challenge.
74
+ */
75
+ export declare function requireAnyScope(ctx: McpRequestContext, canonicalUri: string, ...alternatives: string[]): void;
76
+ export { McpError, insufficientScope };
@@ -10,11 +10,51 @@ export const SCOPES_SUPPORTED = [
10
10
  "secrets:write",
11
11
  "webhooks:write",
12
12
  "budget_groups:write",
13
+ // Approve coding agents' package allowlists (see docs/coding-packages.md).
14
+ "packages:approve",
15
+ // Create, update and delete coding-run service catalog entries (see docs/coding-services.md).
16
+ // Reading the catalog is agents:read.
17
+ "services:manage",
13
18
  // Reassigns an agent's owner regardless of who currently owns it (or
14
19
  // whether it's public) — a step above agents:write, which only ever lets
15
20
  // a caller act on agents they already own or that are unowned.
16
21
  "agents:admin",
22
+ // set_agent_memory / delete_agent_memory (reading memory is agents:read).
23
+ "memory:write",
17
24
  ];
25
+ /**
26
+ * Scopes whose operations reach beyond the caller's own (or public)
27
+ * resources — they double as the permission names roles grant:
28
+ * agents:admin reassigns ANY agent's owner (make_owner) and sets a BYO
29
+ * workerImageRef; packages:approve widens coding agents' package allowlists;
30
+ * services:manage changes the coding-run service catalog, which decides what
31
+ * runs next to every coding run that names an entry.
32
+ * A token scope only DELEGATES — it never authorizes on its own:
33
+ * requireScope/requireAnyScope honour one only when one of the caller's
34
+ * roles (McpRequestContext.roles, resolved live per request) grants it.
35
+ */
36
+ export const PRIVILEGED_SCOPES = ["agents:admin", "packages:approve", "services:manage"];
37
+ /**
38
+ * The built-in roles and the permissions (privileged scope names) each
39
+ * grants. No roles = member: every non-privileged scope, nothing privileged.
40
+ */
41
+ export const ROLE_PERMISSIONS = {
42
+ admin: ["agents:admin", "packages:approve", "services:manage"],
43
+ "package-approver": ["packages:approve"],
44
+ "service-manager": ["services:manage"],
45
+ };
46
+ export const ROLE_NAMES = Object.keys(ROLE_PERMISSIONS);
47
+ export function isRoleName(name) {
48
+ return Object.prototype.hasOwnProperty.call(ROLE_PERMISSIONS, name);
49
+ }
50
+ /** The permissions granted by `roles`; unknown role names grant nothing. */
51
+ export function permissionsOf(roles) {
52
+ return new Set((roles ?? []).filter(isRoleName).flatMap((r) => ROLE_PERMISSIONS[r]));
53
+ }
54
+ /** Intersects a space-delimited scope string with `allowed`, deduplicated, keeping request order. */
55
+ export function limitScope(scope, allowed) {
56
+ return [...new Set(scope.split(/\s+/).filter((s) => allowed.includes(s)))].join(" ");
57
+ }
18
58
  /** RFC 9728 §3.1: insert `/.well-known/oauth-protected-resource` before the resource's path. */
19
59
  export function protectedResourceMetadataUrl(canonicalUri) {
20
60
  const url = new URL(canonicalUri);
@@ -55,6 +95,9 @@ export async function authenticate(headers, deps) {
55
95
  return {
56
96
  principal,
57
97
  scopes: new Set(verified.scopes),
98
+ // Decided by the provider on THIS request (self-hosted: AuthUser.roles
99
+ // read from the database; delegating: the configured signed role claim).
100
+ roles: (verified.wardbyRoles ?? []).filter(isRoleName),
58
101
  canonicalUri: deps.canonicalUri,
59
102
  providers: deps.providers,
60
103
  db: deps.db,
@@ -75,8 +118,46 @@ export async function authenticate(headers, deps) {
75
118
  */
76
119
  export function requireScope(ctx, canonicalUri, ...scopes) {
77
120
  const held = scopes.every((s) => ctx.scopes.has(s));
78
- if (held)
121
+ if (!held)
122
+ throw insufficientScope(scopes, protectedResourceMetadataUrl(canonicalUri));
123
+ requirePermission(ctx, scopes);
124
+ }
125
+ /**
126
+ * Like requireScope, but any ONE of `alternatives` suffices (e.g. package
127
+ * approval: packages:approve or agents:admin). The first alternative is the
128
+ * one named in the insufficient_scope challenge.
129
+ */
130
+ export function requireAnyScope(ctx, canonicalUri, ...alternatives) {
131
+ const held = alternatives.filter((s) => ctx.scopes.has(s));
132
+ if (held.length === 0)
133
+ throw insufficientScope(alternatives.slice(0, 1), protectedResourceMetadataUrl(canonicalUri));
134
+ // One held alternative that is non-privileged, or that a role permits, suffices.
135
+ const permitted = permissionsOf(ctx.roles);
136
+ if (held.some((s) => !PRIVILEGED_SCOPES.includes(s) || permitted.has(s)))
79
137
  return;
80
- throw insufficientScope(scopes, protectedResourceMetadataUrl(canonicalUri));
138
+ // A role DOES grant one of the unheld alternatives: re-authorizing for that
139
+ // scope is the fix, so say so with a scope challenge, not a role 403.
140
+ const reachable = alternatives.find((s) => !ctx.scopes.has(s) && permitted.has(s));
141
+ if (reachable)
142
+ throw insufficientScope([reachable], protectedResourceMetadataUrl(canonicalUri));
143
+ throw forbidden(held);
144
+ }
145
+ /**
146
+ * The one role check: a privileged scope is honoured only when one of the
147
+ * caller's roles grants it as a permission. This is authorization, not
148
+ * delegation, so the 403 carries no scope challenge — a client re-authorizing
149
+ * for more scopes can't fix it; an operator must grant a role.
150
+ */
151
+ function requirePermission(ctx, scopes) {
152
+ const permitted = permissionsOf(ctx.roles);
153
+ const missing = scopes.filter((s) => PRIVILEGED_SCOPES.includes(s) && !permitted.has(s));
154
+ if (missing.length > 0)
155
+ throw forbidden(missing);
156
+ }
157
+ function forbidden(permissions) {
158
+ const grantedBy = ROLE_NAMES.filter((r) => permissions.some((p) => ROLE_PERMISSIONS[r].includes(p)));
159
+ return new McpError(403, `Forbidden: ${permissions.join(" ")} requires a role that grants it (${grantedBy.join(" or ")}); ` +
160
+ "the token's scope alone is not enough. An operator grants roles (self-hosted: " +
161
+ "`wardby auth user grant --subject <subject> --role <role>`; delegating: AUTH_ROLE_CLAIM/AUTH_ROLE_MAP).");
81
162
  }
82
- export { McpError };
163
+ export { McpError, insufficientScope };
@@ -1,6 +1,7 @@
1
1
  import { randomUUID, randomBytes } from "node:crypto";
2
2
  import { PostgresRateLimiter, RateLimitError } from "./rate-limit.js";
3
3
  import { logger } from "../../../core/logger.js";
4
+ import { PAGE_STYLE } from "../../shared/page-style.js";
4
5
  const authLog = logger.child({ module: "self-hosted-oauth" });
5
6
  const paths = new Set([
6
7
  "/login",
@@ -29,12 +30,21 @@ const FORM_SCRIPT = `document.addEventListener('submit', async (event) => {
29
30
  });`;
30
31
  function html(res, content) {
31
32
  const nonce = randomBytes(18).toString("base64");
32
- res.setHeader("content-security-policy", `default-src 'none'; script-src 'nonce-${nonce}'; connect-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'`);
33
+ res.setHeader("content-security-policy",
34
+ // style-src reuses the same per-request nonce as script-src rather than
35
+ // 'unsafe-inline', so the one inline <style> stays as tightly scoped as
36
+ // the inline <script>.
37
+ `default-src 'none'; script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}'; connect-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'`);
33
38
  res
34
39
  .writeHead(200, { "content-type": "text/html; charset=utf-8" })
35
- .end('<!doctype html><html lang="en"><meta charset="utf-8"><title>wardby authorization</title><body>' +
40
+ .end('<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">' +
41
+ '<title>wardby authorization</title><style nonce="' +
42
+ nonce +
43
+ '">' +
44
+ PAGE_STYLE +
45
+ '</style><body><div class="card"><div class="kicker">wardby</div>' +
36
46
  content +
37
- '<p id="auth-error" role="alert"></p><noscript>JavaScript is required for secure form submission.</noscript><script nonce="' +
47
+ '<p id="auth-error" role="alert"></p></div><noscript>JavaScript is required for secure form submission.</noscript><script nonce="' +
38
48
  nonce +
39
49
  '">' +
40
50
  FORM_SCRIPT +
@@ -155,7 +165,7 @@ export function browserHandler(provider) {
155
165
  html(res, '<h1>Sign in to wardby</h1><form method="post" action="/login">' +
156
166
  hidden("interaction", interaction) +
157
167
  hidden("csrf", challenge) +
158
- '<label>Login key <input type="password" name="login_key" required autocomplete="off"></label><button>Sign in</button></form>');
168
+ '<label>Login key<input type="password" name="login_key" required autocomplete="off"></label><button>Sign in</button></form>');
159
169
  return true;
160
170
  }
161
171
  if (req.method === "POST" &&
@@ -187,18 +197,19 @@ export function browserHandler(provider) {
187
197
  }
188
198
  if (req.method === "GET" && url.pathname === "/consent") {
189
199
  const id = url.searchParams.get("interaction") ?? "";
190
- const { interaction, challenge } = await provider.consentPage(cookies.get(sessionName) ?? "", id);
200
+ const { interaction, challenge, scopes } = await provider.consentPage(cookies.get(sessionName) ?? "", id);
191
201
  const name = interaction.client.metadata.client_name;
192
202
  html(res, "<h1>Authorize " +
193
203
  escape(name) +
194
204
  "</h1><p>Resource: " +
195
205
  escape(interaction.resource) +
196
206
  "</p><p>Scopes: " +
197
- escape(interaction.requestedScope) +
207
+ escape(scopes.length ? scopes.join(" ") : "(none)") +
198
208
  '</p><form action="/consent" method="post">' +
199
209
  hidden("interaction", id) +
200
210
  hidden("csrf", challenge) +
201
- '<button name="decision" value="approve">Approve</button><button name="decision" value="deny">Deny</button></form>');
211
+ '<div class="actions"><button name="decision" value="approve">Approve</button>' +
212
+ '<button name="decision" value="deny">Deny</button></div></form>');
202
213
  return true;
203
214
  }
204
215
  if (req.method === "POST" && url.pathname === "/consent") {
@@ -210,7 +221,10 @@ export function browserHandler(provider) {
210
221
  if (req.method === "GET" && url.pathname === "/logout") {
211
222
  const session = await provider.sessions.get(cookies.get(sessionName) ?? "");
212
223
  const challenge = await provider.sessions.challenge("logout", session.sessionId, "logout");
213
- html(res, '<form action="/logout" method="post">' + hidden("csrf", challenge) + "<button>Sign out</button></form>");
224
+ html(res, "<h1>Sign out</h1>" +
225
+ '<form action="/logout" method="post">' +
226
+ hidden("csrf", challenge) +
227
+ "<button>Sign out</button></form>");
214
228
  return true;
215
229
  }
216
230
  if (req.method === "POST" && url.pathname === "/logout") {
@@ -1,2 +1,2 @@
1
- import type { PrismaClient } from "@prisma/client";
1
+ import type { PrismaClient } from "#prisma";
2
2
  export declare function authCommand(args: string[], db: PrismaClient, hashKey: string, output?: (value: string) => void): Promise<void>;
@@ -1,39 +1,73 @@
1
1
  import { parseArgs } from "node:util";
2
2
  import { IdentityService } from "./credentials.js";
3
+ const USAGE = "Use auth user create --subject <s> [--role <role>]... | user list | " +
4
+ "user grant --subject <s> [--role <role>]... [--revoke-role <role>]... | user disable --subject <s> | " +
5
+ "key create|list --subject <s> | key revoke <key-id>.";
6
+ /** The flags each command accepts. Anything else is rejected, never silently ignored. */
7
+ const COMMANDS = {
8
+ "user create": ["subject", "role"],
9
+ "user list": [],
10
+ "user grant": ["subject", "role", "revoke-role"],
11
+ "user disable": ["subject"],
12
+ "key create": ["subject"],
13
+ "key list": ["subject"],
14
+ };
15
+ const FLAG_OPTIONS = {
16
+ subject: { type: "string" },
17
+ role: { type: "string", multiple: true },
18
+ "revoke-role": { type: "string", multiple: true },
19
+ };
3
20
  export async function authCommand(args, db, hashKey, output = console.log) {
4
21
  const [kind, action, ...rest] = args;
5
22
  const service = new IdentityService(db, hashKey);
6
23
  if (hashKey.toLowerCase() === process.env.SECRET_APP_KEY?.toLowerCase() ||
7
24
  hashKey.toLowerCase() === process.env.AUTH_SIGNING_KEY?.toLowerCase())
8
25
  throw new Error("Authentication keys must be independent.");
9
- if (kind === "key" && action === "revoke" && rest.length === 1) {
26
+ const command = `${kind} ${action}`;
27
+ if (command === "key revoke") {
28
+ if (rest.length !== 1 || rest[0].startsWith("-"))
29
+ throw new Error("Use auth key revoke <key-id> (no flags).");
10
30
  await service.revokeKey(rest[0]);
11
31
  output("Key, sessions, and grants revoked.");
12
32
  return;
13
33
  }
14
- const { values } = parseArgs({ args: rest, options: { subject: { type: "string" } } });
15
- if (kind === "user" && action === "list") {
34
+ const allowed = COMMANDS[command];
35
+ if (!allowed)
36
+ throw new Error(USAGE);
37
+ // strict parsing throws on any flag not declared for THIS command, so a
38
+ // flag another command takes (e.g. `key create --role admin`) is an error.
39
+ const { values } = parseArgs({
40
+ args: rest,
41
+ strict: true,
42
+ allowPositionals: false,
43
+ options: Object.fromEntries(allowed.map((flag) => [flag, FLAG_OPTIONS[flag]])),
44
+ });
45
+ if (command === "user list") {
16
46
  output(JSON.stringify(await service.listUsers(), null, 2));
17
47
  return;
18
48
  }
19
49
  if (!values.subject)
20
- throw new Error("auth requires --subject (except user list and key revoke <key-id>).");
21
- if (kind === "user" && action === "create") {
22
- output(JSON.stringify(await service.createUser(values.subject)));
50
+ throw new Error(`auth ${command} requires --subject.`);
51
+ if (command === "user create") {
52
+ output(JSON.stringify(await service.createUser(values.subject, values.role ?? [])));
23
53
  return;
24
54
  }
25
- if (kind === "user" && action === "disable") {
55
+ if (command === "user grant") {
56
+ const add = values.role ?? [];
57
+ const remove = values["revoke-role"] ?? [];
58
+ if (add.length + remove.length === 0)
59
+ throw new Error("auth user grant requires --role <role> and/or --revoke-role <role> (each repeatable).");
60
+ output(JSON.stringify(await service.changeRoles(values.subject, add, remove)));
61
+ return;
62
+ }
63
+ if (command === "user disable") {
26
64
  await service.disableUser(values.subject);
27
65
  output("User disabled; credentials, sessions, and grants revoked.");
28
66
  return;
29
67
  }
30
- if (kind === "key" && action === "create") {
68
+ if (command === "key create") {
31
69
  output(await service.createKey(values.subject));
32
70
  return;
33
71
  }
34
- if (kind === "key" && action === "list") {
35
- output(JSON.stringify(await service.listKeys(values.subject), null, 2));
36
- return;
37
- }
38
- throw new Error("Use auth user create|list|disable or auth key create|list|revoke.");
72
+ output(JSON.stringify(await service.listKeys(values.subject), null, 2));
39
73
  }