@epiphytic/claudecodeui 1.1.0 → 1.2.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.
@@ -18,6 +18,7 @@ export {
18
18
  createRegisterMessage,
19
19
  createStatusUpdateMessage,
20
20
  createPingMessage,
21
+ createPendingRegisterMessage,
21
22
  createResponseMessage,
22
23
  createResponseChunkMessage,
23
24
  createResponseCompleteMessage,
@@ -61,6 +62,11 @@ export {
61
62
  /**
62
63
  * Creates and configures an orchestrator client from environment variables
63
64
  *
65
+ * Token Precedence:
66
+ * 1. .env file ORCHESTRATOR_TOKEN - Highest priority, always used if set
67
+ * 2. Database token for specific host - Used when .env token is empty/missing
68
+ * 3. No token available - Uses pending mode with claim patterns
69
+ *
64
70
  * @param {Object} [overrides] - Configuration overrides
65
71
  * @returns {Promise<OrchestratorClient|null>} Configured client or null if not in client mode
66
72
  */
@@ -74,7 +80,6 @@ export async function createOrchestratorClientFromEnv(overrides = {}) {
74
80
  }
75
81
 
76
82
  const url = overrides.url || process.env.ORCHESTRATOR_URL;
77
- const token = overrides.token || process.env.ORCHESTRATOR_TOKEN;
78
83
 
79
84
  if (!url) {
80
85
  console.warn(
@@ -83,16 +88,46 @@ export async function createOrchestratorClientFromEnv(overrides = {}) {
83
88
  return null;
84
89
  }
85
90
 
86
- if (!token) {
91
+ // Token is now optional - if not set, pending mode will be used
92
+ const token = overrides.token || process.env.ORCHESTRATOR_TOKEN || null;
93
+
94
+ // Build claim patterns for pending mode
95
+ // Fall back to ORCHESTRATOR_GITHUB_* variables if ORCHESTRATOR_CLAIM_* not set
96
+ const claimPatterns = overrides.claimPatterns || {
97
+ user:
98
+ process.env.ORCHESTRATOR_CLAIM_USER ||
99
+ process.env.ORCHESTRATOR_GITHUB_USERS ||
100
+ "",
101
+ org:
102
+ process.env.ORCHESTRATOR_CLAIM_ORG ||
103
+ process.env.ORCHESTRATOR_GITHUB_ORG ||
104
+ "",
105
+ team:
106
+ process.env.ORCHESTRATOR_CLAIM_TEAM ||
107
+ process.env.ORCHESTRATOR_GITHUB_TEAM ||
108
+ "",
109
+ };
110
+
111
+ // Validate: need either a token or at least one claim pattern
112
+ const hasToken = token && token.trim() !== "";
113
+ const hasClaimPattern =
114
+ (claimPatterns.user && claimPatterns.user.trim()) ||
115
+ (claimPatterns.org && claimPatterns.org.trim()) ||
116
+ (claimPatterns.team && claimPatterns.team.trim());
117
+
118
+ if (!hasToken && !hasClaimPattern) {
87
119
  console.warn(
88
- "[ORCHESTRATOR] ORCHESTRATOR_TOKEN not set, running in standalone mode",
120
+ "[ORCHESTRATOR] No token and no claim patterns set, running in standalone mode",
121
+ );
122
+ console.warn(
123
+ "[ORCHESTRATOR] Set ORCHESTRATOR_TOKEN or at least one of ORCHESTRATOR_CLAIM_USER, ORCHESTRATOR_CLAIM_ORG, ORCHESTRATOR_CLAIM_TEAM",
89
124
  );
90
125
  return null;
91
126
  }
92
127
 
93
128
  const config = {
94
129
  url,
95
- token,
130
+ token: hasToken ? token : null,
96
131
  clientId: overrides.clientId || process.env.ORCHESTRATOR_CLIENT_ID,
97
132
  reconnectInterval:
98
133
  overrides.reconnectInterval ||
@@ -103,6 +138,7 @@ export async function createOrchestratorClientFromEnv(overrides = {}) {
103
138
  parseInt(process.env.ORCHESTRATOR_HEARTBEAT_INTERVAL) ||
104
139
  30000,
105
140
  metadata: overrides.metadata || {},
141
+ claimPatterns,
106
142
  };
107
143
 
108
144
  return new OrchestratorClient(config);
@@ -117,6 +153,11 @@ export async function createOrchestratorClientFromEnv(overrides = {}) {
117
153
  * 3. Connects to the orchestrator
118
154
  * 4. Sets up user request handling
119
155
  *
156
+ * Token Precedence:
157
+ * 1. .env file ORCHESTRATOR_TOKEN - Highest priority, always used if set
158
+ * 2. Database token for specific host - Used when .env token is empty/missing
159
+ * 3. No token available - Uses pending mode with claim patterns
160
+ *
120
161
  * @param {Object} options - Initialization options
121
162
  * @param {Object} options.handlers - Handler functions for proxied requests
122
163
  * @param {Object} [options.config] - Configuration overrides
@@ -138,11 +179,44 @@ export async function initializeOrchestrator(options = {}) {
138
179
  }
139
180
 
140
181
  const url = config.url || process.env.ORCHESTRATOR_URL;
141
- const token = config.token || process.env.ORCHESTRATOR_TOKEN;
142
182
 
143
- if (!url || !token) {
183
+ if (!url) {
184
+ console.warn(
185
+ "[ORCHESTRATOR] URL not configured, running in standalone mode",
186
+ );
187
+ return null;
188
+ }
189
+
190
+ // Token is now optional - if not set, pending mode will be used
191
+ const token = config.token || process.env.ORCHESTRATOR_TOKEN || null;
192
+
193
+ // Build claim patterns for pending mode
194
+ // Fall back to ORCHESTRATOR_GITHUB_* variables if ORCHESTRATOR_CLAIM_* not set
195
+ const claimPatterns = config.claimPatterns || {
196
+ user:
197
+ process.env.ORCHESTRATOR_CLAIM_USER ||
198
+ process.env.ORCHESTRATOR_GITHUB_USERS ||
199
+ "",
200
+ org:
201
+ process.env.ORCHESTRATOR_CLAIM_ORG ||
202
+ process.env.ORCHESTRATOR_GITHUB_ORG ||
203
+ "",
204
+ team:
205
+ process.env.ORCHESTRATOR_CLAIM_TEAM ||
206
+ process.env.ORCHESTRATOR_GITHUB_TEAM ||
207
+ "",
208
+ };
209
+
210
+ // Validate: need either a token or at least one claim pattern
211
+ const hasToken = token && token.trim() !== "";
212
+ const hasClaimPattern =
213
+ (claimPatterns.user && claimPatterns.user.trim()) ||
214
+ (claimPatterns.org && claimPatterns.org.trim()) ||
215
+ (claimPatterns.team && claimPatterns.team.trim());
216
+
217
+ if (!hasToken && !hasClaimPattern) {
144
218
  console.warn(
145
- "[ORCHESTRATOR] URL or token not configured, running in standalone mode",
219
+ "[ORCHESTRATOR] No token and no claim patterns set, running in standalone mode",
146
220
  );
147
221
  return null;
148
222
  }
@@ -155,7 +229,7 @@ export async function initializeOrchestrator(options = {}) {
155
229
  // Create client
156
230
  const client = new OrchestratorClient({
157
231
  url,
158
- token,
232
+ token: hasToken ? token : null,
159
233
  clientId: config.clientId || process.env.ORCHESTRATOR_CLIENT_ID,
160
234
  reconnectInterval:
161
235
  config.reconnectInterval ||
@@ -167,6 +241,7 @@ export async function initializeOrchestrator(options = {}) {
167
241
  30000,
168
242
  metadata: config.metadata || {},
169
243
  callbackUrl,
244
+ claimPatterns,
170
245
  });
171
246
 
172
247
  // Create status hooks
@@ -17,6 +17,8 @@ export const OutboundMessageTypes = {
17
17
  RESPONSE_COMPLETE: "response_complete",
18
18
  ERROR: "error",
19
19
  HTTP_PROXY_RESPONSE: "http_proxy_response",
20
+ // Pending mode messages
21
+ PENDING_REGISTER: "pending_register",
20
22
  };
21
23
 
22
24
  /**
@@ -30,6 +32,11 @@ export const InboundMessageTypes = {
30
32
  USER_REQUEST: "user_request",
31
33
  USER_REQUEST_FOLLOW_UP: "user_request_follow_up",
32
34
  HTTP_PROXY_REQUEST: "http_proxy_request",
35
+ // Pending mode responses
36
+ PENDING_REGISTERED: "pending_registered",
37
+ TOKEN_GRANTED: "token_granted",
38
+ AUTHORIZATION_DENIED: "authorization_denied",
39
+ AUTHORIZATION_TIMEOUT: "authorization_timeout",
33
40
  };
34
41
 
35
42
  /**
@@ -97,6 +104,29 @@ export function createPingMessage(clientId) {
97
104
  };
98
105
  }
99
106
 
107
+ /**
108
+ * Creates a pending register message for pending mode
109
+ * @param {string} pendingId - Unique pending client identifier
110
+ * @param {string} hostname - Machine hostname
111
+ * @param {string} project - Current project/working directory
112
+ * @param {string} platform - Operating system platform
113
+ * @returns {Object} Pending register message
114
+ */
115
+ export function createPendingRegisterMessage(
116
+ pendingId,
117
+ hostname,
118
+ project,
119
+ platform,
120
+ ) {
121
+ return {
122
+ type: OutboundMessageTypes.PENDING_REGISTER,
123
+ pending_id: pendingId,
124
+ hostname,
125
+ project,
126
+ platform,
127
+ };
128
+ }
129
+
100
130
  /**
101
131
  * Creates a response message (for proxied requests)
102
132
  * @param {string} requestId - Original request ID
@@ -254,6 +284,43 @@ export function validateInboundMessage(message) {
254
284
  typeof message.path === "string"
255
285
  );
256
286
 
287
+ case InboundMessageTypes.PENDING_REGISTERED:
288
+ // pending_registered message structure:
289
+ // {
290
+ // type: "pending_registered",
291
+ // success: boolean,
292
+ // message?: string
293
+ // }
294
+ return typeof message.success === "boolean";
295
+
296
+ case InboundMessageTypes.TOKEN_GRANTED:
297
+ // token_granted message structure:
298
+ // {
299
+ // type: "token_granted",
300
+ // token: string, // Full token: "ao_xxx_yyy"
301
+ // client_id: string // Assigned client ID
302
+ // }
303
+ return (
304
+ typeof message.token === "string" &&
305
+ typeof message.client_id === "string"
306
+ );
307
+
308
+ case InboundMessageTypes.AUTHORIZATION_DENIED:
309
+ // authorization_denied message structure:
310
+ // {
311
+ // type: "authorization_denied",
312
+ // reason: string
313
+ // }
314
+ return typeof message.reason === "string";
315
+
316
+ case InboundMessageTypes.AUTHORIZATION_TIMEOUT:
317
+ // authorization_timeout message structure:
318
+ // {
319
+ // type: "authorization_timeout",
320
+ // message: string
321
+ // }
322
+ return typeof message.message === "string";
323
+
257
324
  default:
258
325
  // Unknown message types are considered valid (forward compatibility)
259
326
  return true;
@@ -1134,7 +1134,8 @@ async function addProjectManually(projectPath, displayName = null) {
1134
1134
  }
1135
1135
 
1136
1136
  // Generate project name (encode path for use as directory name)
1137
- const projectName = absolutePath.replace(/\//g, "-");
1137
+ // Claude's encoding replaces /, :, spaces, ~, _, and . with -
1138
+ const projectName = absolutePath.replace(/[\\/:\s~_.]/g, "-");
1138
1139
 
1139
1140
  // Check if project already exists in config
1140
1141
  const config = await loadProjectConfig();