episoda 0.2.90 → 0.2.91

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.
@@ -2786,7 +2786,7 @@ var require_package = __commonJS({
2786
2786
  "package.json"(exports2, module2) {
2787
2787
  module2.exports = {
2788
2788
  name: "episoda",
2789
- version: "0.2.90",
2789
+ version: "0.2.91",
2790
2790
  description: "CLI tool for Episoda local development workflow orchestration",
2791
2791
  main: "dist/index.js",
2792
2792
  types: "dist/index.d.ts",
@@ -7379,6 +7379,130 @@ async function handleWorktreeList(workspaceSlug, projectSlug) {
7379
7379
  return { success: false, worktrees: [], error: error.message };
7380
7380
  }
7381
7381
  }
7382
+ async function handleProjectEject(request2) {
7383
+ const { workspaceSlug, projectSlug, projectId } = request2;
7384
+ console.log(`[Worktree] EP1144: Ejecting project ${projectSlug} from workspace ${workspaceSlug}`);
7385
+ try {
7386
+ const projectPath = getProjectPath(workspaceSlug, projectSlug);
7387
+ const bareRepoPath = path14.join(projectPath, ".bare");
7388
+ if (!fs13.existsSync(projectPath)) {
7389
+ console.log(`[Worktree] EP1144: Project path not found, nothing to eject: ${projectPath}`);
7390
+ return { success: true };
7391
+ }
7392
+ if (!fs13.existsSync(bareRepoPath)) {
7393
+ console.log(`[Worktree] EP1144: Bare repo not found, nothing to eject: ${bareRepoPath}`);
7394
+ return { success: true };
7395
+ }
7396
+ const manager = new WorktreeManager(projectPath);
7397
+ const initialized2 = await manager.initialize();
7398
+ if (initialized2) {
7399
+ const worktrees = manager.listWorktrees();
7400
+ if (worktrees.length > 0) {
7401
+ return {
7402
+ success: false,
7403
+ error: `Cannot eject project with ${worktrees.length} active worktrees`
7404
+ };
7405
+ }
7406
+ }
7407
+ const artifactsPath = path14.join(projectPath, "artifacts");
7408
+ if (fs13.existsSync(artifactsPath)) {
7409
+ await persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug);
7410
+ }
7411
+ console.log(`[Worktree] EP1144: Removing project directory: ${projectPath}`);
7412
+ await fs13.promises.rm(projectPath, { recursive: true, force: true });
7413
+ console.log(`[Worktree] EP1144: Successfully ejected project ${projectSlug}`);
7414
+ return { success: true };
7415
+ } catch (error) {
7416
+ console.error(`[Worktree] EP1144: Eject failed: ${error.message}`);
7417
+ return {
7418
+ success: false,
7419
+ error: error.message
7420
+ };
7421
+ }
7422
+ }
7423
+ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug) {
7424
+ const MAX_ARTIFACT_SIZE = 10 * 1024 * 1024;
7425
+ try {
7426
+ const files = await fs13.promises.readdir(artifactsPath);
7427
+ if (files.length === 0) {
7428
+ return;
7429
+ }
7430
+ console.log(`[Worktree] EP1144: Found ${files.length} artifacts to persist for ${projectSlug}`);
7431
+ const config = await getConfigForApi();
7432
+ const containerId = process.env.EPISODA_CONTAINER_ID;
7433
+ if (!config?.access_token || !containerId || !projectId) {
7434
+ console.warn(`[Worktree] EP1144: Cannot persist artifacts - missing auth, container ID, or project ID`);
7435
+ console.warn(`[Worktree] EP1144: ${files.length} artifacts will be lost for ${projectSlug}`);
7436
+ return;
7437
+ }
7438
+ const artifacts = [];
7439
+ for (const fileName of files) {
7440
+ const filePath = path14.join(artifactsPath, fileName);
7441
+ const stat = await fs13.promises.stat(filePath);
7442
+ if (stat.isDirectory()) {
7443
+ continue;
7444
+ }
7445
+ if (stat.size > MAX_ARTIFACT_SIZE) {
7446
+ console.warn(`[Worktree] EP1144: Skipping artifact ${fileName} - exceeds 10MB limit`);
7447
+ continue;
7448
+ }
7449
+ try {
7450
+ const content = await fs13.promises.readFile(filePath);
7451
+ const base64Content = content.toString("base64");
7452
+ const ext = path14.extname(fileName).toLowerCase();
7453
+ const mimeTypes = {
7454
+ ".json": "application/json",
7455
+ ".txt": "text/plain",
7456
+ ".md": "text/markdown",
7457
+ ".html": "text/html",
7458
+ ".css": "text/css",
7459
+ ".js": "application/javascript",
7460
+ ".ts": "application/typescript",
7461
+ ".png": "image/png",
7462
+ ".jpg": "image/jpeg",
7463
+ ".jpeg": "image/jpeg",
7464
+ ".gif": "image/gif",
7465
+ ".svg": "image/svg+xml",
7466
+ ".pdf": "application/pdf"
7467
+ };
7468
+ artifacts.push({
7469
+ name: fileName,
7470
+ content: base64Content,
7471
+ mime_type: mimeTypes[ext] || "application/octet-stream"
7472
+ });
7473
+ } catch (readError) {
7474
+ console.warn(`[Worktree] EP1144: Failed to read artifact ${fileName}: ${readError.message}`);
7475
+ }
7476
+ }
7477
+ if (artifacts.length === 0) {
7478
+ console.log(`[Worktree] EP1144: No valid artifacts to persist`);
7479
+ return;
7480
+ }
7481
+ const apiUrl = config.api_url || "https://episoda.dev";
7482
+ const url = `${apiUrl}/api/cloud/containers/${containerId}/artifacts`;
7483
+ console.log(`[Worktree] EP1144: Uploading ${artifacts.length} artifacts to ${url}`);
7484
+ const response = await fetch(url, {
7485
+ method: "POST",
7486
+ headers: {
7487
+ "Authorization": `Bearer ${config.access_token}`,
7488
+ "Content-Type": "application/json"
7489
+ },
7490
+ body: JSON.stringify({
7491
+ project_id: projectId,
7492
+ artifacts
7493
+ })
7494
+ });
7495
+ if (!response.ok) {
7496
+ const text = await response.text();
7497
+ console.warn(`[Worktree] EP1144: Artifact upload failed: ${response.status} ${text}`);
7498
+ return;
7499
+ }
7500
+ const result = await response.json();
7501
+ console.log(`[Worktree] EP1144: Successfully persisted ${result.data?.uploaded || 0} artifacts for ${projectSlug}`);
7502
+ } catch (error) {
7503
+ console.warn(`[Worktree] EP1144: Failed to persist artifacts: ${error.message}`);
7504
+ }
7505
+ }
7382
7506
 
7383
7507
  // src/daemon/handlers/stale-commit-cleanup.ts
7384
7508
  var import_child_process9 = require("child_process");
@@ -9400,6 +9524,9 @@ var Daemon = class _Daemon {
9400
9524
  this.ipcServer.on("worktree-list", async (params) => {
9401
9525
  return handleWorktreeList(params.workspaceSlug, params.projectSlug);
9402
9526
  });
9527
+ this.ipcServer.on("project-eject", async (params) => {
9528
+ return handleProjectEject(params);
9529
+ });
9403
9530
  }
9404
9531
  /**
9405
9532
  * Restore WebSocket connections for tracked projects
@@ -9645,6 +9772,10 @@ var Daemon = class _Daemon {
9645
9772
  cmd.projectSlug || ""
9646
9773
  );
9647
9774
  break;
9775
+ // EP1144: Project ejection for Tier 1 cleanup
9776
+ case "eject_project":
9777
+ result = await handleProjectEject(cmd);
9778
+ break;
9648
9779
  default:
9649
9780
  result = {
9650
9781
  success: false,