@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.
- package/LICENSE +675 -0
- package/README.md +414 -0
- package/dist/api-docs.html +879 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/index-DfR9xEkp.css +32 -0
- package/dist/assets/index-DvlVn6Eb.js +1231 -0
- package/dist/assets/vendor-codemirror-CJLzwpLB.js +39 -0
- package/dist/assets/vendor-react-DcyRfQm3.js +59 -0
- package/dist/assets/vendor-xterm-DfaPXD3y.js +66 -0
- package/dist/clear-cache.html +85 -0
- package/dist/convert-icons.md +53 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +9 -0
- package/dist/generate-icons.js +49 -0
- package/dist/icons/claude-ai-icon.svg +1 -0
- package/dist/icons/codex-white.svg +3 -0
- package/dist/icons/codex.svg +3 -0
- package/dist/icons/cursor-white.svg +12 -0
- package/dist/icons/cursor.svg +1 -0
- package/dist/icons/generate-icons.md +19 -0
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-128x128.svg +12 -0
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-144x144.svg +12 -0
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-152x152.svg +12 -0
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-192x192.svg +12 -0
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-384x384.svg +12 -0
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-512x512.svg +12 -0
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-72x72.svg +12 -0
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/icon-96x96.svg +12 -0
- package/dist/icons/icon-template.svg +12 -0
- package/dist/index.html +52 -0
- package/dist/logo-128.png +0 -0
- package/dist/logo-256.png +0 -0
- package/dist/logo-32.png +0 -0
- package/dist/logo-512.png +0 -0
- package/dist/logo-64.png +0 -0
- package/dist/logo.svg +17 -0
- package/dist/manifest.json +61 -0
- package/dist/screenshots/cli-selection.png +0 -0
- package/dist/screenshots/desktop-main.png +0 -0
- package/dist/screenshots/mobile-chat.png +0 -0
- package/dist/screenshots/tools-modal.png +0 -0
- package/dist/sw.js +107 -0
- package/package.json +120 -0
- package/server/claude-sdk.js +721 -0
- package/server/cli.js +469 -0
- package/server/cursor-cli.js +267 -0
- package/server/database/db.js +554 -0
- package/server/database/init.sql +54 -0
- package/server/index.js +2120 -0
- package/server/middleware/auth.js +161 -0
- package/server/openai-codex.js +389 -0
- package/server/orchestrator/client.js +989 -0
- package/server/orchestrator/github-auth.js +308 -0
- package/server/orchestrator/index.js +216 -0
- package/server/orchestrator/protocol.js +299 -0
- package/server/orchestrator/proxy.js +364 -0
- package/server/orchestrator/status-tracker.js +226 -0
- package/server/projects.js +1604 -0
- package/server/routes/agent.js +1230 -0
- package/server/routes/auth.js +135 -0
- package/server/routes/cli-auth.js +341 -0
- package/server/routes/codex.js +345 -0
- package/server/routes/commands.js +521 -0
- package/server/routes/cursor.js +795 -0
- package/server/routes/git.js +1128 -0
- package/server/routes/mcp-utils.js +48 -0
- package/server/routes/mcp.js +650 -0
- package/server/routes/projects.js +378 -0
- package/server/routes/settings.js +178 -0
- package/server/routes/taskmaster.js +1963 -0
- package/server/routes/user.js +106 -0
- package/server/utils/commandParser.js +303 -0
- package/server/utils/gitConfig.js +24 -0
- package/server/utils/mcp-detector.js +198 -0
- package/server/utils/taskmaster-websocket.js +129 -0
- 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
|
+
}
|