@ultrst-armada/kloks 1.3.0 → 1.4.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.
@@ -37,10 +37,48 @@ import {
37
37
  b2UploadFile
38
38
  } from "./chunk-4QEMALXF.js";
39
39
  import {
40
+ RemoteWorkspaceService,
40
41
  SyncEngine,
42
+ WorkingWorkspaceService,
41
43
  WorkspaceCloneSyncEngine,
42
- WorkspaceFS
43
- } from "./chunk-KTLD7Z4L.js";
44
+ WorkspaceFS,
45
+ b2DeleteFileVersion,
46
+ b2DeleteFolder,
47
+ b2ListBuckets as b2ListBuckets2,
48
+ cleanDbUrl,
49
+ createWorkspace,
50
+ deleteWorkspace,
51
+ deleteWorkspaceFile,
52
+ deleteWorkspaceFolder,
53
+ findUserWorkspace,
54
+ findWorkspaceRecord,
55
+ getB2BucketId,
56
+ getDefaultWorkingWorkspaceDir,
57
+ getUnifiedDbUrl,
58
+ getWorkingWorkspaceConfig,
59
+ getWorkingWorkspaceDir,
60
+ getWorkspaceFolderKey,
61
+ getWorkspaceFolderPrefix,
62
+ getWorkspaceStorageDetails,
63
+ getWorkspaces,
64
+ isB2Workspace,
65
+ listWorkspaceFiles,
66
+ loadWorkspaceData,
67
+ queryUnifiedDb,
68
+ readWorkspaceFile,
69
+ remoteWorkspaceService,
70
+ renameAppInWorkspaceData,
71
+ resolveWorkspaceConnection,
72
+ saveWorkspaceData,
73
+ setWorkingWorkspaceConfig,
74
+ syncWorkspace,
75
+ syncWorkspaceCreateToBucket,
76
+ syncWorkspaceDataToBucket,
77
+ syncWorkspaceDeleteToBucket,
78
+ updateWorkspace,
79
+ workingWorkspaceService,
80
+ writeWorkspaceFile
81
+ } from "./chunk-DZ4ZGIZM.js";
44
82
  import {
45
83
  AuthServiceDefinition,
46
84
  AuthServiceFacade,
@@ -83,6 +121,11 @@ import {
83
121
  withAuth,
84
122
  withCors
85
123
  } from "./chunk-TWCEVES2.js";
124
+ import {
125
+ BackendProvider,
126
+ useBackend,
127
+ useBackendClient
128
+ } from "./chunk-G3ZOSXUR.js";
86
129
  import {
87
130
  AdminGuard,
88
131
  BaseAccountProvider,
@@ -96,10 +139,13 @@ import {
96
139
  SessionManager
97
140
  } from "./chunk-25Y7AVTP.js";
98
141
  import {
99
- BackendProvider,
100
- useBackend,
101
- useBackendClient
102
- } from "./chunk-G3ZOSXUR.js";
142
+ AuditLogger,
143
+ ControlPlaneManager,
144
+ DatabaseClusterManager,
145
+ detectDatabaseEngine,
146
+ getSystemDatabaseUrls,
147
+ resolveSystemDatabaseClusterNodes
148
+ } from "./chunk-RCCQ7IRM.js";
103
149
  import {
104
150
  BackendRegistry,
105
151
  ConcurrencyConflictError,
@@ -122,14 +168,6 @@ import {
122
168
  WebSocketTransport,
123
169
  createBackendClient
124
170
  } from "./chunk-NIP22V2G.js";
125
- import {
126
- AuditLogger,
127
- ControlPlaneManager,
128
- DatabaseClusterManager,
129
- detectDatabaseEngine,
130
- getSystemDatabaseUrls,
131
- resolveSystemDatabaseClusterNodes
132
- } from "./chunk-RCCQ7IRM.js";
133
171
  import {
134
172
  JobDependencyGraph,
135
173
  ResultFlowBridge,
@@ -218,40 +256,6 @@ function validateServiceDefinition(def) {
218
256
  // src/core/authCore.ts
219
257
  import crypto2 from "crypto";
220
258
  import bcrypt from "bcryptjs";
221
-
222
- // src/core/unifiedDb.ts
223
- import { neon } from "@neondatabase/serverless";
224
- function cleanDbUrl(url) {
225
- if (!url || typeof url !== "string") return "";
226
- return url.trim().replace(/^["']|["']$/g, "");
227
- }
228
- function getUnifiedDbUrl(env) {
229
- const fromEnv = cleanDbUrl(env?.SYSTEM_DATABASE_URL_1) || cleanDbUrl(env?.DATABASE_URL) || cleanDbUrl(env?.SYSTEM_DATABASE_URL_2);
230
- if (fromEnv) return fromEnv;
231
- if (typeof process !== "undefined" && process.env) {
232
- return cleanDbUrl(process.env.SYSTEM_DATABASE_URL_1) || cleanDbUrl(process.env.DATABASE_URL) || cleanDbUrl(process.env.SYSTEM_DATABASE_URL_2) || cleanDbUrl(process.env.SYSTEM_DATABASE_URL_3) || "";
233
- }
234
- return "";
235
- }
236
- async function queryUnifiedDb(sqlText, params = [], env) {
237
- const dbUrl = getUnifiedDbUrl(env);
238
- if (!dbUrl) {
239
- throw new Error("[UnifiedDB] No database URL configured in environment.");
240
- }
241
- const sql = neon(dbUrl);
242
- try {
243
- const rows = await (sql.query ? sql.query(sqlText, params) : sql(sqlText, params));
244
- return {
245
- rows: Array.isArray(rows) ? rows : [],
246
- rowCount: Array.isArray(rows) ? rows.length : 0
247
- };
248
- } catch (err) {
249
- console.error("[UnifiedDB] Query execution error:", err.message);
250
- throw err;
251
- }
252
- }
253
-
254
- // src/core/authCore.ts
255
259
  async function loginUser(email, password, isAdmin = false, env) {
256
260
  const normEmail = (email || "").trim().toLowerCase();
257
261
  const rawPassword = (password || "").trim();
@@ -327,473 +331,11 @@ async function getUsers(env) {
327
331
  }
328
332
  }
329
333
 
330
- // src/core/b2DirectCore.ts
331
- var authCache = /* @__PURE__ */ new Map();
332
- var bucketIdCache = /* @__PURE__ */ new Map();
333
- async function authorizeB22(keyId, applicationKey) {
334
- const cacheKey = `${keyId.trim()}:${applicationKey.trim()}`;
335
- const cached = authCache.get(cacheKey);
336
- if (cached && Date.now() < cached.expiresAt) return cached.auth;
337
- const authHeader = typeof Buffer !== "undefined" ? Buffer.from(`${keyId.trim()}:${applicationKey.trim()}`).toString("base64") : btoa(`${keyId.trim()}:${applicationKey.trim()}`);
338
- let res = await fetch("https://api.backblazeb2.com/b2api/v2/b2_authorize_account", {
339
- headers: { Authorization: `Basic ${authHeader}` },
340
- signal: AbortSignal.timeout(1e4)
341
- });
342
- if (!res.ok) {
343
- res = await fetch("https://api.backblazeb2.com/b2api/v4/b2_authorize_account", {
344
- headers: { Authorization: `Basic ${authHeader}` },
345
- signal: AbortSignal.timeout(1e4)
346
- });
347
- }
348
- if (!res.ok) {
349
- const errText = await res.text();
350
- throw new Error(`Backblaze B2 authorization failed (${res.status}): ${errText}`);
351
- }
352
- const data = await res.json();
353
- const auth = {
354
- accountId: data.accountId,
355
- authorizationToken: data.authorizationToken,
356
- apiUrl: (data.apiInfo?.storageApi?.apiUrl || data.apiUrl || "https://api.backblazeb2.com").replace(/\/+$/, ""),
357
- downloadUrl: (data.apiInfo?.storageApi?.downloadUrl || data.downloadUrl || data.apiUrl || "https://f003.backblazeb2.com").replace(/\/+$/, ""),
358
- s3ApiUrl: data.apiInfo?.storageApi?.s3ApiUrl || data.s3ApiUrl,
359
- allowed: data.apiInfo?.storageApi?.allowed || data.allowed
360
- };
361
- authCache.set(cacheKey, { auth, expiresAt: Date.now() + 20 * 60 * 1e3 });
362
- return auth;
363
- }
364
- async function b2ListBuckets2(keyId, applicationKey, bucketId, bucketName) {
365
- const auth = await authorizeB22(keyId, applicationKey);
366
- const rBucketId = bucketId || auth.allowed?.bucketId || auth.allowed?.buckets?.[0]?.bucketId;
367
- const rBucketName = bucketName || auth.allowed?.bucketName || auth.allowed?.buckets?.[0]?.bucketName;
368
- if (!auth.allowed?.bucketId && !auth.allowed?.bucketName) {
369
- try {
370
- const res = await fetch(`${auth.apiUrl}/b2api/v2/b2_list_buckets`, {
371
- method: "POST",
372
- headers: { Authorization: auth.authorizationToken, "Content-Type": "application/json" },
373
- body: JSON.stringify({ accountId: auth.accountId }),
374
- signal: AbortSignal.timeout(8e3)
375
- });
376
- if (res.ok) {
377
- const data = await res.json();
378
- if (Array.isArray(data.buckets) && data.buckets.length > 0) return data.buckets;
379
- }
380
- } catch (err) {
381
- console.warn("[B2DirectCore] list_buckets query failed:", err.message);
382
- }
383
- }
384
- if (rBucketId || rBucketName) {
385
- const body = { accountId: auth.accountId };
386
- if (rBucketId) body.bucketId = rBucketId;
387
- else if (rBucketName) body.bucketName = rBucketName;
388
- try {
389
- const res = await fetch(`${auth.apiUrl}/b2api/v2/b2_list_buckets`, {
390
- method: "POST",
391
- headers: { Authorization: auth.authorizationToken, "Content-Type": "application/json" },
392
- body: JSON.stringify(body),
393
- signal: AbortSignal.timeout(8e3)
394
- });
395
- if (res.ok) {
396
- const data = await res.json();
397
- if (Array.isArray(data.buckets) && data.buckets.length > 0) return data.buckets;
398
- }
399
- } catch {
400
- }
401
- return [{
402
- accountId: auth.accountId,
403
- bucketId: rBucketId || "restricted",
404
- bucketName: rBucketName || (bucketName || "default-bucket"),
405
- bucketType: "allPrivate"
406
- }];
407
- }
408
- return [];
409
- }
410
- async function getB2BucketId(keyId, applicationKey, bucketName) {
411
- const cacheKey = `${keyId.trim()}:${bucketName.trim()}`;
412
- if (bucketIdCache.has(cacheKey)) return bucketIdCache.get(cacheKey);
413
- const buckets = await b2ListBuckets2(keyId, applicationKey, void 0, bucketName);
414
- const match = buckets.find((b) => b.bucketName === bucketName);
415
- if (!match) throw new Error(`B2 bucket "${bucketName}" not found in account`);
416
- bucketIdCache.set(cacheKey, match.bucketId);
417
- return match.bucketId;
418
- }
419
- async function b2UploadFile2(keyId, applicationKey, bucketName, fileName, buffer, contentType = "b2/x-auto") {
420
- const auth = await authorizeB22(keyId, applicationKey);
421
- const bucketId = await getB2BucketId(keyId, applicationKey, bucketName);
422
- const urlRes = await fetch(`${auth.apiUrl}/b2api/v2/b2_get_upload_url`, {
423
- method: "POST",
424
- headers: { Authorization: auth.authorizationToken, "Content-Type": "application/json" },
425
- body: JSON.stringify({ bucketId })
426
- });
427
- if (!urlRes.ok) throw new Error(`B2 get upload url failed (${urlRes.status})`);
428
- const { uploadUrl, authorizationToken } = await urlRes.json();
429
- const encodedFileName = encodeURIComponent(fileName).replace(/%2F/g, "/");
430
- const uploadRes = await fetch(uploadUrl, {
431
- method: "POST",
432
- headers: {
433
- Authorization: authorizationToken,
434
- "X-Bz-File-Name": encodedFileName,
435
- "Content-Type": contentType,
436
- "Content-Length": String(buffer.byteLength),
437
- "X-Bz-Content-Sha1": "do_not_verify"
438
- },
439
- body: buffer
440
- });
441
- if (!uploadRes.ok) throw new Error(`B2 upload failed for ${fileName} (${uploadRes.status}): ${await uploadRes.text()}`);
442
- return uploadRes.json();
443
- }
444
- async function b2DownloadFile2(keyId, applicationKey, bucketName, fileName) {
445
- const auth = await authorizeB22(keyId, applicationKey);
446
- const encodedName = encodeURIComponent(fileName).replace(/%2F/g, "/");
447
- const res = await fetch(`${auth.downloadUrl}/file/${bucketName}/${encodedName}`, { headers: { Authorization: auth.authorizationToken } });
448
- if (res.status === 404) return null;
449
- if (!res.ok) throw new Error(`B2 download failed for ${fileName} (${res.status})`);
450
- return new Uint8Array(await res.arrayBuffer());
451
- }
452
- async function b2ListFileNames2(keyId, applicationKey, bucketName, prefix = "") {
453
- const auth = await authorizeB22(keyId, applicationKey);
454
- const bucketId = await getB2BucketId(keyId, applicationKey, bucketName);
455
- const res = await fetch(`${auth.apiUrl}/b2api/v2/b2_list_file_names`, {
456
- method: "POST",
457
- headers: { Authorization: auth.authorizationToken, "Content-Type": "application/json" },
458
- body: JSON.stringify({ bucketId, prefix, maxFileCount: 1e3 })
459
- });
460
- if (!res.ok) throw new Error(`B2 list files failed (${res.status})`);
461
- const data = await res.json();
462
- return data.files || [];
463
- }
464
- async function b2DeleteFileVersion(keyId, applicationKey, fileName, fileId) {
465
- const auth = await authorizeB22(keyId, applicationKey);
466
- const res = await fetch(`${auth.apiUrl}/b2api/v2/b2_delete_file_version`, {
467
- method: "POST",
468
- headers: { Authorization: auth.authorizationToken, "Content-Type": "application/json" },
469
- body: JSON.stringify({ fileName, fileId })
470
- });
471
- if (!res.ok) throw new Error(`B2 delete file version failed (${res.status})`);
472
- return res.json();
473
- }
474
- async function b2DeleteFolder(keyId, applicationKey, bucketName, folderPrefix) {
475
- const cleanPrefix = folderPrefix.replace(/\/+$/, "") + "/";
476
- const files = await b2ListFileNames2(keyId, applicationKey, bucketName, cleanPrefix);
477
- let deletedCount = 0;
478
- for (const file of files) {
479
- await b2DeleteFileVersion(keyId, applicationKey, file.fileName, file.fileId);
480
- deletedCount++;
481
- }
482
- return { success: true, deletedCount };
483
- }
484
-
485
- // src/core/workspaceSyncEngine.ts
486
- import fs from "fs";
487
- import path from "path";
488
-
489
- // src/core/workspaceCanonical.ts
490
- function formatCanonicalWorkspaceFolderName2(appName, userName, workspaceName) {
491
- const safeApp = (appName || "qwnsr").replace(/[^a-zA-Z0-9]/g, "_").toLowerCase();
492
- const safeUser = (userName || "user").replace(/[^a-zA-Z0-9]/g, "_").toLowerCase();
493
- const safeWs = (workspaceName || "workspace").replace(/[^a-zA-Z0-9]/g, "_").toLowerCase();
494
- return `ultrst_${safeApp}_${safeUser}_${safeWs}`;
495
- }
496
-
497
- // src/core/workspaceSyncEngine.ts
498
- var ACCOUNTS_FILE = path.resolve(".skipo/accounts.json");
499
- var CONFIG_FILE = path.resolve(".skipo/workspace_config.json");
500
- function getDefaultWorkingWorkspaceDir() {
501
- if (process.env.VERCEL || process.env.AWS_LAMBDA_FUNCTION_NAME) {
502
- return path.join("/tmp", "workspace");
503
- }
504
- return path.join(process.cwd(), "workspace");
505
- }
506
- function getWorkingWorkspaceConfig() {
507
- try {
508
- if (fs.existsSync(CONFIG_FILE)) {
509
- const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
510
- const parsed = JSON.parse(raw);
511
- if (parsed.workingDirectory) {
512
- return {
513
- workingDirectory: parsed.workingDirectory,
514
- autoSyncIntervalMinutes: parsed.autoSyncIntervalMinutes || 5
515
- };
516
- }
517
- }
518
- } catch {
519
- }
520
- return {
521
- workingDirectory: getDefaultWorkingWorkspaceDir(),
522
- autoSyncIntervalMinutes: 5
523
- };
524
- }
525
- function setWorkingWorkspaceConfig(cfg) {
526
- const current = getWorkingWorkspaceConfig();
527
- const updated = {
528
- workingDirectory: (cfg.workingDirectory || current.workingDirectory).trim(),
529
- autoSyncIntervalMinutes: cfg.autoSyncIntervalMinutes || current.autoSyncIntervalMinutes
530
- };
531
- try {
532
- fs.mkdirSync(path.dirname(CONFIG_FILE), { recursive: true });
533
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2), "utf-8");
534
- } catch (err) {
535
- console.warn("[WorkspaceSync] Failed to persist working config:", err);
536
- }
537
- return updated;
538
- }
539
- function getWorkingWorkspaceDir() {
540
- const dir = getWorkingWorkspaceConfig().workingDirectory;
541
- if (!fs.existsSync(dir)) {
542
- fs.mkdirSync(dir, { recursive: true });
543
- }
544
- return dir;
545
- }
546
- async function findWorkspaceRecord(workspaceId) {
547
- try {
548
- if (fs.existsSync(ACCOUNTS_FILE)) {
549
- const raw = fs.readFileSync(ACCOUNTS_FILE, "utf-8");
550
- const accounts = JSON.parse(raw);
551
- for (const u of Object.values(accounts)) {
552
- if (Array.isArray(u.workspaces)) {
553
- const ws = u.workspaces.find((w) => w.id === workspaceId);
554
- if (ws) {
555
- const conns = Array.isArray(u.storage_connections) ? u.storage_connections : [];
556
- const conn = conns.find((c) => c.id === ws.storageConnectionId) || conns[0] || null;
557
- return { user: u, workspace: ws, connection: conn };
558
- }
559
- }
560
- }
561
- }
562
- } catch {
563
- }
564
- try {
565
- const res = await queryUnifiedDb(`SELECT id, email, username, app_profiles FROM "ultrst-users"`);
566
- for (const u of res.rows) {
567
- const profiles = typeof u.app_profiles === "string" ? JSON.parse(u.app_profiles) : u.app_profiles || {};
568
- const qwnsr = profiles.qwnsr || {};
569
- const workspaces = Array.isArray(qwnsr.workspaces) ? qwnsr.workspaces : [];
570
- const ws = workspaces.find((w) => w.id === workspaceId);
571
- if (ws) {
572
- const conns = qwnsr.storage_connections || profiles.srkl?.storage_connections || [];
573
- const conn = conns.find((c) => c.id === ws.storageConnectionId) || conns[0] || null;
574
- return { user: u, workspace: ws, connection: conn };
575
- }
576
- }
577
- } catch {
578
- }
579
- return null;
580
- }
581
- function getWorkspaceFolderKey(workspace, user) {
582
- if (workspace.localFolderKey) return workspace.localFolderKey;
583
- if (workspace.localFolderName && workspace.localFolderName.startsWith("ultrst_")) return workspace.localFolderName;
584
- const userName = user?.username || user?.name || (user?.email ? user.email.split("@")[0] : "user");
585
- return formatCanonicalWorkspaceFolderName2("qwnsr", userName, workspace.name);
586
- }
587
- async function collectLocalFiles(dir, baseDir = dir) {
588
- if (!fs.existsSync(dir)) return [];
589
- const entries = await fs.promises.readdir(dir, { withFileTypes: true });
590
- const results = [];
591
- for (const entry of entries) {
592
- if (entry.name.startsWith(".") && entry.name !== ".bzEmpty") continue;
593
- const full = path.join(dir, entry.name);
594
- if (entry.isDirectory()) {
595
- const nested = await collectLocalFiles(full, baseDir);
596
- results.push(...nested);
597
- } else if (entry.isFile()) {
598
- const st = await fs.promises.stat(full);
599
- const rel = path.relative(baseDir, full).replace(/\\/g, "/");
600
- results.push({ relPath: rel, fullPath: full, mtimeMs: st.mtimeMs, sizeBytes: st.size });
601
- }
602
- }
603
- return results;
604
- }
605
- async function syncWorkspace(workspaceId) {
606
- const match = await findWorkspaceRecord(workspaceId);
607
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
608
- if (!match) {
609
- return {
610
- success: false,
611
- workspaceId,
612
- folderKey: workspaceId,
613
- workingDir: getWorkingWorkspaceDir(),
614
- persistenceType: "local_fs",
615
- targetLocation: "Unknown",
616
- syncedFiles: [],
617
- uploadedCount: 0,
618
- downloadedCount: 0,
619
- syncedAt: nowIso,
620
- error: `Workspace '${workspaceId}' not found.`
621
- };
622
- }
623
- const { workspace, user, connection } = match;
624
- const folderKey = getWorkspaceFolderKey(workspace, user);
625
- const workingDir = getWorkingWorkspaceDir();
626
- const isB2 = workspace.storageProvider === "backblaze_b2" || workspace.storageConfig?.type === "b2";
627
- const syncedFiles = [];
628
- let uploadedCount = 0;
629
- let downloadedCount = 0;
630
- if (isB2) {
631
- const creds = workspace.storageConfig || connection?.credentials || connection || {};
632
- const keyId = creds.keyId || creds.b2KeyId;
633
- const appKey = creds.applicationKey || creds.b2ApplicationKey || creds.appKey;
634
- const bucketName = creds.bucketName || creds.b2BucketName || workspace.b2BucketName || "ultrst-apps";
635
- if (!keyId || !appKey) {
636
- return {
637
- success: false,
638
- workspaceId,
639
- folderKey,
640
- workingDir,
641
- persistenceType: "backblaze_b2",
642
- targetLocation: bucketName,
643
- syncedFiles: [],
644
- uploadedCount: 0,
645
- downloadedCount: 0,
646
- syncedAt: nowIso,
647
- error: "Missing Backblaze B2 credentials for sync."
648
- };
649
- }
650
- try {
651
- const b2Prefix = `${folderKey}/`;
652
- const remoteFiles = await b2ListFileNames2(keyId, appKey, bucketName, b2Prefix);
653
- const remoteMap = /* @__PURE__ */ new Map();
654
- for (const rf of remoteFiles) {
655
- const cleanName = rf.fileName.slice(b2Prefix.length);
656
- if (cleanName) remoteMap.set(cleanName, { fileId: rf.fileId, size: rf.contentLength, timestamp: rf.uploadTimestamp });
657
- }
658
- const localFiles = await collectLocalFiles(workingDir);
659
- const localMap = /* @__PURE__ */ new Map();
660
- for (const lf of localFiles) {
661
- localMap.set(lf.relPath, lf);
662
- }
663
- for (const [relPath, rInfo] of remoteMap.entries()) {
664
- const lFile = localMap.get(relPath);
665
- const shouldDownload = !lFile || rInfo.timestamp > lFile.mtimeMs + 2e3;
666
- if (shouldDownload) {
667
- const buf = await b2DownloadFile2(keyId, appKey, bucketName, `${b2Prefix}${relPath}`);
668
- if (buf) {
669
- const destPath = path.join(workingDir, relPath);
670
- await fs.promises.mkdir(path.dirname(destPath), { recursive: true });
671
- await fs.promises.writeFile(destPath, buf);
672
- try {
673
- const fileTime = new Date(rInfo.timestamp);
674
- await fs.promises.utimes(destPath, fileTime, fileTime);
675
- } catch {
676
- }
677
- syncedFiles.push({ fileName: relPath, action: "DOWNLOADED", sizeBytes: buf.byteLength });
678
- downloadedCount++;
679
- }
680
- }
681
- }
682
- for (const [relPath, lFile] of localMap.entries()) {
683
- const rInfo = remoteMap.get(relPath);
684
- const shouldUpload = !rInfo || lFile.mtimeMs > rInfo.timestamp + 2e3;
685
- if (shouldUpload) {
686
- const content = await fs.promises.readFile(lFile.fullPath);
687
- await b2UploadFile2(keyId, appKey, bucketName, `${b2Prefix}${relPath}`, new Uint8Array(content));
688
- syncedFiles.push({ fileName: relPath, action: "UPLOADED", sizeBytes: lFile.sizeBytes });
689
- uploadedCount++;
690
- } else if (!syncedFiles.some((f) => f.fileName === relPath)) {
691
- syncedFiles.push({ fileName: relPath, action: "UP_TO_DATE", sizeBytes: lFile.sizeBytes });
692
- }
693
- }
694
- return {
695
- success: true,
696
- workspaceId,
697
- folderKey,
698
- workingDir,
699
- persistenceType: "backblaze_b2",
700
- targetLocation: `B2://${bucketName}/${folderKey}/`,
701
- syncedFiles,
702
- uploadedCount,
703
- downloadedCount,
704
- syncedAt: nowIso
705
- };
706
- } catch (err) {
707
- return {
708
- success: false,
709
- workspaceId,
710
- folderKey,
711
- workingDir,
712
- persistenceType: "backblaze_b2",
713
- targetLocation: bucketName,
714
- syncedFiles,
715
- uploadedCount,
716
- downloadedCount,
717
- syncedAt: nowIso,
718
- error: err.message || "Backblaze sync failed."
719
- };
720
- }
721
- }
722
- const vaultDir = path.resolve(process.cwd(), folderKey);
723
- await fs.promises.mkdir(vaultDir, { recursive: true });
724
- try {
725
- const vaultFiles = await collectLocalFiles(vaultDir);
726
- const vaultMap = new Map(vaultFiles.map((f) => [f.relPath, f]));
727
- const workingFiles = await collectLocalFiles(workingDir);
728
- const workingMap = new Map(workingFiles.map((f) => [f.relPath, f]));
729
- for (const [relPath, vFile] of vaultMap.entries()) {
730
- const wFile = workingMap.get(relPath);
731
- const shouldDownload = !wFile || vFile.mtimeMs > wFile.mtimeMs + 1e3;
732
- if (shouldDownload) {
733
- const dest = path.join(workingDir, relPath);
734
- await fs.promises.mkdir(path.dirname(dest), { recursive: true });
735
- await fs.promises.copyFile(vFile.fullPath, dest);
736
- try {
737
- const vTime = new Date(vFile.mtimeMs);
738
- await fs.promises.utimes(dest, vTime, vTime);
739
- } catch {
740
- }
741
- syncedFiles.push({ fileName: relPath, action: "DOWNLOADED", sizeBytes: vFile.sizeBytes });
742
- downloadedCount++;
743
- }
744
- }
745
- for (const [relPath, wFile] of workingMap.entries()) {
746
- const vFile = vaultMap.get(relPath);
747
- const shouldUpload = !vFile || wFile.mtimeMs > vFile.mtimeMs + 1e3;
748
- if (shouldUpload) {
749
- const dest = path.join(vaultDir, relPath);
750
- await fs.promises.mkdir(path.dirname(dest), { recursive: true });
751
- await fs.promises.copyFile(wFile.fullPath, dest);
752
- try {
753
- const wTime = new Date(wFile.mtimeMs);
754
- await fs.promises.utimes(dest, wTime, wTime);
755
- } catch {
756
- }
757
- syncedFiles.push({ fileName: relPath, action: "UPLOADED", sizeBytes: wFile.sizeBytes });
758
- uploadedCount++;
759
- } else if (!syncedFiles.some((f) => f.fileName === relPath)) {
760
- syncedFiles.push({ fileName: relPath, action: "UP_TO_DATE", sizeBytes: wFile.sizeBytes });
761
- }
762
- }
763
- return {
764
- success: true,
765
- workspaceId,
766
- folderKey,
767
- workingDir,
768
- persistenceType: "local_fs",
769
- targetLocation: vaultDir,
770
- syncedFiles,
771
- uploadedCount,
772
- downloadedCount,
773
- syncedAt: nowIso
774
- };
775
- } catch (err) {
776
- return {
777
- success: false,
778
- workspaceId,
779
- folderKey,
780
- workingDir,
781
- persistenceType: "local_fs",
782
- targetLocation: vaultDir,
783
- syncedFiles,
784
- uploadedCount,
785
- downloadedCount,
786
- syncedAt: nowIso,
787
- error: err.message
788
- };
789
- }
790
- }
791
-
792
334
  // src/core/SrklStorageService.ts
793
335
  import { S3Client, ListBucketsCommand, HeadBucketCommand } from "@aws-sdk/client-s3";
794
- import { promises as fs2 } from "fs";
795
- import path2 from "path";
796
- var ACCOUNTS_FILE2 = path2.resolve(".skipo/accounts.json");
336
+ import { promises as fs } from "fs";
337
+ import path from "path";
338
+ var ACCOUNTS_FILE = path.resolve(".skipo/accounts.json");
797
339
  var SrklStorageService = class {
798
340
  srklBaseUrl;
799
341
  constructor(srklBaseUrl) {
@@ -906,10 +448,10 @@ var SrklStorageService = class {
906
448
  }
907
449
  async persistWorkspaceConfig(wsId, config) {
908
450
  try {
909
- await fs2.mkdir(path2.dirname(ACCOUNTS_FILE2), { recursive: true });
451
+ await fs.mkdir(path.dirname(ACCOUNTS_FILE), { recursive: true });
910
452
  let accounts = {};
911
453
  try {
912
- accounts = JSON.parse(await fs2.readFile(ACCOUNTS_FILE2, "utf-8"));
454
+ accounts = JSON.parse(await fs.readFile(ACCOUNTS_FILE, "utf-8"));
913
455
  } catch {
914
456
  accounts = {};
915
457
  }
@@ -920,7 +462,7 @@ var SrklStorageService = class {
920
462
  ws.storageConfig = config;
921
463
  ws.storageProvider = "backblaze_b2";
922
464
  ws.updatedAt = Date.now();
923
- await fs2.writeFile(ACCOUNTS_FILE2, JSON.stringify(accounts, null, 2), "utf-8");
465
+ await fs.writeFile(ACCOUNTS_FILE, JSON.stringify(accounts, null, 2), "utf-8");
924
466
  return;
925
467
  }
926
468
  }
@@ -945,7 +487,7 @@ var SrklStorageService = class {
945
487
  });
946
488
  }
947
489
  try {
948
- const raw = await fs2.readFile(ACCOUNTS_FILE2, "utf-8");
490
+ const raw = await fs.readFile(ACCOUNTS_FILE, "utf-8");
949
491
  const accounts = JSON.parse(raw);
950
492
  for (const user of Object.values(accounts)) {
951
493
  const conns = Array.isArray(user?.storage_connections) ? user.storage_connections : [];
@@ -1074,10 +616,10 @@ async function getSrklAccounts2(userId, service, env) {
1074
616
  }
1075
617
  }
1076
618
  try {
1077
- const fs10 = await import("fs/promises");
1078
- const path10 = await import("path");
1079
- const accPath = path10.resolve(".skipo/accounts.json");
1080
- const raw = await fs10.readFile(accPath, "utf-8");
619
+ const fs9 = await import("fs/promises");
620
+ const path9 = await import("path");
621
+ const accPath = path9.resolve(".skipo/accounts.json");
622
+ const raw = await fs9.readFile(accPath, "utf-8");
1081
623
  const localUsers = JSON.parse(raw);
1082
624
  for (const u of Object.values(localUsers)) {
1083
625
  const conns = Array.isArray(u.storage_connections) ? u.storage_connections : [];
@@ -1184,155 +726,6 @@ async function getSrklStatus(env) {
1184
726
  }
1185
727
  }
1186
728
 
1187
- // src/core/workspaceFileCore.ts
1188
- async function getFs() {
1189
- if (typeof process !== "undefined" && process.versions?.node) {
1190
- try {
1191
- const mod = await import("fs");
1192
- return mod.promises || mod.default?.promises || mod;
1193
- } catch {
1194
- }
1195
- }
1196
- return null;
1197
- }
1198
- async function getPath() {
1199
- if (typeof process !== "undefined" && process.versions?.node) {
1200
- try {
1201
- const mod = await import("path");
1202
- return mod.default || mod;
1203
- } catch {
1204
- }
1205
- }
1206
- return null;
1207
- }
1208
- function getWorkspaceFolderPrefix(workspace) {
1209
- return workspace.localFolderKey || workspace.localFolderName || (workspace.workspacePath ? workspace.workspacePath.replace(/^\.\//, "") : workspace.id);
1210
- }
1211
- function isB2Workspace(workspace, connection) {
1212
- return connection?.provider === "backblaze_b2" || connection?.provider === "BACKBLAZE_B2" || workspace?.storageProvider === "backblaze_b2";
1213
- }
1214
- async function writeWorkspaceFile(workspace, connection, relativePath, content, contentType = "application/json") {
1215
- const cleanRel = relativePath.replace(/^\/+/, "");
1216
- const prefix = getWorkspaceFolderPrefix(workspace);
1217
- const buffer = typeof content === "string" ? new TextEncoder().encode(content) : content;
1218
- if (isB2Workspace(workspace, connection)) {
1219
- const keyId = connection.b2KeyId || connection.keyId;
1220
- const appKey = connection.b2ApplicationKey || connection.appKey;
1221
- const bucketName = connection.b2BucketName || workspace.b2BucketName || "ultrst-apps";
1222
- const fullKey = `${prefix}/${cleanRel}`;
1223
- await b2UploadFile2(keyId, appKey, bucketName, fullKey, buffer, contentType);
1224
- return;
1225
- }
1226
- const fs10 = await getFs();
1227
- const path10 = await getPath();
1228
- if (!fs10 || !path10) throw new Error("Local filesystem operations not supported in this runtime.");
1229
- const localDir = path10.resolve(process.cwd(), prefix, path10.dirname(cleanRel));
1230
- await fs10.mkdir(localDir, { recursive: true });
1231
- await fs10.writeFile(path10.resolve(process.cwd(), prefix, cleanRel), buffer);
1232
- }
1233
- async function readWorkspaceFile(workspace, connection, relativePath) {
1234
- const cleanRel = relativePath.replace(/^\/+/, "");
1235
- const prefix = getWorkspaceFolderPrefix(workspace);
1236
- if (isB2Workspace(workspace, connection)) {
1237
- const keyId = connection.b2KeyId || connection.keyId;
1238
- const appKey = connection.b2ApplicationKey || connection.appKey;
1239
- const bucketName = connection.b2BucketName || workspace.b2BucketName || "ultrst-apps";
1240
- const fullKey = `${prefix}/${cleanRel}`;
1241
- return await b2DownloadFile2(keyId, appKey, bucketName, fullKey);
1242
- }
1243
- const fs10 = await getFs();
1244
- const path10 = await getPath();
1245
- if (!fs10 || !path10) return null;
1246
- const fullPath = path10.resolve(process.cwd(), prefix, cleanRel);
1247
- try {
1248
- const buf = await fs10.readFile(fullPath);
1249
- return new Uint8Array(buf);
1250
- } catch {
1251
- return null;
1252
- }
1253
- }
1254
- async function deleteWorkspaceFile(workspace, connection, relativePath) {
1255
- const cleanRel = relativePath.replace(/^\/+/, "");
1256
- const prefix = getWorkspaceFolderPrefix(workspace);
1257
- if (isB2Workspace(workspace, connection)) {
1258
- const keyId = connection.b2KeyId || connection.keyId;
1259
- const appKey = connection.b2ApplicationKey || connection.appKey;
1260
- const bucketName = connection.b2BucketName || workspace.b2BucketName || "ultrst-apps";
1261
- const fullKey = `${prefix}/${cleanRel}`;
1262
- const files = await b2ListFileNames2(keyId, appKey, bucketName, fullKey);
1263
- const match = files.find((f) => f.fileName === fullKey);
1264
- if (match) {
1265
- await b2DeleteFileVersion(keyId, appKey, match.fileName, match.fileId);
1266
- return true;
1267
- }
1268
- return false;
1269
- }
1270
- const fs10 = await getFs();
1271
- const path10 = await getPath();
1272
- if (!fs10 || !path10) return false;
1273
- const fullPath = path10.resolve(process.cwd(), prefix, cleanRel);
1274
- try {
1275
- await fs10.unlink(fullPath);
1276
- return true;
1277
- } catch {
1278
- return false;
1279
- }
1280
- }
1281
- async function listWorkspaceFiles(workspace, connection, subPrefix = "") {
1282
- const folderPrefix = getWorkspaceFolderPrefix(workspace);
1283
- const targetPrefix = subPrefix ? `${folderPrefix}/${subPrefix.replace(/^\/+/, "")}` : `${folderPrefix}/`;
1284
- if (isB2Workspace(workspace, connection)) {
1285
- const keyId = connection.b2KeyId || connection.keyId;
1286
- const appKey = connection.b2ApplicationKey || connection.appKey;
1287
- const bucketName = connection.b2BucketName || workspace.b2BucketName || "ultrst-apps";
1288
- const rawFiles = await b2ListFileNames2(keyId, appKey, bucketName, targetPrefix);
1289
- return rawFiles.map((f) => ({
1290
- fileName: f.fileName.slice(folderPrefix.length + 1),
1291
- sizeBytes: f.contentLength,
1292
- uploadTimestamp: f.uploadTimestamp
1293
- }));
1294
- }
1295
- const fs10 = await getFs();
1296
- const path10 = await getPath();
1297
- if (!fs10 || !path10) return [];
1298
- const baseDir = path10.resolve(process.cwd(), folderPrefix, subPrefix);
1299
- try {
1300
- const entries = await fs10.readdir(baseDir, { withFileTypes: true, recursive: true });
1301
- const result = [];
1302
- for (const entry of entries) {
1303
- if (entry.isFile()) {
1304
- const full = path10.join(entry.parentPath || baseDir, entry.name);
1305
- const st = await fs10.stat(full);
1306
- result.push({
1307
- fileName: path10.relative(path10.resolve(process.cwd(), folderPrefix), full),
1308
- sizeBytes: st.size
1309
- });
1310
- }
1311
- }
1312
- return result;
1313
- } catch {
1314
- return [];
1315
- }
1316
- }
1317
- async function deleteWorkspaceFolder(workspace, connection) {
1318
- const prefix = getWorkspaceFolderPrefix(workspace);
1319
- if (isB2Workspace(workspace, connection)) {
1320
- const keyId = connection.b2KeyId || connection.keyId;
1321
- const appKey = connection.b2ApplicationKey || connection.appKey;
1322
- const bucketName = connection.b2BucketName || workspace.b2BucketName || "ultrst-apps";
1323
- await b2DeleteFolder(keyId, appKey, bucketName, prefix);
1324
- return;
1325
- }
1326
- const fs10 = await getFs();
1327
- const path10 = await getPath();
1328
- if (!fs10 || !path10) return;
1329
- const localDir = path10.resolve(process.cwd(), prefix);
1330
- try {
1331
- await fs10.rm(localDir, { recursive: true, force: true });
1332
- } catch {
1333
- }
1334
- }
1335
-
1336
729
  // src/core/credentialVaultCore.ts
1337
730
  var MANIFEST_PATH = "credentials/manifest.json";
1338
731
  var SECRET_PREFIX = "credentials/secrets";
@@ -2697,385 +2090,6 @@ async function matchGatewayIngressAsync(urlPath, search, env) {
2697
2090
  return null;
2698
2091
  }
2699
2092
 
2700
- // src/core/workspaceBucketSyncCore.ts
2701
- function resolveWorkspaceConnection(profiles, workspace) {
2702
- const depm = profiles.depm || {};
2703
- const conns = depm.storage_connections?.length ? depm.storage_connections : profiles.srkl?.storage_connections || profiles.docustack?.storage_connections || [];
2704
- return conns.find((c) => c.id === workspace.storageConnectionId) || conns.find((c) => c.role === "primary") || conns[0] || null;
2705
- }
2706
- async function syncWorkspaceCreateToBucket(workspace, connection, env) {
2707
- if (!connection || connection.provider !== "backblaze_b2" && connection.provider !== "BACKBLAZE_B2") return;
2708
- try {
2709
- const wsMeta = {
2710
- id: workspace.id,
2711
- name: workspace.name,
2712
- createdAt: workspace.createdAt,
2713
- updatedAt: workspace.updatedAt,
2714
- storageProvider: "backblaze_b2",
2715
- bucket: connection.b2BucketName || "ultrst-apps"
2716
- };
2717
- await writeWorkspaceFile(workspace, connection, ".bzEmpty", new Uint8Array(0), "application/x-empty");
2718
- await writeWorkspaceFile(workspace, connection, "workspace.json", JSON.stringify(wsMeta, null, 2), "application/json");
2719
- await writeWorkspaceFile(
2720
- workspace,
2721
- connection,
2722
- "ecosystem.json",
2723
- JSON.stringify({ apps: [], nodes: [], edges: [], ecosystems: [] }, null, 2),
2724
- "application/json"
2725
- );
2726
- } catch (err) {
2727
- console.warn("[WorkspaceBucketSync] Create sync error:", err.message);
2728
- }
2729
- }
2730
- async function syncWorkspaceDeleteToBucket(workspace, connection, env) {
2731
- if (!connection || connection.provider !== "backblaze_b2" && connection.provider !== "BACKBLAZE_B2") return;
2732
- try {
2733
- await deleteWorkspaceFolder(workspace, connection);
2734
- } catch (err) {
2735
- console.warn("[WorkspaceBucketSync] Delete sync error:", err.message);
2736
- }
2737
- }
2738
- async function syncWorkspaceDataToBucket(workspace, connection, data, env) {
2739
- if (!connection || connection.provider !== "backblaze_b2" && connection.provider !== "BACKBLAZE_B2") return;
2740
- try {
2741
- await writeWorkspaceFile(workspace, connection, "ecosystem.json", JSON.stringify(data, null, 2), "application/json");
2742
- } catch (err) {
2743
- console.warn("[WorkspaceBucketSync] Data sync error:", err.message);
2744
- }
2745
- }
2746
-
2747
- // src/core/workspaceDataCore.ts
2748
- async function resolveUserRow(userId, env) {
2749
- if (userId) {
2750
- const res = await queryUnifiedDb(`SELECT * FROM "ultrst-users" WHERE id = $1 OR email = $1 LIMIT 1`, [userId], env);
2751
- if (res.rows.length > 0) return res.rows[0];
2752
- }
2753
- return null;
2754
- }
2755
- async function loadWorkspaceData(userId, workspaceId, env) {
2756
- if (!workspaceId) return { success: false, error: "workspaceId required", workspace: null };
2757
- const user = await resolveUserRow(userId, env);
2758
- if (!user) return { success: false, error: "User not found", workspace: null };
2759
- const profiles = typeof user.app_profiles === "string" ? JSON.parse(user.app_profiles) : user.app_profiles || {};
2760
- const depm = profiles.depm || {};
2761
- const workspaceDataMap = depm.workspace_data || {};
2762
- let data = workspaceDataMap[workspaceId];
2763
- const isEmptyData = !data || (!data.nodes || data.nodes.length === 0) && (!data.apps || data.apps.length === 0);
2764
- if (isEmptyData) {
2765
- const ws = (depm.workspaces || []).find((w) => w.id === workspaceId);
2766
- if (ws) {
2767
- const conn = resolveWorkspaceConnection(profiles, ws);
2768
- if (conn) {
2769
- try {
2770
- const fileBuf = await readWorkspaceFile(ws, conn, "ecosystem.json");
2771
- if (fileBuf && fileBuf.length > 0) {
2772
- const parsed = JSON.parse(new TextDecoder().decode(fileBuf));
2773
- if (parsed.nodes && parsed.nodes.length > 0 || parsed.apps && parsed.apps.length > 0) {
2774
- if (!profiles.depm) profiles.depm = {};
2775
- if (!profiles.depm.workspace_data) profiles.depm.workspace_data = {};
2776
- profiles.depm.workspace_data[workspaceId] = parsed;
2777
- data = parsed;
2778
- await queryUnifiedDb(
2779
- `UPDATE "ultrst-users" SET app_profiles = $1, updated_at = NOW() WHERE id = $2`,
2780
- [JSON.stringify(profiles), user.id],
2781
- env
2782
- );
2783
- return { success: true, workspace: data, workspaceId };
2784
- }
2785
- }
2786
- } catch (err) {
2787
- console.warn("[WorkspaceDataCore] Storage recovery check:", err.message);
2788
- }
2789
- }
2790
- }
2791
- }
2792
- if (!data) {
2793
- const ws = (depm.workspaces || []).find((w) => w.id === workspaceId);
2794
- const ecoName = ws?.ecosystemName || `${ws?.name || "Workspace"} Ecosystem`;
2795
- const fallback = {
2796
- apps: [],
2797
- accounts: [],
2798
- deployments: [],
2799
- nodes: [],
2800
- edges: [],
2801
- ecosystems: [
2802
- {
2803
- id: `eco_${workspaceId}`,
2804
- name: ecoName,
2805
- icon: "Globe",
2806
- health: "HEALTHY",
2807
- status: "SYSTEM ONLINE",
2808
- connectedAppsCount: 0,
2809
- meshLinkCount: 0,
2810
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2811
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2812
- }
2813
- ],
2814
- ecosystemName: ecoName,
2815
- ecosystemNodes: [
2816
- {
2817
- id: `node_eco_root_${workspaceId}`,
2818
- type: "ECOSYSTEM",
2819
- name: ecoName,
2820
- x: 0,
2821
- y: 0,
2822
- appId: "__ecosystem__",
2823
- icon: "Globe",
2824
- ecosystemData: { connectedAppsCount: 0, health: "HEALTHY", status: "SYSTEM ONLINE" }
2825
- }
2826
- ],
2827
- ecosystemEdges: []
2828
- };
2829
- return { success: true, workspace: fallback, workspaceId };
2830
- }
2831
- return { success: true, workspace: data, workspaceId };
2832
- }
2833
- async function saveWorkspaceData(userId, workspaceId, data, env) {
2834
- if (!workspaceId) return { success: false, error: "workspaceId required" };
2835
- const user = await resolveUserRow(userId, env);
2836
- if (!user) return { success: false, error: "User not found" };
2837
- const profiles = typeof user.app_profiles === "string" ? JSON.parse(user.app_profiles) : user.app_profiles || {};
2838
- if (!profiles.depm) profiles.depm = {};
2839
- if (!profiles.depm.workspace_data) profiles.depm.workspace_data = {};
2840
- const existing = profiles.depm.workspace_data[workspaceId];
2841
- const incomingEmpty = (!data.nodes || data.nodes.length === 0) && (!data.apps || data.apps.length === 0);
2842
- const existingHasData = existing && (existing.nodes && existing.nodes.length > 0 || existing.apps && existing.apps.length > 0);
2843
- if (incomingEmpty && existingHasData && !data._forceEmptySave) {
2844
- console.warn(`[WorkspaceDataCore] Blocked empty overwrite for workspace ${workspaceId}.`);
2845
- return { success: true, workspaceId, workspace: existing, protected: true };
2846
- }
2847
- profiles.depm.workspace_data[workspaceId] = data;
2848
- if (Array.isArray(profiles.depm.workspaces)) {
2849
- const ws2 = profiles.depm.workspaces.find((w) => w.id === workspaceId);
2850
- if (ws2) {
2851
- ws2.appsCount = Array.isArray(data.apps) ? data.apps.length : 0;
2852
- ws2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2853
- }
2854
- }
2855
- await queryUnifiedDb(
2856
- `UPDATE "ultrst-users" SET app_profiles = $1, updated_at = NOW() WHERE id = $2`,
2857
- [JSON.stringify(profiles), user.id],
2858
- env
2859
- );
2860
- const ws = Array.isArray(profiles.depm?.workspaces) ? profiles.depm.workspaces.find((w) => w.id === workspaceId) : null;
2861
- if (ws) {
2862
- const conn = resolveWorkspaceConnection(profiles, ws);
2863
- await syncWorkspaceDataToBucket(ws, conn, data, env);
2864
- }
2865
- return { success: true, workspaceId, workspace: data };
2866
- }
2867
- async function renameAppInWorkspaceData(userId, workspaceId, appId, newName, env) {
2868
- if (!workspaceId || !appId || !newName) {
2869
- return { success: false, error: "workspaceId, appId and newName required" };
2870
- }
2871
- const loadRes = await loadWorkspaceData(userId, workspaceId, env);
2872
- if (!loadRes.success || !loadRes.workspace) return loadRes;
2873
- const wsData = loadRes.workspace;
2874
- const trimmed = newName.trim();
2875
- const folderName = trimmed.replace(/[/\\?%*:|"<>]/g, "_");
2876
- if (Array.isArray(wsData.apps)) {
2877
- const targetApp = wsData.apps.find((a) => a.id === appId);
2878
- if (targetApp) {
2879
- targetApp.name = trimmed;
2880
- targetApp.folderName = folderName;
2881
- targetApp.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2882
- }
2883
- }
2884
- if (Array.isArray(wsData.nodes)) {
2885
- wsData.nodes = wsData.nodes.map((n) => {
2886
- if (n.appId === appId && (n.type === "APP" || n.type === "APP_NODE" || n.type === "APP_GRAPH_NODE")) {
2887
- return { ...n, name: trimmed, appData: n.appData ? { ...n.appData, name: trimmed, folderName } : void 0 };
2888
- }
2889
- return n;
2890
- });
2891
- }
2892
- return await saveWorkspaceData(userId, workspaceId, wsData, env);
2893
- }
2894
-
2895
- // src/core/workspaceStorageDetailsCore.ts
2896
- async function getWorkspaceStorageDetails(workspaceId, env) {
2897
- if (!workspaceId) return { success: false, error: "workspaceId is required" };
2898
- const { workspace, connection } = await findUserWorkspace(workspaceId, env);
2899
- const folderPrefix = workspace?.localFolderKey || workspace?.localFolderName || (workspace?.workspacePath ? workspace.workspacePath.replace(/^\.\//, "") : workspaceId);
2900
- if (!connection || connection.provider !== "backblaze_b2" && connection.provider !== "BACKBLAZE_B2") {
2901
- return {
2902
- success: true,
2903
- workspaceId,
2904
- storageProvider: workspace?.storageProvider || "local_fs",
2905
- connection: connection || null,
2906
- folderPrefix,
2907
- localStats: { exists: false, appsCount: 0, ecosystemsCount: 0 },
2908
- bucket: null,
2909
- files: []
2910
- };
2911
- }
2912
- const keyId = connection.b2KeyId || connection.keyId;
2913
- const applicationKey = connection.b2ApplicationKey || connection.appKey;
2914
- const bucketName = connection.b2BucketName || workspace.b2BucketName || "ultrst-apps";
2915
- try {
2916
- const buckets = await b2ListBuckets2(keyId, applicationKey, void 0, bucketName);
2917
- const bucketObj = buckets.find((b) => b.bucketName === bucketName);
2918
- if (!bucketObj) {
2919
- return {
2920
- success: true,
2921
- workspaceId,
2922
- storageProvider: "backblaze_b2",
2923
- connection: { id: connection.id, label: connection.label, provider: connection.provider },
2924
- folderPrefix,
2925
- bucket: { name: bucketName, status: "BUCKET_NOT_FOUND" },
2926
- files: []
2927
- };
2928
- }
2929
- const rawFiles = await b2ListFileNames2(keyId, applicationKey, bucketName, `${folderPrefix}/`);
2930
- return {
2931
- success: true,
2932
- workspaceId,
2933
- storageProvider: "backblaze_b2",
2934
- connection: { id: connection.id, label: connection.label, provider: connection.provider },
2935
- folderPrefix,
2936
- bucket: {
2937
- id: bucketObj.bucketId,
2938
- name: bucketObj.bucketName,
2939
- type: bucketObj.bucketType,
2940
- endpoint: connection.b2Endpoint || "s3.eu-central-003.backblazeb2.com",
2941
- region: connection.b2Region || "eu-central-003",
2942
- status: "CONNECTED"
2943
- },
2944
- fileCount: rawFiles.length,
2945
- files: rawFiles.map((f) => ({
2946
- fileName: f.fileName,
2947
- sizeBytes: f.contentLength,
2948
- uploadTimestamp: f.uploadTimestamp
2949
- }))
2950
- };
2951
- } catch (err) {
2952
- return {
2953
- success: false,
2954
- workspaceId,
2955
- storageProvider: "backblaze_b2",
2956
- error: err.message,
2957
- folderPrefix
2958
- };
2959
- }
2960
- }
2961
-
2962
- // src/core/workspaceCore.ts
2963
- async function findUserWorkspace(workspaceId, env) {
2964
- const res = await queryUnifiedDb(`SELECT id, email, username, app_profiles FROM "ultrst-users"`, [], env);
2965
- for (const user of res.rows) {
2966
- const profiles = typeof user.app_profiles === "string" ? JSON.parse(user.app_profiles) : user.app_profiles || {};
2967
- const depm = profiles.depm || {};
2968
- const workspaces = Array.isArray(depm.workspaces) ? depm.workspaces : [];
2969
- const ws = workspaces.find((w) => w.id === workspaceId);
2970
- if (ws) {
2971
- const conns = depm.storage_connections || profiles.srkl?.storage_connections || profiles.docustack?.storage_connections || [];
2972
- const conn = conns.find((c) => c.id === ws.storageConnectionId) || conns.find((c) => c.role === "primary") || conns[0] || null;
2973
- return { user, workspace: ws, connection: conn };
2974
- }
2975
- }
2976
- return { user: null, workspace: null, connection: null };
2977
- }
2978
- async function getWorkspaces(userId, env) {
2979
- if (!userId) {
2980
- return { success: true, workspaces: [], activeWorkspaceId: null, storage_connections: [] };
2981
- }
2982
- const res = await queryUnifiedDb(`SELECT * FROM "ultrst-users" WHERE id = $1 OR email = $1 LIMIT 1`, [userId], env);
2983
- const userRow = res.rows[0];
2984
- if (!userRow) {
2985
- return { success: true, workspaces: [], activeWorkspaceId: null, storage_connections: [] };
2986
- }
2987
- const profiles = typeof userRow.app_profiles === "string" ? JSON.parse(userRow.app_profiles) : userRow.app_profiles || {};
2988
- const depm = profiles.depm || {};
2989
- const workspaces = Array.isArray(depm.workspaces) ? depm.workspaces : [];
2990
- const activeWorkspaceId = depm.activeWorkspaceId || null;
2991
- const conns = depm.storage_connections?.length ? depm.storage_connections : profiles.srkl?.storage_connections || [];
2992
- return { success: true, workspaces, activeWorkspaceId, storage_connections: conns };
2993
- }
2994
- async function createWorkspace(userId, wsData, env) {
2995
- const uRes = await queryUnifiedDb(`SELECT * FROM "ultrst-users" WHERE id = $1`, [userId], env);
2996
- const user = uRes.rows[0];
2997
- if (!user) throw new Error("User account not found.");
2998
- const profiles = typeof user.app_profiles === "string" ? JSON.parse(user.app_profiles) : user.app_profiles || {};
2999
- if (!profiles.depm) profiles.depm = {};
3000
- const workspaces = Array.isArray(profiles.depm.workspaces) ? profiles.depm.workspaces : [];
3001
- const wsName = (wsData.name || "New Workspace").trim();
3002
- const wsId = wsData.id || `ws-depm-${Date.now()}`;
3003
- const userName = user.username || user.name || "user";
3004
- const folderKey = `ultrst_depm_${userName.toLowerCase().replace(/[^a-z0-9]/g, "_")}_${wsName.toLowerCase().replace(/[^a-z0-9]/g, "_")}`;
3005
- const newWs = {
3006
- ...wsData,
3007
- id: wsId,
3008
- name: wsName,
3009
- localFolderName: folderKey,
3010
- localFolderKey: folderKey,
3011
- workspacePath: `./${folderKey}`,
3012
- storageProvider: wsData.storageProvider || "backblaze_b2",
3013
- b2BucketName: wsData.b2BucketName || "ultrst-apps",
3014
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3015
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3016
- };
3017
- workspaces.push(newWs);
3018
- profiles.depm.workspaces = workspaces;
3019
- profiles.depm.activeWorkspaceId = wsId;
3020
- await queryUnifiedDb(`UPDATE "ultrst-users" SET app_profiles = $1, updated_at = NOW() WHERE id = $2`, [JSON.stringify(profiles), user.id], env);
3021
- const conn = resolveWorkspaceConnection(profiles, newWs);
3022
- await syncWorkspaceCreateToBucket(newWs, conn, env);
3023
- return { success: true, workspace: newWs, workspaces };
3024
- }
3025
- async function updateWorkspace(userId, params, env) {
3026
- const uRes = await queryUnifiedDb(`SELECT * FROM "ultrst-users" WHERE id = $1`, [userId], env);
3027
- const user = uRes.rows[0];
3028
- if (!user) throw new Error("User not found.");
3029
- const profiles = typeof user.app_profiles === "string" ? JSON.parse(user.app_profiles) : user.app_profiles || {};
3030
- if (!profiles.depm) profiles.depm = {};
3031
- const workspaces = Array.isArray(profiles.depm.workspaces) ? profiles.depm.workspaces : [];
3032
- const { action, workspaceId, updates } = params;
3033
- const targetId = workspaceId || updates?.id;
3034
- const match = workspaces.find((w) => w.id === targetId);
3035
- if (!match) throw new Error("Workspace not found.");
3036
- if (action === "switch") {
3037
- profiles.depm.activeWorkspaceId = targetId;
3038
- } else {
3039
- const safeUpdates = { ...updates || params };
3040
- delete safeUpdates.storageProvider;
3041
- delete safeUpdates.storageConnectionId;
3042
- delete safeUpdates.b2BucketName;
3043
- delete safeUpdates.b2Endpoint;
3044
- delete safeUpdates.b2Region;
3045
- delete safeUpdates.localFolderName;
3046
- delete safeUpdates.localFolderKey;
3047
- delete safeUpdates.workspacePath;
3048
- delete safeUpdates.primaryStorage;
3049
- delete safeUpdates.id;
3050
- delete safeUpdates.action;
3051
- delete safeUpdates.workspaceId;
3052
- Object.assign(match, safeUpdates, { updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
3053
- }
3054
- await queryUnifiedDb(`UPDATE "ultrst-users" SET app_profiles = $1, updated_at = NOW() WHERE id = $2`, [JSON.stringify(profiles), user.id], env);
3055
- return { success: true, workspace: match, workspaces };
3056
- }
3057
- async function deleteWorkspace(userId, wsId, env) {
3058
- const uRes = await queryUnifiedDb(`SELECT * FROM "ultrst-users" WHERE id = $1`, [userId], env);
3059
- const user = uRes.rows[0];
3060
- if (!user) throw new Error("User not found.");
3061
- const profiles = typeof user.app_profiles === "string" ? JSON.parse(user.app_profiles) : user.app_profiles || {};
3062
- if (!profiles.depm?.workspaces) return { success: true, workspaces: [] };
3063
- const targetWs = profiles.depm.workspaces.find((w) => w.id === wsId);
3064
- const conn = targetWs ? resolveWorkspaceConnection(profiles, targetWs) : null;
3065
- profiles.depm.workspaces = profiles.depm.workspaces.filter((w) => w.id !== wsId);
3066
- if (profiles.depm.activeWorkspaceId === wsId) {
3067
- profiles.depm.activeWorkspaceId = profiles.depm.workspaces[0]?.id || null;
3068
- }
3069
- if (profiles.depm.workspace_data?.[wsId]) {
3070
- delete profiles.depm.workspace_data[wsId];
3071
- }
3072
- await queryUnifiedDb(`UPDATE "ultrst-users" SET app_profiles = $1, updated_at = NOW() WHERE id = $2`, [JSON.stringify(profiles), user.id], env);
3073
- if (targetWs && conn) {
3074
- await syncWorkspaceDeleteToBucket(targetWs, conn, env);
3075
- }
3076
- return { success: true, workspaces: profiles.depm.workspaces };
3077
- }
3078
-
3079
2093
  // src/core/cloudflareIntegrationCore.ts
3080
2094
  var cfFetcher = new CloudflareResourceFetcher();
3081
2095
  function buildHeaders(token, authKey, authEmail) {
@@ -3542,8 +2556,8 @@ function createWavBuffer(sampleRate, numChannels, samples) {
3542
2556
  }
3543
2557
 
3544
2558
  // src/server/sampleGenerator.ts
3545
- import fs3 from "fs";
3546
- import path3 from "path";
2559
+ import fs2 from "fs";
2560
+ import path2 from "path";
3547
2561
  var GLOBAL_SAMPLE_DEFS = [
3548
2562
  {
3549
2563
  filename: "808_sub_boom.wav",
@@ -3640,13 +2654,13 @@ var GLOBAL_SAMPLE_DEFS = [
3640
2654
  }
3641
2655
  ];
3642
2656
  async function ensureGlobalSamples(globalSamplesDir) {
3643
- if (!fs3.existsSync(globalSamplesDir)) {
3644
- fs3.mkdirSync(globalSamplesDir, { recursive: true });
2657
+ if (!fs2.existsSync(globalSamplesDir)) {
2658
+ fs2.mkdirSync(globalSamplesDir, { recursive: true });
3645
2659
  }
3646
2660
  const sampleRate = 44100;
3647
2661
  for (const def of GLOBAL_SAMPLE_DEFS) {
3648
- const filePath = path3.join(globalSamplesDir, def.filename);
3649
- if (!fs3.existsSync(filePath)) {
2662
+ const filePath = path2.join(globalSamplesDir, def.filename);
2663
+ if (!fs2.existsSync(filePath)) {
3650
2664
  const numSamples = Math.floor(sampleRate * def.durationSec);
3651
2665
  const chL = new Float32Array(numSamples);
3652
2666
  const chR = new Float32Array(numSamples);
@@ -3658,16 +2672,16 @@ async function ensureGlobalSamples(globalSamplesDir) {
3658
2672
  chR[i] = r;
3659
2673
  }
3660
2674
  const wavBuf = createWavBuffer(sampleRate, 2, [chL, chR]);
3661
- await fs3.promises.writeFile(filePath, wavBuf);
2675
+ await fs2.promises.writeFile(filePath, wavBuf);
3662
2676
  }
3663
2677
  }
3664
2678
  }
3665
2679
 
3666
2680
  // src/server/services/assetPoolService.ts
3667
- import fs4 from "fs";
3668
- import path4 from "path";
3669
- var WORKSPACE_ASSETS_ROOT = path4.join(process.cwd(), "assets");
3670
- var PROJECTS_ROOT = path4.join(process.cwd(), "projects");
2681
+ import fs3 from "fs";
2682
+ import path3 from "path";
2683
+ var WORKSPACE_ASSETS_ROOT = path3.join(process.cwd(), "assets");
2684
+ var PROJECTS_ROOT = path3.join(process.cwd(), "projects");
3671
2685
  var SUPPORTED_AUDIO_EXTS = [
3672
2686
  ".wav",
3673
2687
  ".mp3",
@@ -3685,27 +2699,27 @@ var SUPPORTED_AUDIO_EXTS = [
3685
2699
  var SUPPORTED_MIDI_EXTS = [".mid", ".midi", ".json"];
3686
2700
  async function ensureAssetPoolDirectories(rootDir) {
3687
2701
  const dirs = [
3688
- path4.join(rootDir, "audio", "stereo", "default"),
3689
- path4.join(rootDir, "audio", "stereo", "userdefined"),
3690
- path4.join(rootDir, "audio", "mono", "default"),
3691
- path4.join(rootDir, "audio", "mono", "userdefined"),
3692
- path4.join(rootDir, "midi", "default"),
3693
- path4.join(rootDir, "midi", "userdefined")
2702
+ path3.join(rootDir, "audio", "stereo", "default"),
2703
+ path3.join(rootDir, "audio", "stereo", "userdefined"),
2704
+ path3.join(rootDir, "audio", "mono", "default"),
2705
+ path3.join(rootDir, "audio", "mono", "userdefined"),
2706
+ path3.join(rootDir, "midi", "default"),
2707
+ path3.join(rootDir, "midi", "userdefined")
3694
2708
  ];
3695
2709
  for (const dir of dirs) {
3696
- if (!fs4.existsSync(dir)) {
3697
- await fs4.promises.mkdir(dir, { recursive: true });
2710
+ if (!fs3.existsSync(dir)) {
2711
+ await fs3.promises.mkdir(dir, { recursive: true });
3698
2712
  }
3699
2713
  }
3700
2714
  }
3701
2715
  async function seedDefaultAssets() {
3702
2716
  await ensureAssetPoolDirectories(WORKSPACE_ASSETS_ROOT);
3703
- const stereoDefaultDir = path4.join(WORKSPACE_ASSETS_ROOT, "audio", "stereo", "default");
3704
- const monoDefaultDir = path4.join(WORKSPACE_ASSETS_ROOT, "audio", "mono", "default");
3705
- const midiDefaultDir = path4.join(WORKSPACE_ASSETS_ROOT, "midi", "default");
2717
+ const stereoDefaultDir = path3.join(WORKSPACE_ASSETS_ROOT, "audio", "stereo", "default");
2718
+ const monoDefaultDir = path3.join(WORKSPACE_ASSETS_ROOT, "audio", "mono", "default");
2719
+ const midiDefaultDir = path3.join(WORKSPACE_ASSETS_ROOT, "midi", "default");
3706
2720
  const sampleRate = 44100;
3707
- const subBoomPath = path4.join(stereoDefaultDir, "808_sub_boom.wav");
3708
- if (!fs4.existsSync(subBoomPath)) {
2721
+ const subBoomPath = path3.join(stereoDefaultDir, "808_sub_boom.wav");
2722
+ if (!fs3.existsSync(subBoomPath)) {
3709
2723
  const numSamples = Math.floor(sampleRate * 1.8);
3710
2724
  const chL = new Float32Array(numSamples);
3711
2725
  const chR = new Float32Array(numSamples);
@@ -3716,10 +2730,10 @@ async function seedDefaultAssets() {
3716
2730
  chL[i] = s;
3717
2731
  chR[i] = s;
3718
2732
  }
3719
- await fs4.promises.writeFile(subBoomPath, createWavBuffer(sampleRate, 2, [chL, chR]));
2733
+ await fs3.promises.writeFile(subBoomPath, createWavBuffer(sampleRate, 2, [chL, chR]));
3720
2734
  }
3721
- const leadPath = path4.join(stereoDefaultDir, "celestial_lead.wav");
3722
- if (!fs4.existsSync(leadPath)) {
2735
+ const leadPath = path3.join(stereoDefaultDir, "celestial_lead.wav");
2736
+ if (!fs3.existsSync(leadPath)) {
3723
2737
  const numSamples = Math.floor(sampleRate * 2.5);
3724
2738
  const chL = new Float32Array(numSamples);
3725
2739
  const chR = new Float32Array(numSamples);
@@ -3733,10 +2747,10 @@ async function seedDefaultAssets() {
3733
2747
  chL[i] = s1 * env;
3734
2748
  chR[i] = (s1 + s2 * 0.5) * env;
3735
2749
  }
3736
- await fs4.promises.writeFile(leadPath, createWavBuffer(sampleRate, 2, [chL, chR]));
2750
+ await fs3.promises.writeFile(leadPath, createWavBuffer(sampleRate, 2, [chL, chR]));
3737
2751
  }
3738
- const padPath = path4.join(stereoDefaultDir, "ethereal_pad.wav");
3739
- if (!fs4.existsSync(padPath)) {
2752
+ const padPath = path3.join(stereoDefaultDir, "ethereal_pad.wav");
2753
+ if (!fs3.existsSync(padPath)) {
3740
2754
  const numSamples = Math.floor(sampleRate * 3);
3741
2755
  const chL = new Float32Array(numSamples);
3742
2756
  const chR = new Float32Array(numSamples);
@@ -3755,10 +2769,10 @@ async function seedDefaultAssets() {
3755
2769
  chL[i] = left * env;
3756
2770
  chR[i] = right * env;
3757
2771
  }
3758
- await fs4.promises.writeFile(padPath, createWavBuffer(sampleRate, 2, [chL, chR]));
2772
+ await fs3.promises.writeFile(padPath, createWavBuffer(sampleRate, 2, [chL, chR]));
3759
2773
  }
3760
- const snarePath = path4.join(monoDefaultDir, "snare_snappy.wav");
3761
- if (!fs4.existsSync(snarePath)) {
2774
+ const snarePath = path3.join(monoDefaultDir, "snare_snappy.wav");
2775
+ if (!fs3.existsSync(snarePath)) {
3762
2776
  const numSamples = Math.floor(sampleRate * 0.8);
3763
2777
  const chM = new Float32Array(numSamples);
3764
2778
  for (let i = 0; i < numSamples; i++) {
@@ -3768,10 +2782,10 @@ async function seedDefaultAssets() {
3768
2782
  const noise = (Math.random() * 2 - 1) * Math.exp(-p * 4.5) * 0.6;
3769
2783
  chM[i] = tone + noise;
3770
2784
  }
3771
- await fs4.promises.writeFile(snarePath, createWavBuffer(sampleRate, 1, [chM]));
2785
+ await fs3.promises.writeFile(snarePath, createWavBuffer(sampleRate, 1, [chM]));
3772
2786
  }
3773
- const hihatPath = path4.join(monoDefaultDir, "hihat_closed.wav");
3774
- if (!fs4.existsSync(hihatPath)) {
2787
+ const hihatPath = path3.join(monoDefaultDir, "hihat_closed.wav");
2788
+ if (!fs3.existsSync(hihatPath)) {
3775
2789
  const numSamples = Math.floor(sampleRate * 0.25);
3776
2790
  const chM = new Float32Array(numSamples);
3777
2791
  for (let i = 0; i < numSamples; i++) {
@@ -3779,10 +2793,10 @@ async function seedDefaultAssets() {
3779
2793
  const noise = (Math.random() * 2 - 1) * Math.exp(-p * 20);
3780
2794
  chM[i] = noise * 0.5;
3781
2795
  }
3782
- await fs4.promises.writeFile(hihatPath, createWavBuffer(sampleRate, 1, [chM]));
2796
+ await fs3.promises.writeFile(hihatPath, createWavBuffer(sampleRate, 1, [chM]));
3783
2797
  }
3784
- const midiChordPath = path4.join(midiDefaultDir, "lydian_celestial_pad.json");
3785
- if (!fs4.existsSync(midiChordPath)) {
2798
+ const midiChordPath = path3.join(midiDefaultDir, "lydian_celestial_pad.json");
2799
+ if (!fs3.existsSync(midiChordPath)) {
3786
2800
  const starterMidiPattern = {
3787
2801
  id: "pat-default-lydian",
3788
2802
  name: "Lydian Celestial Pad",
@@ -3795,31 +2809,31 @@ async function seedDefaultAssets() {
3795
2809
  { note: 71, name: "B4", velocity: 0.75, time: 0, duration: 3.8 }
3796
2810
  ]
3797
2811
  };
3798
- await fs4.promises.writeFile(midiChordPath, JSON.stringify(starterMidiPattern, null, 2), "utf-8");
2812
+ await fs3.promises.writeFile(midiChordPath, JSON.stringify(starterMidiPattern, null, 2), "utf-8");
3799
2813
  }
3800
2814
  await ensureProjectAssets("default-kingdom");
3801
2815
  }
3802
2816
  async function ensureProjectAssets(projectId) {
3803
- const safeId = path4.basename(projectId);
3804
- const projectAssetsDir = path4.join(PROJECTS_ROOT, safeId, "assets");
2817
+ const safeId = path3.basename(projectId);
2818
+ const projectAssetsDir = path3.join(PROJECTS_ROOT, safeId, "assets");
3805
2819
  await ensureAssetPoolDirectories(projectAssetsDir);
3806
2820
  return projectAssetsDir;
3807
2821
  }
3808
2822
  function getAssetPoolLocationPath(scope, projectId, dataType, channelType, subfolder = "userdefined") {
3809
- const root = scope === "project" && projectId ? path4.join(PROJECTS_ROOT, path4.basename(projectId), "assets") : WORKSPACE_ASSETS_ROOT;
3810
- const safeSub = path4.basename(subfolder).replace(/[^a-zA-Z0-9._-]/g, "_");
2823
+ const root = scope === "project" && projectId ? path3.join(PROJECTS_ROOT, path3.basename(projectId), "assets") : WORKSPACE_ASSETS_ROOT;
2824
+ const safeSub = path3.basename(subfolder).replace(/[^a-zA-Z0-9._-]/g, "_");
3811
2825
  if (dataType === "audio") {
3812
2826
  const safeChannel = channelType === "mono" ? "mono" : "stereo";
3813
- return path4.join(root, "audio", safeChannel, safeSub);
2827
+ return path3.join(root, "audio", safeChannel, safeSub);
3814
2828
  } else {
3815
- return path4.join(root, "midi", safeSub);
2829
+ return path3.join(root, "midi", safeSub);
3816
2830
  }
3817
2831
  }
3818
2832
  function getAssetPoolPublicUrl(scope, projectId, dataType, channelType, subfolder, filename) {
3819
- const safeFilename = path4.basename(filename);
3820
- const safeSub = path4.basename(subfolder);
2833
+ const safeFilename = path3.basename(filename);
2834
+ const safeSub = path3.basename(subfolder);
3821
2835
  if (scope === "project" && projectId) {
3822
- const safeProj = path4.basename(projectId);
2836
+ const safeProj = path3.basename(projectId);
3823
2837
  if (dataType === "audio") {
3824
2838
  const channel = channelType === "mono" ? "mono" : "stereo";
3825
2839
  return `/projects/${safeProj}/assets/audio/${channel}/${safeSub}/${safeFilename}`;
@@ -3841,31 +2855,31 @@ async function scanAssetPool(scope, projectId) {
3841
2855
  } else {
3842
2856
  await ensureAssetPoolDirectories(WORKSPACE_ASSETS_ROOT);
3843
2857
  }
3844
- const rootDir = scope === "project" && projectId ? path4.join(PROJECTS_ROOT, path4.basename(projectId), "assets") : WORKSPACE_ASSETS_ROOT;
2858
+ const rootDir = scope === "project" && projectId ? path3.join(PROJECTS_ROOT, path3.basename(projectId), "assets") : WORKSPACE_ASSETS_ROOT;
3845
2859
  const folders = [];
3846
2860
  const items = [];
3847
2861
  async function scanCategoryFolder(dataType, channelType) {
3848
- const catDir = dataType === "audio" ? path4.join(rootDir, "audio", channelType === "mono" ? "mono" : "stereo") : path4.join(rootDir, "midi");
3849
- if (!fs4.existsSync(catDir)) {
3850
- await fs4.promises.mkdir(catDir, { recursive: true });
2862
+ const catDir = dataType === "audio" ? path3.join(rootDir, "audio", channelType === "mono" ? "mono" : "stereo") : path3.join(rootDir, "midi");
2863
+ if (!fs3.existsSync(catDir)) {
2864
+ await fs3.promises.mkdir(catDir, { recursive: true });
3851
2865
  }
3852
- const subentries = await fs4.promises.readdir(catDir, { withFileTypes: true });
2866
+ const subentries = await fs3.promises.readdir(catDir, { withFileTypes: true });
3853
2867
  for (const entry of subentries) {
3854
2868
  if (!entry.isDirectory()) continue;
3855
2869
  const subfolderName = entry.name;
3856
- const subfolderDir = path4.join(catDir, subfolderName);
3857
- const files = await fs4.promises.readdir(subfolderDir);
2870
+ const subfolderDir = path3.join(catDir, subfolderName);
2871
+ const files = await fs3.promises.readdir(subfolderDir);
3858
2872
  let validFileCount = 0;
3859
2873
  for (const file of files) {
3860
2874
  if (file.startsWith(".")) continue;
3861
- const ext = path4.extname(file).toLowerCase();
2875
+ const ext = path3.extname(file).toLowerCase();
3862
2876
  const isAudio = SUPPORTED_AUDIO_EXTS.includes(ext);
3863
2877
  const isMidi = SUPPORTED_MIDI_EXTS.includes(ext);
3864
2878
  if (dataType === "audio" && !isAudio || dataType === "midi" && !isMidi) continue;
3865
2879
  validFileCount++;
3866
- const filePath = path4.join(subfolderDir, file);
3867
- const stat = await fs4.promises.stat(filePath);
3868
- const cleanName = path4.basename(file, ext).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2880
+ const filePath = path3.join(subfolderDir, file);
2881
+ const stat = await fs3.promises.stat(filePath);
2882
+ const cleanName = path3.basename(file, ext).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
3869
2883
  const relPath = dataType === "audio" ? `audio/${channelType === "mono" ? "mono" : "stereo"}/${subfolderName}/${file}` : `midi/${subfolderName}/${file}`;
3870
2884
  const url = getAssetPoolPublicUrl(
3871
2885
  scope,
@@ -3906,7 +2920,7 @@ async function scanAssetPool(scope, projectId) {
3906
2920
  return { folders, items };
3907
2921
  }
3908
2922
  async function createAssetSubfolder(params) {
3909
- const safeName = path4.basename(params.folderName).trim().replace(/[^a-zA-Z0-9._-]/g, "_");
2923
+ const safeName = path3.basename(params.folderName).trim().replace(/[^a-zA-Z0-9._-]/g, "_");
3910
2924
  if (!safeName) throw new Error("Invalid subfolder name");
3911
2925
  const dirPath = getAssetPoolLocationPath(
3912
2926
  params.scope,
@@ -3915,8 +2929,8 @@ async function createAssetSubfolder(params) {
3915
2929
  params.channelType,
3916
2930
  safeName
3917
2931
  );
3918
- if (!fs4.existsSync(dirPath)) {
3919
- await fs4.promises.mkdir(dirPath, { recursive: true });
2932
+ if (!fs3.existsSync(dirPath)) {
2933
+ await fs3.promises.mkdir(dirPath, { recursive: true });
3920
2934
  }
3921
2935
  return {
3922
2936
  name: safeName,
@@ -3935,16 +2949,16 @@ async function saveAssetToPool(params) {
3935
2949
  params.channelType,
3936
2950
  subfolder
3937
2951
  );
3938
- if (!fs4.existsSync(targetDir)) {
3939
- await fs4.promises.mkdir(targetDir, { recursive: true });
2952
+ if (!fs3.existsSync(targetDir)) {
2953
+ await fs3.promises.mkdir(targetDir, { recursive: true });
3940
2954
  }
3941
- const safeFilename = path4.basename(params.filename).replace(/[^a-zA-Z0-9._-]/g, "_");
3942
- const ext = path4.extname(safeFilename).toLowerCase() || (params.dataType === "audio" ? ".wav" : ".json");
3943
- const filePath = path4.join(targetDir, safeFilename);
2955
+ const safeFilename = path3.basename(params.filename).replace(/[^a-zA-Z0-9._-]/g, "_");
2956
+ const ext = path3.extname(safeFilename).toLowerCase() || (params.dataType === "audio" ? ".wav" : ".json");
2957
+ const filePath = path3.join(targetDir, safeFilename);
3944
2958
  const buffer = Buffer.from(params.base64Content, "base64");
3945
- await fs4.promises.writeFile(filePath, buffer);
3946
- const stat = await fs4.promises.stat(filePath);
3947
- const cleanName = path4.basename(safeFilename, ext).replace(/[-_]/g, " ");
2959
+ await fs3.promises.writeFile(filePath, buffer);
2960
+ const stat = await fs3.promises.stat(filePath);
2961
+ const cleanName = path3.basename(safeFilename, ext).replace(/[-_]/g, " ");
3948
2962
  const relPath = params.dataType === "audio" ? `audio/${params.channelType === "mono" ? "mono" : "stereo"}/${subfolder}/${safeFilename}` : `midi/${subfolder}/${safeFilename}`;
3949
2963
  const url = getAssetPoolPublicUrl(
3950
2964
  params.scope,
@@ -3978,19 +2992,19 @@ async function deleteAssetFromPool(params) {
3978
2992
  params.channelType,
3979
2993
  params.subfolder
3980
2994
  );
3981
- const filePath = path4.join(targetDir, path4.basename(params.filename));
3982
- if (fs4.existsSync(filePath)) {
3983
- await fs4.promises.unlink(filePath);
2995
+ const filePath = path3.join(targetDir, path3.basename(params.filename));
2996
+ if (fs3.existsSync(filePath)) {
2997
+ await fs3.promises.unlink(filePath);
3984
2998
  return true;
3985
2999
  }
3986
3000
  return false;
3987
3001
  }
3988
3002
 
3989
3003
  // src/server/services/graphAssetService.ts
3990
- import fs5 from "fs";
3991
- import path5 from "path";
3992
- var WORKSPACE_ROOT = path5.join(process.cwd(), "workspaces", "default");
3993
- var GRAPHS_ROOT = path5.join(WORKSPACE_ROOT, "graphs");
3004
+ import fs4 from "fs";
3005
+ import path4 from "path";
3006
+ var WORKSPACE_ROOT = path4.join(process.cwd(), "workspaces", "default");
3007
+ var GRAPHS_ROOT = path4.join(WORKSPACE_ROOT, "graphs");
3994
3008
  var SUPPORTED_AUDIO_EXTS2 = [
3995
3009
  ".wav",
3996
3010
  ".mp3",
@@ -4008,26 +3022,26 @@ var SUPPORTED_AUDIO_EXTS2 = [
4008
3022
  var SUPPORTED_MIDI_EXTS2 = [".mid", ".midi", ".json"];
4009
3023
  function ensureGraphAssetDirectories(graphDir) {
4010
3024
  const dirs = [
4011
- path5.join(graphDir, "assets", "audio", "stereo", "default"),
4012
- path5.join(graphDir, "assets", "audio", "stereo", "userdefined"),
4013
- path5.join(graphDir, "assets", "audio", "mono", "default"),
4014
- path5.join(graphDir, "assets", "audio", "mono", "userdefined"),
4015
- path5.join(graphDir, "assets", "midi", "default"),
4016
- path5.join(graphDir, "assets", "midi", "userdefined")
3025
+ path4.join(graphDir, "assets", "audio", "stereo", "default"),
3026
+ path4.join(graphDir, "assets", "audio", "stereo", "userdefined"),
3027
+ path4.join(graphDir, "assets", "audio", "mono", "default"),
3028
+ path4.join(graphDir, "assets", "audio", "mono", "userdefined"),
3029
+ path4.join(graphDir, "assets", "midi", "default"),
3030
+ path4.join(graphDir, "assets", "midi", "userdefined")
4017
3031
  ];
4018
3032
  for (const dir of dirs) {
4019
- if (!fs5.existsSync(dir)) {
4020
- fs5.mkdirSync(dir, { recursive: true });
3033
+ if (!fs4.existsSync(dir)) {
3034
+ fs4.mkdirSync(dir, { recursive: true });
4021
3035
  }
4022
3036
  }
4023
3037
  }
4024
3038
  function seedStarterGraphAssets(graphDir) {
4025
- const stereoDefaultDir = path5.join(graphDir, "assets", "audio", "stereo", "default");
4026
- const monoDefaultDir = path5.join(graphDir, "assets", "audio", "mono", "default");
4027
- const midiDefaultDir = path5.join(graphDir, "assets", "midi", "default");
3039
+ const stereoDefaultDir = path4.join(graphDir, "assets", "audio", "stereo", "default");
3040
+ const monoDefaultDir = path4.join(graphDir, "assets", "audio", "mono", "default");
3041
+ const midiDefaultDir = path4.join(graphDir, "assets", "midi", "default");
4028
3042
  const sampleRate = 44100;
4029
- const subBoomPath = path5.join(stereoDefaultDir, "808_sub_boom.wav");
4030
- if (!fs5.existsSync(subBoomPath)) {
3043
+ const subBoomPath = path4.join(stereoDefaultDir, "808_sub_boom.wav");
3044
+ if (!fs4.existsSync(subBoomPath)) {
4031
3045
  const numSamples = Math.floor(sampleRate * 1.8);
4032
3046
  const chL = new Float32Array(numSamples);
4033
3047
  const chR = new Float32Array(numSamples);
@@ -4038,10 +3052,10 @@ function seedStarterGraphAssets(graphDir) {
4038
3052
  chL[i] = s;
4039
3053
  chR[i] = s;
4040
3054
  }
4041
- fs5.writeFileSync(subBoomPath, createWavBuffer(sampleRate, 2, [chL, chR]));
3055
+ fs4.writeFileSync(subBoomPath, createWavBuffer(sampleRate, 2, [chL, chR]));
4042
3056
  }
4043
- const leadPath = path5.join(stereoDefaultDir, "celestial_lead.wav");
4044
- if (!fs5.existsSync(leadPath)) {
3057
+ const leadPath = path4.join(stereoDefaultDir, "celestial_lead.wav");
3058
+ if (!fs4.existsSync(leadPath)) {
4045
3059
  const numSamples = Math.floor(sampleRate * 2.5);
4046
3060
  const chL = new Float32Array(numSamples);
4047
3061
  const chR = new Float32Array(numSamples);
@@ -4055,10 +3069,10 @@ function seedStarterGraphAssets(graphDir) {
4055
3069
  chL[i] = s1 * env;
4056
3070
  chR[i] = (s1 + s2 * 0.5) * env;
4057
3071
  }
4058
- fs5.writeFileSync(leadPath, createWavBuffer(sampleRate, 2, [chL, chR]));
3072
+ fs4.writeFileSync(leadPath, createWavBuffer(sampleRate, 2, [chL, chR]));
4059
3073
  }
4060
- const padPath = path5.join(stereoDefaultDir, "ethereal_pad.wav");
4061
- if (!fs5.existsSync(padPath)) {
3074
+ const padPath = path4.join(stereoDefaultDir, "ethereal_pad.wav");
3075
+ if (!fs4.existsSync(padPath)) {
4062
3076
  const numSamples = Math.floor(sampleRate * 3);
4063
3077
  const chL = new Float32Array(numSamples);
4064
3078
  const chR = new Float32Array(numSamples);
@@ -4077,10 +3091,10 @@ function seedStarterGraphAssets(graphDir) {
4077
3091
  chL[i] = left * env;
4078
3092
  chR[i] = right * env;
4079
3093
  }
4080
- fs5.writeFileSync(padPath, createWavBuffer(sampleRate, 2, [chL, chR]));
3094
+ fs4.writeFileSync(padPath, createWavBuffer(sampleRate, 2, [chL, chR]));
4081
3095
  }
4082
- const snarePath = path5.join(monoDefaultDir, "snare_snappy.wav");
4083
- if (!fs5.existsSync(snarePath)) {
3096
+ const snarePath = path4.join(monoDefaultDir, "snare_snappy.wav");
3097
+ if (!fs4.existsSync(snarePath)) {
4084
3098
  const numSamples = Math.floor(sampleRate * 0.8);
4085
3099
  const chM = new Float32Array(numSamples);
4086
3100
  for (let i = 0; i < numSamples; i++) {
@@ -4090,10 +3104,10 @@ function seedStarterGraphAssets(graphDir) {
4090
3104
  const noise = (Math.random() * 2 - 1) * Math.exp(-p * 4.5) * 0.6;
4091
3105
  chM[i] = tone + noise;
4092
3106
  }
4093
- fs5.writeFileSync(snarePath, createWavBuffer(sampleRate, 1, [chM]));
3107
+ fs4.writeFileSync(snarePath, createWavBuffer(sampleRate, 1, [chM]));
4094
3108
  }
4095
- const hihatPath = path5.join(monoDefaultDir, "hihat_closed.wav");
4096
- if (!fs5.existsSync(hihatPath)) {
3109
+ const hihatPath = path4.join(monoDefaultDir, "hihat_closed.wav");
3110
+ if (!fs4.existsSync(hihatPath)) {
4097
3111
  const numSamples = Math.floor(sampleRate * 0.25);
4098
3112
  const chM = new Float32Array(numSamples);
4099
3113
  for (let i = 0; i < numSamples; i++) {
@@ -4101,10 +3115,10 @@ function seedStarterGraphAssets(graphDir) {
4101
3115
  const noise = (Math.random() * 2 - 1) * Math.exp(-p * 20);
4102
3116
  chM[i] = noise * 0.5;
4103
3117
  }
4104
- fs5.writeFileSync(hihatPath, createWavBuffer(sampleRate, 1, [chM]));
3118
+ fs4.writeFileSync(hihatPath, createWavBuffer(sampleRate, 1, [chM]));
4105
3119
  }
4106
- const midiChordPath = path5.join(midiDefaultDir, "lydian_celestial_pad.json");
4107
- if (!fs5.existsSync(midiChordPath)) {
3120
+ const midiChordPath = path4.join(midiDefaultDir, "lydian_celestial_pad.json");
3121
+ if (!fs4.existsSync(midiChordPath)) {
4108
3122
  const starterMidiPattern = {
4109
3123
  id: "pat-default-lydian",
4110
3124
  name: "Lydian Celestial Pad",
@@ -4117,43 +3131,43 @@ function seedStarterGraphAssets(graphDir) {
4117
3131
  { note: 71, name: "B4", velocity: 0.75, time: 0, duration: 3.8 }
4118
3132
  ]
4119
3133
  };
4120
- fs5.writeFileSync(midiChordPath, JSON.stringify(starterMidiPattern, null, 2), "utf-8");
3134
+ fs4.writeFileSync(midiChordPath, JSON.stringify(starterMidiPattern, null, 2), "utf-8");
4121
3135
  }
4122
3136
  }
4123
3137
  async function scanGraphAssets(graphId, includeImported = true) {
4124
- const safeGraphId = path5.basename(graphId);
4125
- const graphDir = path5.join(GRAPHS_ROOT, safeGraphId);
4126
- if (!fs5.existsSync(graphDir)) {
4127
- await fs5.promises.mkdir(graphDir, { recursive: true });
3138
+ const safeGraphId = path4.basename(graphId);
3139
+ const graphDir = path4.join(GRAPHS_ROOT, safeGraphId);
3140
+ if (!fs4.existsSync(graphDir)) {
3141
+ await fs4.promises.mkdir(graphDir, { recursive: true });
4128
3142
  }
4129
3143
  ensureGraphAssetDirectories(graphDir);
4130
3144
  const folders = [];
4131
3145
  const items = [];
4132
3146
  const importedItems = [];
4133
3147
  async function scanSingleGraph(gId, isImported, ownerName) {
4134
- const currentGraphDir = path5.join(GRAPHS_ROOT, path5.basename(gId));
4135
- const assetsDir = path5.join(currentGraphDir, "assets");
4136
- if (!fs5.existsSync(assetsDir)) return;
3148
+ const currentGraphDir = path4.join(GRAPHS_ROOT, path4.basename(gId));
3149
+ const assetsDir = path4.join(currentGraphDir, "assets");
3150
+ if (!fs4.existsSync(assetsDir)) return;
4137
3151
  async function scanCategoryFolder(dataType, channelType) {
4138
- const catDir = dataType === "audio" ? path5.join(assetsDir, "audio", channelType === "mono" ? "mono" : "stereo") : path5.join(assetsDir, "midi");
4139
- if (!fs5.existsSync(catDir)) return;
4140
- const subentries = await fs5.promises.readdir(catDir, { withFileTypes: true });
3152
+ const catDir = dataType === "audio" ? path4.join(assetsDir, "audio", channelType === "mono" ? "mono" : "stereo") : path4.join(assetsDir, "midi");
3153
+ if (!fs4.existsSync(catDir)) return;
3154
+ const subentries = await fs4.promises.readdir(catDir, { withFileTypes: true });
4141
3155
  for (const entry of subentries) {
4142
3156
  if (!entry.isDirectory()) continue;
4143
3157
  const subfolderName = entry.name;
4144
- const subfolderDir = path5.join(catDir, subfolderName);
4145
- const files = await fs5.promises.readdir(subfolderDir);
3158
+ const subfolderDir = path4.join(catDir, subfolderName);
3159
+ const files = await fs4.promises.readdir(subfolderDir);
4146
3160
  let validFileCount = 0;
4147
3161
  for (const file of files) {
4148
3162
  if (file.startsWith(".")) continue;
4149
- const ext = path5.extname(file).toLowerCase();
3163
+ const ext = path4.extname(file).toLowerCase();
4150
3164
  const isAudio = SUPPORTED_AUDIO_EXTS2.includes(ext);
4151
3165
  const isMidi = SUPPORTED_MIDI_EXTS2.includes(ext);
4152
3166
  if (dataType === "audio" && !isAudio || dataType === "midi" && !isMidi) continue;
4153
3167
  validFileCount++;
4154
- const filePath = path5.join(subfolderDir, file);
4155
- const stat = await fs5.promises.stat(filePath);
4156
- const cleanName = path5.basename(file, ext).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
3168
+ const filePath = path4.join(subfolderDir, file);
3169
+ const stat = await fs4.promises.stat(filePath);
3170
+ const cleanName = path4.basename(file, ext).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
4157
3171
  const relPath = dataType === "audio" ? `audio/${channelType === "mono" ? "mono" : "stereo"}/${subfolderName}/${file}` : `midi/${subfolderName}/${file}`;
4158
3172
  const url = dataType === "audio" ? `/graphs/${gId}/assets/audio/${channelType === "mono" ? "mono" : "stereo"}/${subfolderName}/${file}` : `/graphs/${gId}/assets/midi/${subfolderName}/${file}`;
4159
3173
  const item = {
@@ -4196,18 +3210,18 @@ async function scanGraphAssets(graphId, includeImported = true) {
4196
3210
  }
4197
3211
  await scanSingleGraph(safeGraphId, false);
4198
3212
  if (includeImported) {
4199
- const qmsFile = path5.join(graphDir, "graph.qms");
4200
- if (fs5.existsSync(qmsFile)) {
3213
+ const qmsFile = path4.join(graphDir, "graph.qms");
3214
+ if (fs4.existsSync(qmsFile)) {
4201
3215
  try {
4202
- const raw = await fs5.promises.readFile(qmsFile, "utf-8");
3216
+ const raw = await fs4.promises.readFile(qmsFile, "utf-8");
4203
3217
  const pkg = JSON.parse(raw);
4204
3218
  const importedIds = pkg?.metadata?.importedGraphIds || [];
4205
3219
  for (const impId of importedIds) {
4206
- const impQms = path5.join(GRAPHS_ROOT, impId, "graph.qms");
3220
+ const impQms = path4.join(GRAPHS_ROOT, impId, "graph.qms");
4207
3221
  let impName = impId;
4208
- if (fs5.existsSync(impQms)) {
3222
+ if (fs4.existsSync(impQms)) {
4209
3223
  try {
4210
- const impRaw = await fs5.promises.readFile(impQms, "utf-8");
3224
+ const impRaw = await fs4.promises.readFile(impQms, "utf-8");
4211
3225
  const impPkg = JSON.parse(impRaw);
4212
3226
  impName = impPkg?.metadata?.name || impId;
4213
3227
  } catch {
@@ -4223,14 +3237,14 @@ async function scanGraphAssets(graphId, includeImported = true) {
4223
3237
  return { folders, items, importedItems };
4224
3238
  }
4225
3239
  async function createGraphAssetFolder(params) {
4226
- const safeGraphId = path5.basename(params.graphId);
4227
- const safeFolderName = path5.basename(params.folderName).trim().replace(/[^a-zA-Z0-9._-]/g, "_");
3240
+ const safeGraphId = path4.basename(params.graphId);
3241
+ const safeFolderName = path4.basename(params.folderName).trim().replace(/[^a-zA-Z0-9._-]/g, "_");
4228
3242
  if (!safeFolderName) throw new Error("Invalid subfolder name");
4229
- const graphDir = path5.join(GRAPHS_ROOT, safeGraphId);
3243
+ const graphDir = path4.join(GRAPHS_ROOT, safeGraphId);
4230
3244
  ensureGraphAssetDirectories(graphDir);
4231
- const targetDir = params.dataType === "audio" ? path5.join(graphDir, "assets", "audio", params.channelType === "mono" ? "mono" : "stereo", safeFolderName) : path5.join(graphDir, "assets", "midi", safeFolderName);
4232
- if (!fs5.existsSync(targetDir)) {
4233
- await fs5.promises.mkdir(targetDir, { recursive: true });
3245
+ const targetDir = params.dataType === "audio" ? path4.join(graphDir, "assets", "audio", params.channelType === "mono" ? "mono" : "stereo", safeFolderName) : path4.join(graphDir, "assets", "midi", safeFolderName);
3246
+ if (!fs4.existsSync(targetDir)) {
3247
+ await fs4.promises.mkdir(targetDir, { recursive: true });
4234
3248
  }
4235
3249
  return {
4236
3250
  name: safeFolderName,
@@ -4242,21 +3256,21 @@ async function createGraphAssetFolder(params) {
4242
3256
  };
4243
3257
  }
4244
3258
  async function saveAssetToGraph(params) {
4245
- const safeGraphId = path5.basename(params.graphId);
3259
+ const safeGraphId = path4.basename(params.graphId);
4246
3260
  const subfolder = params.subfolder || "userdefined";
4247
- const graphDir = path5.join(GRAPHS_ROOT, safeGraphId);
3261
+ const graphDir = path4.join(GRAPHS_ROOT, safeGraphId);
4248
3262
  ensureGraphAssetDirectories(graphDir);
4249
- const targetDir = params.dataType === "audio" ? path5.join(graphDir, "assets", "audio", params.channelType === "mono" ? "mono" : "stereo", subfolder) : path5.join(graphDir, "assets", "midi", subfolder);
4250
- if (!fs5.existsSync(targetDir)) {
4251
- await fs5.promises.mkdir(targetDir, { recursive: true });
3263
+ const targetDir = params.dataType === "audio" ? path4.join(graphDir, "assets", "audio", params.channelType === "mono" ? "mono" : "stereo", subfolder) : path4.join(graphDir, "assets", "midi", subfolder);
3264
+ if (!fs4.existsSync(targetDir)) {
3265
+ await fs4.promises.mkdir(targetDir, { recursive: true });
4252
3266
  }
4253
- const safeFilename = path5.basename(params.filename).replace(/[^a-zA-Z0-9._-]/g, "_");
4254
- const ext = path5.extname(safeFilename).toLowerCase() || (params.dataType === "audio" ? ".wav" : ".json");
4255
- const filePath = path5.join(targetDir, safeFilename);
3267
+ const safeFilename = path4.basename(params.filename).replace(/[^a-zA-Z0-9._-]/g, "_");
3268
+ const ext = path4.extname(safeFilename).toLowerCase() || (params.dataType === "audio" ? ".wav" : ".json");
3269
+ const filePath = path4.join(targetDir, safeFilename);
4256
3270
  const buffer = Buffer.from(params.base64Content, "base64");
4257
- await fs5.promises.writeFile(filePath, buffer);
4258
- const stat = await fs5.promises.stat(filePath);
4259
- const cleanName = path5.basename(safeFilename, ext).replace(/[-_]/g, " ");
3271
+ await fs4.promises.writeFile(filePath, buffer);
3272
+ const stat = await fs4.promises.stat(filePath);
3273
+ const cleanName = path4.basename(safeFilename, ext).replace(/[-_]/g, " ");
4260
3274
  const relPath = params.dataType === "audio" ? `audio/${params.channelType === "mono" ? "mono" : "stereo"}/${subfolder}/${safeFilename}` : `midi/${subfolder}/${safeFilename}`;
4261
3275
  const url = params.dataType === "audio" ? `/graphs/${safeGraphId}/assets/audio/${params.channelType === "mono" ? "mono" : "stereo"}/${subfolder}/${safeFilename}` : `/graphs/${safeGraphId}/assets/midi/${subfolder}/${safeFilename}`;
4262
3276
  return {
@@ -4275,32 +3289,32 @@ async function saveAssetToGraph(params) {
4275
3289
  };
4276
3290
  }
4277
3291
  async function deleteAssetFromGraph(params) {
4278
- const safeGraphId = path5.basename(params.graphId);
4279
- const graphDir = path5.join(GRAPHS_ROOT, safeGraphId);
4280
- const targetDir = params.dataType === "audio" ? path5.join(graphDir, "assets", "audio", params.channelType === "mono" ? "mono" : "stereo", params.subfolder) : path5.join(graphDir, "assets", "midi", params.subfolder);
4281
- const filePath = path5.join(targetDir, path5.basename(params.filename));
4282
- if (fs5.existsSync(filePath)) {
4283
- await fs5.promises.unlink(filePath);
3292
+ const safeGraphId = path4.basename(params.graphId);
3293
+ const graphDir = path4.join(GRAPHS_ROOT, safeGraphId);
3294
+ const targetDir = params.dataType === "audio" ? path4.join(graphDir, "assets", "audio", params.channelType === "mono" ? "mono" : "stereo", params.subfolder) : path4.join(graphDir, "assets", "midi", params.subfolder);
3295
+ const filePath = path4.join(targetDir, path4.basename(params.filename));
3296
+ if (fs4.existsSync(filePath)) {
3297
+ await fs4.promises.unlink(filePath);
4284
3298
  return true;
4285
3299
  }
4286
3300
  return false;
4287
3301
  }
4288
3302
 
4289
3303
  // src/server/services/graphPackageService.ts
4290
- import fs6 from "fs";
4291
- import path6 from "path";
3304
+ import fs5 from "fs";
3305
+ import path5 from "path";
4292
3306
  import { validateGraphImport } from "@ultrst-armada/kloks/state-graph";
4293
- var WORKSPACE_ROOT2 = path6.join(process.cwd(), "workspaces", "default");
4294
- var GRAPHS_ROOT2 = path6.join(WORKSPACE_ROOT2, "graphs");
4295
- var LEGACY_PROJECTS_ROOT = path6.join(process.cwd(), "projects");
3307
+ var WORKSPACE_ROOT2 = path5.join(process.cwd(), "workspaces", "default");
3308
+ var GRAPHS_ROOT2 = path5.join(WORKSPACE_ROOT2, "graphs");
3309
+ var LEGACY_PROJECTS_ROOT = path5.join(process.cwd(), "projects");
4296
3310
  function ensureWorkspaceStructure() {
4297
- if (!fs6.existsSync(GRAPHS_ROOT2)) {
4298
- fs6.mkdirSync(GRAPHS_ROOT2, { recursive: true });
3311
+ if (!fs5.existsSync(GRAPHS_ROOT2)) {
3312
+ fs5.mkdirSync(GRAPHS_ROOT2, { recursive: true });
4299
3313
  }
4300
3314
  seedStarterGraphsIfEmpty();
4301
3315
  }
4302
3316
  function seedStarterGraphsIfEmpty() {
4303
- const existing = fs6.existsSync(GRAPHS_ROOT2) ? fs6.readdirSync(GRAPHS_ROOT2) : [];
3317
+ const existing = fs5.existsSync(GRAPHS_ROOT2) ? fs5.readdirSync(GRAPHS_ROOT2) : [];
4304
3318
  if (existing.length > 0) return;
4305
3319
  const introId = "intro-motif-starter";
4306
3320
  createInitialGraphPackageSync(
@@ -4569,9 +3583,9 @@ function seedStarterGraphsIfEmpty() {
4569
3583
  );
4570
3584
  }
4571
3585
  function createInitialGraphPackageSync(id, name, description, type, bpm, masterVolume, currentBiome, nodes, connections, importedGraphIds = []) {
4572
- const graphDir = path6.join(GRAPHS_ROOT2, id);
4573
- if (!fs6.existsSync(graphDir)) {
4574
- fs6.mkdirSync(graphDir, { recursive: true });
3586
+ const graphDir = path5.join(GRAPHS_ROOT2, id);
3587
+ if (!fs5.existsSync(graphDir)) {
3588
+ fs5.mkdirSync(graphDir, { recursive: true });
4575
3589
  }
4576
3590
  ensureGraphAssetDirectories(graphDir);
4577
3591
  seedStarterGraphAssets(graphDir);
@@ -4604,29 +3618,29 @@ function createInitialGraphPackageSync(id, name, description, type, bpm, masterV
4604
3618
  connections
4605
3619
  }
4606
3620
  };
4607
- fs6.writeFileSync(path6.join(graphDir, "graph.qms"), JSON.stringify(graphPackage, null, 2), "utf-8");
3621
+ fs5.writeFileSync(path5.join(graphDir, "graph.qms"), JSON.stringify(graphPackage, null, 2), "utf-8");
4608
3622
  }
4609
3623
  async function listAllGraphPackages() {
4610
3624
  ensureWorkspaceStructure();
4611
- const dirs = await fs6.promises.readdir(GRAPHS_ROOT2);
3625
+ const dirs = await fs5.promises.readdir(GRAPHS_ROOT2);
4612
3626
  const packages = [];
4613
3627
  for (const dir of dirs) {
4614
- const graphDir = path6.join(GRAPHS_ROOT2, dir);
4615
- const stat = await fs6.promises.stat(graphDir);
3628
+ const graphDir = path5.join(GRAPHS_ROOT2, dir);
3629
+ const stat = await fs5.promises.stat(graphDir);
4616
3630
  if (!stat.isDirectory()) continue;
4617
- const qmsFile = path6.join(graphDir, "graph.qms");
4618
- const legacyProjectFile = path6.join(graphDir, "project.json");
3631
+ const qmsFile = path5.join(graphDir, "graph.qms");
3632
+ const legacyProjectFile = path5.join(graphDir, "project.json");
4619
3633
  let packageData = null;
4620
- if (fs6.existsSync(qmsFile)) {
3634
+ if (fs5.existsSync(qmsFile)) {
4621
3635
  try {
4622
- const raw = await fs6.promises.readFile(qmsFile, "utf-8");
3636
+ const raw = await fs5.promises.readFile(qmsFile, "utf-8");
4623
3637
  packageData = JSON.parse(raw);
4624
3638
  } catch (err) {
4625
3639
  console.warn(`Corrupted graph.qms in ${dir}:`, err);
4626
3640
  }
4627
- } else if (fs6.existsSync(legacyProjectFile)) {
3641
+ } else if (fs5.existsSync(legacyProjectFile)) {
4628
3642
  try {
4629
- const raw = await fs6.promises.readFile(legacyProjectFile, "utf-8");
3643
+ const raw = await fs5.promises.readFile(legacyProjectFile, "utf-8");
4630
3644
  const legacy = JSON.parse(raw);
4631
3645
  packageData = {
4632
3646
  format: "qms_graph_package",
@@ -4656,7 +3670,7 @@ async function listAllGraphPackages() {
4656
3670
  mapIoConfig: legacy.mapIoConfig
4657
3671
  }
4658
3672
  };
4659
- await fs6.promises.writeFile(qmsFile, JSON.stringify(packageData, null, 2), "utf-8");
3673
+ await fs5.promises.writeFile(qmsFile, JSON.stringify(packageData, null, 2), "utf-8");
4660
3674
  } catch (err) {
4661
3675
  console.warn(`Failed to auto-migrate legacy project ${dir}:`, err);
4662
3676
  }
@@ -4678,13 +3692,13 @@ async function listAllGraphPackages() {
4678
3692
  }
4679
3693
  async function getGraphPackageById(id) {
4680
3694
  ensureWorkspaceStructure();
4681
- const safeId = path6.basename(id);
4682
- const graphDir = path6.join(GRAPHS_ROOT2, safeId);
4683
- const qmsFile = path6.join(graphDir, "graph.qms");
4684
- if (!fs6.existsSync(qmsFile)) {
4685
- const legacyProject = path6.join(LEGACY_PROJECTS_ROOT, safeId, "project.json");
4686
- if (fs6.existsSync(legacyProject)) {
4687
- const raw2 = await fs6.promises.readFile(legacyProject, "utf-8");
3695
+ const safeId = path5.basename(id);
3696
+ const graphDir = path5.join(GRAPHS_ROOT2, safeId);
3697
+ const qmsFile = path5.join(graphDir, "graph.qms");
3698
+ if (!fs5.existsSync(qmsFile)) {
3699
+ const legacyProject = path5.join(LEGACY_PROJECTS_ROOT, safeId, "project.json");
3700
+ if (fs5.existsSync(legacyProject)) {
3701
+ const raw2 = await fs5.promises.readFile(legacyProject, "utf-8");
4688
3702
  const legacy = JSON.parse(raw2);
4689
3703
  return {
4690
3704
  format: "qms_graph_package",
@@ -4717,7 +3731,7 @@ async function getGraphPackageById(id) {
4717
3731
  }
4718
3732
  return null;
4719
3733
  }
4720
- const raw = await fs6.promises.readFile(qmsFile, "utf-8");
3734
+ const raw = await fs5.promises.readFile(qmsFile, "utf-8");
4721
3735
  return JSON.parse(raw);
4722
3736
  }
4723
3737
  async function createGraphPackage(params) {
@@ -4725,8 +3739,8 @@ async function createGraphPackage(params) {
4725
3739
  const name = params.name.trim();
4726
3740
  const safeSlug = name.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-");
4727
3741
  const id = `${safeSlug}-${Date.now().toString(36)}`;
4728
- const graphDir = path6.join(GRAPHS_ROOT2, id);
4729
- await fs6.promises.mkdir(graphDir, { recursive: true });
3742
+ const graphDir = path5.join(GRAPHS_ROOT2, id);
3743
+ await fs5.promises.mkdir(graphDir, { recursive: true });
4730
3744
  await ensureGraphAssetDirectories(graphDir);
4731
3745
  seedStarterGraphAssets(graphDir);
4732
3746
  const now = Date.now();
@@ -4761,8 +3775,8 @@ async function createGraphPackage(params) {
4761
3775
  connections: params.connections || []
4762
3776
  }
4763
3777
  };
4764
- await fs6.promises.writeFile(
4765
- path6.join(graphDir, "graph.qms"),
3778
+ await fs5.promises.writeFile(
3779
+ path5.join(graphDir, "graph.qms"),
4766
3780
  JSON.stringify(graphPackage, null, 2),
4767
3781
  "utf-8"
4768
3782
  );
@@ -4770,10 +3784,10 @@ async function createGraphPackage(params) {
4770
3784
  }
4771
3785
  async function saveGraphPackage(id, payload) {
4772
3786
  ensureWorkspaceStructure();
4773
- const safeId = path6.basename(id);
4774
- const graphDir = path6.join(GRAPHS_ROOT2, safeId);
4775
- if (!fs6.existsSync(graphDir)) {
4776
- await fs6.promises.mkdir(graphDir, { recursive: true });
3787
+ const safeId = path5.basename(id);
3788
+ const graphDir = path5.join(GRAPHS_ROOT2, safeId);
3789
+ if (!fs5.existsSync(graphDir)) {
3790
+ await fs5.promises.mkdir(graphDir, { recursive: true });
4777
3791
  await ensureGraphAssetDirectories(graphDir);
4778
3792
  }
4779
3793
  const existing = await getGraphPackageById(safeId);
@@ -4808,18 +3822,18 @@ async function saveGraphPackage(id, payload) {
4808
3822
  mapIoConfig: payload.mapIoConfig || existing?.graph?.mapIoConfig
4809
3823
  }
4810
3824
  };
4811
- await fs6.promises.writeFile(
4812
- path6.join(graphDir, "graph.qms"),
3825
+ await fs5.promises.writeFile(
3826
+ path5.join(graphDir, "graph.qms"),
4813
3827
  JSON.stringify(graphPackage, null, 2),
4814
3828
  "utf-8"
4815
3829
  );
4816
3830
  return graphPackage;
4817
3831
  }
4818
3832
  async function deleteGraphPackage(id) {
4819
- const safeId = path6.basename(id);
4820
- const graphDir = path6.join(GRAPHS_ROOT2, safeId);
4821
- if (!fs6.existsSync(graphDir)) return false;
4822
- await fs6.promises.rm(graphDir, { recursive: true, force: true });
3833
+ const safeId = path5.basename(id);
3834
+ const graphDir = path5.join(GRAPHS_ROOT2, safeId);
3835
+ if (!fs5.existsSync(graphDir)) return false;
3836
+ await fs5.promises.rm(graphDir, { recursive: true, force: true });
4823
3837
  return true;
4824
3838
  }
4825
3839
  async function importGraphDependency(targetGraphId, candidateImportId) {
@@ -4861,26 +3875,26 @@ async function removeGraphDependency(targetGraphId, importIdToRemove) {
4861
3875
  }
4862
3876
 
4863
3877
  // src/server/services/projectService.ts
4864
- import fs7 from "fs";
4865
- import path7 from "path";
4866
- var PROJECTS_ROOT2 = path7.join(process.cwd(), "projects");
3878
+ import fs6 from "fs";
3879
+ import path6 from "path";
3880
+ var PROJECTS_ROOT2 = path6.join(process.cwd(), "projects");
4867
3881
  var customStarterProjectTemplate = null;
4868
3882
  function configureStarterProjectTemplate(template) {
4869
3883
  customStarterProjectTemplate = template;
4870
3884
  }
4871
3885
  function ensureProjectsRoot(starterTemplate) {
4872
- if (!fs7.existsSync(PROJECTS_ROOT2)) {
4873
- fs7.mkdirSync(PROJECTS_ROOT2, { recursive: true });
3886
+ if (!fs6.existsSync(PROJECTS_ROOT2)) {
3887
+ fs6.mkdirSync(PROJECTS_ROOT2, { recursive: true });
4874
3888
  }
4875
3889
  const template = starterTemplate || customStarterProjectTemplate;
4876
3890
  if (template && template.id) {
4877
- const defaultProjDir = path7.join(PROJECTS_ROOT2, String(template.id));
4878
- if (!fs7.existsSync(defaultProjDir)) {
4879
- fs7.mkdirSync(defaultProjDir, { recursive: true });
4880
- fs7.mkdirSync(path7.join(defaultProjDir, "samples"), { recursive: true });
4881
- fs7.mkdirSync(path7.join(defaultProjDir, "assets"), { recursive: true });
4882
- fs7.writeFileSync(
4883
- path7.join(defaultProjDir, "project.json"),
3891
+ const defaultProjDir = path6.join(PROJECTS_ROOT2, String(template.id));
3892
+ if (!fs6.existsSync(defaultProjDir)) {
3893
+ fs6.mkdirSync(defaultProjDir, { recursive: true });
3894
+ fs6.mkdirSync(path6.join(defaultProjDir, "samples"), { recursive: true });
3895
+ fs6.mkdirSync(path6.join(defaultProjDir, "assets"), { recursive: true });
3896
+ fs6.writeFileSync(
3897
+ path6.join(defaultProjDir, "project.json"),
4884
3898
  JSON.stringify({ ...template, createdAt: Date.now(), updatedAt: Date.now() }, null, 2),
4885
3899
  "utf-8"
4886
3900
  );
@@ -4889,22 +3903,22 @@ function ensureProjectsRoot(starterTemplate) {
4889
3903
  }
4890
3904
  async function listAllProjects() {
4891
3905
  ensureProjectsRoot();
4892
- const dirs = await fs7.promises.readdir(PROJECTS_ROOT2);
3906
+ const dirs = await fs6.promises.readdir(PROJECTS_ROOT2);
4893
3907
  const projects = [];
4894
3908
  for (const dir of dirs) {
4895
- const projDir = path7.join(PROJECTS_ROOT2, dir);
4896
- const stat = await fs7.promises.stat(projDir);
3909
+ const projDir = path6.join(PROJECTS_ROOT2, dir);
3910
+ const stat = await fs6.promises.stat(projDir);
4897
3911
  if (!stat.isDirectory()) continue;
4898
- const projectFile = path7.join(projDir, "project.json");
4899
- const samplesDir = path7.join(projDir, "samples");
3912
+ const projectFile = path6.join(projDir, "project.json");
3913
+ const samplesDir = path6.join(projDir, "samples");
4900
3914
  let sampleCount = 0;
4901
- if (fs7.existsSync(samplesDir)) {
4902
- const samples = await fs7.promises.readdir(samplesDir);
3915
+ if (fs6.existsSync(samplesDir)) {
3916
+ const samples = await fs6.promises.readdir(samplesDir);
4903
3917
  sampleCount = samples.filter((f) => !f.startsWith(".")).length;
4904
3918
  }
4905
- if (fs7.existsSync(projectFile)) {
3919
+ if (fs6.existsSync(projectFile)) {
4906
3920
  try {
4907
- const raw = await fs7.promises.readFile(projectFile, "utf-8");
3921
+ const raw = await fs6.promises.readFile(projectFile, "utf-8");
4908
3922
  const data = JSON.parse(raw);
4909
3923
  projects.push({
4910
3924
  id: data.id || dir,
@@ -4924,24 +3938,24 @@ async function listAllProjects() {
4924
3938
  return projects.sort((a, b) => b.updatedAt - a.updatedAt);
4925
3939
  }
4926
3940
  async function getProjectById(id) {
4927
- const safeId = path7.basename(id);
4928
- const projectFile = path7.join(PROJECTS_ROOT2, safeId, "project.json");
4929
- if (!fs7.existsSync(projectFile)) return null;
4930
- const raw = await fs7.promises.readFile(projectFile, "utf-8");
3941
+ const safeId = path6.basename(id);
3942
+ const projectFile = path6.join(PROJECTS_ROOT2, safeId, "project.json");
3943
+ if (!fs6.existsSync(projectFile)) return null;
3944
+ const raw = await fs6.promises.readFile(projectFile, "utf-8");
4931
3945
  return JSON.parse(raw);
4932
3946
  }
4933
3947
  async function saveProject(id, payload) {
4934
- const safeId = path7.basename(id);
4935
- const projDir = path7.join(PROJECTS_ROOT2, safeId);
4936
- const projectFile = path7.join(projDir, "project.json");
4937
- if (!fs7.existsSync(projDir)) {
4938
- await fs7.promises.mkdir(projDir, { recursive: true });
4939
- await fs7.promises.mkdir(path7.join(projDir, "samples"), { recursive: true });
4940
- await fs7.promises.mkdir(path7.join(projDir, "assets"), { recursive: true });
3948
+ const safeId = path6.basename(id);
3949
+ const projDir = path6.join(PROJECTS_ROOT2, safeId);
3950
+ const projectFile = path6.join(projDir, "project.json");
3951
+ if (!fs6.existsSync(projDir)) {
3952
+ await fs6.promises.mkdir(projDir, { recursive: true });
3953
+ await fs6.promises.mkdir(path6.join(projDir, "samples"), { recursive: true });
3954
+ await fs6.promises.mkdir(path6.join(projDir, "assets"), { recursive: true });
4941
3955
  }
4942
3956
  let existingData = {};
4943
- if (fs7.existsSync(projectFile)) {
4944
- const raw = await fs7.promises.readFile(projectFile, "utf-8");
3957
+ if (fs6.existsSync(projectFile)) {
3958
+ const raw = await fs6.promises.readFile(projectFile, "utf-8");
4945
3959
  existingData = JSON.parse(raw);
4946
3960
  }
4947
3961
  const updatedData = {
@@ -4950,22 +3964,22 @@ async function saveProject(id, payload) {
4950
3964
  id: safeId,
4951
3965
  updatedAt: Date.now()
4952
3966
  };
4953
- await fs7.promises.writeFile(projectFile, JSON.stringify(updatedData, null, 2), "utf-8");
3967
+ await fs6.promises.writeFile(projectFile, JSON.stringify(updatedData, null, 2), "utf-8");
4954
3968
  return updatedData;
4955
3969
  }
4956
3970
  async function deleteProject(id) {
4957
- const safeId = path7.basename(id);
4958
- const projDir = path7.join(PROJECTS_ROOT2, safeId);
4959
- if (!fs7.existsSync(projDir)) return false;
4960
- await fs7.promises.rm(projDir, { recursive: true, force: true });
3971
+ const safeId = path6.basename(id);
3972
+ const projDir = path6.join(PROJECTS_ROOT2, safeId);
3973
+ if (!fs6.existsSync(projDir)) return false;
3974
+ await fs6.promises.rm(projDir, { recursive: true, force: true });
4961
3975
  return true;
4962
3976
  }
4963
3977
 
4964
3978
  // src/server/services/sampleService.ts
4965
- import fs8 from "fs";
4966
- import path8 from "path";
4967
- var SAMPLES_GLOBAL_ROOT = path8.join(process.cwd(), "samples", "global");
4968
- var PROJECTS_ROOT3 = path8.join(process.cwd(), "projects");
3979
+ import fs7 from "fs";
3980
+ import path7 from "path";
3981
+ var SAMPLES_GLOBAL_ROOT = path7.join(process.cwd(), "samples", "global");
3982
+ var PROJECTS_ROOT3 = path7.join(process.cwd(), "projects");
4969
3983
  var SUPPORTED_AUDIO_EXTS3 = [".wav", ".mp3", ".ogg", ".flac", ".m4a", ".webm", ".weba", ".aac", ".aiff", ".aif", ".opus", ".mp4"];
4970
3984
  function initGlobalSamplesDirectory() {
4971
3985
  ensureGlobalSamples(SAMPLES_GLOBAL_ROOT).catch((err) => {
@@ -4973,17 +3987,17 @@ function initGlobalSamplesDirectory() {
4973
3987
  });
4974
3988
  }
4975
3989
  async function listGlobalSamples() {
4976
- if (!fs8.existsSync(SAMPLES_GLOBAL_ROOT)) {
4977
- await fs8.promises.mkdir(SAMPLES_GLOBAL_ROOT, { recursive: true });
3990
+ if (!fs7.existsSync(SAMPLES_GLOBAL_ROOT)) {
3991
+ await fs7.promises.mkdir(SAMPLES_GLOBAL_ROOT, { recursive: true });
4978
3992
  }
4979
- const files = await fs8.promises.readdir(SAMPLES_GLOBAL_ROOT);
3993
+ const files = await fs7.promises.readdir(SAMPLES_GLOBAL_ROOT);
4980
3994
  const samples = [];
4981
3995
  for (const file of files) {
4982
- const ext = path8.extname(file).toLowerCase();
3996
+ const ext = path7.extname(file).toLowerCase();
4983
3997
  if (!SUPPORTED_AUDIO_EXTS3.includes(ext)) continue;
4984
- const filePath = path8.join(SAMPLES_GLOBAL_ROOT, file);
4985
- const stat = await fs8.promises.stat(filePath);
4986
- const baseName = path8.basename(file, ext);
3998
+ const filePath = path7.join(SAMPLES_GLOBAL_ROOT, file);
3999
+ const stat = await fs7.promises.stat(filePath);
4000
+ const baseName = path7.basename(file, ext);
4987
4001
  const cleanName = baseName.replace(/[-_]/g, " ");
4988
4002
  let category = "synth";
4989
4003
  if (/808|kick|snare|hihat|drum|perc/i.test(baseName)) category = "drums";
@@ -5003,15 +4017,15 @@ async function listGlobalSamples() {
5003
4017
  return samples;
5004
4018
  }
5005
4019
  async function saveGlobalSample(filename, base64Content, category) {
5006
- const safeFilename = path8.basename(filename).replace(/[^a-zA-Z0-9._-]/g, "_");
5007
- const ext = path8.extname(safeFilename).toLowerCase() || ".wav";
5008
- const filePath = path8.join(SAMPLES_GLOBAL_ROOT, safeFilename);
4020
+ const safeFilename = path7.basename(filename).replace(/[^a-zA-Z0-9._-]/g, "_");
4021
+ const ext = path7.extname(safeFilename).toLowerCase() || ".wav";
4022
+ const filePath = path7.join(SAMPLES_GLOBAL_ROOT, safeFilename);
5009
4023
  const buffer = Buffer.from(base64Content, "base64");
5010
- await fs8.promises.writeFile(filePath, buffer);
5011
- const stat = await fs8.promises.stat(filePath);
4024
+ await fs7.promises.writeFile(filePath, buffer);
4025
+ const stat = await fs7.promises.stat(filePath);
5012
4026
  return {
5013
4027
  filename: safeFilename,
5014
- name: path8.basename(safeFilename, ext).replace(/[-_]/g, " "),
4028
+ name: path7.basename(safeFilename, ext).replace(/[-_]/g, " "),
5015
4029
  category: category || "custom",
5016
4030
  format: ext.replace(".", "").toUpperCase(),
5017
4031
  url: `/samples/global/${safeFilename}`,
@@ -5021,28 +4035,28 @@ async function saveGlobalSample(filename, base64Content, category) {
5021
4035
  };
5022
4036
  }
5023
4037
  async function deleteGlobalSample(filename) {
5024
- const safeFilename = path8.basename(filename);
5025
- const filePath = path8.join(SAMPLES_GLOBAL_ROOT, safeFilename);
5026
- if (fs8.existsSync(filePath)) {
5027
- await fs8.promises.unlink(filePath);
4038
+ const safeFilename = path7.basename(filename);
4039
+ const filePath = path7.join(SAMPLES_GLOBAL_ROOT, safeFilename);
4040
+ if (fs7.existsSync(filePath)) {
4041
+ await fs7.promises.unlink(filePath);
5028
4042
  return true;
5029
4043
  }
5030
4044
  return false;
5031
4045
  }
5032
4046
  async function listProjectSamples(projectId) {
5033
- const safeId = path8.basename(projectId);
5034
- const samplesDir = path8.join(PROJECTS_ROOT3, safeId, "samples");
5035
- if (!fs8.existsSync(samplesDir)) {
5036
- await fs8.promises.mkdir(samplesDir, { recursive: true });
4047
+ const safeId = path7.basename(projectId);
4048
+ const samplesDir = path7.join(PROJECTS_ROOT3, safeId, "samples");
4049
+ if (!fs7.existsSync(samplesDir)) {
4050
+ await fs7.promises.mkdir(samplesDir, { recursive: true });
5037
4051
  }
5038
- const files = await fs8.promises.readdir(samplesDir);
4052
+ const files = await fs7.promises.readdir(samplesDir);
5039
4053
  const samples = [];
5040
4054
  for (const file of files) {
5041
- const ext = path8.extname(file).toLowerCase();
4055
+ const ext = path7.extname(file).toLowerCase();
5042
4056
  if (!SUPPORTED_AUDIO_EXTS3.includes(ext)) continue;
5043
- const filePath = path8.join(samplesDir, file);
5044
- const stat = await fs8.promises.stat(filePath);
5045
- const baseName = path8.basename(file, ext);
4057
+ const filePath = path7.join(samplesDir, file);
4058
+ const stat = await fs7.promises.stat(filePath);
4059
+ const baseName = path7.basename(file, ext);
5046
4060
  samples.push({
5047
4061
  filename: file,
5048
4062
  name: baseName.replace(/[-_]/g, " "),
@@ -5058,21 +4072,21 @@ async function listProjectSamples(projectId) {
5058
4072
  return samples;
5059
4073
  }
5060
4074
  async function saveProjectSample(projectId, filename, base64Content, isRecording) {
5061
- const safeId = path8.basename(projectId);
5062
- const samplesDir = path8.join(PROJECTS_ROOT3, safeId, "samples");
5063
- if (!fs8.existsSync(samplesDir)) {
5064
- await fs8.promises.mkdir(samplesDir, { recursive: true });
4075
+ const safeId = path7.basename(projectId);
4076
+ const samplesDir = path7.join(PROJECTS_ROOT3, safeId, "samples");
4077
+ if (!fs7.existsSync(samplesDir)) {
4078
+ await fs7.promises.mkdir(samplesDir, { recursive: true });
5065
4079
  }
5066
4080
  const defaultName = isRecording ? `recording_take_${Date.now()}.wav` : `clip_${Date.now()}.wav`;
5067
- const targetFilename = filename ? path8.basename(filename).replace(/[^a-zA-Z0-9._-]/g, "_") : defaultName;
5068
- const ext = path8.extname(targetFilename).toLowerCase() || ".wav";
5069
- const filePath = path8.join(samplesDir, targetFilename);
4081
+ const targetFilename = filename ? path7.basename(filename).replace(/[^a-zA-Z0-9._-]/g, "_") : defaultName;
4082
+ const ext = path7.extname(targetFilename).toLowerCase() || ".wav";
4083
+ const filePath = path7.join(samplesDir, targetFilename);
5070
4084
  const buffer = Buffer.from(base64Content, "base64");
5071
- await fs8.promises.writeFile(filePath, buffer);
5072
- const stat = await fs8.promises.stat(filePath);
4085
+ await fs7.promises.writeFile(filePath, buffer);
4086
+ const stat = await fs7.promises.stat(filePath);
5073
4087
  return {
5074
4088
  filename: targetFilename,
5075
- name: path8.basename(targetFilename, ext).replace(/[-_]/g, " "),
4089
+ name: path7.basename(targetFilename, ext).replace(/[-_]/g, " "),
5076
4090
  category: isRecording ? "recording" : "import",
5077
4091
  format: ext.replace(".", "").toUpperCase(),
5078
4092
  url: `/projects/${safeId}/samples/${targetFilename}`,
@@ -5083,26 +4097,26 @@ async function saveProjectSample(projectId, filename, base64Content, isRecording
5083
4097
  };
5084
4098
  }
5085
4099
  async function deleteProjectSample(projectId, filename) {
5086
- const safeId = path8.basename(projectId);
5087
- const safeFilename = path8.basename(filename);
5088
- const filePath = path8.join(PROJECTS_ROOT3, safeId, "samples", safeFilename);
5089
- if (fs8.existsSync(filePath)) {
5090
- await fs8.promises.unlink(filePath);
4100
+ const safeId = path7.basename(projectId);
4101
+ const safeFilename = path7.basename(filename);
4102
+ const filePath = path7.join(PROJECTS_ROOT3, safeId, "samples", safeFilename);
4103
+ if (fs7.existsSync(filePath)) {
4104
+ await fs7.promises.unlink(filePath);
5091
4105
  return true;
5092
4106
  }
5093
4107
  return false;
5094
4108
  }
5095
4109
 
5096
4110
  // src/server/services/workspaceGraphService.ts
5097
- import fs9 from "fs";
5098
- import path9 from "path";
5099
- var AUTH_FILE_PATH = path9.resolve(".skipo/accounts.json");
4111
+ import fs8 from "fs";
4112
+ import path8 from "path";
4113
+ var AUTH_FILE_PATH = path8.resolve(".skipo/accounts.json");
5100
4114
  async function resolveWorkspaceDir(workspaceIdOrSlug) {
5101
4115
  const workingDir = getWorkingWorkspaceDir();
5102
4116
  let targetWs = null;
5103
4117
  try {
5104
- if (fs9.existsSync(AUTH_FILE_PATH)) {
5105
- const raw = await fs9.promises.readFile(AUTH_FILE_PATH, "utf-8");
4118
+ if (fs8.existsSync(AUTH_FILE_PATH)) {
4119
+ const raw = await fs8.promises.readFile(AUTH_FILE_PATH, "utf-8");
5106
4120
  const accounts = JSON.parse(raw);
5107
4121
  for (const user of Object.values(accounts)) {
5108
4122
  if (Array.isArray(user.workspaces)) {
@@ -5122,35 +4136,35 @@ async function resolveWorkspaceDir(workspaceIdOrSlug) {
5122
4136
  if (targetWs) {
5123
4137
  const rawFolderName = targetWs.localFolderName || targetWs.name.replace(/[/\\?%*:|"<>]/g, "_");
5124
4138
  const folderName = rawFolderName.trim();
5125
- await fs9.promises.mkdir(workingDir, { recursive: true });
4139
+ await fs8.promises.mkdir(workingDir, { recursive: true });
5126
4140
  return { wsDir: workingDir, wsName: targetWs.name, wsFolder: folderName, wsId: targetWs.id };
5127
4141
  }
5128
- await fs9.promises.mkdir(workingDir, { recursive: true });
4142
+ await fs8.promises.mkdir(workingDir, { recursive: true });
5129
4143
  return { wsDir: workingDir, wsName: "Active Workspace", wsFolder: "workspace", wsId: workspaceIdOrSlug || "default" };
5130
4144
  }
5131
4145
  async function listWorkspaceGraphs(workspaceIdOrSlug) {
5132
4146
  const { wsDir, wsFolder } = await resolveWorkspaceDir(workspaceIdOrSlug);
5133
4147
  const graphs = [];
5134
4148
  const inspectDirForGraph = async (dirPath, folderName) => {
5135
- const projectJson = path9.join(dirPath, "project.json");
5136
- const graphQms = path9.join(dirPath, "graph.qms");
4149
+ const projectJson = path8.join(dirPath, "project.json");
4150
+ const graphQms = path8.join(dirPath, "graph.qms");
5137
4151
  let data = null;
5138
- if (fs9.existsSync(projectJson)) {
4152
+ if (fs8.existsSync(projectJson)) {
5139
4153
  try {
5140
- const raw = await fs9.promises.readFile(projectJson, "utf-8");
4154
+ const raw = await fs8.promises.readFile(projectJson, "utf-8");
5141
4155
  data = JSON.parse(raw);
5142
4156
  } catch {
5143
4157
  }
5144
- } else if (fs9.existsSync(graphQms)) {
4158
+ } else if (fs8.existsSync(graphQms)) {
5145
4159
  try {
5146
- const raw = await fs9.promises.readFile(graphQms, "utf-8");
4160
+ const raw = await fs8.promises.readFile(graphQms, "utf-8");
5147
4161
  const pkg = JSON.parse(raw);
5148
4162
  data = { ...pkg.metadata || {}, ...pkg.graph || {} };
5149
4163
  } catch {
5150
4164
  }
5151
4165
  }
5152
4166
  if (!data) return null;
5153
- const stat = await fs9.promises.stat(dirPath);
4167
+ const stat = await fs8.promises.stat(dirPath);
5154
4168
  const nodeCount = Array.isArray(data.nodes) ? data.nodes.length : 0;
5155
4169
  const connectionCount = Array.isArray(data.connections) ? data.connections.length : 0;
5156
4170
  return {
@@ -5158,7 +4172,7 @@ async function listWorkspaceGraphs(workspaceIdOrSlug) {
5158
4172
  name: data.name || folderName,
5159
4173
  description: data.description || "",
5160
4174
  folderName,
5161
- folderPath: path9.relative(process.cwd(), dirPath).replace(/\\/g, "/"),
4175
+ folderPath: path8.relative(process.cwd(), dirPath).replace(/\\/g, "/"),
5162
4176
  createdAt: data.createdAt || stat.birthtimeMs,
5163
4177
  updatedAt: data.updatedAt || stat.mtimeMs,
5164
4178
  bpm: data.bpm || 120,
@@ -5171,18 +4185,18 @@ async function listWorkspaceGraphs(workspaceIdOrSlug) {
5171
4185
  connectionCount
5172
4186
  };
5173
4187
  };
5174
- const entries = await fs9.promises.readdir(wsDir);
4188
+ const entries = await fs8.promises.readdir(wsDir);
5175
4189
  for (const entry of entries) {
5176
4190
  if (entry.startsWith(".") || entry === "samples" || entry === "assets") continue;
5177
- const fullPath = path9.join(wsDir, entry);
5178
- const stat = await fs9.promises.stat(fullPath);
4191
+ const fullPath = path8.join(wsDir, entry);
4192
+ const stat = await fs8.promises.stat(fullPath);
5179
4193
  if (!stat.isDirectory()) continue;
5180
4194
  if (entry === "graphs") {
5181
- const subEntries = await fs9.promises.readdir(fullPath);
4195
+ const subEntries = await fs8.promises.readdir(fullPath);
5182
4196
  for (const sub of subEntries) {
5183
4197
  if (sub.startsWith(".")) continue;
5184
- const subPath = path9.join(fullPath, sub);
5185
- const subStat = await fs9.promises.stat(subPath);
4198
+ const subPath = path8.join(fullPath, sub);
4199
+ const subStat = await fs8.promises.stat(subPath);
5186
4200
  if (subStat.isDirectory()) {
5187
4201
  const g2 = await inspectDirForGraph(subPath, sub);
5188
4202
  if (g2) graphs.push(g2);
@@ -5199,10 +4213,10 @@ async function createWorkspaceGraph(workspaceIdOrSlug, params) {
5199
4213
  const { wsDir, wsFolder, wsId } = await resolveWorkspaceDir(workspaceIdOrSlug);
5200
4214
  const rawName = params.name.trim();
5201
4215
  const folderName = rawName.replace(/[/\\?%*:|"<>]/g, "_");
5202
- const graphDir = path9.join(wsDir, folderName);
5203
- await fs9.promises.mkdir(graphDir, { recursive: true });
5204
- await fs9.promises.mkdir(path9.join(graphDir, "samples"), { recursive: true });
5205
- await fs9.promises.mkdir(path9.join(graphDir, "assets"), { recursive: true });
4216
+ const graphDir = path8.join(wsDir, folderName);
4217
+ await fs8.promises.mkdir(graphDir, { recursive: true });
4218
+ await fs8.promises.mkdir(path8.join(graphDir, "samples"), { recursive: true });
4219
+ await fs8.promises.mkdir(path8.join(graphDir, "assets"), { recursive: true });
5206
4220
  const now = Date.now();
5207
4221
  const nodes = params.templateData?.nodes || [];
5208
4222
  const connections = params.templateData?.connections || [];
@@ -5213,7 +4227,7 @@ async function createWorkspaceGraph(workspaceIdOrSlug, params) {
5213
4227
  id: folderName,
5214
4228
  name: rawName,
5215
4229
  description: params.description || `Workspace graph in ${wsFolder}`,
5216
- folderPath: path9.relative(process.cwd(), graphDir).replace(/\\/g, "/"),
4230
+ folderPath: path8.relative(process.cwd(), graphDir).replace(/\\/g, "/"),
5217
4231
  createdAt: now,
5218
4232
  updatedAt: now,
5219
4233
  bpm,
@@ -5222,7 +4236,7 @@ async function createWorkspaceGraph(workspaceIdOrSlug, params) {
5222
4236
  nodes,
5223
4237
  connections
5224
4238
  };
5225
- await fs9.promises.writeFile(path9.join(graphDir, "project.json"), JSON.stringify(projectData, null, 2), "utf-8");
4239
+ await fs8.promises.writeFile(path8.join(graphDir, "project.json"), JSON.stringify(projectData, null, 2), "utf-8");
5226
4240
  const graphPackage = {
5227
4241
  format: "qms_graph_package",
5228
4242
  version: "2.0.0",
@@ -5240,27 +4254,27 @@ async function createWorkspaceGraph(workspaceIdOrSlug, params) {
5240
4254
  },
5241
4255
  graph: { bpm, masterVolume, currentBiome, nodes, connections }
5242
4256
  };
5243
- await fs9.promises.writeFile(path9.join(graphDir, "graph.qms"), JSON.stringify(graphPackage, null, 2), "utf-8");
4257
+ await fs8.promises.writeFile(path8.join(graphDir, "graph.qms"), JSON.stringify(graphPackage, null, 2), "utf-8");
5244
4258
  return projectData;
5245
4259
  }
5246
4260
  async function getWorkspaceGraph(workspaceIdOrSlug, graphIdOrName) {
5247
4261
  const { wsDir } = await resolveWorkspaceDir(workspaceIdOrSlug);
5248
4262
  const safeTarget = graphIdOrName.replace(/[/\\?%*:|"<>]/g, "_");
5249
4263
  const candidates = [
5250
- path9.join(wsDir, safeTarget),
5251
- path9.join(wsDir, graphIdOrName),
5252
- path9.join(wsDir, "graphs", safeTarget)
4264
+ path8.join(wsDir, safeTarget),
4265
+ path8.join(wsDir, graphIdOrName),
4266
+ path8.join(wsDir, "graphs", safeTarget)
5253
4267
  ];
5254
4268
  for (const cand of candidates) {
5255
- if (fs9.existsSync(cand)) {
5256
- const pJson = path9.join(cand, "project.json");
5257
- if (fs9.existsSync(pJson)) {
5258
- const raw = await fs9.promises.readFile(pJson, "utf-8");
4269
+ if (fs8.existsSync(cand)) {
4270
+ const pJson = path8.join(cand, "project.json");
4271
+ if (fs8.existsSync(pJson)) {
4272
+ const raw = await fs8.promises.readFile(pJson, "utf-8");
5259
4273
  return JSON.parse(raw);
5260
4274
  }
5261
- const gQms = path9.join(cand, "graph.qms");
5262
- if (fs9.existsSync(gQms)) {
5263
- const raw = await fs9.promises.readFile(gQms, "utf-8");
4275
+ const gQms = path8.join(cand, "graph.qms");
4276
+ if (fs8.existsSync(gQms)) {
4277
+ const raw = await fs8.promises.readFile(gQms, "utf-8");
5264
4278
  const pkg = JSON.parse(raw);
5265
4279
  return { ...pkg.metadata || {}, ...pkg.graph || {} };
5266
4280
  }
@@ -5271,39 +4285,39 @@ async function getWorkspaceGraph(workspaceIdOrSlug, graphIdOrName) {
5271
4285
  async function saveWorkspaceGraph(workspaceIdOrSlug, graphIdOrName, payload) {
5272
4286
  const { wsDir } = await resolveWorkspaceDir(workspaceIdOrSlug);
5273
4287
  const safeTarget = graphIdOrName.replace(/[/\\?%*:|"<>]/g, "_");
5274
- let targetDir = path9.join(wsDir, safeTarget);
5275
- if (!fs9.existsSync(targetDir) && fs9.existsSync(path9.join(wsDir, "graphs", safeTarget))) {
5276
- targetDir = path9.join(wsDir, "graphs", safeTarget);
4288
+ let targetDir = path8.join(wsDir, safeTarget);
4289
+ if (!fs8.existsSync(targetDir) && fs8.existsSync(path8.join(wsDir, "graphs", safeTarget))) {
4290
+ targetDir = path8.join(wsDir, "graphs", safeTarget);
5277
4291
  }
5278
- await fs9.promises.mkdir(targetDir, { recursive: true });
5279
- const pJson = path9.join(targetDir, "project.json");
4292
+ await fs8.promises.mkdir(targetDir, { recursive: true });
4293
+ const pJson = path8.join(targetDir, "project.json");
5280
4294
  let existing = {};
5281
- if (fs9.existsSync(pJson)) {
4295
+ if (fs8.existsSync(pJson)) {
5282
4296
  try {
5283
- existing = JSON.parse(await fs9.promises.readFile(pJson, "utf-8"));
4297
+ existing = JSON.parse(await fs8.promises.readFile(pJson, "utf-8"));
5284
4298
  } catch {
5285
4299
  }
5286
4300
  }
5287
4301
  const updated = {
5288
4302
  ...existing,
5289
4303
  ...payload,
5290
- id: existing.id || path9.basename(targetDir),
4304
+ id: existing.id || path8.basename(targetDir),
5291
4305
  updatedAt: Date.now()
5292
4306
  };
5293
- await fs9.promises.writeFile(pJson, JSON.stringify(updated, null, 2), "utf-8");
4307
+ await fs8.promises.writeFile(pJson, JSON.stringify(updated, null, 2), "utf-8");
5294
4308
  return updated;
5295
4309
  }
5296
4310
  async function deleteWorkspaceGraph(workspaceIdOrSlug, graphIdOrName) {
5297
4311
  const { wsDir } = await resolveWorkspaceDir(workspaceIdOrSlug);
5298
4312
  const safeTarget = graphIdOrName.replace(/[/\\?%*:|"<>]/g, "_");
5299
4313
  const candidates = [
5300
- path9.join(wsDir, safeTarget),
5301
- path9.join(wsDir, graphIdOrName),
5302
- path9.join(wsDir, "graphs", safeTarget)
4314
+ path8.join(wsDir, safeTarget),
4315
+ path8.join(wsDir, graphIdOrName),
4316
+ path8.join(wsDir, "graphs", safeTarget)
5303
4317
  ];
5304
4318
  for (const cand of candidates) {
5305
- if (fs9.existsSync(cand)) {
5306
- await fs9.promises.rm(cand, { recursive: true, force: true });
4319
+ if (fs8.existsSync(cand)) {
4320
+ await fs8.promises.rm(cand, { recursive: true, force: true });
5307
4321
  return true;
5308
4322
  }
5309
4323
  }
@@ -5363,6 +4377,7 @@ export {
5363
4377
  PasswordHasher,
5364
4378
  RateLimiter,
5365
4379
  RemoteExecutor,
4380
+ RemoteWorkspaceService,
5366
4381
  ResourceFetcherRegistry,
5367
4382
  ResultFlowBridge,
5368
4383
  S3Adapter,
@@ -5386,6 +4401,7 @@ export {
5386
4401
  WORKSPACE_ROOT,
5387
4402
  WebSocketTransport,
5388
4403
  WorkerExecutionBoundary,
4404
+ WorkingWorkspaceService,
5389
4405
  WorkspaceCloneSyncEngine,
5390
4406
  WorkspaceFS,
5391
4407
  WorkspaceServiceDefinition,
@@ -5510,6 +4526,7 @@ export {
5510
4526
  queryUnifiedDb,
5511
4527
  readWorkspaceFile,
5512
4528
  recordExecution,
4529
+ remoteWorkspaceService,
5513
4530
  removeGraphDependency,
5514
4531
  renameAppInWorkspaceData,
5515
4532
  renameS3Folder,
@@ -5554,5 +4571,6 @@ export {
5554
4571
  validateServiceDefinition,
5555
4572
  withAuth,
5556
4573
  withCors,
4574
+ workingWorkspaceService,
5557
4575
  writeWorkspaceFile
5558
4576
  };