@epiphytic/claudecodeui 1.0.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 (142) hide show
  1. package/LICENSE +675 -0
  2. package/README.md +414 -0
  3. package/dist/api-docs.html +879 -0
  4. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  5. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  6. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  7. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  8. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  10. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  11. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  12. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  13. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  14. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  16. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  17. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  18. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  19. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  20. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  21. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  22. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  23. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  24. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  25. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  26. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  27. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  28. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  29. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  30. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  31. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  32. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  33. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  34. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  35. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  36. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  37. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  38. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  44. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  45. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  46. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  47. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  48. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  49. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  50. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  51. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  52. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  53. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  54. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  55. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  56. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  57. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  58. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  59. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  60. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  61. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  62. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  63. package/dist/assets/index-DfR9xEkp.css +32 -0
  64. package/dist/assets/index-DvlVn6Eb.js +1231 -0
  65. package/dist/assets/vendor-codemirror-CJLzwpLB.js +39 -0
  66. package/dist/assets/vendor-react-DcyRfQm3.js +59 -0
  67. package/dist/assets/vendor-xterm-DfaPXD3y.js +66 -0
  68. package/dist/clear-cache.html +85 -0
  69. package/dist/convert-icons.md +53 -0
  70. package/dist/favicon.png +0 -0
  71. package/dist/favicon.svg +9 -0
  72. package/dist/generate-icons.js +49 -0
  73. package/dist/icons/claude-ai-icon.svg +1 -0
  74. package/dist/icons/codex-white.svg +3 -0
  75. package/dist/icons/codex.svg +3 -0
  76. package/dist/icons/cursor-white.svg +12 -0
  77. package/dist/icons/cursor.svg +1 -0
  78. package/dist/icons/generate-icons.md +19 -0
  79. package/dist/icons/icon-128x128.png +0 -0
  80. package/dist/icons/icon-128x128.svg +12 -0
  81. package/dist/icons/icon-144x144.png +0 -0
  82. package/dist/icons/icon-144x144.svg +12 -0
  83. package/dist/icons/icon-152x152.png +0 -0
  84. package/dist/icons/icon-152x152.svg +12 -0
  85. package/dist/icons/icon-192x192.png +0 -0
  86. package/dist/icons/icon-192x192.svg +12 -0
  87. package/dist/icons/icon-384x384.png +0 -0
  88. package/dist/icons/icon-384x384.svg +12 -0
  89. package/dist/icons/icon-512x512.png +0 -0
  90. package/dist/icons/icon-512x512.svg +12 -0
  91. package/dist/icons/icon-72x72.png +0 -0
  92. package/dist/icons/icon-72x72.svg +12 -0
  93. package/dist/icons/icon-96x96.png +0 -0
  94. package/dist/icons/icon-96x96.svg +12 -0
  95. package/dist/icons/icon-template.svg +12 -0
  96. package/dist/index.html +52 -0
  97. package/dist/logo-128.png +0 -0
  98. package/dist/logo-256.png +0 -0
  99. package/dist/logo-32.png +0 -0
  100. package/dist/logo-512.png +0 -0
  101. package/dist/logo-64.png +0 -0
  102. package/dist/logo.svg +17 -0
  103. package/dist/manifest.json +61 -0
  104. package/dist/screenshots/cli-selection.png +0 -0
  105. package/dist/screenshots/desktop-main.png +0 -0
  106. package/dist/screenshots/mobile-chat.png +0 -0
  107. package/dist/screenshots/tools-modal.png +0 -0
  108. package/dist/sw.js +107 -0
  109. package/package.json +120 -0
  110. package/server/claude-sdk.js +721 -0
  111. package/server/cli.js +469 -0
  112. package/server/cursor-cli.js +267 -0
  113. package/server/database/db.js +554 -0
  114. package/server/database/init.sql +54 -0
  115. package/server/index.js +2120 -0
  116. package/server/middleware/auth.js +161 -0
  117. package/server/openai-codex.js +389 -0
  118. package/server/orchestrator/client.js +989 -0
  119. package/server/orchestrator/github-auth.js +308 -0
  120. package/server/orchestrator/index.js +216 -0
  121. package/server/orchestrator/protocol.js +299 -0
  122. package/server/orchestrator/proxy.js +364 -0
  123. package/server/orchestrator/status-tracker.js +226 -0
  124. package/server/projects.js +1604 -0
  125. package/server/routes/agent.js +1230 -0
  126. package/server/routes/auth.js +135 -0
  127. package/server/routes/cli-auth.js +341 -0
  128. package/server/routes/codex.js +345 -0
  129. package/server/routes/commands.js +521 -0
  130. package/server/routes/cursor.js +795 -0
  131. package/server/routes/git.js +1128 -0
  132. package/server/routes/mcp-utils.js +48 -0
  133. package/server/routes/mcp.js +650 -0
  134. package/server/routes/projects.js +378 -0
  135. package/server/routes/settings.js +178 -0
  136. package/server/routes/taskmaster.js +1963 -0
  137. package/server/routes/user.js +106 -0
  138. package/server/utils/commandParser.js +303 -0
  139. package/server/utils/gitConfig.js +24 -0
  140. package/server/utils/mcp-detector.js +198 -0
  141. package/server/utils/taskmaster-websocket.js +129 -0
  142. package/shared/modelConstants.js +65 -0
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Orchestrator GitHub Authentication
3
+ *
4
+ * Validates GitHub OAuth tokens passed through from the orchestrator
5
+ * and checks if the user belongs to an allowed org, team, or is a specific user.
6
+ */
7
+
8
+ /**
9
+ * Configuration for GitHub authentication
10
+ * @typedef {Object} GitHubAuthConfig
11
+ * @property {string} [allowedOrg] - GitHub organization the user must belong to
12
+ * @property {string} [allowedTeam] - GitHub team (format: "org/team-slug") the user must belong to
13
+ * @property {string|string[]} [allowedUsers] - Specific GitHub username(s) that are allowed
14
+ */
15
+
16
+ /**
17
+ * Result of GitHub authentication
18
+ * @typedef {Object} GitHubAuthResult
19
+ * @property {boolean} authenticated - Whether authentication succeeded
20
+ * @property {Object|null} user - GitHub user info if authenticated
21
+ * @property {string|null} error - Error message if authentication failed
22
+ */
23
+
24
+ const GITHUB_API_BASE = "https://api.github.com";
25
+
26
+ /**
27
+ * Fetches GitHub API with proper headers
28
+ * @param {string} endpoint - API endpoint (e.g., "/user")
29
+ * @param {string} token - GitHub OAuth token
30
+ * @returns {Promise<Object>} API response
31
+ */
32
+ async function githubFetch(endpoint, token) {
33
+ const controller = new AbortController();
34
+ const timeoutId = setTimeout(() => controller.abort(), 8000);
35
+
36
+ try {
37
+ const response = await fetch(`${GITHUB_API_BASE}${endpoint}`, {
38
+ headers: {
39
+ Authorization: `Bearer ${token}`,
40
+ Accept: "application/vnd.github+json",
41
+ "X-GitHub-Api-Version": "2022-11-28",
42
+ "User-Agent": "claudecodeui-orchestrator",
43
+ },
44
+ signal: controller.signal,
45
+ });
46
+
47
+ if (!response.ok) {
48
+ const errorText = await response.text();
49
+ throw new Error(`GitHub API error (${response.status}): ${errorText}`);
50
+ }
51
+
52
+ return response.json();
53
+ } catch (error) {
54
+ if (error.name === "AbortError") {
55
+ throw new Error("GitHub API request timed out after 8 seconds");
56
+ }
57
+ throw error;
58
+ } finally {
59
+ clearTimeout(timeoutId);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Gets the authenticated user's information
65
+ * @param {string} token - GitHub OAuth token
66
+ * @returns {Promise<Object>} User object with login, id, name, etc.
67
+ */
68
+ export async function getGitHubUser(token) {
69
+ return githubFetch("/user", token);
70
+ }
71
+
72
+ /**
73
+ * Checks if a user belongs to a specific GitHub organization
74
+ * @param {string} token - GitHub OAuth token
75
+ * @param {string} org - Organization name
76
+ * @returns {Promise<boolean>} True if user is a member
77
+ */
78
+ export async function checkOrgMembership(token, org) {
79
+ try {
80
+ // GET /user/memberships/orgs/{org} returns membership info if user is a member
81
+ // Returns 404 if not a member, 403 if org requires 2FA and user doesn't have it
82
+ const membership = await githubFetch(
83
+ `/user/memberships/orgs/${org}`,
84
+ token,
85
+ );
86
+ return membership.state === "active";
87
+ } catch (error) {
88
+ // 404 means not a member, other errors are actual failures
89
+ if (error.message.includes("404")) {
90
+ return false;
91
+ }
92
+ // For other errors, we'll try the orgs list as a fallback
93
+ try {
94
+ const orgs = await githubFetch("/user/orgs", token);
95
+ return orgs.some((o) => o.login.toLowerCase() === org.toLowerCase());
96
+ } catch {
97
+ return false;
98
+ }
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Checks if a user belongs to a specific GitHub team
104
+ * @param {string} token - GitHub OAuth token
105
+ * @param {string} org - Organization name
106
+ * @param {string} teamSlug - Team slug (e.g., "engineering")
107
+ * @returns {Promise<boolean>} True if user is a member
108
+ */
109
+ export async function checkTeamMembership(token, org, teamSlug) {
110
+ try {
111
+ // First get the user's login
112
+ const user = await getGitHubUser(token);
113
+ const username = user.login;
114
+
115
+ // Check team membership
116
+ // GET /orgs/{org}/teams/{team_slug}/memberships/{username}
117
+ const membership = await githubFetch(
118
+ `/orgs/${org}/teams/${teamSlug}/memberships/${username}`,
119
+ token,
120
+ );
121
+ return membership.state === "active";
122
+ } catch (error) {
123
+ // 404 means not a member
124
+ if (error.message.includes("404")) {
125
+ return false;
126
+ }
127
+ console.error("[GITHUB-AUTH] Team membership check failed:", error.message);
128
+ return false;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Checks if a username matches the allowed user(s)
134
+ * @param {string} username - GitHub username
135
+ * @param {string|string[]} allowedUsers - Allowed username(s)
136
+ * @returns {boolean} True if user is allowed
137
+ */
138
+ export function checkUserAllowed(username, allowedUsers) {
139
+ const allowed = Array.isArray(allowedUsers) ? allowedUsers : [allowedUsers];
140
+ return allowed.some((u) => u.toLowerCase() === username.toLowerCase());
141
+ }
142
+
143
+ /**
144
+ * Validates a GitHub OAuth token and checks authorization
145
+ * @param {string} token - GitHub OAuth token
146
+ * @param {GitHubAuthConfig} config - Authorization configuration
147
+ * @returns {Promise<GitHubAuthResult>} Authentication result
148
+ */
149
+ export async function validateGitHubToken(token, config = {}) {
150
+ if (!token) {
151
+ return {
152
+ authenticated: false,
153
+ user: null,
154
+ error: "No authentication token provided",
155
+ };
156
+ }
157
+
158
+ const { allowedOrg, allowedTeam, allowedUsers } = config;
159
+
160
+ // If no restrictions are configured, reject all
161
+ if (!allowedOrg && !allowedTeam && !allowedUsers) {
162
+ return {
163
+ authenticated: false,
164
+ user: null,
165
+ error:
166
+ "No authorization rules configured (set ORCHESTRATOR_GITHUB_ORG, ORCHESTRATOR_GITHUB_TEAM, or ORCHESTRATOR_GITHUB_USERS)",
167
+ };
168
+ }
169
+
170
+ try {
171
+ // First, validate the token by getting user info
172
+ const user = await getGitHubUser(token);
173
+
174
+ if (!user || !user.login) {
175
+ return {
176
+ authenticated: false,
177
+ user: null,
178
+ error: "Invalid GitHub token - could not retrieve user",
179
+ };
180
+ }
181
+
182
+ // Check if user is explicitly allowed
183
+ if (allowedUsers && checkUserAllowed(user.login, allowedUsers)) {
184
+ return {
185
+ authenticated: true,
186
+ user: {
187
+ id: user.id,
188
+ username: user.login,
189
+ name: user.name,
190
+ email: user.email,
191
+ avatarUrl: user.avatar_url,
192
+ authMethod: "github-user",
193
+ },
194
+ error: null,
195
+ };
196
+ }
197
+
198
+ // Check team membership (more specific than org)
199
+ if (allowedTeam) {
200
+ const [teamOrg, teamSlug] = allowedTeam.includes("/")
201
+ ? allowedTeam.split("/")
202
+ : [allowedOrg, allowedTeam];
203
+
204
+ if (!teamOrg || !teamSlug) {
205
+ return {
206
+ authenticated: false,
207
+ user: null,
208
+ error: "Invalid team format - use 'org/team-slug'",
209
+ };
210
+ }
211
+
212
+ const isTeamMember = await checkTeamMembership(token, teamOrg, teamSlug);
213
+ if (isTeamMember) {
214
+ return {
215
+ authenticated: true,
216
+ user: {
217
+ id: user.id,
218
+ username: user.login,
219
+ name: user.name,
220
+ email: user.email,
221
+ avatarUrl: user.avatar_url,
222
+ authMethod: "github-team",
223
+ team: allowedTeam,
224
+ },
225
+ error: null,
226
+ };
227
+ }
228
+ }
229
+
230
+ // Check org membership
231
+ if (allowedOrg) {
232
+ const isOrgMember = await checkOrgMembership(token, allowedOrg);
233
+ if (isOrgMember) {
234
+ return {
235
+ authenticated: true,
236
+ user: {
237
+ id: user.id,
238
+ username: user.login,
239
+ name: user.name,
240
+ email: user.email,
241
+ avatarUrl: user.avatar_url,
242
+ authMethod: "github-org",
243
+ org: allowedOrg,
244
+ },
245
+ error: null,
246
+ };
247
+ }
248
+ }
249
+
250
+ // User is authenticated but not authorized
251
+ return {
252
+ authenticated: false,
253
+ user: null,
254
+ error: `User '${user.login}' is not authorized - not a member of required org/team`,
255
+ };
256
+ } catch (error) {
257
+ console.error("[GITHUB-AUTH] Token validation failed:", error.message);
258
+ return {
259
+ authenticated: false,
260
+ user: null,
261
+ error: `Authentication failed: ${error.message}`,
262
+ };
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Creates a GitHub auth validator from environment variables
268
+ * @returns {Object} Validator with config and validate method
269
+ */
270
+ export function createGitHubAuthFromEnv() {
271
+ const config = {
272
+ allowedOrg: process.env.ORCHESTRATOR_GITHUB_ORG || null,
273
+ allowedTeam: process.env.ORCHESTRATOR_GITHUB_TEAM || null,
274
+ allowedUsers: process.env.ORCHESTRATOR_GITHUB_USERS
275
+ ? process.env.ORCHESTRATOR_GITHUB_USERS.split(",").map((u) => u.trim())
276
+ : null,
277
+ };
278
+
279
+ const isConfigured =
280
+ config.allowedOrg || config.allowedTeam || config.allowedUsers;
281
+
282
+ return {
283
+ config,
284
+ isConfigured,
285
+ validate: (token) => validateGitHubToken(token, config),
286
+ };
287
+ }
288
+
289
+ /**
290
+ * Middleware-style function for validating requests
291
+ * @param {Object} message - Incoming message with auth_token
292
+ * @param {GitHubAuthConfig} config - Authorization configuration
293
+ * @returns {Promise<{authorized: boolean, user: Object|null, error: string|null}>}
294
+ */
295
+ export async function authenticateOrchestratorRequest(message, config) {
296
+ const token = message?.payload?.auth_token || message?.auth_token;
297
+ return validateGitHubToken(token, config);
298
+ }
299
+
300
+ export default {
301
+ validateGitHubToken,
302
+ createGitHubAuthFromEnv,
303
+ authenticateOrchestratorRequest,
304
+ getGitHubUser,
305
+ checkOrgMembership,
306
+ checkTeamMembership,
307
+ checkUserAllowed,
308
+ };
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Orchestrator Module
3
+ *
4
+ * Provides functionality for connecting claudecodeui to a central orchestrator
5
+ * server. This enables:
6
+ * - Registration with the orchestrator
7
+ * - Token-based authentication
8
+ * - Status reporting
9
+ * - User request proxying
10
+ */
11
+
12
+ // Re-export protocol types and functions
13
+ export {
14
+ OutboundMessageTypes,
15
+ InboundMessageTypes,
16
+ StatusValues,
17
+ CommandTypes,
18
+ createRegisterMessage,
19
+ createStatusUpdateMessage,
20
+ createPingMessage,
21
+ createResponseMessage,
22
+ createResponseChunkMessage,
23
+ createResponseCompleteMessage,
24
+ createErrorMessage,
25
+ createAuthErrorMessage,
26
+ createHttpProxyResponseMessage,
27
+ serialize,
28
+ parse,
29
+ validateInboundMessage,
30
+ } from "./protocol.js";
31
+
32
+ // Re-export GitHub authentication
33
+ export {
34
+ validateGitHubToken,
35
+ createGitHubAuthFromEnv,
36
+ authenticateOrchestratorRequest,
37
+ getGitHubUser,
38
+ checkOrgMembership,
39
+ checkTeamMembership,
40
+ checkUserAllowed,
41
+ } from "./github-auth.js";
42
+
43
+ // Re-export client
44
+ export { OrchestratorClient } from "./client.js";
45
+
46
+ // Re-export status tracker
47
+ export {
48
+ StatusTracker,
49
+ getStatusTracker,
50
+ createStatusHooks,
51
+ } from "./status-tracker.js";
52
+
53
+ // Re-export proxy components
54
+ export {
55
+ OrchestratorProxySocket,
56
+ OrchestratorProxyWriter,
57
+ createUserRequestHandler,
58
+ handleFollowUpMessage,
59
+ } from "./proxy.js";
60
+
61
+ /**
62
+ * Creates and configures an orchestrator client from environment variables
63
+ *
64
+ * @param {Object} [overrides] - Configuration overrides
65
+ * @returns {Promise<OrchestratorClient|null>} Configured client or null if not in client mode
66
+ */
67
+ export async function createOrchestratorClientFromEnv(overrides = {}) {
68
+ const { OrchestratorClient } = await import("./client.js");
69
+
70
+ // Check if orchestrator mode is enabled
71
+ const mode = overrides.mode || process.env.ORCHESTRATOR_MODE || "standalone";
72
+ if (mode !== "client") {
73
+ return null;
74
+ }
75
+
76
+ const url = overrides.url || process.env.ORCHESTRATOR_URL;
77
+ const token = overrides.token || process.env.ORCHESTRATOR_TOKEN;
78
+
79
+ if (!url) {
80
+ console.warn(
81
+ "[ORCHESTRATOR] ORCHESTRATOR_URL not set, running in standalone mode",
82
+ );
83
+ return null;
84
+ }
85
+
86
+ if (!token) {
87
+ console.warn(
88
+ "[ORCHESTRATOR] ORCHESTRATOR_TOKEN not set, running in standalone mode",
89
+ );
90
+ return null;
91
+ }
92
+
93
+ const config = {
94
+ url,
95
+ token,
96
+ clientId: overrides.clientId || process.env.ORCHESTRATOR_CLIENT_ID,
97
+ reconnectInterval:
98
+ overrides.reconnectInterval ||
99
+ parseInt(process.env.ORCHESTRATOR_RECONNECT_INTERVAL) ||
100
+ 5000,
101
+ heartbeatInterval:
102
+ overrides.heartbeatInterval ||
103
+ parseInt(process.env.ORCHESTRATOR_HEARTBEAT_INTERVAL) ||
104
+ 30000,
105
+ metadata: overrides.metadata || {},
106
+ };
107
+
108
+ return new OrchestratorClient(config);
109
+ }
110
+
111
+ /**
112
+ * Initializes the orchestrator integration
113
+ *
114
+ * This function:
115
+ * 1. Creates the orchestrator client (if in client mode)
116
+ * 2. Sets up status tracking hooks
117
+ * 3. Connects to the orchestrator
118
+ * 4. Sets up user request handling
119
+ *
120
+ * @param {Object} options - Initialization options
121
+ * @param {Object} options.handlers - Handler functions for proxied requests
122
+ * @param {Object} [options.config] - Configuration overrides
123
+ * @returns {Promise<Object|null>} Object with client, statusHooks, and requestHandler, or null if standalone
124
+ */
125
+ export async function initializeOrchestrator(options = {}) {
126
+ const { handlers = {}, config = {} } = options;
127
+
128
+ // Import dynamically to avoid circular dependencies
129
+ const { OrchestratorClient } = await import("./client.js");
130
+ const { createStatusHooks } = await import("./status-tracker.js");
131
+ const { createUserRequestHandler } = await import("./proxy.js");
132
+
133
+ // Check mode
134
+ const mode = config.mode || process.env.ORCHESTRATOR_MODE || "standalone";
135
+ if (mode !== "client") {
136
+ console.log("[ORCHESTRATOR] Running in standalone mode");
137
+ return null;
138
+ }
139
+
140
+ const url = config.url || process.env.ORCHESTRATOR_URL;
141
+ const token = config.token || process.env.ORCHESTRATOR_TOKEN;
142
+
143
+ if (!url || !token) {
144
+ console.warn(
145
+ "[ORCHESTRATOR] URL or token not configured, running in standalone mode",
146
+ );
147
+ return null;
148
+ }
149
+
150
+ // Determine callback URL for HTTP proxying
151
+ // If not explicitly set, construct from PORT and public hostname
152
+ const callbackUrl =
153
+ config.callbackUrl || process.env.ORCHESTRATOR_CALLBACK_URL || null;
154
+
155
+ // Create client
156
+ const client = new OrchestratorClient({
157
+ url,
158
+ token,
159
+ clientId: config.clientId || process.env.ORCHESTRATOR_CLIENT_ID,
160
+ reconnectInterval:
161
+ config.reconnectInterval ||
162
+ parseInt(process.env.ORCHESTRATOR_RECONNECT_INTERVAL) ||
163
+ 5000,
164
+ heartbeatInterval:
165
+ config.heartbeatInterval ||
166
+ parseInt(process.env.ORCHESTRATOR_HEARTBEAT_INTERVAL) ||
167
+ 30000,
168
+ metadata: config.metadata || {},
169
+ callbackUrl,
170
+ });
171
+
172
+ // Create status hooks
173
+ const statusHooks = createStatusHooks(client);
174
+
175
+ // Create request handler
176
+ const requestHandler = createUserRequestHandler(handlers, statusHooks);
177
+
178
+ // Set up user request handling
179
+ client.on("user_request", (message) => {
180
+ requestHandler(client, message);
181
+ });
182
+
183
+ // Log connection events
184
+ client.on("connected", () => {
185
+ console.log("[ORCHESTRATOR] Connected to orchestrator");
186
+ });
187
+
188
+ client.on("disconnected", ({ code, reason }) => {
189
+ console.log(
190
+ `[ORCHESTRATOR] Disconnected from orchestrator: ${code} ${reason || ""}`,
191
+ );
192
+ });
193
+
194
+ client.on("error", (error) => {
195
+ console.error("[ORCHESTRATOR] Error:", error.message);
196
+ });
197
+
198
+ // Connect
199
+ try {
200
+ await client.connect();
201
+ console.log("[ORCHESTRATOR] Successfully connected and registered");
202
+ } catch (error) {
203
+ console.warn(
204
+ "[ORCHESTRATOR] Orchestrator unavailable, running in standalone mode:",
205
+ error.message,
206
+ );
207
+ // Return null so callers know orchestrator is not available
208
+ return null;
209
+ }
210
+
211
+ return {
212
+ client,
213
+ statusHooks,
214
+ requestHandler,
215
+ };
216
+ }