@agent-relay/cloud 0.1.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 (269) hide show
  1. package/dist/api/admin.d.ts +8 -0
  2. package/dist/api/admin.d.ts.map +1 -0
  3. package/dist/api/admin.js +225 -0
  4. package/dist/api/admin.js.map +1 -0
  5. package/dist/api/auth.d.ts +20 -0
  6. package/dist/api/auth.d.ts.map +1 -0
  7. package/dist/api/auth.js +136 -0
  8. package/dist/api/auth.js.map +1 -0
  9. package/dist/api/billing.d.ts +7 -0
  10. package/dist/api/billing.d.ts.map +1 -0
  11. package/dist/api/billing.js +564 -0
  12. package/dist/api/billing.js.map +1 -0
  13. package/dist/api/cli-pty-runner.d.ts +53 -0
  14. package/dist/api/cli-pty-runner.d.ts.map +1 -0
  15. package/dist/api/cli-pty-runner.js +193 -0
  16. package/dist/api/cli-pty-runner.js.map +1 -0
  17. package/dist/api/codex-auth-helper.d.ts +21 -0
  18. package/dist/api/codex-auth-helper.d.ts.map +1 -0
  19. package/dist/api/codex-auth-helper.js +327 -0
  20. package/dist/api/codex-auth-helper.js.map +1 -0
  21. package/dist/api/consensus.d.ts +13 -0
  22. package/dist/api/consensus.d.ts.map +1 -0
  23. package/dist/api/consensus.js +261 -0
  24. package/dist/api/consensus.js.map +1 -0
  25. package/dist/api/coordinators.d.ts +8 -0
  26. package/dist/api/coordinators.d.ts.map +1 -0
  27. package/dist/api/coordinators.js +750 -0
  28. package/dist/api/coordinators.js.map +1 -0
  29. package/dist/api/daemons.d.ts +12 -0
  30. package/dist/api/daemons.d.ts.map +1 -0
  31. package/dist/api/daemons.js +535 -0
  32. package/dist/api/daemons.js.map +1 -0
  33. package/dist/api/generic-webhooks.d.ts +8 -0
  34. package/dist/api/generic-webhooks.d.ts.map +1 -0
  35. package/dist/api/generic-webhooks.js +129 -0
  36. package/dist/api/generic-webhooks.js.map +1 -0
  37. package/dist/api/git.d.ts +8 -0
  38. package/dist/api/git.d.ts.map +1 -0
  39. package/dist/api/git.js +269 -0
  40. package/dist/api/git.js.map +1 -0
  41. package/dist/api/github-app.d.ts +11 -0
  42. package/dist/api/github-app.d.ts.map +1 -0
  43. package/dist/api/github-app.js +223 -0
  44. package/dist/api/github-app.js.map +1 -0
  45. package/dist/api/middleware/planLimits.d.ts +43 -0
  46. package/dist/api/middleware/planLimits.d.ts.map +1 -0
  47. package/dist/api/middleware/planLimits.js +202 -0
  48. package/dist/api/middleware/planLimits.js.map +1 -0
  49. package/dist/api/monitoring.d.ts +11 -0
  50. package/dist/api/monitoring.d.ts.map +1 -0
  51. package/dist/api/monitoring.js +578 -0
  52. package/dist/api/monitoring.js.map +1 -0
  53. package/dist/api/nango-auth.d.ts +9 -0
  54. package/dist/api/nango-auth.d.ts.map +1 -0
  55. package/dist/api/nango-auth.js +674 -0
  56. package/dist/api/nango-auth.js.map +1 -0
  57. package/dist/api/onboarding.d.ts +15 -0
  58. package/dist/api/onboarding.d.ts.map +1 -0
  59. package/dist/api/onboarding.js +679 -0
  60. package/dist/api/onboarding.js.map +1 -0
  61. package/dist/api/policy.d.ts +8 -0
  62. package/dist/api/policy.d.ts.map +1 -0
  63. package/dist/api/policy.js +229 -0
  64. package/dist/api/policy.js.map +1 -0
  65. package/dist/api/provider-env.d.ts +14 -0
  66. package/dist/api/provider-env.d.ts.map +1 -0
  67. package/dist/api/provider-env.js +75 -0
  68. package/dist/api/provider-env.js.map +1 -0
  69. package/dist/api/providers.d.ts +7 -0
  70. package/dist/api/providers.d.ts.map +1 -0
  71. package/dist/api/providers.js +564 -0
  72. package/dist/api/providers.js.map +1 -0
  73. package/dist/api/repos.d.ts +8 -0
  74. package/dist/api/repos.d.ts.map +1 -0
  75. package/dist/api/repos.js +577 -0
  76. package/dist/api/repos.js.map +1 -0
  77. package/dist/api/sessions.d.ts +11 -0
  78. package/dist/api/sessions.d.ts.map +1 -0
  79. package/dist/api/sessions.js +302 -0
  80. package/dist/api/sessions.js.map +1 -0
  81. package/dist/api/teams.d.ts +7 -0
  82. package/dist/api/teams.d.ts.map +1 -0
  83. package/dist/api/teams.js +281 -0
  84. package/dist/api/teams.js.map +1 -0
  85. package/dist/api/test-helpers.d.ts +10 -0
  86. package/dist/api/test-helpers.d.ts.map +1 -0
  87. package/dist/api/test-helpers.js +745 -0
  88. package/dist/api/test-helpers.js.map +1 -0
  89. package/dist/api/usage.d.ts +7 -0
  90. package/dist/api/usage.d.ts.map +1 -0
  91. package/dist/api/usage.js +111 -0
  92. package/dist/api/usage.js.map +1 -0
  93. package/dist/api/webhooks.d.ts +8 -0
  94. package/dist/api/webhooks.d.ts.map +1 -0
  95. package/dist/api/webhooks.js +645 -0
  96. package/dist/api/webhooks.js.map +1 -0
  97. package/dist/api/workspaces.d.ts +25 -0
  98. package/dist/api/workspaces.d.ts.map +1 -0
  99. package/dist/api/workspaces.js +1799 -0
  100. package/dist/api/workspaces.js.map +1 -0
  101. package/dist/billing/index.d.ts +9 -0
  102. package/dist/billing/index.d.ts.map +1 -0
  103. package/dist/billing/index.js +9 -0
  104. package/dist/billing/index.js.map +1 -0
  105. package/dist/billing/plans.d.ts +39 -0
  106. package/dist/billing/plans.d.ts.map +1 -0
  107. package/dist/billing/plans.js +245 -0
  108. package/dist/billing/plans.js.map +1 -0
  109. package/dist/billing/service.d.ts +80 -0
  110. package/dist/billing/service.d.ts.map +1 -0
  111. package/dist/billing/service.js +388 -0
  112. package/dist/billing/service.js.map +1 -0
  113. package/dist/billing/types.d.ts +141 -0
  114. package/dist/billing/types.d.ts.map +1 -0
  115. package/dist/billing/types.js +7 -0
  116. package/dist/billing/types.js.map +1 -0
  117. package/dist/config.d.ts +5 -0
  118. package/dist/config.d.ts.map +1 -0
  119. package/dist/config.js +5 -0
  120. package/dist/config.js.map +1 -0
  121. package/dist/db/bulk-ingest.d.ts +89 -0
  122. package/dist/db/bulk-ingest.d.ts.map +1 -0
  123. package/dist/db/bulk-ingest.js +268 -0
  124. package/dist/db/bulk-ingest.js.map +1 -0
  125. package/dist/db/drizzle.d.ts +256 -0
  126. package/dist/db/drizzle.d.ts.map +1 -0
  127. package/dist/db/drizzle.js +1286 -0
  128. package/dist/db/drizzle.js.map +1 -0
  129. package/dist/db/index.d.ts +55 -0
  130. package/dist/db/index.d.ts.map +1 -0
  131. package/dist/db/index.js +68 -0
  132. package/dist/db/index.js.map +1 -0
  133. package/dist/db/schema.d.ts +4873 -0
  134. package/dist/db/schema.d.ts.map +1 -0
  135. package/dist/db/schema.js +620 -0
  136. package/dist/db/schema.js.map +1 -0
  137. package/dist/index.d.ts +11 -0
  138. package/dist/index.d.ts.map +1 -0
  139. package/dist/index.js +38 -0
  140. package/dist/index.js.map +1 -0
  141. package/dist/provisioner/index.d.ts +207 -0
  142. package/dist/provisioner/index.d.ts.map +1 -0
  143. package/dist/provisioner/index.js +2114 -0
  144. package/dist/provisioner/index.js.map +1 -0
  145. package/dist/server.d.ts +17 -0
  146. package/dist/server.d.ts.map +1 -0
  147. package/dist/server.js +1924 -0
  148. package/dist/server.js.map +1 -0
  149. package/dist/services/auto-scaler.d.ts +152 -0
  150. package/dist/services/auto-scaler.d.ts.map +1 -0
  151. package/dist/services/auto-scaler.js +439 -0
  152. package/dist/services/auto-scaler.js.map +1 -0
  153. package/dist/services/capacity-manager.d.ts +148 -0
  154. package/dist/services/capacity-manager.d.ts.map +1 -0
  155. package/dist/services/capacity-manager.js +449 -0
  156. package/dist/services/capacity-manager.js.map +1 -0
  157. package/dist/services/ci-agent-spawner.d.ts +49 -0
  158. package/dist/services/ci-agent-spawner.d.ts.map +1 -0
  159. package/dist/services/ci-agent-spawner.js +373 -0
  160. package/dist/services/ci-agent-spawner.js.map +1 -0
  161. package/dist/services/cloud-message-bus.d.ts +28 -0
  162. package/dist/services/cloud-message-bus.d.ts.map +1 -0
  163. package/dist/services/cloud-message-bus.js +19 -0
  164. package/dist/services/cloud-message-bus.js.map +1 -0
  165. package/dist/services/compute-enforcement.d.ts +57 -0
  166. package/dist/services/compute-enforcement.d.ts.map +1 -0
  167. package/dist/services/compute-enforcement.js +175 -0
  168. package/dist/services/compute-enforcement.js.map +1 -0
  169. package/dist/services/coordinator.d.ts +62 -0
  170. package/dist/services/coordinator.d.ts.map +1 -0
  171. package/dist/services/coordinator.js +389 -0
  172. package/dist/services/coordinator.js.map +1 -0
  173. package/dist/services/index.d.ts +17 -0
  174. package/dist/services/index.d.ts.map +1 -0
  175. package/dist/services/index.js +25 -0
  176. package/dist/services/index.js.map +1 -0
  177. package/dist/services/intro-expiration.d.ts +60 -0
  178. package/dist/services/intro-expiration.d.ts.map +1 -0
  179. package/dist/services/intro-expiration.js +252 -0
  180. package/dist/services/intro-expiration.js.map +1 -0
  181. package/dist/services/mention-handler.d.ts +65 -0
  182. package/dist/services/mention-handler.d.ts.map +1 -0
  183. package/dist/services/mention-handler.js +405 -0
  184. package/dist/services/mention-handler.js.map +1 -0
  185. package/dist/services/nango.d.ts +201 -0
  186. package/dist/services/nango.d.ts.map +1 -0
  187. package/dist/services/nango.js +392 -0
  188. package/dist/services/nango.js.map +1 -0
  189. package/dist/services/persistence.d.ts +131 -0
  190. package/dist/services/persistence.d.ts.map +1 -0
  191. package/dist/services/persistence.js +200 -0
  192. package/dist/services/persistence.js.map +1 -0
  193. package/dist/services/planLimits.d.ts +147 -0
  194. package/dist/services/planLimits.d.ts.map +1 -0
  195. package/dist/services/planLimits.js +335 -0
  196. package/dist/services/planLimits.js.map +1 -0
  197. package/dist/services/presence-registry.d.ts +56 -0
  198. package/dist/services/presence-registry.d.ts.map +1 -0
  199. package/dist/services/presence-registry.js +91 -0
  200. package/dist/services/presence-registry.js.map +1 -0
  201. package/dist/services/scaling-orchestrator.d.ts +159 -0
  202. package/dist/services/scaling-orchestrator.d.ts.map +1 -0
  203. package/dist/services/scaling-orchestrator.js +502 -0
  204. package/dist/services/scaling-orchestrator.js.map +1 -0
  205. package/dist/services/scaling-policy.d.ts +121 -0
  206. package/dist/services/scaling-policy.d.ts.map +1 -0
  207. package/dist/services/scaling-policy.js +415 -0
  208. package/dist/services/scaling-policy.js.map +1 -0
  209. package/dist/services/ssh-security.d.ts +31 -0
  210. package/dist/services/ssh-security.d.ts.map +1 -0
  211. package/dist/services/ssh-security.js +63 -0
  212. package/dist/services/ssh-security.js.map +1 -0
  213. package/dist/services/workspace-keepalive.d.ts +76 -0
  214. package/dist/services/workspace-keepalive.d.ts.map +1 -0
  215. package/dist/services/workspace-keepalive.js +234 -0
  216. package/dist/services/workspace-keepalive.js.map +1 -0
  217. package/dist/shims/consensus.d.ts +23 -0
  218. package/dist/shims/consensus.d.ts.map +1 -0
  219. package/dist/shims/consensus.js +5 -0
  220. package/dist/shims/consensus.js.map +1 -0
  221. package/dist/webhooks/index.d.ts +24 -0
  222. package/dist/webhooks/index.d.ts.map +1 -0
  223. package/dist/webhooks/index.js +29 -0
  224. package/dist/webhooks/index.js.map +1 -0
  225. package/dist/webhooks/parsers/github.d.ts +8 -0
  226. package/dist/webhooks/parsers/github.d.ts.map +1 -0
  227. package/dist/webhooks/parsers/github.js +234 -0
  228. package/dist/webhooks/parsers/github.js.map +1 -0
  229. package/dist/webhooks/parsers/index.d.ts +23 -0
  230. package/dist/webhooks/parsers/index.d.ts.map +1 -0
  231. package/dist/webhooks/parsers/index.js +30 -0
  232. package/dist/webhooks/parsers/index.js.map +1 -0
  233. package/dist/webhooks/parsers/linear.d.ts +9 -0
  234. package/dist/webhooks/parsers/linear.d.ts.map +1 -0
  235. package/dist/webhooks/parsers/linear.js +258 -0
  236. package/dist/webhooks/parsers/linear.js.map +1 -0
  237. package/dist/webhooks/parsers/slack.d.ts +9 -0
  238. package/dist/webhooks/parsers/slack.d.ts.map +1 -0
  239. package/dist/webhooks/parsers/slack.js +214 -0
  240. package/dist/webhooks/parsers/slack.js.map +1 -0
  241. package/dist/webhooks/responders/github.d.ts +8 -0
  242. package/dist/webhooks/responders/github.d.ts.map +1 -0
  243. package/dist/webhooks/responders/github.js +73 -0
  244. package/dist/webhooks/responders/github.js.map +1 -0
  245. package/dist/webhooks/responders/index.d.ts +23 -0
  246. package/dist/webhooks/responders/index.d.ts.map +1 -0
  247. package/dist/webhooks/responders/index.js +30 -0
  248. package/dist/webhooks/responders/index.js.map +1 -0
  249. package/dist/webhooks/responders/linear.d.ts +9 -0
  250. package/dist/webhooks/responders/linear.d.ts.map +1 -0
  251. package/dist/webhooks/responders/linear.js +149 -0
  252. package/dist/webhooks/responders/linear.js.map +1 -0
  253. package/dist/webhooks/responders/slack.d.ts +20 -0
  254. package/dist/webhooks/responders/slack.d.ts.map +1 -0
  255. package/dist/webhooks/responders/slack.js +178 -0
  256. package/dist/webhooks/responders/slack.js.map +1 -0
  257. package/dist/webhooks/router.d.ts +25 -0
  258. package/dist/webhooks/router.d.ts.map +1 -0
  259. package/dist/webhooks/router.js +504 -0
  260. package/dist/webhooks/router.js.map +1 -0
  261. package/dist/webhooks/rules-engine.d.ts +24 -0
  262. package/dist/webhooks/rules-engine.d.ts.map +1 -0
  263. package/dist/webhooks/rules-engine.js +287 -0
  264. package/dist/webhooks/rules-engine.js.map +1 -0
  265. package/dist/webhooks/types.d.ts +186 -0
  266. package/dist/webhooks/types.d.ts.map +1 -0
  267. package/dist/webhooks/types.js +8 -0
  268. package/dist/webhooks/types.js.map +1 -0
  269. package/package.json +55 -0
