@studio-foundation/api 0.3.0-beta.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 (188) hide show
  1. package/ARCHITECTURE.md +52 -0
  2. package/LICENSE +663 -0
  3. package/README.md +161 -0
  4. package/dist/api.d.ts +8 -0
  5. package/dist/api.d.ts.map +1 -0
  6. package/dist/api.js +6 -0
  7. package/dist/api.js.map +1 -0
  8. package/dist/bootstrap.d.ts +66 -0
  9. package/dist/bootstrap.d.ts.map +1 -0
  10. package/dist/bootstrap.js +193 -0
  11. package/dist/bootstrap.js.map +1 -0
  12. package/dist/event-bus.d.ts +16 -0
  13. package/dist/event-bus.d.ts.map +1 -0
  14. package/dist/event-bus.js +37 -0
  15. package/dist/event-bus.js.map +1 -0
  16. package/dist/index.d.ts +3 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +52 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/integration-runtime.d.ts +22 -0
  21. package/dist/integration-runtime.d.ts.map +1 -0
  22. package/dist/integration-runtime.js +150 -0
  23. package/dist/integration-runtime.js.map +1 -0
  24. package/dist/integration-store.d.ts +26 -0
  25. package/dist/integration-store.d.ts.map +1 -0
  26. package/dist/integration-store.js +80 -0
  27. package/dist/integration-store.js.map +1 -0
  28. package/dist/integrations/linear/failure-handler.d.ts +5 -0
  29. package/dist/integrations/linear/failure-handler.d.ts.map +1 -0
  30. package/dist/integrations/linear/failure-handler.js +73 -0
  31. package/dist/integrations/linear/failure-handler.js.map +1 -0
  32. package/dist/integrations/linear/webhook-handler.d.ts +6 -0
  33. package/dist/integrations/linear/webhook-handler.d.ts.map +1 -0
  34. package/dist/integrations/linear/webhook-handler.js +131 -0
  35. package/dist/integrations/linear/webhook-handler.js.map +1 -0
  36. package/dist/integrations/registry.d.ts +4 -0
  37. package/dist/integrations/registry.d.ts.map +1 -0
  38. package/dist/integrations/registry.js +9 -0
  39. package/dist/integrations/registry.js.map +1 -0
  40. package/dist/integrations/types.d.ts +33 -0
  41. package/dist/integrations/types.d.ts.map +1 -0
  42. package/dist/integrations/types.js +2 -0
  43. package/dist/integrations/types.js.map +1 -0
  44. package/dist/launcher.d.ts +45 -0
  45. package/dist/launcher.d.ts.map +1 -0
  46. package/dist/launcher.js +149 -0
  47. package/dist/launcher.js.map +1 -0
  48. package/dist/logger.d.ts +7 -0
  49. package/dist/logger.d.ts.map +1 -0
  50. package/dist/logger.js +37 -0
  51. package/dist/logger.js.map +1 -0
  52. package/dist/plans.d.ts +13 -0
  53. package/dist/plans.d.ts.map +1 -0
  54. package/dist/plans.js +33 -0
  55. package/dist/plans.js.map +1 -0
  56. package/dist/routes/agents.d.ts +6 -0
  57. package/dist/routes/agents.d.ts.map +1 -0
  58. package/dist/routes/agents.js +118 -0
  59. package/dist/routes/agents.js.map +1 -0
  60. package/dist/routes/config.d.ts +6 -0
  61. package/dist/routes/config.d.ts.map +1 -0
  62. package/dist/routes/config.js +129 -0
  63. package/dist/routes/config.js.map +1 -0
  64. package/dist/routes/contracts.d.ts +6 -0
  65. package/dist/routes/contracts.d.ts.map +1 -0
  66. package/dist/routes/contracts.js +188 -0
  67. package/dist/routes/contracts.js.map +1 -0
  68. package/dist/routes/pipelines.d.ts +6 -0
  69. package/dist/routes/pipelines.d.ts.map +1 -0
  70. package/dist/routes/pipelines.js +133 -0
  71. package/dist/routes/pipelines.js.map +1 -0
  72. package/dist/routes/projects.d.ts +6 -0
  73. package/dist/routes/projects.d.ts.map +1 -0
  74. package/dist/routes/projects.js +220 -0
  75. package/dist/routes/projects.js.map +1 -0
  76. package/dist/routes/runs.d.ts +6 -0
  77. package/dist/routes/runs.d.ts.map +1 -0
  78. package/dist/routes/runs.js +427 -0
  79. package/dist/routes/runs.js.map +1 -0
  80. package/dist/routes/skills.d.ts +6 -0
  81. package/dist/routes/skills.d.ts.map +1 -0
  82. package/dist/routes/skills.js +123 -0
  83. package/dist/routes/skills.js.map +1 -0
  84. package/dist/routes/tools.d.ts +6 -0
  85. package/dist/routes/tools.d.ts.map +1 -0
  86. package/dist/routes/tools.js +193 -0
  87. package/dist/routes/tools.js.map +1 -0
  88. package/dist/routes/users.d.ts +6 -0
  89. package/dist/routes/users.d.ts.map +1 -0
  90. package/dist/routes/users.js +152 -0
  91. package/dist/routes/users.js.map +1 -0
  92. package/dist/routes/validate.d.ts +6 -0
  93. package/dist/routes/validate.d.ts.map +1 -0
  94. package/dist/routes/validate.js +171 -0
  95. package/dist/routes/validate.js.map +1 -0
  96. package/dist/routes/webhooks.d.ts +8 -0
  97. package/dist/routes/webhooks.d.ts.map +1 -0
  98. package/dist/routes/webhooks.js +102 -0
  99. package/dist/routes/webhooks.js.map +1 -0
  100. package/dist/server.d.ts +46 -0
  101. package/dist/server.d.ts.map +1 -0
  102. package/dist/server.js +101 -0
  103. package/dist/server.js.map +1 -0
  104. package/dist/spawners/http-api-spawner.d.ts +10 -0
  105. package/dist/spawners/http-api-spawner.d.ts.map +1 -0
  106. package/dist/spawners/http-api-spawner.js +88 -0
  107. package/dist/spawners/http-api-spawner.js.map +1 -0
  108. package/dist/user-store-pg.d.ts +21 -0
  109. package/dist/user-store-pg.d.ts.map +1 -0
  110. package/dist/user-store-pg.js +97 -0
  111. package/dist/user-store-pg.js.map +1 -0
  112. package/dist/user-store.d.ts +29 -0
  113. package/dist/user-store.d.ts.map +1 -0
  114. package/dist/user-store.js +97 -0
  115. package/dist/user-store.js.map +1 -0
  116. package/dist/utils/repo-resolver.d.ts +3 -0
  117. package/dist/utils/repo-resolver.d.ts.map +1 -0
  118. package/dist/utils/repo-resolver.js +3 -0
  119. package/dist/utils/repo-resolver.js.map +1 -0
  120. package/dist/webhook-dispatcher.d.ts +13 -0
  121. package/dist/webhook-dispatcher.d.ts.map +1 -0
  122. package/dist/webhook-dispatcher.js +108 -0
  123. package/dist/webhook-dispatcher.js.map +1 -0
  124. package/dist/webhook-store.d.ts +31 -0
  125. package/dist/webhook-store.d.ts.map +1 -0
  126. package/dist/webhook-store.js +91 -0
  127. package/dist/webhook-store.js.map +1 -0
  128. package/package.json +59 -0
  129. package/src/.gitkeep +0 -0
  130. package/src/api.ts +8 -0
  131. package/src/bootstrap.ts +259 -0
  132. package/src/event-bus.ts +64 -0
  133. package/src/index.ts +58 -0
  134. package/src/integration-runtime.ts +180 -0
  135. package/src/integration-store.ts +125 -0
  136. package/src/integrations/linear/failure-handler.ts +93 -0
  137. package/src/integrations/linear/webhook-handler.ts +156 -0
  138. package/src/integrations/registry.ts +12 -0
  139. package/src/integrations/types.ts +37 -0
  140. package/src/launcher.ts +214 -0
  141. package/src/logger.ts +50 -0
  142. package/src/plans.ts +43 -0
  143. package/src/routes/agents.ts +131 -0
  144. package/src/routes/config.ts +154 -0
  145. package/src/routes/contracts.ts +205 -0
  146. package/src/routes/pipelines.ts +146 -0
  147. package/src/routes/projects.ts +237 -0
  148. package/src/routes/runs.ts +468 -0
  149. package/src/routes/skills.ts +136 -0
  150. package/src/routes/tools.ts +222 -0
  151. package/src/routes/users.ts +169 -0
  152. package/src/routes/validate.ts +196 -0
  153. package/src/routes/webhooks.ts +120 -0
  154. package/src/server.ts +155 -0
  155. package/src/spawners/http-api-spawner.ts +96 -0
  156. package/src/user-store-pg.ts +138 -0
  157. package/src/user-store.ts +125 -0
  158. package/src/utils/repo-resolver.ts +3 -0
  159. package/src/webhook-dispatcher.ts +142 -0
  160. package/src/webhook-store.ts +141 -0
  161. package/tests/agents.test.ts +164 -0
  162. package/tests/cancel.test.ts +120 -0
  163. package/tests/config.test.ts +196 -0
  164. package/tests/contracts.test.ts +302 -0
  165. package/tests/event-bus.test.ts +53 -0
  166. package/tests/http-api-spawner.test.ts +158 -0
  167. package/tests/integration-runtime.test.ts +199 -0
  168. package/tests/integration-store.test.ts +66 -0
  169. package/tests/integrations/linear/failure-handler.test.ts +113 -0
  170. package/tests/integrations/linear/webhook-handler.test.ts +191 -0
  171. package/tests/launcher.test.ts +380 -0
  172. package/tests/linear-webhook.test.ts +390 -0
  173. package/tests/pipelines.test.ts +166 -0
  174. package/tests/projects.test.ts +283 -0
  175. package/tests/runs.test.ts +379 -0
  176. package/tests/server.test.ts +208 -0
  177. package/tests/skills.test.ts +149 -0
  178. package/tests/sse.test.ts +129 -0
  179. package/tests/tools.test.ts +233 -0
  180. package/tests/user-store.test.ts +93 -0
  181. package/tests/users.test.ts +189 -0
  182. package/tests/utils/repo-resolver.test.ts +105 -0
  183. package/tests/validate.test.ts +233 -0
  184. package/tests/webhook-dispatcher.test.ts +214 -0
  185. package/tests/webhook-store.test.ts +98 -0
  186. package/tests/webhooks.test.ts +176 -0
  187. package/tsconfig.json +24 -0
  188. package/vitest.config.ts +7 -0
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # @studio-foundation/api
2
+
3
+ HTTP REST API for Studio. Same engine as the CLI, machine-to-machine interface. Like GitHub is to `git`.
4
+
5
+ ## Role
6
+
7
+ api wraps the engine in a Fastify server. It handles fire-and-forget pipeline launches, real-time SSE streaming, webhook dispatch, and integration lifecycle (Linear, etc.). The CLI (`studio api start`) is the primary way to run it, but it can also be imported programmatically.
8
+
9
+ ```
10
+ Linear webhook → POST /api/runs → launcher → engine → pipeline run → webhook dispatch
11
+ Slack bot → POST /api/runs ↗
12
+ CI/CD → POST /api/runs ↗
13
+ Dashboard → GET /api/runs/:id/stream (SSE)
14
+ ```
15
+
16
+ ## Starting the server
17
+
18
+ ```bash
19
+ # Via CLI (recommended)
20
+ studio api start
21
+
22
+ # Standalone (PM2/systemd)
23
+ STUDIO_CWD=/path/to/project node node_modules/.bin/studio-api
24
+
25
+ # Programmatic
26
+ import { createApi } from '@studio-foundation/api';
27
+ const { server, cleanup } = await createApi({ cwd: '/path/to/project' });
28
+ await server.listen({ port: 3700 });
29
+ ```
30
+
31
+ ## Configuration
32
+
33
+ The server reads `.studio/config.yaml` from the project directory (or the first `.studio/` found walking up from `STUDIO_CWD`):
34
+
35
+ ```yaml
36
+ api:
37
+ key: my-secret-key # Optional — enables Bearer auth. Omit for open local use.
38
+ port: 3700 # Default: 3700
39
+
40
+ db:
41
+ type: postgres # 'sqlite' | 'postgres' | 'inmemory'. Default: 'sqlite'
42
+ url: ${DATABASE_URL} # Required if type is postgres
43
+
44
+ providers:
45
+ anthropic:
46
+ apiKey: ${ANTHROPIC_API_KEY}
47
+ ```
48
+
49
+ **Authentication:** If `api.key` is set, all routes require `Authorization: Bearer <key>`. Without a key, the API is open (local use only).
50
+
51
+ ## Endpoints
52
+
53
+ ### Runs
54
+
55
+ ```
56
+ POST /api/runs → Launch a pipeline (fire-and-forget). Returns run ID immediately.
57
+ GET /api/runs → List runs (?status=&limit=)
58
+ GET /api/runs/:id → Run detail
59
+ GET /api/runs/:id/logs → Raw JSONL logs
60
+ GET /api/runs/:id/stream → SSE — live events (?events=csv filter)
61
+ DELETE /api/runs/:id → Cancel a running pipeline
62
+ ```
63
+
64
+ ### Project
65
+
66
+ ```
67
+ GET /api/projects → Current project (name, id, pipelines_dir)
68
+ GET /api/projects/:id/pipelines → Pipelines for a project
69
+ ```
70
+
71
+ ### CRUD (Pipelines, Agents, Contracts, Skills)
72
+
73
+ ```
74
+ GET /api/pipelines → List pipeline names
75
+ GET /api/pipelines/:name → Parsed pipeline (YAML → JSON)
76
+ PUT /api/pipelines/:name → Create or update a pipeline (body: YAML or JSON)
77
+ DELETE /api/pipelines/:name → Delete a pipeline
78
+
79
+ GET /api/agents → List agent names
80
+ GET /api/agents/:name → Parsed agent
81
+ PUT /api/agents/:name → Create or update an agent
82
+ DELETE /api/agents/:name → Delete an agent
83
+
84
+ GET /api/contracts → List contract names
85
+ GET /api/contracts/:name → Parsed contract
86
+ PUT /api/contracts/:name → Create or update a contract
87
+ DELETE /api/contracts/:name → Delete a contract
88
+
89
+ GET /api/skills → List skill names
90
+ GET /api/skills/:name → Skill content (.skill.md)
91
+ PUT /api/skills/:name → Create or update a skill (body: markdown)
92
+ DELETE /api/skills/:name → Delete a skill
93
+ ```
94
+
95
+ ### Tools, Config, Validation
96
+
97
+ ```
98
+ GET /api/tools → Available tools (YAML plugins + builtins)
99
+
100
+ GET /api/config → Current config (API keys masked)
101
+ PUT /api/config → Update config
102
+
103
+ POST /api/validate → Validate a JSON output against a contract
104
+ ```
105
+
106
+ ### Webhooks
107
+
108
+ ```
109
+ POST /api/webhooks → Register a webhook (url + events filter)
110
+ GET /api/webhooks → List webhooks
111
+ DELETE /api/webhooks/:id → Delete a webhook
112
+ ```
113
+
114
+ ### Swagger UI
115
+
116
+ ```
117
+ GET /api/docs → Swagger UI (dev only — disabled in NODE_ENV=production)
118
+ GET /api/docs/json → OpenAPI spec (for client generation)
119
+ ```
120
+
121
+ ## SSE streaming
122
+
123
+ Connect to `/api/runs/:id/stream` to receive live events:
124
+
125
+ ```javascript
126
+ const es = new EventSource('/api/runs/run-123/stream');
127
+ es.addEventListener('stage_complete', (e) => console.log(JSON.parse(e.data)));
128
+ es.addEventListener('pipeline_complete', (e) => { es.close(); });
129
+
130
+ // Filter to specific event types:
131
+ // GET /api/runs/:id/stream?events=stage_complete,tool_call_complete
132
+ ```
133
+
134
+ ## Integrations
135
+
136
+ The API manages integration lifecycle (webhook routing, failure handling). Integrations are configured via `.studio/config.yaml` and declared via `.integration.yaml` files in `.studio/integrations/`.
137
+
138
+ **Linear integration** — drag an issue to "In Progress" → Studio receives the webhook → auto-launches the matching pipeline → posts results as a comment → moves the issue to "Done" on success.
139
+
140
+ ## Sub-pipeline spawning
141
+
142
+ The API uses `HttpApiSpawner` — a self-referential `RunSpawner` that calls back into the same API to spawn child runs. This is how `studio_run` tool calls work when the engine is running behind the API.
143
+
144
+ ## Bootstrap internals
145
+
146
+ `bootstrap(cwd)` is the composition root:
147
+ 1. Finds `.studio/` by walking up from `cwd`
148
+ 2. Loads `config.yaml` (with `${ENV_VAR}` substitution)
149
+ 3. Creates `RunStore` based on `db.type` (`SQLiteRunStore` | `PgRunStore` | `InMemoryRunStore`)
150
+ 4. Creates `ProviderRegistry`, `ToolRegistry` (loads `.tool.yaml` plugins)
151
+ 5. Loads Claude Code plugins (`MCPClient` per server)
152
+ 6. Creates `HttpApiSpawner`, `PipelineEngine`, `InProcessLauncher`
153
+ 7. Creates `WebhookStore`, `IntegrationStore`, `IntegrationRuntime`
154
+ 8. Returns `BootstrapResult` passed to `buildServer()`
155
+
156
+ ## Rules
157
+
158
+ - **api depends on engine + runner.** It is a composition root, same as cli.
159
+ - **Routes must have complete Swagger schemas** — tags, summary, params, response codes. Without this, routes don't appear in Swagger UI.
160
+ - **The engine is the same.** The API doesn't bypass or wrap the engine — it delegates to `InProcessLauncher`, which calls `PipelineEngine.run()` directly.
161
+ - `studio api start` calls `bootstrap()` then `buildServer()`. The two steps are separate so programmatic users can customize between them.
package/dist/api.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export { bootstrap } from './bootstrap.js';
2
+ export { buildServer } from './server.js';
3
+ export type { ServerDeps, ApiConfig } from './server.js';
4
+ export type { RunLauncher, LaunchConfig } from './launcher.js';
5
+ export { InProcessLauncher, generateRunId } from './launcher.js';
6
+ export { cloneRepo, resolveRepoPath } from './utils/repo-resolver.js';
7
+ export type { RepoResolveOptions } from './utils/repo-resolver.js';
8
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACzD,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC"}
package/dist/api.js ADDED
@@ -0,0 +1,6 @@
1
+ // Named exports for programmatic use (CLI, tests)
2
+ export { bootstrap } from './bootstrap.js';
3
+ export { buildServer } from './server.js';
4
+ export { InProcessLauncher, generateRunId } from './launcher.js';
5
+ export { cloneRepo, resolveRepoPath } from './utils/repo-resolver.js';
6
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { type AnyRunStore } from '@studio-foundation/engine';
2
+ import { type RunLauncher } from './launcher.js';
3
+ import type { MaskedConfig } from './server.js';
4
+ import { WebhookStore } from './webhook-store.js';
5
+ import { IntegrationStore } from './integration-store.js';
6
+ import { IntegrationRuntime } from './integration-runtime.js';
7
+ import { type PlansConfig } from './plans.js';
8
+ import { UserStore } from './user-store.js';
9
+ import { PgUserStore } from './user-store-pg.js';
10
+ export interface StudioApiConfig {
11
+ providers?: {
12
+ openai?: {
13
+ apiKey: string;
14
+ };
15
+ anthropic?: {
16
+ apiKey: string;
17
+ };
18
+ ollama?: {
19
+ baseUrl?: string;
20
+ };
21
+ };
22
+ paths?: {
23
+ projects_dir?: string;
24
+ };
25
+ defaults?: {
26
+ provider?: string;
27
+ model?: string;
28
+ };
29
+ api?: {
30
+ key?: string;
31
+ port?: number;
32
+ };
33
+ integrations?: Record<string, Record<string, unknown>>;
34
+ db?: {
35
+ type?: 'sqlite' | 'postgres' | 'inmemory';
36
+ url?: string;
37
+ };
38
+ plans?: Record<string, {
39
+ runs_per_day?: number;
40
+ max_concurrent?: number;
41
+ max_tokens_per_run?: number;
42
+ rate_limit_per_minute?: number;
43
+ }>;
44
+ }
45
+ export interface BootstrapResult {
46
+ store: AnyRunStore;
47
+ launcher: RunLauncher;
48
+ configsDir: string;
49
+ /** Raw projects_dir from config (may contain ~). Used by route handlers for repo cloning. */
50
+ projectsDir?: string;
51
+ projectName: string;
52
+ apiConfig: {
53
+ key?: string;
54
+ port?: number;
55
+ };
56
+ cleanup: () => Promise<void>;
57
+ studioVersion: string;
58
+ maskedConfig: MaskedConfig;
59
+ webhookStore: WebhookStore;
60
+ integrationStore: IntegrationStore;
61
+ integrationRuntime: IntegrationRuntime;
62
+ userStore: UserStore | PgUserStore;
63
+ plans: PlansConfig;
64
+ }
65
+ export declare function bootstrap(cwd?: string): Promise<BootstrapResult>;
66
+ //# sourceMappingURL=bootstrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAOA,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,2BAA2B,CAAC;AAUnC,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE;QACV,MAAM,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QAC5B,SAAS,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/B,MAAM,CAAC,EAAE;YAAE,OAAO,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KAC/B,CAAC;IACF,KAAK,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,GAAG,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvD,EAAE,CAAC,EAAE;QACH,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;QAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC;KAChC,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,WAAW,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,6FAA6F;IAC7F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,YAAY,CAAC;IAC3B,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,SAAS,EAAE,SAAS,GAAG,WAAW,CAAC;IACnC,KAAK,EAAE,WAAW,CAAC;CACpB;AAkBD,wBAAsB,SAAS,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,eAAe,CAAC,CAuKrF"}
@@ -0,0 +1,193 @@
1
+ // Bootstrap — finds .studio/, loads config, creates engine + store + launcher
2
+ // Same pattern as CLI but without terminal output
3
+ import { resolve, join, dirname } from 'node:path';
4
+ import { access, readFile } from 'node:fs/promises';
5
+ import { fileURLToPath } from 'node:url';
6
+ import yaml from 'js-yaml';
7
+ import { SQLiteRunStore, InMemoryRunStore, PgRunStore, } from '@studio-foundation/engine';
8
+ import { createDefaultRegistry, ToolRegistry, loadProjectTools, loadPlugins, loadProjectIntegrations, MCPClient, } from '@studio-foundation/runner';
9
+ import { InProcessLauncher } from './launcher.js';
10
+ import { RunEventBus } from './event-bus.js';
11
+ import { WebhookStore } from './webhook-store.js';
12
+ import { WebhookDispatcher } from './webhook-dispatcher.js';
13
+ import { IntegrationStore } from './integration-store.js';
14
+ import { IntegrationRuntime } from './integration-runtime.js';
15
+ import { HttpApiSpawner } from './spawners/http-api-spawner.js';
16
+ import { resolvePlans } from './plans.js';
17
+ import { UserStore } from './user-store.js';
18
+ import { PgUserStore } from './user-store-pg.js';
19
+ async function findStudioDir(startDir) {
20
+ let current = resolve(startDir);
21
+ while (true) {
22
+ const candidate = join(current, '.studio');
23
+ try {
24
+ await access(candidate);
25
+ return candidate;
26
+ }
27
+ catch {
28
+ // not here, go up
29
+ }
30
+ const parent = dirname(current);
31
+ if (parent === current)
32
+ return null;
33
+ current = parent;
34
+ }
35
+ }
36
+ export async function bootstrap(cwd = process.cwd()) {
37
+ const studioDir = await findStudioDir(cwd);
38
+ if (!studioDir) {
39
+ throw new Error(`No .studio/ directory found from ${cwd}. Run 'studio init' first.`);
40
+ }
41
+ // Load config
42
+ let config = {};
43
+ try {
44
+ const raw = await readFile(join(studioDir, 'config.yaml'), 'utf-8');
45
+ // Resolve env vars
46
+ const resolved = raw.replace(/\$\{([^}]+)\}/g, (_m, v) => process.env[v.trim()] ?? '');
47
+ config = yaml.load(resolved) ?? {};
48
+ }
49
+ catch {
50
+ // No config.yaml — use defaults
51
+ }
52
+ // Read studio version from api/package.json
53
+ const pkgPath = fileURLToPath(new URL('../package.json', import.meta.url));
54
+ const pkgRaw = await readFile(pkgPath, 'utf-8');
55
+ const studioVersion = JSON.parse(pkgRaw).version;
56
+ // Build masked config — provider names only, no API keys
57
+ const maskedConfig = {
58
+ defaults: config.defaults,
59
+ providers: Object.keys(config.providers ?? {}),
60
+ };
61
+ const runsDir = join(studioDir, 'runs');
62
+ const dbType = config.db?.type ?? 'sqlite';
63
+ // WebhookStore and IntegrationStore are always SQLite — they are separate from RunStore
64
+ const dbPath = join(studioDir, 'runs', 'runs.db');
65
+ let store;
66
+ if (dbType === 'postgres') {
67
+ const url = config.db?.url;
68
+ if (!url)
69
+ throw new Error('db.url is required when db.type is postgres');
70
+ store = new PgRunStore(url);
71
+ }
72
+ else if (dbType === 'inmemory') {
73
+ store = new InMemoryRunStore();
74
+ }
75
+ else {
76
+ store = new SQLiteRunStore(dbPath);
77
+ }
78
+ // UserStore — same DB as run store
79
+ let userStore;
80
+ if (dbType === 'postgres') {
81
+ const url = config.db?.url;
82
+ if (!url)
83
+ throw new Error('db.url is required when db.type is postgres');
84
+ userStore = new PgUserStore(url);
85
+ }
86
+ else {
87
+ userStore = new UserStore(dbPath);
88
+ }
89
+ const plans = resolvePlans(config.plans);
90
+ const providerRegistry = createDefaultRegistry({
91
+ openai: config.providers?.openai ? { apiKey: config.providers.openai.apiKey } : undefined,
92
+ anthropic: config.providers?.anthropic ? { apiKey: config.providers.anthropic.apiKey } : undefined,
93
+ ollama: config.providers?.ollama ? { baseUrl: config.providers.ollama.baseUrl } : undefined,
94
+ });
95
+ const toolsDir = join(studioDir, 'tools');
96
+ const loadedPlugins = await loadProjectTools(toolsDir, cwd);
97
+ const toolRegistry = new ToolRegistry();
98
+ for (const plugin of loadedPlugins) {
99
+ toolRegistry.registerPlugin(plugin.name, plugin.tools, plugin.promptSnippet);
100
+ }
101
+ // Load MCP plugins
102
+ const pluginsDir = join(studioDir, 'plugins');
103
+ const pluginManifests = await loadPlugins(pluginsDir);
104
+ const mcpClients = [];
105
+ const pluginSkills = {};
106
+ for (const manifest of pluginManifests) {
107
+ for (const [serverName, serverDef] of Object.entries(manifest.mcpServers)) {
108
+ const client = new MCPClient(manifest.name, serverName, serverDef);
109
+ try {
110
+ await client.start();
111
+ const mcpTools = await client.getTools();
112
+ toolRegistry.registerPlugin(`${manifest.name}-${serverName}`, mcpTools);
113
+ mcpClients.push(client);
114
+ }
115
+ catch {
116
+ // Plugin failed to start — skip silently for API
117
+ }
118
+ }
119
+ if (manifest.skills.length > 0) {
120
+ pluginSkills[manifest.name] = manifest.skills.map((s) => `## Skill: ${s.name}\n\n${s.content}`);
121
+ }
122
+ }
123
+ // Self-referential spawner: allows pipelines to spawn child runs via the API
124
+ // Must match DEFAULT_PORT in index.ts (3700) — was 3000 which collides with common dev servers
125
+ const apiPort = config.api?.port ?? 3700;
126
+ const spawner = new HttpApiSpawner(`http://localhost:${apiPort}`, config.api?.key);
127
+ const engineConfig = {
128
+ configsDir: studioDir,
129
+ providerRegistry,
130
+ toolRegistry,
131
+ db: store,
132
+ pluginSkills,
133
+ spawner,
134
+ maxDepth: 3,
135
+ defaultProvider: config.defaults?.provider,
136
+ defaultModel: config.defaults?.model,
137
+ };
138
+ const bus = new RunEventBus();
139
+ const launcher = new InProcessLauncher(engineConfig, store, runsDir, bus);
140
+ // Derive project name from the directory containing .studio/
141
+ const projectName = studioDir.split('/').slice(-2, -1)[0] ?? 'studio-project';
142
+ const webhookStore = new WebhookStore(dbPath);
143
+ const integrationStore = new IntegrationStore(dbPath);
144
+ const integrationsDir = join(studioDir, 'integrations');
145
+ const loadedIntegrations = await loadProjectIntegrations(integrationsDir);
146
+ const integrationConfigs = (config.integrations ?? {});
147
+ const integrationRuntime = new IntegrationRuntime({
148
+ integrations: loadedIntegrations,
149
+ store: integrationStore,
150
+ launcher,
151
+ configsDir: studioDir,
152
+ projectsDir: config.paths?.projects_dir,
153
+ apiConfig: config.api ?? {},
154
+ integrationConfigs,
155
+ });
156
+ integrationRuntime.setupEventBus(bus);
157
+ const webhookDispatcher = new WebhookDispatcher(webhookStore, projectName);
158
+ bus.subscribeAll((runId, event) => {
159
+ void webhookDispatcher.handleBusEvent(runId, event.type, event.data);
160
+ });
161
+ return {
162
+ store,
163
+ launcher,
164
+ configsDir: studioDir,
165
+ projectsDir: config.paths?.projects_dir,
166
+ projectName,
167
+ apiConfig: config.api ?? {},
168
+ studioVersion,
169
+ maskedConfig,
170
+ webhookStore,
171
+ integrationStore,
172
+ integrationRuntime,
173
+ userStore,
174
+ plans,
175
+ cleanup: async () => {
176
+ for (const client of mcpClients) {
177
+ try {
178
+ await client.close();
179
+ }
180
+ catch { /* ignore */ }
181
+ }
182
+ if ('close' in store && typeof store.close === 'function') {
183
+ await store.close();
184
+ }
185
+ if (userStore && 'close' in userStore && typeof userStore.close === 'function') {
186
+ await userStore.close();
187
+ }
188
+ webhookStore.close();
189
+ integrationStore.close();
190
+ },
191
+ };
192
+ }
193
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export type SseEventType = 'pipeline_start' | 'stage_start' | 'stage_complete' | 'stage_retry' | 'group_start' | 'group_iteration' | 'group_feedback' | 'group_complete' | 'pipeline_complete' | 'pipeline_cancelled' | 'stage_context' | 'tool_call_start' | 'tool_call_complete' | 'done';
2
+ export interface BusEvent {
3
+ type: SseEventType;
4
+ data: unknown;
5
+ }
6
+ export type BusListener = (event: BusEvent) => void;
7
+ export type GlobalBusListener = (runId: string, event: BusEvent) => void;
8
+ export declare class RunEventBus {
9
+ private subs;
10
+ private globalListeners;
11
+ subscribe(runId: string, listener: BusListener): () => void;
12
+ subscribeAll(listener: GlobalBusListener): () => void;
13
+ emit(runId: string, type: SseEventType, data: unknown): void;
14
+ close(runId: string): void;
15
+ }
16
+ //# sourceMappingURL=event-bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../src/event-bus.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GACpB,gBAAgB,GAChB,aAAa,GACb,gBAAgB,GAChB,aAAa,GACb,aAAa,GACb,iBAAiB,GACjB,gBAAgB,GAChB,gBAAgB,GAChB,mBAAmB,GACnB,oBAAoB,GACpB,eAAe,GACf,iBAAiB,GACjB,oBAAoB,GACpB,MAAM,CAAC;AAEX,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AACpD,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAEzE,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAuC;IACnD,OAAO,CAAC,eAAe,CAAgC;IAEvD,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,GAAG,MAAM,IAAI;IAW3D,YAAY,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,IAAI;IAOrD,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI;IAa5D,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;CAI3B"}
@@ -0,0 +1,37 @@
1
+ export class RunEventBus {
2
+ subs = new Map();
3
+ globalListeners = new Set();
4
+ subscribe(runId, listener) {
5
+ if (!this.subs.has(runId)) {
6
+ this.subs.set(runId, new Set());
7
+ }
8
+ this.subs.get(runId).add(listener);
9
+ return () => {
10
+ this.subs.get(runId)?.delete(listener);
11
+ };
12
+ }
13
+ // Global subscription — receives all events across all runs
14
+ subscribeAll(listener) {
15
+ this.globalListeners.add(listener);
16
+ return () => {
17
+ this.globalListeners.delete(listener);
18
+ };
19
+ }
20
+ emit(runId, type, data) {
21
+ const event = { type, data };
22
+ const listeners = this.subs.get(runId);
23
+ if (listeners) {
24
+ for (const listener of listeners) {
25
+ listener(event);
26
+ }
27
+ }
28
+ for (const listener of this.globalListeners) {
29
+ listener(runId, event);
30
+ }
31
+ }
32
+ close(runId) {
33
+ this.emit(runId, 'done', {});
34
+ this.subs.delete(runId);
35
+ }
36
+ }
37
+ //# sourceMappingURL=event-bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../src/event-bus.ts"],"names":[],"mappings":"AAwBA,MAAM,OAAO,WAAW;IACd,IAAI,GAAG,IAAI,GAAG,EAA4B,CAAC;IAC3C,eAAe,GAAG,IAAI,GAAG,EAAqB,CAAC;IAEvD,SAAS,CAAC,KAAa,EAAE,QAAqB;QAC5C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;QAClC,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACpC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QACzC,CAAC,CAAC;IACJ,CAAC;IAED,4DAA4D;IAC5D,YAAY,CAAC,QAA2B;QACtC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACnC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACxC,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,KAAa,EAAE,IAAkB,EAAE,IAAa;QACnD,MAAM,KAAK,GAAa,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACvC,IAAI,SAAS,EAAE,CAAC;YACd,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACjC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;QACD,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAa;QACjB,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ // Standalone entrypoint for @studio/api
3
+ // PM2: pm2 start node_modules/.bin/studio-api --env STUDIO_CWD=/path/to/project
4
+ // systemd: ExecStart=/usr/bin/node /path/to/api/dist/index.js
5
+ import { bootstrap } from './bootstrap.js';
6
+ import { buildServer } from './server.js';
7
+ const DEFAULT_PORT = 3700;
8
+ async function main() {
9
+ const cwd = process.env['STUDIO_CWD'] ?? process.cwd();
10
+ let result;
11
+ try {
12
+ result = await bootstrap(cwd);
13
+ }
14
+ catch (err) {
15
+ console.error('Bootstrap failed:', err instanceof Error ? err.message : String(err));
16
+ process.exit(1);
17
+ }
18
+ const { store, launcher, configsDir, projectName, apiConfig, cleanup, studioVersion, maskedConfig, webhookStore, integrationStore, integrationRuntime, userStore, plans } = result;
19
+ const port = apiConfig.port ?? DEFAULT_PORT;
20
+ let hasUsers = false;
21
+ try {
22
+ // listUsers() is sync on UserStore and async on PgUserStore; await handles both
23
+ const users = await userStore.listUsers();
24
+ hasUsers = users.length > 0;
25
+ }
26
+ catch {
27
+ // DB unavailable at startup — default to open/dev mode
28
+ }
29
+ const server = buildServer({ store, launcher, configsDir, projectName, apiConfig, studioVersion, maskedConfig, webhookStore, integrationStore, integrationRuntime, userStore, plans, hasUsers });
30
+ // Graceful shutdown
31
+ const shutdown = async () => {
32
+ await server.close();
33
+ await cleanup();
34
+ process.exit(0);
35
+ };
36
+ process.on('SIGTERM', () => void shutdown());
37
+ process.on('SIGINT', () => void shutdown());
38
+ try {
39
+ await server.listen({ port, host: '0.0.0.0' });
40
+ console.log(`Studio API listening on port ${port}`);
41
+ if (!apiConfig.key) {
42
+ console.log('Warning: no api.key configured — running without auth');
43
+ }
44
+ }
45
+ catch (err) {
46
+ console.error('Failed to start server:', err);
47
+ await cleanup();
48
+ process.exit(1);
49
+ }
50
+ }
51
+ void main();
52
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,wCAAwC;AACxC,gFAAgF;AAChF,8DAA8D;AAE9D,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,YAAY,GAAG,IAAI,CAAC;AAE1B,KAAK,UAAU,IAAI;IACjB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAEvD,IAAI,MAA6C,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACrF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC;IACnL,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,IAAI,YAAY,CAAC;IAE5C,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,CAAC;QACH,gFAAgF;QAChF,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,SAAS,EAAE,CAAC;QAC1C,QAAQ,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,uDAAuD;IACzD,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAEjM,oBAAoB;IACpB,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,OAAO,EAAE,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;IAC7C,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;IAE5C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAC/C,OAAO,CAAC,GAAG,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;QAC9C,MAAM,OAAO,EAAE,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,KAAK,IAAI,EAAE,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { IntegrationPluginDef } from '@studio-foundation/contracts';
3
+ import type { IntegrationStore } from './integration-store.js';
4
+ import type { RunLauncher } from './launcher.js';
5
+ import type { RunEventBus } from './event-bus.js';
6
+ import type { ApiConfig } from './server.js';
7
+ export interface IntegrationRuntimeDeps {
8
+ integrations: IntegrationPluginDef[];
9
+ store: IntegrationStore;
10
+ launcher: RunLauncher;
11
+ configsDir: string;
12
+ projectsDir?: string;
13
+ apiConfig: ApiConfig;
14
+ integrationConfigs: Record<string, Record<string, unknown>>;
15
+ }
16
+ export declare class IntegrationRuntime {
17
+ private deps;
18
+ constructor(deps: IntegrationRuntimeDeps);
19
+ setupEventBus(bus: RunEventBus): void;
20
+ registerRoutes(fastify: FastifyInstance, prefix: string): void;
21
+ }
22
+ //# sourceMappingURL=integration-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration-runtime.d.ts","sourceRoot":"","sources":["../src/integration-runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAK7C,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,KAAK,EAAE,gBAAgB,CAAC;IACxB,QAAQ,EAAE,WAAW,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,SAAS,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC7D;AAED,qBAAa,kBAAkB;IACjB,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,sBAAsB;IAEhD,aAAa,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI;IAiCrC,cAAc,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CA0H/D"}