@hatchway/cli 0.50.68 → 0.50.70

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.
@@ -0,0 +1,859 @@
1
+ // Hatchway CLI - Built with Rollup
2
+ import { drizzle } from 'drizzle-orm/node-postgres';
3
+ import pg from 'pg';
4
+ import { pgTable, timestamp, boolean, text, uuid, index, uniqueIndex, integer, jsonb } from 'drizzle-orm/pg-core';
5
+ import { sql } from 'drizzle-orm';
6
+ import 'net';
7
+ import { readFile } from 'fs/promises';
8
+ import { existsSync } from 'fs';
9
+ import { join } from 'path';
10
+
11
+ var __defProp = Object.defineProperty;
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+
17
+ // src/lib/db/schema.ts
18
+ var schema_exports = {};
19
+ __export(schema_exports, {
20
+ accounts: () => accounts,
21
+ cliAuthSessions: () => cliAuthSessions,
22
+ generationNotes: () => generationNotes,
23
+ generationSessions: () => generationSessions,
24
+ generationTodos: () => generationTodos,
25
+ generationToolCalls: () => generationToolCalls,
26
+ githubConnections: () => githubConnections,
27
+ messages: () => messages,
28
+ portAllocations: () => portAllocations,
29
+ projects: () => projects,
30
+ railwayConnections: () => railwayConnections,
31
+ railwayDeployments: () => railwayDeployments,
32
+ runnerKeys: () => runnerKeys,
33
+ runningProcesses: () => runningProcesses,
34
+ serverOperations: () => serverOperations,
35
+ sessions: () => sessions,
36
+ users: () => users,
37
+ verifications: () => verifications
38
+ });
39
+ var users = pgTable("users", {
40
+ id: uuid("id").primaryKey().defaultRandom(),
41
+ name: text("name").notNull(),
42
+ email: text("email").notNull().unique(),
43
+ emailVerified: boolean("email_verified").notNull().default(false),
44
+ image: text("image"),
45
+ hasCompletedOnboarding: boolean("has_completed_onboarding").notNull().default(false),
46
+ createdAt: timestamp("created_at").notNull().defaultNow(),
47
+ updatedAt: timestamp("updated_at").notNull().defaultNow()
48
+ });
49
+ var sessions = pgTable("sessions", {
50
+ id: uuid("id").primaryKey().defaultRandom(),
51
+ userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
52
+ token: text("token").notNull().unique(),
53
+ expiresAt: timestamp("expires_at").notNull(),
54
+ ipAddress: text("ip_address"),
55
+ userAgent: text("user_agent"),
56
+ createdAt: timestamp("created_at").notNull().defaultNow(),
57
+ updatedAt: timestamp("updated_at").notNull().defaultNow()
58
+ }, (table) => ({
59
+ userIdIdx: index("sessions_user_id_idx").on(table.userId),
60
+ tokenIdx: index("sessions_token_idx").on(table.token)
61
+ }));
62
+ var accounts = pgTable("accounts", {
63
+ id: uuid("id").primaryKey().defaultRandom(),
64
+ userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
65
+ accountId: text("account_id").notNull(),
66
+ providerId: text("provider_id").notNull(),
67
+ // 'credential', 'google', etc.
68
+ accessToken: text("access_token"),
69
+ refreshToken: text("refresh_token"),
70
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
71
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
72
+ scope: text("scope"),
73
+ idToken: text("id_token"),
74
+ // ID token from OAuth providers
75
+ password: text("password"),
76
+ // Hashed password for credential provider
77
+ createdAt: timestamp("created_at").notNull().defaultNow(),
78
+ updatedAt: timestamp("updated_at").notNull().defaultNow()
79
+ }, (table) => ({
80
+ userIdIdx: index("accounts_user_id_idx").on(table.userId),
81
+ providerAccountIdx: uniqueIndex("accounts_provider_account_idx").on(table.providerId, table.accountId)
82
+ }));
83
+ var verifications = pgTable("verifications", {
84
+ id: uuid("id").primaryKey().defaultRandom(),
85
+ identifier: text("identifier").notNull(),
86
+ // email or other identifier
87
+ value: text("value").notNull(),
88
+ // verification token
89
+ expiresAt: timestamp("expires_at").notNull(),
90
+ createdAt: timestamp("created_at").notNull().defaultNow(),
91
+ updatedAt: timestamp("updated_at").notNull().defaultNow()
92
+ }, (table) => ({
93
+ identifierIdx: index("verifications_identifier_idx").on(table.identifier)
94
+ }));
95
+ var runnerKeys = pgTable("runner_keys", {
96
+ id: uuid("id").primaryKey().defaultRandom(),
97
+ userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
98
+ name: text("name").notNull(),
99
+ // User-friendly name like "My MacBook"
100
+ keyHash: text("key_hash").notNull(),
101
+ // SHA-256 hash of the full key
102
+ keyPrefix: text("key_prefix").notNull(),
103
+ // First 8 chars for display: "sv_abc123..."
104
+ source: text("source").default("web"),
105
+ // 'web' | 'cli' - how the key was created
106
+ lastUsedAt: timestamp("last_used_at"),
107
+ createdAt: timestamp("created_at").notNull().defaultNow(),
108
+ revokedAt: timestamp("revoked_at")
109
+ // Soft delete - null means active
110
+ }, (table) => ({
111
+ userIdIdx: index("runner_keys_user_id_idx").on(table.userId),
112
+ keyHashIdx: uniqueIndex("runner_keys_key_hash_idx").on(table.keyHash)
113
+ }));
114
+ var cliAuthSessions = pgTable("cli_auth_sessions", {
115
+ id: uuid("id").primaryKey().defaultRandom(),
116
+ token: text("token").notNull().unique(),
117
+ // Random token for session identification
118
+ callbackPort: integer("callback_port").notNull(),
119
+ // Port the CLI is listening on
120
+ callbackHost: text("callback_host").default("localhost"),
121
+ // Host for callback
122
+ state: text("state").notNull().default("pending"),
123
+ // 'pending' | 'authenticated' | 'completed' | 'expired'
124
+ userId: uuid("user_id").references(() => users.id, { onDelete: "cascade" }),
125
+ // Set after auth
126
+ runnerKeyId: uuid("runner_key_id").references(() => runnerKeys.id, { onDelete: "cascade" }),
127
+ // Created key
128
+ deviceName: text("device_name"),
129
+ // Auto-detected device name
130
+ expiresAt: timestamp("expires_at").notNull(),
131
+ // Session expiration (short-lived)
132
+ createdAt: timestamp("created_at").notNull().defaultNow(),
133
+ authenticatedAt: timestamp("authenticated_at")
134
+ // When user completed OAuth
135
+ }, (table) => ({
136
+ tokenIdx: uniqueIndex("cli_auth_sessions_token_idx").on(table.token),
137
+ expiresAtIdx: index("cli_auth_sessions_expires_at_idx").on(table.expiresAt)
138
+ }));
139
+ var projects = pgTable("projects", {
140
+ id: uuid("id").primaryKey().defaultRandom(),
141
+ userId: uuid("user_id").references(() => users.id, { onDelete: "set null" }),
142
+ // Owner of the project (nullable for migration/local mode)
143
+ name: text("name").notNull(),
144
+ slug: text("slug").notNull().unique(),
145
+ description: text("description"),
146
+ originalPrompt: text("original_prompt"),
147
+ icon: text("icon").default("Folder"),
148
+ status: text("status").notNull().default("pending"),
149
+ projectType: text("project_type"),
150
+ detectedFramework: text("detected_framework"),
151
+ // Auto-detected framework (astro, next, vite, etc.)
152
+ path: text("path"),
153
+ // Nullable - deprecated, path should be calculated from slug
154
+ runCommand: text("run_command"),
155
+ port: integer("port"),
156
+ devServerPid: integer("dev_server_pid"),
157
+ devServerPort: integer("dev_server_port"),
158
+ devServerStatus: text("dev_server_status").default("stopped"),
159
+ devServerStatusUpdatedAt: timestamp("dev_server_status_updated_at").defaultNow(),
160
+ tunnelUrl: text("tunnel_url"),
161
+ runnerId: text("runner_id"),
162
+ // Runner that created/manages this project
163
+ generationState: text("generation_state"),
164
+ designPreferences: jsonb("design_preferences"),
165
+ // User-specified design constraints (deprecated - use tags)
166
+ tags: jsonb("tags"),
167
+ // Tag-based configuration system
168
+ lastActivityAt: timestamp("last_activity_at").defaultNow(),
169
+ errorMessage: text("error_message"),
170
+ // GitHub integration fields
171
+ githubRepo: text("github_repo"),
172
+ // e.g., "owner/repo-name"
173
+ githubUrl: text("github_url"),
174
+ // Full repository URL
175
+ githubBranch: text("github_branch"),
176
+ // Default branch (e.g., "main")
177
+ githubLastPushedAt: timestamp("github_last_pushed_at"),
178
+ // Last push timestamp
179
+ githubAutoPush: boolean("github_auto_push").default(false),
180
+ // Auto-push after builds
181
+ githubLastSyncAt: timestamp("github_last_sync_at"),
182
+ // Last time we synced repo info
183
+ githubMeta: jsonb("github_meta"),
184
+ // Additional metadata (issues count, recent commits, etc.)
185
+ // NeonDB integration fields
186
+ neondbConnectionString: text("neondb_connection_string"),
187
+ // DATABASE_URL (encrypted/partial)
188
+ neondbClaimUrl: text("neondb_claim_url"),
189
+ // URL to claim the database
190
+ neondbHost: text("neondb_host"),
191
+ // Database host endpoint
192
+ neondbDatabase: text("neondb_database"),
193
+ // Database name
194
+ neondbCreatedAt: timestamp("neondb_created_at"),
195
+ // When database was provisioned
196
+ neondbExpiresAt: timestamp("neondb_expires_at"),
197
+ // When unclaimed DB expires (72 hours)
198
+ // Railway integration fields
199
+ railwayProjectId: text("railway_project_id"),
200
+ // Railway project ID
201
+ railwayServiceId: text("railway_service_id"),
202
+ // Railway service ID
203
+ railwayEnvironmentId: text("railway_environment_id"),
204
+ // Railway environment ID (production)
205
+ railwayDomain: text("railway_domain"),
206
+ // e.g., "myapp-production.up.railway.app"
207
+ railwayDeploymentStatus: text("railway_deployment_status"),
208
+ // 'deploying' | 'success' | 'failed' | 'crashed'
209
+ railwayLastDeployedAt: timestamp("railway_last_deployed_at"),
210
+ // Last successful deployment
211
+ // Execution mode: where builds run for this project
212
+ executionMode: text("execution_mode").default("local"),
213
+ // 'local' | 'sandbox'
214
+ sandboxId: text("sandbox_id"),
215
+ // Railway sandbox id when executionMode='sandbox' and currently running (warm)
216
+ sandboxStatus: text("sandbox_status"),
217
+ // 'provisioning' | 'running' | 'stopped' | 'failed'
218
+ sandboxCheckpoint: text("sandbox_checkpoint"),
219
+ // Railway checkpoint key for this project's saved workspace (restore point)
220
+ sandboxSubdomain: text("sandbox_subdomain"),
221
+ // Stable railgate subdomain so the preview URL persists across restarts
222
+ createdAt: timestamp("created_at").notNull().defaultNow(),
223
+ updatedAt: timestamp("updated_at").notNull().defaultNow()
224
+ }, (table) => ({
225
+ // Indexes for performance
226
+ userIdIdx: index("projects_user_id_idx").on(table.userId),
227
+ runnerIdIdx: index("projects_runner_id_idx").on(table.runnerId),
228
+ statusIdx: index("projects_status_idx").on(table.status),
229
+ lastActivityIdx: index("projects_last_activity_idx").on(table.lastActivityAt)
230
+ }));
231
+ var messages = pgTable("messages", {
232
+ id: uuid("id").primaryKey().defaultRandom(),
233
+ projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
234
+ role: text("role").notNull(),
235
+ content: text("content").notNull(),
236
+ createdAt: timestamp("created_at").notNull().defaultNow()
237
+ });
238
+ var portAllocations = pgTable("port_allocations", {
239
+ port: integer("port").primaryKey(),
240
+ framework: text("framework").notNull(),
241
+ projectId: uuid("project_id").references(() => projects.id, { onDelete: "set null" }),
242
+ reservedAt: timestamp("reserved_at").defaultNow()
243
+ });
244
+ var runningProcesses = pgTable("running_processes", {
245
+ projectId: uuid("project_id").primaryKey().notNull().references(() => projects.id, { onDelete: "cascade" }),
246
+ pid: integer("pid").notNull(),
247
+ port: integer("port"),
248
+ command: text("command"),
249
+ runnerId: text("runner_id"),
250
+ // Runner that manages this process
251
+ startedAt: timestamp("started_at").notNull().defaultNow(),
252
+ lastHealthCheck: timestamp("last_health_check"),
253
+ healthCheckFailCount: integer("health_check_fail_count").notNull().default(0)
254
+ }, (table) => ({
255
+ // Index for filtering by runner
256
+ runnerIdIdx: index("running_processes_runner_id_idx").on(table.runnerId)
257
+ }));
258
+ var generationSessions = pgTable("generation_sessions", {
259
+ id: uuid("id").primaryKey().defaultRandom(),
260
+ projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
261
+ buildId: text("build_id").notNull(),
262
+ operationType: text("operation_type"),
263
+ status: text("status").notNull().default("active"),
264
+ startedAt: timestamp("started_at").notNull().defaultNow(),
265
+ endedAt: timestamp("ended_at"),
266
+ summary: text("summary"),
267
+ rawState: jsonb("raw_state"),
268
+ isAutoFix: boolean("is_auto_fix").default(false),
269
+ // Flag for auto-fix sessions triggered by startup errors
270
+ autoFixError: text("auto_fix_error"),
271
+ // The error message that triggered the auto-fix
272
+ createdAt: timestamp("created_at").notNull().defaultNow(),
273
+ updatedAt: timestamp("updated_at").notNull().defaultNow()
274
+ }, (table) => ({
275
+ projectIdIdx: index("generation_sessions_project_id_idx").on(table.projectId),
276
+ buildIdUnique: uniqueIndex("generation_sessions_build_id_unique").on(table.buildId)
277
+ }));
278
+ var generationTodos = pgTable("generation_todos", {
279
+ id: uuid("id").primaryKey().defaultRandom(),
280
+ sessionId: uuid("session_id").notNull().references(() => generationSessions.id, { onDelete: "cascade" }),
281
+ todoIndex: integer("todo_index").notNull(),
282
+ content: text("content").notNull(),
283
+ activeForm: text("active_form"),
284
+ status: text("status").notNull(),
285
+ createdAt: timestamp("created_at").notNull().defaultNow(),
286
+ updatedAt: timestamp("updated_at").notNull().defaultNow()
287
+ }, (table) => ({
288
+ sessionIdIdx: index("generation_todos_session_id_idx").on(table.sessionId),
289
+ sessionIndexUnique: uniqueIndex("generation_todos_session_index_unique").on(table.sessionId, table.todoIndex)
290
+ }));
291
+ var generationToolCalls = pgTable("generation_tool_calls", {
292
+ id: uuid("id").primaryKey().defaultRandom(),
293
+ sessionId: uuid("session_id").notNull().references(() => generationSessions.id, { onDelete: "cascade" }),
294
+ todoIndex: integer("todo_index").notNull(),
295
+ toolCallId: text("tool_call_id").notNull(),
296
+ name: text("name").notNull(),
297
+ input: jsonb("input"),
298
+ output: jsonb("output"),
299
+ state: text("state").notNull(),
300
+ startedAt: timestamp("started_at").notNull().defaultNow(),
301
+ endedAt: timestamp("ended_at"),
302
+ createdAt: timestamp("created_at").notNull().defaultNow(),
303
+ updatedAt: timestamp("updated_at").notNull().defaultNow()
304
+ }, (table) => ({
305
+ sessionIdIdx: index("generation_tool_calls_session_id_idx").on(table.sessionId),
306
+ toolCallUnique: uniqueIndex("generation_tool_calls_call_id_unique").on(table.sessionId, table.toolCallId)
307
+ }));
308
+ var generationNotes = pgTable("generation_notes", {
309
+ id: uuid("id").primaryKey().defaultRandom(),
310
+ sessionId: uuid("session_id").notNull().references(() => generationSessions.id, { onDelete: "cascade" }),
311
+ todoIndex: integer("todo_index").notNull(),
312
+ textId: text("text_id"),
313
+ kind: text("kind").notNull().default("text"),
314
+ content: text("content").notNull(),
315
+ createdAt: timestamp("created_at").notNull().defaultNow()
316
+ }, (table) => ({
317
+ sessionIdIdx: index("generation_notes_session_id_idx").on(table.sessionId),
318
+ textIdUnique: uniqueIndex("generation_notes_text_id_unique").on(table.sessionId, table.textId).where(sql`${table.textId} is not null`)
319
+ }));
320
+ var railwayConnections = pgTable("railway_connections", {
321
+ id: uuid("id").primaryKey().defaultRandom(),
322
+ userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
323
+ // Encrypted OAuth tokens
324
+ accessTokenEncrypted: text("access_token_encrypted").notNull(),
325
+ refreshTokenEncrypted: text("refresh_token_encrypted"),
326
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
327
+ // Railway user info (from OAuth)
328
+ railwayUserId: text("railway_user_id").notNull(),
329
+ // "sub" claim from OIDC
330
+ railwayEmail: text("railway_email"),
331
+ railwayName: text("railway_name"),
332
+ // Selected workspaces from OAuth consent
333
+ defaultWorkspaceId: text("default_workspace_id"),
334
+ defaultWorkspaceName: text("default_workspace_name"),
335
+ grantedWorkspaces: jsonb("granted_workspaces").$type(),
336
+ // Connection status
337
+ status: text("status").notNull().default("active"),
338
+ // 'active' | 'disconnected' | 'expired'
339
+ createdAt: timestamp("created_at").notNull().defaultNow(),
340
+ updatedAt: timestamp("updated_at").notNull().defaultNow()
341
+ }, (table) => ({
342
+ userIdUnique: uniqueIndex("railway_connections_user_id_unique").on(table.userId),
343
+ railwayUserIdIdx: index("railway_connections_railway_user_id_idx").on(table.railwayUserId)
344
+ }));
345
+ var railwayDeployments = pgTable("railway_deployments", {
346
+ id: uuid("id").primaryKey().defaultRandom(),
347
+ projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
348
+ // Railway resource IDs
349
+ railwayProjectId: text("railway_project_id").notNull(),
350
+ railwayServiceId: text("railway_service_id").notNull(),
351
+ railwayDeploymentId: text("railway_deployment_id").notNull(),
352
+ railwayEnvironmentId: text("railway_environment_id"),
353
+ // Deployment info
354
+ status: text("status").notNull(),
355
+ // Railway deployment status
356
+ url: text("url"),
357
+ // Deployment URL
358
+ commitSha: text("commit_sha"),
359
+ // Git commit deployed
360
+ // Timestamps
361
+ deployedAt: timestamp("deployed_at").notNull().defaultNow(),
362
+ completedAt: timestamp("completed_at"),
363
+ createdAt: timestamp("created_at").notNull().defaultNow()
364
+ }, (table) => ({
365
+ projectIdIdx: index("railway_deployments_project_id_idx").on(table.projectId),
366
+ railwayDeploymentIdIdx: index("railway_deployments_railway_deployment_id_idx").on(table.railwayDeploymentId)
367
+ }));
368
+ var githubConnections = pgTable("github_connections", {
369
+ id: uuid("id").primaryKey().defaultRandom(),
370
+ userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
371
+ // Encrypted OAuth tokens
372
+ accessTokenEncrypted: text("access_token_encrypted").notNull(),
373
+ // GitHub user info (from OAuth)
374
+ githubUserId: text("github_user_id").notNull(),
375
+ // GitHub user ID
376
+ githubUsername: text("github_username").notNull(),
377
+ // GitHub username/login
378
+ githubEmail: text("github_email"),
379
+ githubAvatarUrl: text("github_avatar_url"),
380
+ // OAuth scopes granted
381
+ scopes: text("scopes"),
382
+ // Comma-separated list of scopes
383
+ // Connection status
384
+ status: text("status").notNull().default("active"),
385
+ // 'active' | 'disconnected' | 'expired'
386
+ createdAt: timestamp("created_at").notNull().defaultNow(),
387
+ updatedAt: timestamp("updated_at").notNull().defaultNow()
388
+ }, (table) => ({
389
+ userIdUnique: uniqueIndex("github_connections_user_id_unique").on(table.userId),
390
+ githubUserIdIdx: index("github_connections_github_user_id_idx").on(table.githubUserId)
391
+ }));
392
+ var serverOperations = pgTable("server_operations", {
393
+ id: uuid("id").primaryKey().defaultRandom(),
394
+ projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
395
+ operation: text("operation").notNull(),
396
+ // 'start', 'stop', 'restart'
397
+ status: text("status").notNull().default("pending"),
398
+ // 'pending', 'sent', 'ack', 'completed', 'failed', 'timeout'
399
+ runnerId: text("runner_id"),
400
+ port: integer("port"),
401
+ pid: integer("pid"),
402
+ error: text("error"),
403
+ failureReason: text("failure_reason"),
404
+ // 'port_in_use', 'health_check_timeout', 'immediate_crash', etc.
405
+ retryCount: integer("retry_count").notNull().default(0),
406
+ metadata: jsonb("metadata"),
407
+ createdAt: timestamp("created_at").notNull().defaultNow(),
408
+ sentAt: timestamp("sent_at"),
409
+ ackAt: timestamp("ack_at"),
410
+ completedAt: timestamp("completed_at")
411
+ }, (table) => ({
412
+ projectIdIdx: index("server_operations_project_id_idx").on(table.projectId),
413
+ statusIdx: index("server_operations_status_idx").on(table.status),
414
+ createdAtIdx: index("server_operations_created_at_idx").on(table.createdAt)
415
+ }));
416
+
417
+ // src/lib/db/client.ts
418
+ var { Pool } = pg;
419
+ function createPostgresClient() {
420
+ const connectionString = process.env.DATABASE_URL;
421
+ if (!connectionString) {
422
+ throw new Error(
423
+ 'DATABASE_URL is not set. Please configure your database connection:\n - Run "hatchway init" to set up a Neon database\n - Or set DATABASE_URL environment variable to your PostgreSQL connection string'
424
+ );
425
+ }
426
+ const pool = new Pool({
427
+ connectionString,
428
+ ssl: process.env.PGSSLMODE === "disable" ? false : { rejectUnauthorized: false }
429
+ });
430
+ const client = drizzle(pool, { schema: schema_exports });
431
+ return client;
432
+ }
433
+ new Proxy({}, {
434
+ get(_target, prop) {
435
+ if (!global.__db) {
436
+ global.__db = createPostgresClient();
437
+ }
438
+ return global.__db[prop];
439
+ }
440
+ });
441
+
442
+ // src/lib/logging/build-logger.ts
443
+ var BuildLogger = class {
444
+ buildId = null;
445
+ projectId = null;
446
+ /**
447
+ * Set correlation IDs for the current build
448
+ * Call this at the start of each build to enable correlation tracking
449
+ */
450
+ setBuildContext(buildId, projectId) {
451
+ this.buildId = buildId;
452
+ this.projectId = projectId;
453
+ this.log("debug", "runner", `Build context set: ${buildId} / ${projectId}`);
454
+ }
455
+ /**
456
+ * Clear correlation IDs after build completes
457
+ */
458
+ clearBuildContext() {
459
+ this.log("debug", "runner", "Build context cleared");
460
+ this.buildId = null;
461
+ this.projectId = null;
462
+ }
463
+ /**
464
+ * Core logging method - creates structured log entries
465
+ * Public for custom logging needs
466
+ */
467
+ log(level, context, message, data) {
468
+ if (process.env.SILENT_MODE === "1") {
469
+ return;
470
+ }
471
+ if (level === "info" || level === "debug") {
472
+ if (process.env.DEBUG_BUILD === "0") {
473
+ return;
474
+ }
475
+ }
476
+ ({
477
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
478
+ buildId: this.buildId ?? void 0,
479
+ projectId: this.projectId ?? void 0});
480
+ const prefix = `[${context}]`;
481
+ const icon = {
482
+ debug: "\u{1F50D}",
483
+ info: "\u{1F4CB}",
484
+ warn: "\u26A0\uFE0F ",
485
+ error: "\u274C"
486
+ }[level];
487
+ const logFn = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
488
+ if (data && Object.keys(data).length > 0) {
489
+ logFn(`${icon} ${prefix} ${message}`, data);
490
+ } else {
491
+ logFn(`${icon} ${prefix} ${message}`);
492
+ }
493
+ }
494
+ /**
495
+ * Orchestrator-specific logging methods
496
+ */
497
+ orchestrator = {
498
+ newProject: (operationType) => this.log("info", "orchestrator", `NEW PROJECT (operationType: ${operationType})`),
499
+ existingProject: (operationType) => this.log("info", "orchestrator", `EXISTING PROJECT (operationType: ${operationType})`),
500
+ templateProvided: (templateName, templateId, framework) => this.log("info", "orchestrator", `Frontend provided template: ${templateName}`, {
501
+ templateId,
502
+ framework
503
+ }),
504
+ templateSelecting: (method) => this.log("info", "orchestrator", `Template selection: ${method}`),
505
+ templateSelected: (templateName, templateId) => this.log("info", "orchestrator", `Selected template: ${templateName}`, { templateId }),
506
+ templateDownloading: (templateName, repository, target) => this.log("info", "orchestrator", `Downloading template: ${templateName}`, {
507
+ repository,
508
+ target
509
+ }),
510
+ templateDownloaded: (templateName, path, fileTreeSize) => this.log("info", "orchestrator", `Template downloaded: ${templateName}`, {
511
+ path,
512
+ fileTreeSize
513
+ }),
514
+ catalogPrepared: (catalogSize) => this.log("info", "orchestrator", `Template catalog prepared (${catalogSize} chars)`, {
515
+ catalogSize
516
+ }),
517
+ systemPromptGenerated: (size) => this.log("info", "orchestrator", `System prompt generated (${size} chars)`, { size }),
518
+ orchestrationComplete: (data) => this.log("info", "orchestrator", "Orchestration complete", data),
519
+ error: (message, error) => this.log("error", "orchestrator", message, {
520
+ error: error instanceof Error ? error.message : String(error)
521
+ })
522
+ };
523
+ /**
524
+ * Message transformer-specific logging methods
525
+ */
526
+ transformer = {
527
+ todoListFound: () => this.log("debug", "transformer", "Found Codex task list, parsing..."),
528
+ todoListParsed: (todoCount, completed, inProgress, pending) => this.log("info", "transformer", `Parsed ${todoCount} todos`, {
529
+ completed,
530
+ inProgress,
531
+ pending
532
+ }),
533
+ todoListInvalidFormat: (expected, got) => this.log("error", "transformer", "Invalid todo format from Codex", {
534
+ expected,
535
+ got: JSON.stringify(got).substring(0, 200)
536
+ }),
537
+ todoListParseError: (error, rawJson) => this.log("error", "transformer", "Failed to parse Codex todolist", {
538
+ error: String(error),
539
+ rawJson: rawJson.substring(0, 300)
540
+ }),
541
+ todoListRemoved: () => this.log("debug", "transformer", "Removed task list from chat text"),
542
+ toolStarted: (toolName, toolId) => this.log("debug", "transformer", `Tool started: ${toolName}`, { toolName, toolId }),
543
+ toolCompleted: (toolName, toolId) => this.log("debug", "transformer", `Tool completed: ${toolName}`, { toolName, toolId }),
544
+ pathViolationWarning: (toolName, path, workspace) => this.log("warn", "transformer", `Path outside workspace: ${path}`, {
545
+ toolName,
546
+ path,
547
+ workspace
548
+ }),
549
+ desktopPathDetected: (path) => this.log("error", "transformer", `DESKTOP PATH DETECTED - Likely hallucinated: ${path}`, { path })
550
+ };
551
+ /**
552
+ * Codex query-specific logging methods
553
+ */
554
+ codexQuery = {
555
+ promptBuilding: (workingDirectory, systemPromptSize, userPromptSize) => this.log("info", "codex-query", "Building Codex prompt", {
556
+ workingDirectory,
557
+ systemPromptSize,
558
+ userPromptSize
559
+ }),
560
+ threadStarting: () => this.log("info", "codex-query", "Starting Codex thread (multi-turn)"),
561
+ turnStarted: (turnNumber, maxTurns, promptSize) => this.log("info", "codex-query", `\u2550\u2550\u2550 Turn ${turnNumber}/${maxTurns} \u2550\u2550\u2550`, {
562
+ turnNumber,
563
+ maxTurns,
564
+ promptSize
565
+ }),
566
+ taskListExtracted: () => this.log("info", "codex-query", "Task list extracted and updated"),
567
+ taskListStatus: (completed, inProgress, pending, total) => this.log("info", "codex-query", `Tasks: ${completed} completed | ${inProgress} in_progress | ${pending} pending (total: ${total})`, {
568
+ completed,
569
+ inProgress,
570
+ pending,
571
+ total
572
+ }),
573
+ taskListTask: (index2, content, status, icon) => this.log("debug", "codex-query", ` ${icon} ${index2 + 1}. ${content}`, { status }),
574
+ taskListParseError: (error, rawContent) => this.log("error", "codex-query", "PARSE ERROR: Could not parse task list JSON", {
575
+ error: String(error),
576
+ rawContent: rawContent.substring(0, 200)
577
+ }),
578
+ taskListMissing: (turnNumber) => this.log("warn", "codex-query", `WARNING: No <start-todolist> tags found in Turn ${turnNumber}`, {
579
+ turnNumber
580
+ }),
581
+ turnComplete: (turnNumber, hadToolCalls, messageLength) => this.log("info", "codex-query", `Turn ${turnNumber} complete`, {
582
+ hadToolCalls,
583
+ messageLength
584
+ }),
585
+ tasksComplete: (completed, total) => this.log("info", "codex-query", `Task status: ${completed}/${total} completed`, {
586
+ completed,
587
+ total
588
+ }),
589
+ allComplete: () => this.log("info", "codex-query", "\u2705 All MVP tasks complete!"),
590
+ allTasksComplete: () => this.log("info", "codex-query", "\u2705 All MVP tasks complete!"),
591
+ taskCompleteDetected: () => this.log("info", "codex-query", "\u2705 Task complete (detected completion signal)"),
592
+ continuePrompting: (reason) => this.log("warn", "codex-query", `No tools used but not done - ${reason}`),
593
+ continuing: () => this.log("info", "codex-query", "\u23ED\uFE0F Continuing to next turn (had tool calls)"),
594
+ loopExited: (turnCount, maxTurns) => this.log("info", "codex-query", `EXITED WHILE LOOP after ${turnCount} turns`, {
595
+ turnCount,
596
+ maxTurns
597
+ }),
598
+ sessionComplete: (turnCount) => this.log("info", "codex-query", `Session complete after ${turnCount} turns`, { turnCount }),
599
+ error: (message, error) => this.log("error", "codex-query", message, {
600
+ error: error instanceof Error ? error.message : String(error)
601
+ })
602
+ };
603
+ /**
604
+ * Claude query-specific logging methods
605
+ */
606
+ claudeQuery = {
607
+ queryStarted: (model, cwd, maxTurns) => this.log("info", "claude-query", `Starting Claude query (${model})`, {
608
+ cwd,
609
+ maxTurns
610
+ }),
611
+ error: (message, error) => this.log("error", "claude-query", message, {
612
+ error: error instanceof Error ? error.message : String(error)
613
+ })
614
+ };
615
+ /**
616
+ * Runner-specific logging methods
617
+ */
618
+ runner = {
619
+ workspaceRoot: (path) => this.log("info", "runner", `Workspace root: ${path}`, { path }),
620
+ commandReceived: (commandType, projectId) => this.log("info", "runner", `Received command: ${commandType}`, { commandType, projectId }),
621
+ buildOperation: (operationType, projectSlug, agentId) => this.log("info", "runner", `Build operation: ${operationType}`, {
622
+ operationType,
623
+ projectSlug,
624
+ agentId
625
+ }),
626
+ templateProvided: (templateId) => this.log("info", "runner", `Template provided by frontend: ${templateId}`, { templateId }),
627
+ buildStreamCreated: () => this.log("info", "runner", "Build stream created, starting to process chunks..."),
628
+ firstChunkReceived: (agentLabel) => this.log("info", "runner", `First chunk received from ${agentLabel}`, { agentLabel }),
629
+ streamEnded: (chunkCount) => this.log("info", "runner", `Stream ended after ${chunkCount} chunks`, { chunkCount }),
630
+ buildCompleted: (projectId) => this.log("info", "runner", `Build completed successfully`, { projectId }),
631
+ buildFailed: (error) => this.log("error", "runner", `Build failed: ${error}`, { error }),
632
+ portDetected: (port) => this.log("info", "runner", `Port detected: ${port}`, { port }),
633
+ tunnelCreated: (port, tunnelUrl) => this.log("info", "runner", `Tunnel created: ${tunnelUrl} \u2192 localhost:${port}`, {
634
+ port,
635
+ tunnelUrl
636
+ }),
637
+ error: (message, error, context) => this.log("error", "runner", message, {
638
+ error: error instanceof Error ? error.message : String(error),
639
+ stack: error instanceof Error ? error.stack : void 0,
640
+ ...context
641
+ })
642
+ };
643
+ /**
644
+ * Build stream-specific logging (tool calls, text, etc.)
645
+ */
646
+ build = {
647
+ agentText: (agentLabel, text2) => {
648
+ const truncated = text2.length > 200 ? text2.slice(0, 200) + "..." : text2;
649
+ this.log("debug", "build", `${agentLabel}: ${truncated}`, {
650
+ agentLabel,
651
+ textLength: text2.length
652
+ });
653
+ },
654
+ agentThinking: (thinking) => {
655
+ const truncated = thinking.length > 300 ? thinking.slice(0, 300) + "..." : thinking;
656
+ this.log("debug", "build", `Thinking: ${truncated}`, {
657
+ thinkingLength: thinking.length
658
+ });
659
+ },
660
+ toolCalled: (toolName, toolId, inputSize) => this.log("info", "build", `Tool called: ${toolName} (${toolId})`, {
661
+ toolName,
662
+ toolId,
663
+ inputSize
664
+ }),
665
+ toolResult: (toolId, outputSize, isError) => this.log(isError ? "error" : "info", "build", `Tool result (${toolId})`, {
666
+ toolId,
667
+ outputSize,
668
+ isError
669
+ }),
670
+ runCommandDetected: (runCommand) => this.log("info", "build", `Detected runCommand: ${runCommand}`, { runCommand })
671
+ };
672
+ /**
673
+ * WebSocket-specific logging methods
674
+ */
675
+ websocket = {
676
+ serverCreated: (instanceId) => this.log("info", "websocket", `WebSocket server instance created`, { instanceId }),
677
+ serverInitialized: (path, runnerPath) => this.log("info", "websocket", `Server initialized`, { path, runnerPath }),
678
+ clientConnected: (clientId, projectId, sessionId) => this.log("info", "websocket", `Client connected: ${clientId}`, { clientId, projectId, sessionId }),
679
+ clientDisconnected: (clientId) => this.log("info", "websocket", `Client disconnected: ${clientId}`, { clientId }),
680
+ clientSubscribed: (clientId, projectId) => this.log("info", "websocket", `Client subscribed to project: ${projectId}`, { clientId, projectId }),
681
+ clientTimeout: (clientId) => this.log("warn", "websocket", `Client timeout: ${clientId}`, { clientId }),
682
+ runnerConnected: (runnerId) => this.log("info", "websocket", `Runner connected: ${runnerId}`, { runnerId }),
683
+ runnerDisconnected: (runnerId, code) => this.log("info", "websocket", `Runner disconnected: ${runnerId}`, { runnerId, code }),
684
+ runnerNotConnected: (runnerId, commandType) => this.log("warn", "websocket", `Cannot send command to runner ${runnerId}: not connected`, {
685
+ runnerId,
686
+ commandType
687
+ }),
688
+ runnerAuthRejected: () => this.log("warn", "websocket", `Runner connection rejected: invalid auth`),
689
+ runnerAuthMissing: () => this.log("error", "websocket", `RUNNER_SHARED_SECRET is not configured`),
690
+ runnerStaleRemoved: (runnerId) => this.log("info", "websocket", `Removing stale runner connection: ${runnerId}`, { runnerId }),
691
+ commandSent: (runnerId, commandType, traceAttached) => this.log("debug", "websocket", `Sent command to runner: ${commandType}`, {
692
+ runnerId,
693
+ commandType,
694
+ traceAttached
695
+ }),
696
+ eventReceived: (runnerId, eventType) => this.log("debug", "websocket", `Received event from runner: ${eventType}`, {
697
+ runnerId,
698
+ eventType
699
+ }),
700
+ broadcastToolCall: (toolName, toolState, subscriberCount) => this.log("info", "websocket", `Broadcasting planning tool: ${toolName} (state=${toolState})`, {
701
+ toolName,
702
+ toolState,
703
+ subscriberCount
704
+ }),
705
+ broadcastBuildComplete: (projectId, sessionId, subscriberCount) => this.log("info", "websocket", `Broadcasting build-complete`, {
706
+ projectId,
707
+ sessionId,
708
+ subscriberCount
709
+ }),
710
+ unknownUpgradePath: (pathname) => this.log("warn", "websocket", `Unknown upgrade path: ${pathname}`, { pathname }),
711
+ shutdown: () => this.log("info", "websocket", `Shutting down server...`),
712
+ shutdownComplete: () => this.log("info", "websocket", `Server shut down`),
713
+ error: (message, error, context) => this.log("error", "websocket", message, {
714
+ error: error instanceof Error ? error.message : String(error),
715
+ stack: error instanceof Error ? error.stack : void 0,
716
+ ...context
717
+ })
718
+ };
719
+ /**
720
+ * Port allocator-specific logging methods
721
+ */
722
+ portAllocator = {
723
+ portAllocated: (port, projectId) => this.log("info", "port-allocator", `Port allocated: ${port}`, { port, projectId }),
724
+ portReleased: (port, projectId) => this.log("info", "port-allocator", `Port released: ${port}`, { port, projectId }),
725
+ portInUse: (port) => this.log("warn", "port-allocator", `Port ${port} is already in use`, { port }),
726
+ portRangeExhausted: (minPort, maxPort) => this.log("error", "port-allocator", `No available ports in range ${minPort}-${maxPort}`, {
727
+ minPort,
728
+ maxPort
729
+ }),
730
+ portConflict: (port, projectId, existingProjectId) => this.log("warn", "port-allocator", `Port ${port} conflict detected`, {
731
+ port,
732
+ projectId,
733
+ existingProjectId
734
+ }),
735
+ allocationsCleared: (count) => this.log("info", "port-allocator", `Cleared ${count} port allocations`, { count }),
736
+ error: (message, error, context) => this.log("error", "port-allocator", message, {
737
+ error: error instanceof Error ? error.message : String(error),
738
+ ...context
739
+ })
740
+ };
741
+ /**
742
+ * Process manager-specific logging methods
743
+ */
744
+ processManager = {
745
+ processStarting: (projectId, command, cwd) => this.log("info", "process-manager", `Starting process: ${command}`, {
746
+ projectId,
747
+ command,
748
+ cwd
749
+ }),
750
+ processStarted: (projectId, pid) => this.log("info", "process-manager", `Process started`, { projectId, pid }),
751
+ processOutput: (projectId, output) => this.log("debug", "process-manager", `Process output: ${output.substring(0, 100)}`, {
752
+ projectId,
753
+ outputLength: output.length
754
+ }),
755
+ processError: (projectId, error) => this.log("error", "process-manager", `Process error: ${error}`, { projectId, error }),
756
+ processExited: (projectId, code, signal) => this.log("info", "process-manager", `Process exited`, { projectId, code, signal }),
757
+ processStopped: (projectId) => this.log("info", "process-manager", `Process stopped`, { projectId }),
758
+ processNotFound: (projectId) => this.log("warn", "process-manager", `Process not found for project: ${projectId}`, { projectId }),
759
+ processKilled: (projectId, signal) => this.log("info", "process-manager", `Process killed with signal: ${signal}`, {
760
+ projectId,
761
+ signal
762
+ }),
763
+ processCleanup: (projectId) => this.log("info", "process-manager", `Cleaning up process`, { projectId }),
764
+ processListRetrieved: (count) => this.log("debug", "process-manager", `Retrieved ${count} running processes`, { count }),
765
+ error: (message, error, context) => this.log("error", "process-manager", message, {
766
+ error: error instanceof Error ? error.message : String(error),
767
+ stack: error instanceof Error ? error.stack : void 0,
768
+ ...context
769
+ })
770
+ };
771
+ /**
772
+ * Build events-specific logging methods
773
+ */
774
+ buildEvents = {
775
+ eventReceived: (eventType, projectId, sessionId) => this.log("info", "build-events", `Received event: ${eventType}`, {
776
+ eventType,
777
+ projectId,
778
+ sessionId
779
+ }),
780
+ eventProcessed: (eventType, projectId) => this.log("debug", "build-events", `Processed event: ${eventType}`, { eventType, projectId }),
781
+ buildStarted: (projectId, sessionId) => this.log("info", "build-events", `Build started`, { projectId, sessionId }),
782
+ buildCompleted: (projectId, sessionId, success) => this.log("info", "build-events", `Build ${success ? "completed" : "failed"}`, {
783
+ projectId,
784
+ sessionId,
785
+ success
786
+ }),
787
+ portDetected: (projectId, port) => this.log("info", "build-events", `Port detected: ${port}`, { projectId, port }),
788
+ devServerStarted: (projectId, port, url) => this.log("info", "build-events", `Dev server started: ${url}`, { projectId, port, url }),
789
+ devServerError: (projectId, error) => this.log("error", "build-events", `Dev server error: ${error}`, { projectId, error }),
790
+ toolCallReceived: (toolName, toolId) => this.log("debug", "build-events", `Tool call: ${toolName}`, { toolName, toolId }),
791
+ logChunkReceived: (projectId, chunkSize) => this.log("debug", "build-events", `Log chunk received`, { projectId, chunkSize }),
792
+ invalidEvent: (reason) => this.log("warn", "build-events", `Invalid event: ${reason}`, { reason }),
793
+ error: (message, error, context) => this.log("error", "build-events", message, {
794
+ error: error instanceof Error ? error.message : String(error),
795
+ stack: error instanceof Error ? error.stack : void 0,
796
+ ...context
797
+ })
798
+ };
799
+ };
800
+ var buildLogger = new BuildLogger();
801
+ async function detectFrameworkFromFilesystem(projectPath) {
802
+ try {
803
+ let cachedPkg = null;
804
+ const loadPackageJson = async () => {
805
+ if (cachedPkg) return cachedPkg;
806
+ const pkgPath = join(projectPath, "package.json");
807
+ if (!existsSync(pkgPath)) {
808
+ cachedPkg = null;
809
+ return cachedPkg;
810
+ }
811
+ const pkgContent = await readFile(pkgPath, "utf-8");
812
+ const pkg2 = JSON.parse(pkgContent);
813
+ cachedPkg = {
814
+ deps: { ...pkg2.dependencies ?? {}, ...pkg2.peerDependencies ?? {} },
815
+ devDeps: pkg2.devDependencies ?? {},
816
+ devScript: pkg2.scripts?.dev?.toLowerCase() ?? ""
817
+ };
818
+ return cachedPkg;
819
+ };
820
+ const hasTanStackDependency = async () => {
821
+ const pkg2 = await loadPackageJson();
822
+ if (!pkg2) return false;
823
+ const combined = { ...pkg2.deps, ...pkg2.devDeps };
824
+ if (combined["@tanstack/react-start"]) return true;
825
+ if (pkg2.devScript.includes("tanstack")) return true;
826
+ return false;
827
+ };
828
+ if (existsSync(join(projectPath, "astro.config.mjs")) || existsSync(join(projectPath, "astro.config.ts")) || existsSync(join(projectPath, "astro.config.js"))) {
829
+ return "astro";
830
+ }
831
+ if (existsSync(join(projectPath, "next.config.ts")) || existsSync(join(projectPath, "next.config.js")) || existsSync(join(projectPath, "next.config.mjs"))) {
832
+ return "next";
833
+ }
834
+ if (existsSync(join(projectPath, "vite.config.ts")) || existsSync(join(projectPath, "vite.config.js"))) {
835
+ if (await hasTanStackDependency()) {
836
+ return "tanstack";
837
+ }
838
+ return "vite";
839
+ }
840
+ const pkg = await loadPackageJson();
841
+ if (pkg) {
842
+ const allDeps = { ...pkg.deps, ...pkg.devDeps };
843
+ if (allDeps["astro"]) return "astro";
844
+ if (allDeps["@tanstack/react-start"]) return "tanstack";
845
+ if (allDeps["next"]) return "next";
846
+ if (allDeps["vite"]) return "vite";
847
+ if (pkg.devScript.includes("tanstack")) return "tanstack";
848
+ if (pkg.devScript.includes("astro")) return "astro";
849
+ if (pkg.devScript.includes("next")) return "next";
850
+ if (pkg.devScript.includes("vite")) return "vite";
851
+ }
852
+ } catch (error) {
853
+ buildLogger.portAllocator.error("Failed to detect framework from filesystem", error);
854
+ }
855
+ return null;
856
+ }
857
+
858
+ export { detectFrameworkFromFilesystem };
859
+ //# sourceMappingURL=port-allocator-DAjm7X-F.js.map