@@ -0,0 +1,745 @@
1
+ /**
2
+ * Test Helper API Routes
3
+ *
4
+ * These endpoints are ONLY available in test/development mode.
5
+ * They allow integration tests to create users and daemons without OAuth.
6
+ *
7
+ * IMPORTANT: These routes are disabled in production (NODE_ENV=production).
8
+ */
9
+ import { Router } from 'express';
10
+ import { randomUUID, createHash, randomBytes } from 'crypto';
11
+ import { getDb } from '../db/drizzle.js';
12
+ import { users, linkedDaemons, workspaces, repositories } from '../db/schema.js';
13
+ import { getProvisioner } from '../provisioner/index.js';
14
+ import { db } from '../db/index.js';
15
+ import { nangoService } from '../services/nango.js';
16
+ export const testHelpersRouter = Router();
17
+ // Only enable in test/development mode
18
+ const isTestMode = process.env.NODE_ENV !== 'production';
19
+ if (!isTestMode) {
20
+ console.warn('[test-helpers] Test helper routes are disabled in production');
21
+ }
22
+ /**
23
+ * POST /api/test/create-user
24
+ * Creates a test user without OAuth
25
+ */
26
+ testHelpersRouter.post('/create-user', async (req, res) => {
27
+ if (!isTestMode) {
28
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
29
+ }
30
+ try {
31
+ const { email, name } = req.body;
32
+ const db = getDb();
33
+ const testId = `test-${randomUUID()}`;
34
+ // Create user with required GitHub fields
35
+ const [user] = await db.insert(users).values({
36
+ email: email || `${testId}@test.local`,
37
+ githubId: testId,
38
+ githubUsername: name || 'test-user',
39
+ avatarUrl: null,
40
+ }).returning();
41
+ // Create session
42
+ const sessionId = randomUUID();
43
+ req.session.userId = user.id;
44
+ // Get session cookie (simplified for testing)
45
+ const sessionCookie = `connect.sid=s%3A${sessionId}`;
46
+ res.json({
47
+ userId: user.id,
48
+ email: user.email,
49
+ sessionCookie,
50
+ });
51
+ }
52
+ catch (error) {
53
+ console.error('Error creating test user:', error);
54
+ res.status(500).json({ error: 'Failed to create test user' });
55
+ }
56
+ });
57
+ /**
58
+ * POST /api/test/create-daemon
59
+ * Creates a test daemon with API key
60
+ */
61
+ testHelpersRouter.post('/create-daemon', async (req, res) => {
62
+ if (!isTestMode) {
63
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
64
+ }
65
+ try {
66
+ const { name, machineId } = req.body;
67
+ if (!name) {
68
+ return res.status(400).json({ error: 'name is required' });
69
+ }
70
+ const db = getDb();
71
+ // First, ensure we have a test user to associate with the daemon
72
+ let [testUser] = await db.select().from(users).limit(1);
73
+ if (!testUser) {
74
+ // Create a test user if none exists
75
+ const testId = `test-system-${randomUUID()}`;
76
+ [testUser] = await db.insert(users).values({
77
+ email: `${testId}@test.local`,
78
+ githubId: testId,
79
+ githubUsername: 'test-system-user',
80
+ avatarUrl: null,
81
+ }).returning();
82
+ }
83
+ // Generate API key
84
+ const apiKey = `ar_live_${randomBytes(32).toString('hex')}`;
85
+ const apiKeyHash = createHash('sha256').update(apiKey).digest('hex');
86
+ // Create daemon - only include fields that exist in schema
87
+ const [daemon] = await db.insert(linkedDaemons).values({
88
+ userId: testUser.id,
89
+ name,
90
+ machineId: machineId || randomUUID(),
91
+ apiKeyHash,
92
+ status: 'online',
93
+ metadata: {
94
+ hostname: 'test-host',
95
+ platform: 'linux',
96
+ version: '1.0.0-test',
97
+ },
98
+ }).returning();
99
+ res.json({
100
+ daemonId: daemon.id,
101
+ apiKey,
102
+ name: daemon.name,
103
+ machineId: daemon.machineId,
104
+ });
105
+ }
106
+ catch (error) {
107
+ console.error('Error creating test daemon:', error);
108
+ res.status(500).json({ error: 'Failed to create test daemon' });
109
+ }
110
+ });
111
+ /**
112
+ * POST /api/test/create-workspace
113
+ * Creates a test workspace for integration tests with optional linked repository
114
+ */
115
+ testHelpersRouter.post('/create-workspace', async (req, res) => {
116
+ if (!isTestMode) {
117
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
118
+ }
119
+ try {
120
+ const { name, repoFullName, userId: providedUserId } = req.body;
121
+ const db = getDb();
122
+ // Use provided userId, session userId, or create a test user
123
+ let targetUserId = providedUserId || req.session.userId;
124
+ if (!targetUserId) {
125
+ // Create a test user if none exists
126
+ const testId = `test-ws-${randomUUID()}`;
127
+ const [newUser] = await db.insert(users).values({
128
+ email: `${testId}@test.local`,
129
+ githubId: testId,
130
+ githubUsername: 'workspace-test-user',
131
+ avatarUrl: null,
132
+ plan: 'free',
133
+ }).returning();
134
+ targetUserId = newUser.id;
135
+ }
136
+ const targetRepoFullName = repoFullName || `test-org/test-repo-${Date.now()}`;
137
+ // Create workspace
138
+ const [workspace] = await db.insert(workspaces).values({
139
+ userId: targetUserId,
140
+ name: name || `Test Workspace ${Date.now()}`,
141
+ status: 'running',
142
+ publicUrl: 'http://localhost:3889',
143
+ computeProvider: 'docker',
144
+ computeId: `test-${randomUUID().slice(0, 8)}`,
145
+ config: {
146
+ providers: ['anthropic'],
147
+ repositories: [targetRepoFullName],
148
+ supervisorEnabled: true,
149
+ maxAgents: 10,
150
+ },
151
+ }).returning();
152
+ // Create a linked repository for workspace lookup via repoFullName
153
+ const [repo] = await db.insert(repositories).values({
154
+ userId: targetUserId,
155
+ workspaceId: workspace.id,
156
+ githubId: Math.floor(Math.random() * 1000000),
157
+ githubFullName: targetRepoFullName,
158
+ isPrivate: false,
159
+ defaultBranch: 'main',
160
+ syncStatus: 'synced',
161
+ nangoConnectionId: `mock-${randomUUID().slice(0, 8)}`,
162
+ lastSyncedAt: new Date(),
163
+ }).returning();
164
+ res.json({
165
+ workspaceId: workspace.id,
166
+ name: workspace.name,
167
+ repoFullName: repo.githubFullName,
168
+ repoId: repo.id,
169
+ userId: targetUserId,
170
+ });
171
+ }
172
+ catch (error) {
173
+ console.error('Error creating test workspace:', error);
174
+ res.status(500).json({ error: 'Failed to create test workspace' });
175
+ }
176
+ });
177
+ /**
178
+ * POST /api/test/create-daemon-with-workspace
179
+ * Creates a test daemon linked to a workspace
180
+ */
181
+ testHelpersRouter.post('/create-daemon-with-workspace', async (req, res) => {
182
+ if (!isTestMode) {
183
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
184
+ }
185
+ try {
186
+ const { name, machineId, workspaceId, userId: providedUserId } = req.body;
187
+ if (!name) {
188
+ return res.status(400).json({ error: 'name is required' });
189
+ }
190
+ const db = getDb();
191
+ // Get or create test user
192
+ let targetUserId = providedUserId;
193
+ if (!targetUserId) {
194
+ const existingUsers = await db.select().from(users).limit(1);
195
+ if (existingUsers.length > 0) {
196
+ targetUserId = existingUsers[0].id;
197
+ }
198
+ else {
199
+ const testId = `test-daemon-${randomUUID()}`;
200
+ const [newUser] = await db.insert(users).values({
201
+ email: `${testId}@test.local`,
202
+ githubId: testId,
203
+ githubUsername: 'daemon-test-user',
204
+ avatarUrl: null,
205
+ plan: 'free',
206
+ }).returning();
207
+ targetUserId = newUser.id;
208
+ }
209
+ }
210
+ // Generate API key
211
+ const apiKey = `ar_live_${randomBytes(32).toString('hex')}`;
212
+ const apiKeyHash = createHash('sha256').update(apiKey).digest('hex');
213
+ // Create daemon with optional workspace link
214
+ const [daemon] = await db.insert(linkedDaemons).values({
215
+ userId: targetUserId,
216
+ workspaceId: workspaceId || null,
217
+ name,
218
+ machineId: machineId || randomUUID(),
219
+ apiKeyHash,
220
+ status: 'online',
221
+ metadata: {
222
+ hostname: 'test-host',
223
+ platform: 'linux',
224
+ version: '1.0.0-test',
225
+ },
226
+ }).returning();
227
+ res.json({
228
+ daemonId: daemon.id,
229
+ apiKey,
230
+ name: daemon.name,
231
+ machineId: daemon.machineId,
232
+ workspaceId: daemon.workspaceId,
233
+ userId: targetUserId,
234
+ });
235
+ }
236
+ catch (error) {
237
+ console.error('Error creating test daemon:', error);
238
+ res.status(500).json({ error: 'Failed to create test daemon' });
239
+ }
240
+ });
241
+ /**
242
+ * DELETE /api/test/cleanup
243
+ * Cleans up test data
244
+ */
245
+ testHelpersRouter.delete('/cleanup', async (req, res) => {
246
+ if (!isTestMode) {
247
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
248
+ }
249
+ try {
250
+ const _db = getDb();
251
+ // Delete test data (users with test- prefix in githubId)
252
+ // Note: This cascades to linked daemons due to FK constraints
253
+ res.json({ success: true, message: 'Test data cleaned up' });
254
+ }
255
+ catch (error) {
256
+ console.error('Error cleaning up test data:', error);
257
+ res.status(500).json({ error: 'Failed to cleanup test data' });
258
+ }
259
+ });
260
+ /**
261
+ * GET /api/test/status
262
+ * Returns test mode status
263
+ */
264
+ testHelpersRouter.get('/status', (req, res) => {
265
+ res.json({
266
+ testMode: isTestMode,
267
+ nodeEnv: process.env.NODE_ENV,
268
+ timestamp: new Date().toISOString(),
269
+ });
270
+ });
271
+ /**
272
+ * POST /api/test/create-mock-workspace
273
+ * Creates a mock workspace pointing to a local dashboard server
274
+ *
275
+ * Use this to test the cloud flow locally without real provisioning.
276
+ * The workspace will have publicUrl pointing to localhost:3889.
277
+ */
278
+ testHelpersRouter.post('/create-mock-workspace', async (req, res) => {
279
+ if (!isTestMode) {
280
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
281
+ }
282
+ try {
283
+ const { name, publicUrl } = req.body;
284
+ const userId = req.session.userId;
285
+ if (!userId) {
286
+ return res.status(401).json({ error: 'Must be logged in. Use /api/test/create-user first or log in via OAuth.' });
287
+ }
288
+ const db = getDb();
289
+ // Create a mock workspace with local publicUrl
290
+ const [workspace] = await db.insert(workspaces).values({
291
+ userId,
292
+ name: name || 'Local Test Workspace',
293
+ status: 'running',
294
+ publicUrl: publicUrl || 'http://localhost:3889',
295
+ computeProvider: 'docker',
296
+ computeId: `mock-${randomUUID().slice(0, 8)}`,
297
+ config: {
298
+ providers: ['anthropic'],
299
+ repositories: [],
300
+ supervisorEnabled: true,
301
+ maxAgents: 10,
302
+ },
303
+ }).returning();
304
+ res.json({
305
+ workspaceId: workspace.id,
306
+ name: workspace.name,
307
+ status: workspace.status,
308
+ publicUrl: workspace.publicUrl,
309
+ message: 'Mock workspace created. Start agent-relay locally and navigate to /app.',
310
+ });
311
+ }
312
+ catch (error) {
313
+ console.error('Error creating mock workspace:', error);
314
+ res.status(500).json({ error: 'Failed to create mock workspace' });
315
+ }
316
+ });
317
+ /**
318
+ * POST /api/test/create-mock-repo
319
+ * Creates a mock repository for the current user
320
+ *
321
+ * Use this to test the cloud flow without connecting real GitHub repos.
322
+ */
323
+ testHelpersRouter.post('/create-mock-repo', async (req, res) => {
324
+ if (!isTestMode) {
325
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
326
+ }
327
+ try {
328
+ const { fullName, isPrivate } = req.body;
329
+ const userId = req.session.userId;
330
+ if (!userId) {
331
+ return res.status(401).json({ error: 'Must be logged in. Use /api/test/create-user first or log in via OAuth.' });
332
+ }
333
+ if (!fullName) {
334
+ return res.status(400).json({ error: 'fullName is required (e.g., "owner/repo")' });
335
+ }
336
+ const db = getDb();
337
+ // Create a mock repository
338
+ const [repo] = await db.insert(repositories).values({
339
+ userId,
340
+ githubId: Math.floor(Math.random() * 1000000),
341
+ githubFullName: fullName,
342
+ isPrivate: isPrivate ?? false,
343
+ defaultBranch: 'main',
344
+ syncStatus: 'synced',
345
+ nangoConnectionId: `mock-connection-${randomUUID().slice(0, 8)}`,
346
+ lastSyncedAt: new Date(),
347
+ }).returning();
348
+ res.json({
349
+ repoId: repo.id,
350
+ fullName: repo.githubFullName,
351
+ isPrivate: repo.isPrivate,
352
+ message: 'Mock repository created.',
353
+ });
354
+ }
355
+ catch (error) {
356
+ console.error('Error creating mock repo:', error);
357
+ res.status(500).json({ error: 'Failed to create mock repo' });
358
+ }
359
+ });
360
+ /**
361
+ * GET /api/test/auto-login
362
+ * Browser-friendly auto-login - visit this URL to login and redirect
363
+ * Usage: /api/test/auto-login?redirect=/providers/setup/claude?workspace=xxx
364
+ */
365
+ testHelpersRouter.get('/auto-login', async (req, res) => {
366
+ if (!isTestMode) {
367
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
368
+ }
369
+ try {
370
+ const db = getDb();
371
+ const redirect = req.query.redirect || '/app';
372
+ // Find or create test user
373
+ let user;
374
+ const existingUsers = await db.select().from(users).limit(1);
375
+ if (existingUsers.length > 0) {
376
+ user = existingUsers[0];
377
+ }
378
+ else {
379
+ const testId = `test-${randomUUID()}`;
380
+ const [newUser] = await db.insert(users).values({
381
+ email: `${testId}@test.local`,
382
+ githubId: testId,
383
+ githubUsername: 'test-user',
384
+ avatarUrl: null,
385
+ plan: 'free',
386
+ }).returning();
387
+ user = newUser;
388
+ }
389
+ // Set session and CSRF token
390
+ req.session.userId = user.id;
391
+ req.session.csrfToken = randomUUID();
392
+ // Redirect to requested page
393
+ res.redirect(redirect);
394
+ }
395
+ catch (error) {
396
+ console.error('Error in auto-login:', error);
397
+ res.status(500).json({ error: 'Failed to auto-login' });
398
+ }
399
+ });
400
+ /**
401
+ * POST /api/test/login-as
402
+ * Quick login for testing - creates session for existing or new test user
403
+ */
404
+ testHelpersRouter.post('/login-as', async (req, res) => {
405
+ if (!isTestMode) {
406
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
407
+ }
408
+ try {
409
+ const { username } = req.body;
410
+ const db = getDb();
411
+ // Find or create user
412
+ let user;
413
+ const existingUsers = await db.select().from(users).limit(1);
414
+ if (existingUsers.length > 0 && !username) {
415
+ user = existingUsers[0];
416
+ }
417
+ else {
418
+ const testId = `test-${randomUUID()}`;
419
+ const [newUser] = await db.insert(users).values({
420
+ email: `${username || testId}@test.local`,
421
+ githubId: testId,
422
+ githubUsername: username || 'test-user',
423
+ avatarUrl: null,
424
+ plan: 'free',
425
+ }).returning();
426
+ user = newUser;
427
+ }
428
+ // Set session
429
+ req.session.userId = user.id;
430
+ res.json({
431
+ success: true,
432
+ userId: user.id,
433
+ username: user.githubUsername,
434
+ message: 'Logged in. You can now access /app and other authenticated routes.',
435
+ });
436
+ }
437
+ catch (error) {
438
+ console.error('Error in login-as:', error);
439
+ res.status(500).json({ error: 'Failed to login' });
440
+ }
441
+ });
442
+ /**
443
+ * GET /api/test/setup-local-cloud
444
+ * One-shot setup: creates user, mock repo, and mock workspace
445
+ *
446
+ * After calling this, start agent-relay locally and go to /app
447
+ */
448
+ testHelpersRouter.post('/setup-local-cloud', async (req, res) => {
449
+ if (!isTestMode) {
450
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
451
+ }
452
+ try {
453
+ const { repoName, workspaceName } = req.body;
454
+ const db = getDb();
455
+ // 1. Create or get test user
456
+ const testId = `test-${randomUUID().slice(0, 8)}`;
457
+ const [user] = await db.insert(users).values({
458
+ email: `${testId}@test.local`,
459
+ githubId: testId,
460
+ githubUsername: 'local-tester',
461
+ avatarUrl: null,
462
+ plan: 'free',
463
+ }).returning();
464
+ // Set session
465
+ req.session.userId = user.id;
466
+ // 2. Create mock repository
467
+ const [repo] = await db.insert(repositories).values({
468
+ userId: user.id,
469
+ githubId: Math.floor(Math.random() * 1000000),
470
+ githubFullName: repoName || 'test-org/test-repo',
471
+ isPrivate: false,
472
+ defaultBranch: 'main',
473
+ syncStatus: 'synced',
474
+ nangoConnectionId: `mock-${randomUUID().slice(0, 8)}`,
475
+ lastSyncedAt: new Date(),
476
+ }).returning();
477
+ // 3. Create mock workspace pointing to local dashboard
478
+ const [workspace] = await db.insert(workspaces).values({
479
+ userId: user.id,
480
+ name: workspaceName || 'Local Development',
481
+ status: 'running',
482
+ publicUrl: 'http://localhost:3889',
483
+ computeProvider: 'docker',
484
+ computeId: `mock-${randomUUID().slice(0, 8)}`,
485
+ config: {
486
+ providers: ['anthropic'],
487
+ repositories: [repo.githubFullName],
488
+ supervisorEnabled: true,
489
+ maxAgents: 10,
490
+ },
491
+ }).returning();
492
+ res.json({
493
+ success: true,
494
+ user: {
495
+ id: user.id,
496
+ username: user.githubUsername,
497
+ },
498
+ repo: {
499
+ id: repo.id,
500
+ fullName: repo.githubFullName,
501
+ },
502
+ workspace: {
503
+ id: workspace.id,
504
+ name: workspace.name,
505
+ publicUrl: workspace.publicUrl,
506
+ },
507
+ instructions: [
508
+ '1. Start agent-relay daemon: npm run dev (or agent-relay up)',
509
+ '2. Go to http://localhost:4567/app',
510
+ '3. The app should auto-connect to the local workspace',
511
+ '4. The WebSocket will connect to ws://localhost:3889/ws',
512
+ ],
513
+ });
514
+ }
515
+ catch (error) {
516
+ console.error('Error in setup-local-cloud:', error);
517
+ res.status(500).json({ error: 'Failed to setup local cloud' });
518
+ }
519
+ });
520
+ /**
521
+ * POST /api/test/provision-real-workspace
522
+ * Provision a REAL Docker container using your Nango GitHub App connection.
523
+ *
524
+ * This tests the full flow including:
525
+ * - Fetching GitHub App token from Nango
526
+ * - Spinning up a Docker container
527
+ * - Cloning your actual repositories
528
+ *
529
+ * Prerequisites:
530
+ * - Must be logged in (via real OAuth or /api/test/login-as)
531
+ * - Must have connected repos via /connect-repos (real Nango GitHub App OAuth)
532
+ * - Docker must be running locally
533
+ * - COMPUTE_PROVIDER must be 'docker' (default for dev)
534
+ */
535
+ testHelpersRouter.post('/provision-real-workspace', async (req, res) => {
536
+ if (!isTestMode) {
537
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
538
+ }
539
+ const userId = req.session.userId;
540
+ if (!userId) {
541
+ return res.status(401).json({
542
+ error: 'Must be logged in. Use real OAuth or /api/test/login-as first.',
543
+ });
544
+ }
545
+ try {
546
+ const { name, repositoryFullName, providers, githubToken } = req.body;
547
+ // Get user's connected repositories
548
+ const userRepos = await db.repositories.findByUserId(userId);
549
+ const reposWithNango = userRepos.filter(r => r.nangoConnectionId);
550
+ if (reposWithNango.length === 0) {
551
+ return res.status(400).json({
552
+ error: 'No repositories with Nango connection found. Complete /connect-repos first with real GitHub OAuth.',
553
+ hint: 'Go to http://localhost:4567/connect-repos and connect your GitHub App, or pass githubToken directly',
554
+ });
555
+ }
556
+ // Determine which repo to use
557
+ let targetRepo = reposWithNango[0];
558
+ if (repositoryFullName) {
559
+ const found = reposWithNango.find(r => r.githubFullName === repositoryFullName);
560
+ if (!found) {
561
+ return res.status(400).json({
562
+ error: `Repository ${repositoryFullName} not found or not connected via Nango`,
563
+ availableRepos: reposWithNango.map(r => r.githubFullName),
564
+ });
565
+ }
566
+ targetRepo = found;
567
+ }
568
+ // Use the real provisioner (Docker in dev mode)
569
+ const provisioner = getProvisioner();
570
+ const result = await provisioner.provision({
571
+ userId,
572
+ name: name || `Test Workspace - ${targetRepo.githubFullName}`,
573
+ providers: providers || ['anthropic'], // Default to anthropic if not specified
574
+ repositories: [targetRepo.githubFullName],
575
+ supervisorEnabled: true,
576
+ maxAgents: 10,
577
+ // Allow passing GitHub token directly for local testing
578
+ githubToken: githubToken || undefined,
579
+ });
580
+ if (result.status === 'error') {
581
+ return res.status(500).json({
582
+ error: 'Provisioning failed',
583
+ details: result.error,
584
+ });
585
+ }
586
+ res.json({
587
+ success: true,
588
+ workspace: {
589
+ id: result.workspaceId,
590
+ status: result.status,
591
+ publicUrl: result.publicUrl,
592
+ },
593
+ repository: targetRepo.githubFullName,
594
+ instructions: [
595
+ `1. Workspace is running at ${result.publicUrl}`,
596
+ `2. Repository ${targetRepo.githubFullName} should be cloned`,
597
+ `3. Go to http://localhost:4567/app to connect`,
598
+ `4. Check container: docker logs ar-${result.workspaceId.substring(0, 8)}`,
599
+ `5. Verify clone: docker exec ar-${result.workspaceId.substring(0, 8)} ls /workspace/repos`,
600
+ ],
601
+ });
602
+ }
603
+ catch (error) {
604
+ console.error('Error provisioning real workspace:', error);
605
+ res.status(500).json({
606
+ error: 'Failed to provision workspace',
607
+ details: error instanceof Error ? error.message : 'Unknown error',
608
+ });
609
+ }
610
+ });
611
+ /**
612
+ * GET /api/test/my-repos
613
+ * List current user's connected repositories (for debugging)
614
+ */
615
+ testHelpersRouter.get('/my-repos', async (req, res) => {
616
+ if (!isTestMode) {
617
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
618
+ }
619
+ const userId = req.session.userId;
620
+ if (!userId) {
621
+ return res.status(401).json({ error: 'Not logged in' });
622
+ }
623
+ try {
624
+ const repos = await db.repositories.findByUserId(userId);
625
+ res.json({
626
+ userId,
627
+ repositories: repos.map(r => ({
628
+ id: r.id,
629
+ fullName: r.githubFullName,
630
+ isPrivate: r.isPrivate,
631
+ hasNangoConnection: !!r.nangoConnectionId,
632
+ nangoConnectionId: r.nangoConnectionId, // For debugging
633
+ syncStatus: r.syncStatus,
634
+ })),
635
+ });
636
+ }
637
+ catch (error) {
638
+ console.error('Error fetching repos:', error);
639
+ res.status(500).json({ error: 'Failed to fetch repositories' });
640
+ }
641
+ });
642
+ /**
643
+ * GET /api/test/my-workspaces
644
+ * List current user's workspaces (for debugging)
645
+ */
646
+ testHelpersRouter.get('/my-workspaces', async (req, res) => {
647
+ if (!isTestMode) {
648
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
649
+ }
650
+ const userId = req.session.userId;
651
+ if (!userId) {
652
+ return res.status(401).json({ error: 'Not logged in' });
653
+ }
654
+ try {
655
+ const userWorkspaces = await db.workspaces.findByUserId(userId);
656
+ res.json({
657
+ userId,
658
+ workspaces: userWorkspaces.map(w => ({
659
+ id: w.id,
660
+ name: w.name,
661
+ status: w.status,
662
+ publicUrl: w.publicUrl,
663
+ computeProvider: w.computeProvider,
664
+ computeId: w.computeId,
665
+ config: w.config,
666
+ })),
667
+ });
668
+ }
669
+ catch (error) {
670
+ console.error('Error fetching workspaces:', error);
671
+ res.status(500).json({ error: 'Failed to fetch workspaces' });
672
+ }
673
+ });
674
+ /**
675
+ * GET /api/test/nango-token
676
+ * Test fetching GitHub App token from Nango (for debugging)
677
+ */
678
+ testHelpersRouter.get('/nango-token', async (req, res) => {
679
+ if (!isTestMode) {
680
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
681
+ }
682
+ const userId = req.session.userId;
683
+ if (!userId) {
684
+ return res.status(401).json({ error: 'Not logged in' });
685
+ }
686
+ try {
687
+ const repos = await db.repositories.findByUserId(userId);
688
+ const repoWithConnection = repos.find(r => r.nangoConnectionId);
689
+ if (!repoWithConnection?.nangoConnectionId) {
690
+ return res.status(400).json({
691
+ error: 'No Nango connection found',
692
+ repos: repos.map(r => ({ fullName: r.githubFullName, nangoConnectionId: r.nangoConnectionId })),
693
+ });
694
+ }
695
+ console.log('[test] Fetching token for connection:', repoWithConnection.nangoConnectionId);
696
+ const token = await nangoService.getGithubAppToken(repoWithConnection.nangoConnectionId);
697
+ res.json({
698
+ success: true,
699
+ connectionId: repoWithConnection.nangoConnectionId,
700
+ tokenLength: token.length,
701
+ tokenPrefix: token.substring(0, 10) + '...',
702
+ });
703
+ }
704
+ catch (error) {
705
+ console.error('[test] Nango token fetch error:', error);
706
+ res.status(500).json({
707
+ error: 'Failed to fetch token',
708
+ details: error instanceof Error ? error.message : 'Unknown error',
709
+ });
710
+ }
711
+ });
712
+ /**
713
+ * DELETE /api/test/workspace/:id
714
+ * Delete/deprovision a workspace (for cleanup)
715
+ */
716
+ testHelpersRouter.delete('/workspace/:id', async (req, res) => {
717
+ if (!isTestMode) {
718
+ return res.status(403).json({ error: 'Test endpoints disabled in production' });
719
+ }
720
+ const userId = req.session.userId;
721
+ if (!userId) {
722
+ return res.status(401).json({ error: 'Not logged in' });
723
+ }
724
+ try {
725
+ const id = req.params.id;
726
+ const workspace = await db.workspaces.findById(id);
727
+ if (!workspace) {
728
+ return res.status(404).json({ error: 'Workspace not found' });
729
+ }
730
+ if (workspace.userId !== userId) {
731
+ return res.status(403).json({ error: 'Not your workspace' });
732
+ }
733
+ const provisioner = getProvisioner();
734
+ await provisioner.deprovision(id);
735
+ res.json({
736
+ success: true,
737
+ message: `Workspace ${id} deleted`,
738
+ });
739
+ }
740
+ catch (error) {
741
+ console.error('Error deleting workspace:', error);
742
+ res.status(500).json({ error: 'Failed to delete workspace' });
743
+ }
744
+ });
745
+ //# sourceMappingURL=test-helpers.js.map