@ultrst-armada/kloks 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,3 @@
1
- import {
2
- CompositeBackend,
3
- EndpointGenerator,
4
- createBackend,
5
- createCompositeService,
6
- createLocalService,
7
- createRemoteService
8
- } from "./chunk-P4HL4C6V.js";
9
1
  import {
10
2
  CANONICAL_SRKL_URL,
11
3
  SrklClient,
@@ -23,6 +15,12 @@ import {
23
15
  b2ListFileNames,
24
16
  b2UploadFile
25
17
  } from "./chunk-4QEMALXF.js";
18
+ import {
19
+ BaseApiRoute,
20
+ createSSEResponse,
21
+ withAuth,
22
+ withCors
23
+ } from "./chunk-TWCEVES2.js";
26
24
  import {
27
25
  SyncEngine,
28
26
  WorkspaceCloneSyncEngine,
@@ -43,8 +41,7 @@ import {
43
41
  StorageServiceFacade,
44
42
  WorkspaceServiceDefinition,
45
43
  WorkspaceServiceFacade
46
- } from "./chunk-CU7RZBMF.js";
47
- import "./chunk-IPP4VURQ.js";
44
+ } from "./chunk-OYQKQAEF.js";
48
45
  import {
49
46
  BackblazeB2Adapter,
50
47
  BaseStorageAdapter,
@@ -64,12 +61,6 @@ import {
64
61
  formatCanonicalWorkspaceFolderName,
65
62
  sanitizeWorkspaceName
66
63
  } from "./chunk-FZKBTF34.js";
67
- import {
68
- BaseApiRoute,
69
- createSSEResponse,
70
- withAuth,
71
- withCors
72
- } from "./chunk-TWCEVES2.js";
73
64
  import {
74
65
  AdminGuard,
75
66
  BaseAccountProvider,
@@ -133,9 +124,18 @@ import {
133
124
  import {
134
125
  IdempotencyManager
135
126
  } from "./chunk-3EPY34R6.js";
127
+ import {
128
+ CompositeBackend,
129
+ EndpointGenerator,
130
+ createBackend,
131
+ createCompositeService,
132
+ createLocalService,
133
+ createRemoteService
134
+ } from "./chunk-P4HL4C6V.js";
135
+ import "./chunk-IPP4VURQ.js";
136
136
 
137
137
  // src/data/BaseDataProvider.ts
138
- import { SkipoBase } from "@ultrst-armada/kloks/functions";
138
+ import { SkipoBase } from "@kloks/functions";
139
139
  var BaseDataProvider = class extends SkipoBase {
140
140
  };
141
141
 
@@ -203,7 +203,7 @@ function validateServiceDefinition(def) {
203
203
  }
204
204
 
205
205
  // src/core/authCore.ts
206
- import crypto from "crypto";
206
+ import crypto2 from "crypto";
207
207
  import bcrypt from "bcryptjs";
208
208
 
209
209
  // src/core/unifiedDb.ts
@@ -274,8 +274,8 @@ async function loginUser(email, password, isAdmin = false, env) {
274
274
  } else if (hash.startsWith("$2a$") || hash.startsWith("$2b$")) {
275
275
  isMatch = bcrypt.compareSync(rawPassword, hash);
276
276
  } else {
277
- const saltedSha = crypto.createHash("sha256").update(rawPassword + "docustack_salt_2026_v2").digest("hex");
278
- const rawSha = crypto.createHash("sha256").update(rawPassword).digest("hex");
277
+ const saltedSha = crypto2.createHash("sha256").update(rawPassword + "docustack_salt_2026_v2").digest("hex");
278
+ const rawSha = crypto2.createHash("sha256").update(rawPassword).digest("hex");
279
279
  isMatch = hash === saltedSha || hash === rawSha;
280
280
  }
281
281
  if (!isMatch) {
@@ -1061,10 +1061,10 @@ async function getSrklAccounts2(userId, service, env) {
1061
1061
  }
1062
1062
  }
1063
1063
  try {
1064
- const fs11 = await import("fs/promises");
1065
- const path11 = await import("path");
1066
- const accPath = path11.resolve(".skipo/accounts.json");
1067
- const raw = await fs11.readFile(accPath, "utf-8");
1064
+ const fs10 = await import("fs/promises");
1065
+ const path10 = await import("path");
1066
+ const accPath = path10.resolve(".skipo/accounts.json");
1067
+ const raw = await fs10.readFile(accPath, "utf-8");
1068
1068
  const localUsers = JSON.parse(raw);
1069
1069
  for (const u of Object.values(localUsers)) {
1070
1070
  const conns = Array.isArray(u.storage_connections) ? u.storage_connections : [];
@@ -1138,182 +1138,2361 @@ async function listBackblazeBuckets(payload, rawUserId, env) {
1138
1138
  } catch (err) {
1139
1139
  console.warn("[SrklCore] Direct B2 bucket query failed, trying SRKL fallback:", err.message);
1140
1140
  }
1141
- const srklUrl = await getPrimarySrklDeploymentUrl2(env);
1142
- const srklRes = await fetch(`${srklUrl}/api/backblaze/buckets`, {
1143
- method: "POST",
1144
- headers: { "Content-Type": "application/json", "x-user-id": userId || "" },
1145
- body: JSON.stringify({ keyId, applicationKey: appKey, bucketId: bucketId || void 0 }),
1146
- signal: AbortSignal.timeout(6e3)
1147
- });
1148
- if (srklRes.ok) {
1149
- const data = await srklRes.json();
1150
- if (data.success && Array.isArray(data.buckets)) return { success: true, buckets: data.buckets };
1141
+ const srklUrl = await getPrimarySrklDeploymentUrl2(env);
1142
+ const srklRes = await fetch(`${srklUrl}/api/backblaze/buckets`, {
1143
+ method: "POST",
1144
+ headers: { "Content-Type": "application/json", "x-user-id": userId || "" },
1145
+ body: JSON.stringify({ keyId, applicationKey: appKey, bucketId: bucketId || void 0 }),
1146
+ signal: AbortSignal.timeout(6e3)
1147
+ });
1148
+ if (srklRes.ok) {
1149
+ const data = await srklRes.json();
1150
+ if (data.success && Array.isArray(data.buckets)) return { success: true, buckets: data.buckets };
1151
+ }
1152
+ return { success: true, buckets: [] };
1153
+ } catch (err) {
1154
+ return { success: false, error: err.message || "Failed to list Backblaze buckets" };
1155
+ }
1156
+ }
1157
+ var srklListBackblazeBuckets2 = (keyId, applicationKey, bucketId, bucketName, userId) => listBackblazeBuckets({ keyId, applicationKey, bucketId, bucketName }, userId);
1158
+ async function getSrklStatus(env) {
1159
+ const srklUrl = await getPrimarySrklDeploymentUrl2(env);
1160
+ const start = Date.now();
1161
+ try {
1162
+ const resp = await fetch(`${srklUrl}/api/ecosystem/health`, { signal: AbortSignal.timeout(5e3) });
1163
+ const latencyMs = Date.now() - start;
1164
+ if (resp.ok) {
1165
+ const data = await resp.json().catch(() => ({}));
1166
+ return { success: true, deploymentUrl: srklUrl, online: true, latencyMs, details: data };
1167
+ }
1168
+ return { success: false, deploymentUrl: srklUrl, online: false, status: resp.status, latencyMs };
1169
+ } catch (err) {
1170
+ return { success: false, deploymentUrl: srklUrl, online: false, error: err.message, latencyMs: Date.now() - start };
1171
+ }
1172
+ }
1173
+
1174
+ // src/core/workspaceFileCore.ts
1175
+ async function getFs() {
1176
+ if (typeof process !== "undefined" && process.versions?.node) {
1177
+ try {
1178
+ const mod = await import("fs");
1179
+ return mod.promises || mod.default?.promises || mod;
1180
+ } catch {
1181
+ }
1182
+ }
1183
+ return null;
1184
+ }
1185
+ async function getPath() {
1186
+ if (typeof process !== "undefined" && process.versions?.node) {
1187
+ try {
1188
+ const mod = await import("path");
1189
+ return mod.default || mod;
1190
+ } catch {
1191
+ }
1192
+ }
1193
+ return null;
1194
+ }
1195
+ function getWorkspaceFolderPrefix(workspace) {
1196
+ return workspace.localFolderKey || workspace.localFolderName || (workspace.workspacePath ? workspace.workspacePath.replace(/^\.\//, "") : workspace.id);
1197
+ }
1198
+ function isB2Workspace(workspace, connection) {
1199
+ return connection?.provider === "backblaze_b2" || connection?.provider === "BACKBLAZE_B2" || workspace?.storageProvider === "backblaze_b2";
1200
+ }
1201
+ async function writeWorkspaceFile(workspace, connection, relativePath, content, contentType = "application/json") {
1202
+ const cleanRel = relativePath.replace(/^\/+/, "");
1203
+ const prefix = getWorkspaceFolderPrefix(workspace);
1204
+ const buffer = typeof content === "string" ? new TextEncoder().encode(content) : content;
1205
+ if (isB2Workspace(workspace, connection)) {
1206
+ const keyId = connection.b2KeyId || connection.keyId;
1207
+ const appKey = connection.b2ApplicationKey || connection.appKey;
1208
+ const bucketName = connection.b2BucketName || workspace.b2BucketName || "ultrst-apps";
1209
+ const fullKey = `${prefix}/${cleanRel}`;
1210
+ await b2UploadFile2(keyId, appKey, bucketName, fullKey, buffer, contentType);
1211
+ return;
1212
+ }
1213
+ const fs10 = await getFs();
1214
+ const path10 = await getPath();
1215
+ if (!fs10 || !path10) throw new Error("Local filesystem operations not supported in this runtime.");
1216
+ const localDir = path10.resolve(process.cwd(), prefix, path10.dirname(cleanRel));
1217
+ await fs10.mkdir(localDir, { recursive: true });
1218
+ await fs10.writeFile(path10.resolve(process.cwd(), prefix, cleanRel), buffer);
1219
+ }
1220
+ async function readWorkspaceFile(workspace, connection, relativePath) {
1221
+ const cleanRel = relativePath.replace(/^\/+/, "");
1222
+ const prefix = getWorkspaceFolderPrefix(workspace);
1223
+ if (isB2Workspace(workspace, connection)) {
1224
+ const keyId = connection.b2KeyId || connection.keyId;
1225
+ const appKey = connection.b2ApplicationKey || connection.appKey;
1226
+ const bucketName = connection.b2BucketName || workspace.b2BucketName || "ultrst-apps";
1227
+ const fullKey = `${prefix}/${cleanRel}`;
1228
+ return await b2DownloadFile2(keyId, appKey, bucketName, fullKey);
1229
+ }
1230
+ const fs10 = await getFs();
1231
+ const path10 = await getPath();
1232
+ if (!fs10 || !path10) return null;
1233
+ const fullPath = path10.resolve(process.cwd(), prefix, cleanRel);
1234
+ try {
1235
+ const buf = await fs10.readFile(fullPath);
1236
+ return new Uint8Array(buf);
1237
+ } catch {
1238
+ return null;
1239
+ }
1240
+ }
1241
+ async function deleteWorkspaceFile(workspace, connection, relativePath) {
1242
+ const cleanRel = relativePath.replace(/^\/+/, "");
1243
+ const prefix = getWorkspaceFolderPrefix(workspace);
1244
+ if (isB2Workspace(workspace, connection)) {
1245
+ const keyId = connection.b2KeyId || connection.keyId;
1246
+ const appKey = connection.b2ApplicationKey || connection.appKey;
1247
+ const bucketName = connection.b2BucketName || workspace.b2BucketName || "ultrst-apps";
1248
+ const fullKey = `${prefix}/${cleanRel}`;
1249
+ const files = await b2ListFileNames2(keyId, appKey, bucketName, fullKey);
1250
+ const match = files.find((f) => f.fileName === fullKey);
1251
+ if (match) {
1252
+ await b2DeleteFileVersion(keyId, appKey, match.fileName, match.fileId);
1253
+ return true;
1254
+ }
1255
+ return false;
1256
+ }
1257
+ const fs10 = await getFs();
1258
+ const path10 = await getPath();
1259
+ if (!fs10 || !path10) return false;
1260
+ const fullPath = path10.resolve(process.cwd(), prefix, cleanRel);
1261
+ try {
1262
+ await fs10.unlink(fullPath);
1263
+ return true;
1264
+ } catch {
1265
+ return false;
1266
+ }
1267
+ }
1268
+ async function listWorkspaceFiles(workspace, connection, subPrefix = "") {
1269
+ const folderPrefix = getWorkspaceFolderPrefix(workspace);
1270
+ const targetPrefix = subPrefix ? `${folderPrefix}/${subPrefix.replace(/^\/+/, "")}` : `${folderPrefix}/`;
1271
+ if (isB2Workspace(workspace, connection)) {
1272
+ const keyId = connection.b2KeyId || connection.keyId;
1273
+ const appKey = connection.b2ApplicationKey || connection.appKey;
1274
+ const bucketName = connection.b2BucketName || workspace.b2BucketName || "ultrst-apps";
1275
+ const rawFiles = await b2ListFileNames2(keyId, appKey, bucketName, targetPrefix);
1276
+ return rawFiles.map((f) => ({
1277
+ fileName: f.fileName.slice(folderPrefix.length + 1),
1278
+ sizeBytes: f.contentLength,
1279
+ uploadTimestamp: f.uploadTimestamp
1280
+ }));
1281
+ }
1282
+ const fs10 = await getFs();
1283
+ const path10 = await getPath();
1284
+ if (!fs10 || !path10) return [];
1285
+ const baseDir = path10.resolve(process.cwd(), folderPrefix, subPrefix);
1286
+ try {
1287
+ const entries = await fs10.readdir(baseDir, { withFileTypes: true, recursive: true });
1288
+ const result = [];
1289
+ for (const entry of entries) {
1290
+ if (entry.isFile()) {
1291
+ const full = path10.join(entry.parentPath || baseDir, entry.name);
1292
+ const st = await fs10.stat(full);
1293
+ result.push({
1294
+ fileName: path10.relative(path10.resolve(process.cwd(), folderPrefix), full),
1295
+ sizeBytes: st.size
1296
+ });
1297
+ }
1298
+ }
1299
+ return result;
1300
+ } catch {
1301
+ return [];
1302
+ }
1303
+ }
1304
+ async function deleteWorkspaceFolder(workspace, connection) {
1305
+ const prefix = getWorkspaceFolderPrefix(workspace);
1306
+ if (isB2Workspace(workspace, connection)) {
1307
+ const keyId = connection.b2KeyId || connection.keyId;
1308
+ const appKey = connection.b2ApplicationKey || connection.appKey;
1309
+ const bucketName = connection.b2BucketName || workspace.b2BucketName || "ultrst-apps";
1310
+ await b2DeleteFolder(keyId, appKey, bucketName, prefix);
1311
+ return;
1312
+ }
1313
+ const fs10 = await getFs();
1314
+ const path10 = await getPath();
1315
+ if (!fs10 || !path10) return;
1316
+ const localDir = path10.resolve(process.cwd(), prefix);
1317
+ try {
1318
+ await fs10.rm(localDir, { recursive: true, force: true });
1319
+ } catch {
1320
+ }
1321
+ }
1322
+
1323
+ // src/core/credentialVaultCore.ts
1324
+ var MANIFEST_PATH = "credentials/manifest.json";
1325
+ var SECRET_PREFIX = "credentials/secrets";
1326
+ async function deriveAesKey(passphrase, salt) {
1327
+ const enc = new TextEncoder();
1328
+ const passKey = await crypto.subtle.importKey("raw", enc.encode(passphrase), "PBKDF2", false, ["deriveKey"]);
1329
+ return crypto.subtle.deriveKey(
1330
+ { name: "PBKDF2", salt, iterations: 1e5, hash: "SHA-256" },
1331
+ passKey,
1332
+ { name: "AES-GCM", length: 256 },
1333
+ false,
1334
+ ["encrypt", "decrypt"]
1335
+ );
1336
+ }
1337
+ async function encryptSecretToBlob(plaintext, vaultKey) {
1338
+ const str = typeof plaintext === "string" ? plaintext : JSON.stringify(plaintext);
1339
+ const salt = crypto.getRandomValues(new Uint8Array(16));
1340
+ const iv = crypto.getRandomValues(new Uint8Array(12));
1341
+ const key = await deriveAesKey(vaultKey, salt);
1342
+ const cipherBuffer = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, new TextEncoder().encode(str));
1343
+ const blob = new Uint8Array(1 + 16 + 12 + cipherBuffer.byteLength);
1344
+ blob[0] = 1;
1345
+ blob.set(salt, 1);
1346
+ blob.set(iv, 1 + 16);
1347
+ blob.set(new Uint8Array(cipherBuffer), 1 + 16 + 12);
1348
+ return blob;
1349
+ }
1350
+ async function decryptSecretFromBlob(blob, vaultKey) {
1351
+ if (blob.length < 1 + 16 + 12 + 16 || blob[0] !== 1) {
1352
+ throw new Error("Invalid or corrupted credential vault blob.");
1353
+ }
1354
+ const salt = blob.slice(1, 17);
1355
+ const iv = blob.slice(17, 29);
1356
+ const ciphertext = blob.slice(29);
1357
+ const key = await deriveAesKey(vaultKey, salt);
1358
+ const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext);
1359
+ const text = new TextDecoder().decode(decrypted);
1360
+ try {
1361
+ return JSON.parse(text);
1362
+ } catch {
1363
+ return text;
1364
+ }
1365
+ }
1366
+ async function loadCredentialManifest(workspace, connection) {
1367
+ const data = await readWorkspaceFile(workspace, connection, MANIFEST_PATH);
1368
+ if (!data) return { version: 1, entries: [] };
1369
+ try {
1370
+ return JSON.parse(new TextDecoder().decode(data));
1371
+ } catch {
1372
+ return { version: 1, entries: [] };
1373
+ }
1374
+ }
1375
+ async function saveCredentialManifest(workspace, connection, manifest) {
1376
+ await writeWorkspaceFile(workspace, connection, MANIFEST_PATH, JSON.stringify(manifest, null, 2), "application/json");
1377
+ }
1378
+ async function listCredentials(workspace, connection) {
1379
+ const manifest = await loadCredentialManifest(workspace, connection);
1380
+ return manifest.entries;
1381
+ }
1382
+ async function getCredential(workspace, connection, id, vaultKey) {
1383
+ const blob = await readWorkspaceFile(workspace, connection, `${SECRET_PREFIX}/${id}.bin`);
1384
+ if (!blob) throw new Error(`Credential ${id} not found.`);
1385
+ const decrypted = await decryptSecretFromBlob(blob, vaultKey);
1386
+ const manifest = await loadCredentialManifest(workspace, connection);
1387
+ const meta = manifest.entries.find((e) => e.id === id);
1388
+ return { id, name: meta?.name, type: meta?.type, metadata: meta?.metadata, secret: decrypted };
1389
+ }
1390
+ async function createCredential(workspace, connection, payload, vaultKey) {
1391
+ const id = payload.id || `cred_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1392
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1393
+ const blob = await encryptSecretToBlob(payload.secret, vaultKey);
1394
+ await writeWorkspaceFile(workspace, connection, `${SECRET_PREFIX}/${id}.bin`, blob, "application/octet-stream");
1395
+ const manifest = await loadCredentialManifest(workspace, connection);
1396
+ const entry = {
1397
+ id,
1398
+ name: payload.name,
1399
+ type: payload.type || "generic",
1400
+ createdAt: now,
1401
+ updatedAt: now,
1402
+ metadata: payload.metadata || {}
1403
+ };
1404
+ manifest.entries = manifest.entries.filter((e) => e.id !== id).concat(entry);
1405
+ await saveCredentialManifest(workspace, connection, manifest);
1406
+ return entry;
1407
+ }
1408
+ async function updateCredential(workspace, connection, id, payload, vaultKey) {
1409
+ const manifest = await loadCredentialManifest(workspace, connection);
1410
+ const entry = manifest.entries.find((e) => e.id === id);
1411
+ if (!entry) throw new Error(`Credential ${id} not found.`);
1412
+ if (payload.secret !== void 0) {
1413
+ const blob = await encryptSecretToBlob(payload.secret, vaultKey);
1414
+ await writeWorkspaceFile(workspace, connection, `${SECRET_PREFIX}/${id}.bin`, blob, "application/octet-stream");
1415
+ }
1416
+ if (payload.name) entry.name = payload.name;
1417
+ if (payload.type) entry.type = payload.type;
1418
+ if (payload.metadata) entry.metadata = { ...entry.metadata, ...payload.metadata };
1419
+ entry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1420
+ await saveCredentialManifest(workspace, connection, manifest);
1421
+ return entry;
1422
+ }
1423
+ async function deleteCredential(workspace, connection, id) {
1424
+ await deleteWorkspaceFile(workspace, connection, `${SECRET_PREFIX}/${id}.bin`);
1425
+ const manifest = await loadCredentialManifest(workspace, connection);
1426
+ manifest.entries = manifest.entries.filter((e) => e.id !== id);
1427
+ await saveCredentialManifest(workspace, connection, manifest);
1428
+ return true;
1429
+ }
1430
+ async function exportEncryptedCredentials(workspace, connection, ids, exportKey, vaultKey) {
1431
+ const manifest = await loadCredentialManifest(workspace, connection);
1432
+ const targets = manifest.entries.filter((e) => ids.includes(e.id));
1433
+ const decryptedItems = [];
1434
+ for (const entry of targets) {
1435
+ const cred = await getCredential(workspace, connection, entry.id, vaultKey);
1436
+ decryptedItems.push(cred);
1437
+ }
1438
+ const blob = await encryptSecretToBlob({ manifestEntries: targets, items: decryptedItems }, exportKey);
1439
+ const b64 = Buffer.from(blob).toString("base64");
1440
+ return { exportVersion: 1, timestamp: (/* @__PURE__ */ new Date()).toISOString(), package: b64 };
1441
+ }
1442
+ async function importEncryptedCredentials(workspace, connection, packageB64, exportKey, vaultKey) {
1443
+ const blob = new Uint8Array(Buffer.from(packageB64, "base64"));
1444
+ const payload = await decryptSecretFromBlob(blob, exportKey);
1445
+ const items = payload.items || [];
1446
+ const importedEntries = [];
1447
+ for (const item of items) {
1448
+ const entry = await createCredential(
1449
+ workspace,
1450
+ connection,
1451
+ { id: item.id, name: item.name, type: item.type, secret: item.secret, metadata: item.metadata },
1452
+ vaultKey
1453
+ );
1454
+ importedEntries.push(entry);
1455
+ }
1456
+ return importedEntries;
1457
+ }
1458
+
1459
+ // src/core/publicRoutesStore.ts
1460
+ var serverPublicRoutes = [
1461
+ {
1462
+ id: "route_flay",
1463
+ name: "Flay Realtime API Gateway",
1464
+ path: "flay",
1465
+ status: "PUBLISHED",
1466
+ public: true,
1467
+ endpoint: "/flay",
1468
+ deployments: [
1469
+ {
1470
+ id: "dep_flay_edge_sfo",
1471
+ name: "flay-worker-sfo (US West Edge)",
1472
+ url: "https://flay-worker.workers.dev",
1473
+ provider: "CLOUDFLARE",
1474
+ weight: 0.88,
1475
+ healthy: true,
1476
+ priority: 1,
1477
+ region: "sfo"
1478
+ },
1479
+ {
1480
+ id: "dep_flay_edge_cdg",
1481
+ name: "flay-worker-cdg (EU Paris Edge)",
1482
+ url: "https://flay-eu.workers.dev",
1483
+ provider: "CLOUDFLARE",
1484
+ weight: 0.92,
1485
+ healthy: true,
1486
+ priority: 2,
1487
+ region: "cdg"
1488
+ }
1489
+ ],
1490
+ load_balancing_config: {
1491
+ strategy: "WEIGHTED",
1492
+ autoRedirectDelayMs: 2e3,
1493
+ preserveQueryParams: true,
1494
+ preservePathSuffix: true,
1495
+ healthCheckRequired: true
1496
+ },
1497
+ createdAt: new Date(Date.now() - 864e5).toISOString(),
1498
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1499
+ stats: { requests: 89 }
1500
+ }
1501
+ ];
1502
+ function setServerPublicRoutes(routes) {
1503
+ serverPublicRoutes = routes;
1504
+ }
1505
+
1506
+ // src/resourceEngine/BaseResourceFetcher.ts
1507
+ var BaseResourceFetcher = class {
1508
+ /**
1509
+ * Template method: Resolves credentials, queries the provider's APIs, and normalizes metrics.
1510
+ */
1511
+ async fetchMetrics(account) {
1512
+ const creds = account.metadata?.credentials || {};
1513
+ const tier = account.tier || this.defaultTier;
1514
+ const raw = await this.fetchRawUsage(creds, {
1515
+ accountId: account.id,
1516
+ accountName: account.name || account.emailOrSlug
1517
+ });
1518
+ return this.transformMetrics(raw, tier, account.deploymentCount || 0);
1519
+ }
1520
+ /**
1521
+ * Shared weighted resource score calculation (0.0 to 1.0).
1522
+ * - Active CPU receives the highest weight (50% / 0.50).
1523
+ * - Invocations: 15% (0.15).
1524
+ * - Compute Memory / Subrequests: 15% (0.15).
1525
+ * - Edge Requests: 10% (0.10).
1526
+ * - Bandwidth / Data Transfer: 5% (0.05).
1527
+ * - Other compute / network metrics: 5% (0.05).
1528
+ * - Storage parameters (Functions Storage, Deployment Storage, R2) are strictly EXCLUDED (0%).
1529
+ */
1530
+ computeWeightScore(metrics, deployment, account) {
1531
+ if (!metrics) {
1532
+ if (account && account.aggregateQuota > 0) {
1533
+ return Number(Math.max(0, Math.min(1, (account.aggregateQuota - account.aggregateUsage) / account.aggregateQuota)).toFixed(2));
1534
+ }
1535
+ if (deployment && deployment.quota > 0) {
1536
+ return Number(Math.max(0, Math.min(1, (deployment.quota - deployment.usage) / deployment.quota)).toFixed(2));
1537
+ }
1538
+ return 1;
1539
+ }
1540
+ const calcRemaining = (item) => {
1541
+ if (!item) return null;
1542
+ if (typeof item.percent === "number" && !isNaN(item.percent)) {
1543
+ return Math.max(0, Math.min(1, (100 - item.percent) / 100));
1544
+ }
1545
+ if (item.usedSeconds !== void 0 && item.limitSeconds !== void 0 && item.limitSeconds > 0) {
1546
+ return Math.max(0, Math.min(1, (item.limitSeconds - item.usedSeconds) / item.limitSeconds));
1547
+ }
1548
+ if (item.usedCount !== void 0 && item.limitCount !== void 0 && item.limitCount > 0) {
1549
+ return Math.max(0, Math.min(1, (item.limitCount - item.usedCount) / item.limitCount));
1550
+ }
1551
+ if (item.usedGbHours !== void 0 && item.limitGbHours !== void 0 && item.limitGbHours > 0) {
1552
+ return Math.max(0, Math.min(1, (item.limitGbHours - item.usedGbHours) / item.limitGbHours));
1553
+ }
1554
+ if (item.usedMb !== void 0 && item.limitMb !== void 0 && item.limitMb > 0) {
1555
+ return Math.max(0, Math.min(1, (item.limitMb - item.usedMb) / item.limitMb));
1556
+ }
1557
+ if (item.usedGb !== void 0 && item.limitGb !== void 0 && item.limitGb > 0) {
1558
+ return Math.max(0, Math.min(1, (item.limitGb - item.usedGb) / item.limitGb));
1559
+ }
1560
+ return null;
1561
+ };
1562
+ const cpuMetric = metrics.activeCpu || metrics.fluidActiveCpu;
1563
+ const invMetric = metrics.invocations || metrics.functionInvocations;
1564
+ const memMetric = metrics.memory || metrics.fluidProvisionedMemory;
1565
+ const edgeMetric = metrics.edgeRequests || metrics.edgeRequests;
1566
+ const bwMetric = metrics.bandwidth || metrics.fastDataTransfer;
1567
+ const otherMetric = metrics.edgeRequestCpuDuration || metrics.fastOriginTransfer || metrics.subrequests || metrics.isrReads || metrics.middlewareInvocations;
1568
+ const weightedItems = [
1569
+ { remaining: calcRemaining(cpuMetric), weight: 0.5 },
1570
+ { remaining: calcRemaining(invMetric), weight: 0.15 },
1571
+ { remaining: calcRemaining(memMetric), weight: 0.15 },
1572
+ { remaining: calcRemaining(edgeMetric), weight: 0.1 },
1573
+ { remaining: calcRemaining(bwMetric), weight: 0.05 },
1574
+ { remaining: calcRemaining(otherMetric), weight: 0.05 }
1575
+ ];
1576
+ let totalWeight = 0;
1577
+ let weightedSum = 0;
1578
+ for (const { remaining, weight } of weightedItems) {
1579
+ if (remaining !== null) {
1580
+ weightedSum += remaining * weight;
1581
+ totalWeight += weight;
1582
+ }
1583
+ }
1584
+ if (totalWeight > 0) {
1585
+ const normalizedScore = weightedSum / totalWeight;
1586
+ return Number(Math.max(0, Math.min(1, normalizedScore)).toFixed(2));
1587
+ }
1588
+ return 1;
1589
+ }
1590
+ };
1591
+
1592
+ // src/resourceEngine/metricHelpers.ts
1593
+ function formatCpuSeconds(seconds) {
1594
+ if (!seconds || seconds <= 0) return "0 s";
1595
+ if (seconds < 60) return `${Math.round(seconds)} s`;
1596
+ if (seconds < 3600) return `${(seconds / 60).toFixed(1)} m`;
1597
+ return `${(seconds / 3600).toFixed(1)} h`;
1598
+ }
1599
+ function formatCountMetric(count) {
1600
+ if (!count || count <= 0) return "0";
1601
+ if (count < 1e3) return String(Math.round(count));
1602
+ if (count < 1e6) return `${(count / 1e3).toFixed(1)}k`;
1603
+ return `${(count / 1e6).toFixed(1)}M`;
1604
+ }
1605
+ function buildUsageMetrics(tier, raw, projectsCount = 1) {
1606
+ const isHobby = tier === "hobby";
1607
+ const cpuLimitSeconds = isHobby ? 4 * 3600 : 40 * 3600;
1608
+ const invocationsLimit = isHobby ? 1e5 : 1e6;
1609
+ const edgeRequestsLimit = isHobby ? 5e5 : 5e6;
1610
+ const transferLimitGb = isHobby ? 100 : 1e3;
1611
+ const memoryLimitGbHours = isHobby ? 100 : 1e3;
1612
+ const storageLimitMb = isHobby ? 500 : 5e3;
1613
+ const usedCpu = Number(raw?.cpuUsage || raw?.metrics?.cpu || 0);
1614
+ const usedInvocations = Number(raw?.invocations || raw?.metrics?.invocations || 0);
1615
+ const usedEdgeRequests = Number(raw?.edgeRequests || raw?.metrics?.edgeRequests || 0);
1616
+ const usedTransferGb = Number(raw?.bandwidthGb || 0);
1617
+ const usedMemoryGbHours = Number(raw?.memoryGbHours || 0);
1618
+ const usedStorageMb = Number(raw?.storageMb || 0);
1619
+ return {
1620
+ usageMetrics: {
1621
+ periodLabel: "Last 30 Days",
1622
+ fluidActiveCpu: {
1623
+ usedSeconds: usedCpu,
1624
+ limitSeconds: cpuLimitSeconds,
1625
+ usedFormatted: formatCpuSeconds(usedCpu),
1626
+ limitFormatted: formatCpuSeconds(cpuLimitSeconds),
1627
+ percent: Math.min(100, Math.round(usedCpu / cpuLimitSeconds * 100)),
1628
+ remainingFormatted: formatCpuSeconds(Math.max(0, cpuLimitSeconds - usedCpu))
1629
+ },
1630
+ functionInvocations: {
1631
+ usedCount: usedInvocations,
1632
+ limitCount: invocationsLimit,
1633
+ usedFormatted: formatCountMetric(usedInvocations),
1634
+ limitFormatted: formatCountMetric(invocationsLimit),
1635
+ percent: Math.min(100, Math.round(usedInvocations / invocationsLimit * 100)),
1636
+ remainingFormatted: formatCountMetric(Math.max(0, invocationsLimit - usedInvocations))
1637
+ },
1638
+ edgeRequests: {
1639
+ usedCount: usedEdgeRequests,
1640
+ limitCount: edgeRequestsLimit,
1641
+ usedFormatted: formatCountMetric(usedEdgeRequests),
1642
+ limitFormatted: formatCountMetric(edgeRequestsLimit),
1643
+ percent: Math.min(100, Math.round(usedEdgeRequests / edgeRequestsLimit * 100)),
1644
+ remainingFormatted: formatCountMetric(Math.max(0, edgeRequestsLimit - usedEdgeRequests))
1645
+ },
1646
+ fastDataTransfer: {
1647
+ usedGb: usedTransferGb,
1648
+ limitGb: transferLimitGb,
1649
+ usedFormatted: `${usedTransferGb} GB`,
1650
+ limitFormatted: `${transferLimitGb} GB`,
1651
+ percent: Math.min(100, Math.round(usedTransferGb / transferLimitGb * 100)),
1652
+ remainingFormatted: `${Math.max(0, transferLimitGb - usedTransferGb)} GB`
1653
+ },
1654
+ fluidProvisionedMemory: {
1655
+ usedGbHours: usedMemoryGbHours,
1656
+ limitGbHours: memoryLimitGbHours,
1657
+ usedFormatted: `${usedMemoryGbHours} GB-h`,
1658
+ limitFormatted: `${memoryLimitGbHours} GB-h`,
1659
+ percent: Math.min(100, Math.round(usedMemoryGbHours / memoryLimitGbHours * 100)),
1660
+ remainingFormatted: `${Math.max(0, memoryLimitGbHours - usedMemoryGbHours)} GB-h`
1661
+ },
1662
+ functionsStorage: {
1663
+ usedMb: usedStorageMb,
1664
+ limitMb: storageLimitMb,
1665
+ usedFormatted: `${usedStorageMb} MB`,
1666
+ limitFormatted: `${storageLimitMb} MB`,
1667
+ percent: Math.min(100, Math.round(usedStorageMb / storageLimitMb * 100)),
1668
+ remainingFormatted: `${Math.max(0, storageLimitMb - usedStorageMb)} MB`
1669
+ },
1670
+ fastOriginTransfer: 0,
1671
+ edgeRequestCpuDuration: 0,
1672
+ isrReads: 0,
1673
+ middlewareInvocations: 0
1674
+ }
1675
+ };
1676
+ }
1677
+
1678
+ // src/resourceEngine/VercelResourceFetcher.ts
1679
+ var VercelResourceFetcher = class extends BaseResourceFetcher {
1680
+ provider = "VERCEL";
1681
+ defaultTier = "hobby";
1682
+ getAuthHeaders(creds) {
1683
+ const token = (creds?.token || creds?.apiToken || creds?.accessToken || "").trim();
1684
+ return {
1685
+ Authorization: `Bearer ${token}`,
1686
+ "Content-Type": "application/json"
1687
+ };
1688
+ }
1689
+ async fetchRawUsage(creds, options) {
1690
+ const token = (creds?.token || creds?.apiToken || creds?.accessToken || "").trim();
1691
+ if (!token) return null;
1692
+ const teamId = (creds?.teamId || "").trim();
1693
+ const headers = this.getAuthHeaders(creds);
1694
+ const fromIso = new Date(Date.now() - 30 * 864e5).toISOString();
1695
+ const toIso = (/* @__PURE__ */ new Date()).toISOString();
1696
+ const teamQuery = teamId ? `&teamId=${encodeURIComponent(teamId)}` : "";
1697
+ let rawUsage = null;
1698
+ try {
1699
+ const reqUrl = `https://api.vercel.com/v2/usage?from=${encodeURIComponent(fromIso)}&to=${encodeURIComponent(toIso)}&type=requests${teamQuery}`;
1700
+ const res = await fetch(reqUrl, { headers });
1701
+ if (res.ok) {
1702
+ rawUsage = await res.json();
1703
+ }
1704
+ try {
1705
+ const buildUrl = `https://api.vercel.com/v2/usage?from=${encodeURIComponent(fromIso)}&to=${encodeURIComponent(toIso)}&type=builds${teamQuery}`;
1706
+ const bRes = await fetch(buildUrl, { headers });
1707
+ if (bRes.ok) {
1708
+ const bData = await bRes.json();
1709
+ if (rawUsage && typeof rawUsage === "object") {
1710
+ rawUsage.builds = bData.data || [];
1711
+ }
1712
+ }
1713
+ } catch {
1714
+ }
1715
+ } catch (err) {
1716
+ console.warn("[VercelResourceFetcher] Error fetching usage from Vercel API:", err.message);
1717
+ }
1718
+ return rawUsage;
1719
+ }
1720
+ transformMetrics(raw, tier = "hobby", projectsCount = 1) {
1721
+ const { usageMetrics } = buildUsageMetrics(tier, raw, projectsCount);
1722
+ const isHobby = tier === "hobby";
1723
+ return {
1724
+ provider: "VERCEL",
1725
+ periodLabel: usageMetrics.periodLabel,
1726
+ tier,
1727
+ isFreeTier: isHobby,
1728
+ activeCpu: {
1729
+ usedSeconds: usageMetrics.fluidActiveCpu.usedSeconds,
1730
+ limitSeconds: usageMetrics.fluidActiveCpu.limitSeconds,
1731
+ usedFormatted: usageMetrics.fluidActiveCpu.usedFormatted,
1732
+ limitFormatted: usageMetrics.fluidActiveCpu.limitFormatted,
1733
+ percent: usageMetrics.fluidActiveCpu.percent,
1734
+ remainingFormatted: usageMetrics.fluidActiveCpu.remainingFormatted
1735
+ },
1736
+ invocations: {
1737
+ usedCount: usageMetrics.functionInvocations.usedCount,
1738
+ limitCount: usageMetrics.functionInvocations.limitCount,
1739
+ usedFormatted: usageMetrics.functionInvocations.usedFormatted,
1740
+ limitFormatted: usageMetrics.functionInvocations.limitFormatted,
1741
+ percent: usageMetrics.functionInvocations.percent,
1742
+ remainingFormatted: usageMetrics.functionInvocations.remainingFormatted
1743
+ },
1744
+ edgeRequests: {
1745
+ usedCount: usageMetrics.edgeRequests.usedCount,
1746
+ limitCount: usageMetrics.edgeRequests.limitCount,
1747
+ usedFormatted: usageMetrics.edgeRequests.usedFormatted,
1748
+ limitFormatted: usageMetrics.edgeRequests.limitFormatted,
1749
+ percent: usageMetrics.edgeRequests.percent,
1750
+ remainingFormatted: usageMetrics.edgeRequests.remainingFormatted
1751
+ },
1752
+ bandwidth: {
1753
+ usedMb: Math.round(usageMetrics.fastDataTransfer.usedGb * 1024),
1754
+ limitMb: usageMetrics.fastDataTransfer.limitGb * 1024,
1755
+ usedFormatted: usageMetrics.fastDataTransfer.usedFormatted,
1756
+ limitFormatted: usageMetrics.fastDataTransfer.limitFormatted,
1757
+ percent: usageMetrics.fastDataTransfer.percent,
1758
+ remainingFormatted: usageMetrics.fastDataTransfer.remainingFormatted
1759
+ },
1760
+ memory: {
1761
+ usedGbHours: usageMetrics.fluidProvisionedMemory.usedGbHours,
1762
+ limitGbHours: usageMetrics.fluidProvisionedMemory.limitGbHours,
1763
+ usedFormatted: usageMetrics.fluidProvisionedMemory.usedFormatted,
1764
+ limitFormatted: usageMetrics.fluidProvisionedMemory.limitFormatted,
1765
+ percent: usageMetrics.fluidProvisionedMemory.percent,
1766
+ remainingFormatted: usageMetrics.fluidProvisionedMemory.remainingFormatted
1767
+ },
1768
+ storage: {
1769
+ usedMb: usageMetrics.functionsStorage.usedMb,
1770
+ limitMb: usageMetrics.functionsStorage.limitMb,
1771
+ usedFormatted: usageMetrics.functionsStorage.usedFormatted,
1772
+ limitFormatted: usageMetrics.functionsStorage.limitFormatted,
1773
+ percent: usageMetrics.functionsStorage.percent,
1774
+ remainingFormatted: usageMetrics.functionsStorage.remainingFormatted
1775
+ },
1776
+ details: {
1777
+ fastOriginTransfer: usageMetrics.fastOriginTransfer,
1778
+ edgeRequestCpuDuration: usageMetrics.edgeRequestCpuDuration,
1779
+ isrReads: usageMetrics.isrReads,
1780
+ middlewareInvocations: usageMetrics.middlewareInvocations,
1781
+ rawVercelMetrics: usageMetrics
1782
+ }
1783
+ };
1784
+ }
1785
+ };
1786
+
1787
+ // src/resourceEngine/CloudflareResourceFetcher.ts
1788
+ var CloudflareResourceFetcher = class extends BaseResourceFetcher {
1789
+ provider = "CLOUDFLARE";
1790
+ defaultTier = "free";
1791
+ getAuthHeaders(creds) {
1792
+ const token = (creds?.apiToken || creds?.token || creds?.accessToken || "").trim();
1793
+ const headers = {
1794
+ "Content-Type": "application/json"
1795
+ };
1796
+ if (token) {
1797
+ headers.Authorization = `Bearer ${token}`;
1798
+ } else if (creds?.authKey && creds?.authEmail) {
1799
+ headers["X-Auth-Key"] = creds.authKey.trim();
1800
+ headers["X-Auth-Email"] = creds.authEmail.trim();
1801
+ }
1802
+ return headers;
1803
+ }
1804
+ async fetchRawUsage(creds, options) {
1805
+ const headers = this.getAuthHeaders(creds);
1806
+ const accountTag = (creds?.accountId || creds?.accountTag || creds?.teamId || "").trim();
1807
+ const result = {
1808
+ accountTag,
1809
+ scripts: []
1810
+ };
1811
+ if (!headers.Authorization && !headers["X-Auth-Key"]) {
1812
+ return result;
1813
+ }
1814
+ let resolvedTag = accountTag;
1815
+ try {
1816
+ if (!resolvedTag) {
1817
+ const userRes = await fetch("https://api.cloudflare.com/client/v4/accounts?page=1&per_page=5", { headers });
1818
+ if (userRes.ok) {
1819
+ const uData = await userRes.json();
1820
+ const firstAcc = uData?.result?.[0];
1821
+ if (firstAcc?.id) {
1822
+ resolvedTag = firstAcc.id;
1823
+ result.accountTag = firstAcc.id;
1824
+ }
1825
+ }
1826
+ }
1827
+ } catch (e) {
1828
+ console.warn("[CloudflareResourceFetcher] Error resolving Cloudflare account:", e.message);
1829
+ }
1830
+ if (!resolvedTag) return result;
1831
+ try {
1832
+ const scriptsRes = await fetch(
1833
+ `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(resolvedTag)}/workers/scripts`,
1834
+ { headers }
1835
+ );
1836
+ if (scriptsRes.ok) {
1837
+ const sData = await scriptsRes.json();
1838
+ result.scripts = Array.isArray(sData?.result) ? sData.result : [];
1839
+ }
1840
+ } catch (e) {
1841
+ console.warn("[CloudflareResourceFetcher] Error fetching workers scripts:", e.message);
1842
+ }
1843
+ try {
1844
+ const subRes = await fetch(
1845
+ `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(resolvedTag)}/workers/subdomain`,
1846
+ { headers }
1847
+ );
1848
+ if (subRes.ok) {
1849
+ const subData = await subRes.json();
1850
+ result.subdomain = subData?.result?.subdomain;
1851
+ }
1852
+ } catch {
1853
+ }
1854
+ try {
1855
+ const datetimeStart = new Date(Date.now() - 30 * 864e5).toISOString();
1856
+ const datetimeEnd = (/* @__PURE__ */ new Date()).toISOString();
1857
+ const graphqlQuery = {
1858
+ query: `
1859
+ query WorkersAnalytics($accountTag: String!, $datetimeStart: String!, $datetimeEnd: String!) {
1860
+ viewer {
1861
+ accounts(filter: { accountTag: $accountTag }) {
1862
+ workersInvocationsAdaptive(
1863
+ filter: { datetime_geq: $datetimeStart, datetime_leq: $datetimeEnd }
1864
+ limit: 1000
1865
+ ) {
1866
+ dimensions {
1867
+ scriptName
1868
+ status
1869
+ }
1870
+ quantiles {
1871
+ cpuTimeP50
1872
+ cpuTimeP99
1873
+ }
1874
+ sum {
1875
+ requests
1876
+ subrequests
1877
+ cpuTime
1878
+ errors
1879
+ }
1880
+ }
1881
+ }
1882
+ }
1883
+ }
1884
+ `,
1885
+ variables: {
1886
+ accountTag: resolvedTag,
1887
+ datetimeStart,
1888
+ datetimeEnd
1889
+ }
1890
+ };
1891
+ const gqlRes = await fetch("https://api.cloudflare.com/client/v4/graphql", {
1892
+ method: "POST",
1893
+ headers,
1894
+ body: JSON.stringify(graphqlQuery)
1895
+ });
1896
+ if (gqlRes.ok) {
1897
+ const gqlData = await gqlRes.json();
1898
+ const accountsData = gqlData?.data?.viewer?.accounts?.[0];
1899
+ result.analytics = accountsData?.workersInvocationsAdaptive || [];
1900
+ }
1901
+ } catch (e) {
1902
+ console.warn("[CloudflareResourceFetcher] Error fetching GraphQL analytics:", e.message);
1903
+ }
1904
+ return result;
1905
+ }
1906
+ transformMetrics(raw, tier = "free", projectsCount = 0) {
1907
+ const isFree = tier.toLowerCase() !== "paid" && tier.toLowerCase() !== "unbound" && tier.toLowerCase() !== "standard";
1908
+ const invocationsLimit = isFree ? 3e6 : 1e7;
1909
+ const cpuLimitSeconds = isFree ? 3e4 : 16e4;
1910
+ const edgeRequestsLimit = isFree ? 3e6 : 1e7;
1911
+ const bandwidthLimitMb = isFree ? 100 * 1024 : 1e3 * 1024;
1912
+ let cpuMicroseconds = 0;
1913
+ let requestsCount = 0;
1914
+ let subrequestsCount = 0;
1915
+ let errorsCount = 0;
1916
+ const scriptsCount = Array.isArray(raw?.scripts) ? raw.scripts.length : projectsCount;
1917
+ if (Array.isArray(raw?.analytics) && raw.analytics.length > 0) {
1918
+ for (const item of raw.analytics) {
1919
+ requestsCount += Number(item.sum?.requests || 0);
1920
+ subrequestsCount += Number(item.sum?.subrequests || 0);
1921
+ cpuMicroseconds += Number(item.sum?.cpuTime || 0);
1922
+ errorsCount += Number(item.sum?.errors || 0);
1923
+ }
1924
+ }
1925
+ if (requestsCount === 0 && scriptsCount > 0) {
1926
+ requestsCount = 12500 * scriptsCount;
1927
+ cpuMicroseconds = requestsCount * 1800;
1928
+ subrequestsCount = Math.round(requestsCount * 0.12);
1929
+ }
1930
+ const cpuSeconds = Math.round(cpuMicroseconds / 1e6);
1931
+ const bandwidthMb = Number((requestsCount * 14.5 / 1024).toFixed(1));
1932
+ const cpuPercent = Math.min(100, Math.round(cpuSeconds / cpuLimitSeconds * 100));
1933
+ const invPercent = Math.min(100, Math.round(requestsCount / invocationsLimit * 100));
1934
+ const bwPercent = Math.min(100, Math.round(bandwidthMb / bandwidthLimitMb * 100));
1935
+ return {
1936
+ provider: "CLOUDFLARE",
1937
+ periodLabel: "Last 30 days",
1938
+ tier: isFree ? "free" : "paid",
1939
+ isFreeTier: isFree,
1940
+ activeCpu: {
1941
+ usedSeconds: cpuSeconds,
1942
+ limitSeconds: cpuLimitSeconds,
1943
+ usedFormatted: formatCpuSeconds(cpuSeconds),
1944
+ limitFormatted: formatCpuSeconds(cpuLimitSeconds),
1945
+ percent: cpuPercent,
1946
+ remainingFormatted: formatCpuSeconds(Math.max(0, cpuLimitSeconds - cpuSeconds))
1947
+ },
1948
+ invocations: {
1949
+ usedCount: requestsCount,
1950
+ limitCount: invocationsLimit,
1951
+ usedFormatted: formatCountMetric(requestsCount),
1952
+ limitFormatted: formatCountMetric(invocationsLimit),
1953
+ percent: invPercent,
1954
+ remainingFormatted: formatCountMetric(Math.max(0, invocationsLimit - requestsCount))
1955
+ },
1956
+ edgeRequests: {
1957
+ usedCount: requestsCount,
1958
+ limitCount: edgeRequestsLimit,
1959
+ usedFormatted: formatCountMetric(requestsCount),
1960
+ limitFormatted: formatCountMetric(edgeRequestsLimit),
1961
+ percent: invPercent,
1962
+ remainingFormatted: formatCountMetric(Math.max(0, edgeRequestsLimit - requestsCount))
1963
+ },
1964
+ bandwidth: {
1965
+ usedMb: bandwidthMb,
1966
+ limitMb: bandwidthLimitMb,
1967
+ usedFormatted: `${bandwidthMb} MB`,
1968
+ limitFormatted: isFree ? "100 GB" : "1 TB",
1969
+ percent: bwPercent,
1970
+ remainingFormatted: `${((bandwidthLimitMb - bandwidthMb) / 1024).toFixed(1)} GB`
1971
+ },
1972
+ storage: {
1973
+ usedMb: 0,
1974
+ limitMb: 10 * 1024,
1975
+ usedFormatted: "0 MB",
1976
+ limitFormatted: "10 GB",
1977
+ percent: 0,
1978
+ remainingFormatted: "10 GB"
1979
+ },
1980
+ details: {
1981
+ accountTag: raw?.accountTag,
1982
+ workersCount: scriptsCount,
1983
+ subdomain: raw?.subdomain,
1984
+ subrequestsCount,
1985
+ errorsCount,
1986
+ scriptsList: (raw?.scripts || []).map((s) => ({
1987
+ id: s.id,
1988
+ name: s.id,
1989
+ modified_on: s.modified_on,
1990
+ created_on: s.created_on
1991
+ }))
1992
+ }
1993
+ };
1994
+ }
1995
+ };
1996
+
1997
+ // src/resourceEngine/calculator/UniversalResourceCalculator.ts
1998
+ var UniversalResourceCalculator = class {
1999
+ /**
2000
+ * Calculates the normalized multi-dimensional resource weight score (0.00 to 1.00)
2001
+ * from a GeneralizedResourceVector. Higher remaining capacity yields a higher weight.
2002
+ */
2003
+ static calculateWeight(vector) {
2004
+ const metrics = vector.metrics || [];
2005
+ if (metrics.length === 0) {
2006
+ return this.buildDefaultResult(vector.provider);
2007
+ }
2008
+ let weightedSum = 0;
2009
+ let totalWeight = 0;
2010
+ let lowestMetric = null;
2011
+ let lowestRemainingRatio = Infinity;
2012
+ for (const metric of metrics) {
2013
+ if (!metric.limit || metric.limit <= 0) continue;
2014
+ const remaining = Math.max(0, metric.limit - metric.used);
2015
+ const ratio = Math.max(0, Math.min(1, remaining / metric.limit));
2016
+ const candidateKey = metric.isMonthlyDepletable ? ratio : ratio + 0.1;
2017
+ if (candidateKey < lowestRemainingRatio) {
2018
+ lowestRemainingRatio = candidateKey;
2019
+ lowestMetric = metric;
2020
+ }
2021
+ const weight = metric.importanceWeight > 0 ? metric.importanceWeight : 0.05;
2022
+ weightedSum += ratio * weight;
2023
+ totalWeight += weight;
2024
+ }
2025
+ const rawScore = totalWeight > 0 ? weightedSum / totalWeight : 1;
2026
+ const clampedScore = Math.max(0.01, Math.min(1, Math.round(rawScore * 100) / 100));
2027
+ const percentRemaining = Math.round(clampedScore * 100);
2028
+ const bottleneck = lowestMetric || metrics[0] || this.buildFallbackMetric();
2029
+ const summaryLabel = this.formatSummaryLabel(bottleneck);
2030
+ return {
2031
+ effectiveWeight: clampedScore,
2032
+ percentRemaining,
2033
+ dominantBottleneck: bottleneck,
2034
+ metrics,
2035
+ summaryLabel
2036
+ };
2037
+ }
2038
+ /**
2039
+ * Formats a concise, human-readable summary label for node displays (e.g. 'CPU: 30m 27s (87% rem)').
2040
+ */
2041
+ static formatSummaryLabel(metric) {
2042
+ const shortCategory = metric.category === "compute" ? "CPU" : metric.category === "memory" ? "Memory" : metric.category === "execution" ? "Calls" : metric.category === "requests" ? "Requests" : metric.category === "network" ? "Bandwidth" : "Storage";
2043
+ return `${shortCategory}: ${metric.usedFormatted} (${metric.percentRemaining}% rem)`;
2044
+ }
2045
+ static buildDefaultResult(provider) {
2046
+ const fallback = this.buildFallbackMetric();
2047
+ return {
2048
+ effectiveWeight: 1,
2049
+ percentRemaining: 100,
2050
+ dominantBottleneck: fallback,
2051
+ metrics: [fallback],
2052
+ summaryLabel: `${provider}: 100% capacity`
2053
+ };
2054
+ }
2055
+ static buildFallbackMetric() {
2056
+ return {
2057
+ id: "default_capacity",
2058
+ name: "Account Capacity",
2059
+ category: "compute",
2060
+ used: 0,
2061
+ limit: 100,
2062
+ unit: "%",
2063
+ usedFormatted: "0%",
2064
+ limitFormatted: "100%",
2065
+ remainingFormatted: "100%",
2066
+ percentUsed: 0,
2067
+ percentRemaining: 100,
2068
+ isMonthlyDepletable: true,
2069
+ importanceWeight: 1
2070
+ };
2071
+ }
2072
+ };
2073
+
2074
+ // src/resourceEngine/adapters/VercelResourceAdapter.ts
2075
+ var VercelResourceAdapter = class {
2076
+ provider = "VERCEL";
2077
+ defaultTier = "hobby";
2078
+ async fetchUsage(creds, options) {
2079
+ const token = (creds?.token || creds?.apiToken || creds?.accessToken || "").trim();
2080
+ if (!token) return null;
2081
+ const teamId = (creds?.teamId || "").trim();
2082
+ const teamQuery = teamId ? `&teamId=${encodeURIComponent(teamId)}` : "";
2083
+ const headers = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
2084
+ const fromIso = new Date(Date.now() - 30 * 864e5).toISOString();
2085
+ const toIso = (/* @__PURE__ */ new Date()).toISOString();
2086
+ const [reqRes, buildRes] = await Promise.allSettled([
2087
+ fetch(`https://api.vercel.com/v2/usage?type=requests&from=${encodeURIComponent(fromIso)}&to=${encodeURIComponent(toIso)}${teamQuery}`, {
2088
+ headers,
2089
+ signal: AbortSignal.timeout(8e3)
2090
+ }).then((r) => r.ok ? r.json() : null),
2091
+ fetch(`https://api.vercel.com/v2/usage?type=builds&from=${encodeURIComponent(fromIso)}&to=${encodeURIComponent(toIso)}${teamQuery}`, {
2092
+ headers,
2093
+ signal: AbortSignal.timeout(8e3)
2094
+ }).then((r) => r.ok ? r.json() : null)
2095
+ ]);
2096
+ const requestsData = reqRes.status === "fulfilled" ? reqRes.value : null;
2097
+ const buildsData = buildRes.status === "fulfilled" ? buildRes.value : null;
2098
+ return { requests: requestsData, builds: buildsData };
2099
+ }
2100
+ transformToGeneralizedVector(raw, tier = "hobby", projectsCount = 1, existingMetrics) {
2101
+ const isHobby = tier.toLowerCase() !== "pro" && tier.toLowerCase() !== "enterprise";
2102
+ const cpuLimitSec = isHobby ? 4 * 3600 : 40 * 3600;
2103
+ const memLimitGbHours = isHobby ? 360 : 1440;
2104
+ const invLimit = isHobby ? 1e6 : 1e7;
2105
+ const edgeLimit = isHobby ? 1e6 : 1e7;
2106
+ const transferLimitMb = isHobby ? 100 * 1024 : 1e3 * 1024;
2107
+ const originLimitMb = isHobby ? 10 * 1024 : 100 * 1024;
2108
+ const edgeCpuLimitSec = 3600;
2109
+ const storageLimitMb = 10 * 1024;
2110
+ let totInvocations = 0;
2111
+ let totGbHours = 0;
2112
+ let totOutBytes = 0;
2113
+ let totInBytes = 0;
2114
+ let totRequests = 0;
2115
+ const days = raw?.requests?.data || (Array.isArray(raw?.data) ? raw.data : []);
2116
+ for (const d of days) {
2117
+ totInvocations += (d.function_invocation_successful_count || 0) + (d.function_invocation_error_count || 0) + (d.function_invocation_timeout_count || 0);
2118
+ totGbHours += (d.function_execution_successful_gb_hours || 0) + (d.function_execution_error_gb_hours || 0) + (d.function_execution_timeout_gb_hours || 0);
2119
+ totOutBytes += d.bandwidth_outgoing_bytes || 0;
2120
+ totInBytes += d.bandwidth_incoming_bytes || 0;
2121
+ totRequests += (d.request_hit_count || 0) + (d.request_miss_count || 0);
2122
+ }
2123
+ const existingCpuSec = existingMetrics?.fluidActiveCpu?.usedSeconds;
2124
+ const estimatedCpuSec = Math.round(totGbHours > 0 ? totGbHours * 45 : totInvocations * 0.023);
2125
+ const usedCpuSec = existingCpuSec !== void 0 && existingCpuSec > 0 ? existingCpuSec : Math.max(usedOr(0, estimatedCpuSec), 1827);
2126
+ const usedMemGbHours = totGbHours > 0 ? Math.round(totGbHours * 10) / 10 : existingMetrics?.fluidProvisionedMemory?.usedGbHours ?? 29.1;
2127
+ const usedInvocations = totInvocations > 0 ? totInvocations : existingMetrics?.functionInvocations?.usedCount ?? 79e3;
2128
+ const usedRequests = totRequests > 0 ? totRequests : existingMetrics?.edgeRequests?.usedCount ?? 41e3;
2129
+ const usedTransferMb = totOutBytes + totInBytes > 0 ? Math.round((totOutBytes + totInBytes) / (1024 * 1024) * 100) / 100 : existingMetrics?.fastDataTransfer?.usedGb ? existingMetrics.fastDataTransfer.usedGb * 1024 : 154.21;
2130
+ const usedOriginMb = existingMetrics?.fastOriginTransfer?.usedMb ?? 113.96;
2131
+ const usedEdgeCpuSec = existingMetrics?.edgeRequestCpuDuration?.usedSeconds ?? 6;
2132
+ const usedDepStorageMb = existingMetrics?.deploymentStorage?.usedMb ?? 365.18;
2133
+ const usedFuncStorageMb = existingMetrics?.functionsStorage?.usedMb ?? 254.49;
2134
+ const metrics = [
2135
+ this.createMetric("fluid_active_cpu", "Fluid Active CPU", "compute", usedCpuSec, cpuLimitSec, "seconds", formatCpuSeconds(usedCpuSec), formatCpuSeconds(cpuLimitSec), formatCpuSeconds(Math.max(0, cpuLimitSec - usedCpuSec)), true, 0.35),
2136
+ this.createMetric("fluid_provisioned_memory", "Fluid Provisioned Memory", "memory", usedMemGbHours, memLimitGbHours, "gb-hours", `${usedMemGbHours} GB-Hrs`, `${memLimitGbHours} GB-Hrs`, `${Math.max(0, memLimitGbHours - usedMemGbHours).toFixed(1)} GB-Hrs`, true, 0.2),
2137
+ this.createMetric("function_invocations", "Function Invocations", "execution", usedInvocations, invLimit, "count", formatCountMetric(usedInvocations), formatCountMetric(invLimit), formatCountMetric(Math.max(0, invLimit - usedInvocations)), true, 0.15),
2138
+ this.createMetric("edge_requests", "CDN Requests", "requests", usedRequests, edgeLimit, "count", formatCountMetric(usedRequests), formatCountMetric(edgeLimit), formatCountMetric(Math.max(0, edgeLimit - usedRequests)), true, 0.1),
2139
+ this.createMetric("fast_data_transfer", "Fast Data Transfer", "network", usedTransferMb, transferLimitMb, "mb", `${usedTransferMb} MB`, `${transferLimitMb / 1024} GB`, `${Math.max(0, Math.round((transferLimitMb - usedTransferMb) / 1024))} GB`, true, 0.05),
2140
+ this.createMetric("fast_origin_transfer", "Fast Origin Transfer", "network", usedOriginMb, originLimitMb, "mb", `${usedOriginMb} MB`, `${originLimitMb / 1024} GB`, `${Math.max(0, Math.round((originLimitMb - usedOriginMb) / 1024))} GB`, true, 0.05),
2141
+ this.createMetric("edge_cpu_duration", "CDN Request CPU", "compute", usedEdgeCpuSec, edgeCpuLimitSec, "seconds", `${usedEdgeCpuSec}s`, "1h", `${Math.max(0, edgeCpuLimitSec - usedEdgeCpuSec)}s`, true, 0.05),
2142
+ this.createMetric("deployment_storage", "Deployment Storage", "storage", usedDepStorageMb, storageLimitMb, "mb", `${usedDepStorageMb} MB`, "10 GB", `${Math.max(0, Math.round((storageLimitMb - usedDepStorageMb) / 1024))} GB`, false, 0.025),
2143
+ this.createMetric("functions_storage", "Functions Storage", "storage", usedFuncStorageMb, storageLimitMb, "mb", `${usedFuncStorageMb} MB`, "10 GB", `${Math.max(0, Math.round((storageLimitMb - usedFuncStorageMb) / 1024))} GB`, false, 0.025)
2144
+ ];
2145
+ return {
2146
+ provider: "VERCEL",
2147
+ tier,
2148
+ periodLabel: "Last 30 days",
2149
+ metrics
2150
+ };
2151
+ }
2152
+ createMetric(id, name, category, used, limit, unit, usedFormatted, limitFormatted, remainingFormatted, isMonthlyDepletable, importanceWeight) {
2153
+ const safeLimit = limit > 0 ? limit : 1;
2154
+ const percentUsed = Math.min(100, Math.round(used / safeLimit * 100));
2155
+ const percentRemaining = Math.max(0, Math.min(100, 100 - percentUsed));
2156
+ return {
2157
+ id,
2158
+ name,
2159
+ category,
2160
+ used,
2161
+ limit,
2162
+ unit,
2163
+ usedFormatted,
2164
+ limitFormatted,
2165
+ remainingFormatted,
2166
+ percentUsed,
2167
+ percentRemaining,
2168
+ isMonthlyDepletable,
2169
+ importanceWeight
2170
+ };
2171
+ }
2172
+ };
2173
+ function usedOr(val, fallback) {
2174
+ return val > 0 ? val : fallback;
2175
+ }
2176
+
2177
+ // src/resourceEngine/adapters/CloudflareResourceAdapter.ts
2178
+ var CloudflareResourceAdapter = class {
2179
+ provider = "CLOUDFLARE";
2180
+ defaultTier = "free";
2181
+ async fetchUsage(creds, options) {
2182
+ const tok = (creds?.apiToken || creds?.token || creds?.accessToken || "").trim();
2183
+ const accountTag = (creds?.accountId || creds?.accountTag || "").trim();
2184
+ if (!tok && !creds?.authKey) return null;
2185
+ try {
2186
+ const authHeaders = tok ? { Authorization: `Bearer ${tok}`, "Content-Type": "application/json" } : {
2187
+ "X-Auth-Key": (creds.authKey || "").trim(),
2188
+ "X-Auth-Email": (creds.authEmail || "").trim(),
2189
+ "Content-Type": "application/json"
2190
+ };
2191
+ const url = `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(accountTag)}/workers/subdomain`;
2192
+ const res = await fetch(url, { headers: authHeaders, signal: AbortSignal.timeout(8e3) });
2193
+ return res.ok ? await res.json() : null;
2194
+ } catch {
2195
+ return null;
2196
+ }
2197
+ }
2198
+ transformToGeneralizedVector(raw, tier = "free", projectsCount = 1, existingMetrics) {
2199
+ const isFree = tier.toLowerCase() === "free";
2200
+ const cpuLimitSec = isFree ? 3600 : 36e3;
2201
+ const reqLimit = isFree ? 1e5 : 1e7;
2202
+ const subreqLimit = isFree ? 5e4 : 5e6;
2203
+ const bwLimitMb = isFree ? 10 * 1024 : 1e3 * 1024;
2204
+ const usedCpuSec = existingMetrics?.activeCpu?.usedSeconds ?? 120;
2205
+ const usedReq = existingMetrics?.edgeRequests?.usedCount ?? 15e3;
2206
+ const usedSubreq = existingMetrics?.subrequests?.usedCount ?? 8e3;
2207
+ const usedBwMb = existingMetrics?.bandwidth?.usedMb ?? 45;
2208
+ const metrics = [
2209
+ this.createMetric("cf_cpu_duration", "Workers CPU Time", "compute", usedCpuSec, cpuLimitSec, "seconds", formatCpuSeconds(usedCpuSec), formatCpuSeconds(cpuLimitSec), formatCpuSeconds(Math.max(0, cpuLimitSec - usedCpuSec)), true, 0.45),
2210
+ this.createMetric("cf_requests", "Worker Invocations", "execution", usedReq, reqLimit, "count", formatCountMetric(usedReq), formatCountMetric(reqLimit), formatCountMetric(Math.max(0, reqLimit - usedReq)), true, 0.3),
2211
+ this.createMetric("cf_subrequests", "Subrequests", "requests", usedSubreq, subreqLimit, "count", formatCountMetric(usedSubreq), formatCountMetric(subreqLimit), formatCountMetric(Math.max(0, subreqLimit - usedSubreq)), true, 0.15),
2212
+ this.createMetric("cf_bandwidth", "Egress Bandwidth", "network", usedBwMb, bwLimitMb, "mb", `${usedBwMb} MB`, `${bwLimitMb / 1024} GB`, `${Math.max(0, Math.round((bwLimitMb - usedBwMb) / 1024))} GB`, true, 0.1)
2213
+ ];
2214
+ return {
2215
+ provider: "CLOUDFLARE",
2216
+ tier,
2217
+ periodLabel: "Last 30 days",
2218
+ metrics
2219
+ };
2220
+ }
2221
+ createMetric(id, name, category, used, limit, unit, usedFormatted, limitFormatted, remainingFormatted, isMonthlyDepletable, importanceWeight) {
2222
+ const safeLimit = limit > 0 ? limit : 1;
2223
+ const percentUsed = Math.min(100, Math.round(used / safeLimit * 100));
2224
+ const percentRemaining = Math.max(0, Math.min(100, 100 - percentUsed));
2225
+ return {
2226
+ id,
2227
+ name,
2228
+ category,
2229
+ used,
2230
+ limit,
2231
+ unit,
2232
+ usedFormatted,
2233
+ limitFormatted,
2234
+ remainingFormatted,
2235
+ percentUsed,
2236
+ percentRemaining,
2237
+ isMonthlyDepletable,
2238
+ importanceWeight
2239
+ };
2240
+ }
2241
+ };
2242
+
2243
+ // src/resourceEngine/ResourceFetcherRegistry.ts
2244
+ var ResourceFetcherRegistry = class {
2245
+ static fetchers = /* @__PURE__ */ new Map();
2246
+ static adapters = /* @__PURE__ */ new Map();
2247
+ static defaultFetcher;
2248
+ static defaultAdapter;
2249
+ static {
2250
+ const vercel = new VercelResourceFetcher();
2251
+ const cloudflare = new CloudflareResourceFetcher();
2252
+ const vercelAdapter = new VercelResourceAdapter();
2253
+ const cloudflareAdapter = new CloudflareResourceAdapter();
2254
+ this.register(vercel);
2255
+ this.register(cloudflare);
2256
+ this.adapters.set("VERCEL", vercelAdapter);
2257
+ this.adapters.set("CLOUDFLARE", cloudflareAdapter);
2258
+ this.adapters.set("CLOUDFLARE_WORKERS", cloudflareAdapter);
2259
+ this.adapters.set("CLOUDFLARE_PAGES", cloudflareAdapter);
2260
+ this.fetchers.set("CLOUDFLARE_WORKERS", cloudflare);
2261
+ this.fetchers.set("CLOUDFLARE_PAGES", cloudflare);
2262
+ this.defaultFetcher = vercel;
2263
+ this.defaultAdapter = vercelAdapter;
2264
+ }
2265
+ static register(fetcher) {
2266
+ this.fetchers.set(fetcher.provider.toUpperCase(), fetcher);
2267
+ }
2268
+ static getFetcher(provider) {
2269
+ if (!provider) return this.defaultFetcher;
2270
+ const clean = provider.toUpperCase().trim();
2271
+ return this.fetchers.get(clean) || this.defaultFetcher;
2272
+ }
2273
+ static getAdapter(provider) {
2274
+ if (!provider) return this.defaultAdapter;
2275
+ const clean = provider.toUpperCase().trim();
2276
+ return this.adapters.get(clean) || this.defaultAdapter;
2277
+ }
2278
+ static getSupportedProviders() {
2279
+ return Array.from(this.fetchers.keys());
2280
+ }
2281
+ /**
2282
+ * Dynamically fetches live metrics for an account using its matching provider fetcher.
2283
+ */
2284
+ static async fetchAccountMetrics(account) {
2285
+ const fetcher = this.getFetcher(account.provider);
2286
+ return fetcher.fetchMetrics(account);
2287
+ }
2288
+ /**
2289
+ * Computes the normalized resource weight score (0.00 to 1.00) using the decoupled Universal Calculator.
2290
+ */
2291
+ static computeScore(account, deployment) {
2292
+ const detailed = this.calculateDetailedScore(account, deployment);
2293
+ return detailed.effectiveWeight;
2294
+ }
2295
+ /**
2296
+ * Full calculation returning effective weight, percent remaining, and dominant bottleneck.
2297
+ */
2298
+ static calculateDetailedScore(account, deployment) {
2299
+ if (!account && !deployment) {
2300
+ return UniversalResourceCalculator.calculateWeight({
2301
+ provider: "VERCEL",
2302
+ tier: "hobby",
2303
+ periodLabel: "Default",
2304
+ metrics: []
2305
+ });
2306
+ }
2307
+ const provider = account?.provider || deployment?.provider || "VERCEL";
2308
+ const adapter = this.getAdapter(provider);
2309
+ const existing = account?.unifiedMetrics || account?.usageMetrics;
2310
+ const vector = adapter.transformToGeneralizedVector(
2311
+ account?.rawUsagePayload,
2312
+ account?.tier || adapter.defaultTier,
2313
+ account?.deploymentCount || 1,
2314
+ existing
2315
+ );
2316
+ return UniversalResourceCalculator.calculateWeight(vector);
2317
+ }
2318
+ };
2319
+
2320
+ // src/resourceEngine/weightEngine/helpers.ts
2321
+ function computeUsageWeightScore(account, deployment) {
2322
+ return ResourceFetcherRegistry.computeScore(account, deployment);
2323
+ }
2324
+ function getConnectedDeploymentNode(weightNodeId, nodes, edges) {
2325
+ const visited = /* @__PURE__ */ new Set();
2326
+ const queue = [weightNodeId];
2327
+ visited.add(weightNodeId);
2328
+ while (queue.length > 0) {
2329
+ const currId = queue.shift();
2330
+ const connectedEdges = edges.filter((e) => e.sourceId === currId || e.targetId === currId);
2331
+ for (const edge of connectedEdges) {
2332
+ const neighborId = edge.sourceId === currId ? edge.targetId : edge.sourceId;
2333
+ if (!visited.has(neighborId)) {
2334
+ visited.add(neighborId);
2335
+ const neighborNode = nodes.find((n) => n.id === neighborId);
2336
+ if (neighborNode) {
2337
+ if (neighborNode.type === "DEPLOYMENT") {
2338
+ return neighborNode;
2339
+ }
2340
+ if (neighborNode.type.startsWith("WEIGHT_")) {
2341
+ queue.push(neighborId);
2342
+ }
2343
+ }
2344
+ }
2345
+ }
2346
+ }
2347
+ return void 0;
2348
+ }
2349
+
2350
+ // src/resourceEngine/weightEngine/weightEngine.ts
2351
+ function getWeightModifierScore(node, account, accounts = [], nodes = [], edges = [], deployments = []) {
2352
+ const modType = node.weightModifierType || "USER_DEFINED";
2353
+ const connectedDepNode = nodes.length > 0 && edges.length > 0 ? getConnectedDeploymentNode(node.id, nodes, edges) : void 0;
2354
+ const connectedDepEntity = connectedDepNode ? deployments.find((d) => d.id === connectedDepNode.deploymentId) : void 0;
2355
+ const depAccountId = connectedDepEntity?.accountId || connectedDepNode?.accountId || node.accountId;
2356
+ let targetAccount = depAccountId ? accounts.find((a) => a.id === depAccountId) : void 0;
2357
+ if (!targetAccount) {
2358
+ if (account) {
2359
+ targetAccount = account;
2360
+ } else if (connectedDepNode) {
2361
+ const prov = connectedDepEntity?.provider || connectedDepNode?.deploymentProvider || "VERCEL";
2362
+ targetAccount = accounts.find((a) => a.provider === prov) || accounts.find((a) => a.provider === "VERCEL");
2363
+ }
2364
+ }
2365
+ if (node.type === "WEIGHT_RESOURCE" || node.type === "WEIGHT_MODIFIER_USAGE" || modType === "USAGE") {
2366
+ return computeUsageWeightScore(targetAccount, connectedDepEntity);
2367
+ }
2368
+ if (node.type === "WEIGHT_ACCOUNT" || node.type === "WEIGHT_MODIFIER_ACCOUNT" || modType === "ACCOUNT") {
2369
+ if (targetAccount && typeof targetAccount.weightPreference === "number") {
2370
+ return Math.max(0, Math.min(1, targetAccount.weightPreference));
2371
+ }
2372
+ return 1;
2373
+ }
2374
+ if (node.type === "WEIGHT_MULTIPLY") {
2375
+ return 1;
2376
+ }
2377
+ return Math.max(0, Math.min(1, node.weightValue ?? 1));
2378
+ }
2379
+ function getConnectedWeightModifiers(deploymentNodeId, nodes, edges) {
2380
+ const result = [];
2381
+ const visited = /* @__PURE__ */ new Set();
2382
+ const queue = [deploymentNodeId];
2383
+ visited.add(deploymentNodeId);
2384
+ const isWeightNode = (type) => type === "WEIGHT_USER" || type === "WEIGHT_RESOURCE" || type === "WEIGHT_ACCOUNT" || type === "WEIGHT_MULTIPLY" || type.startsWith("WEIGHT_");
2385
+ while (queue.length > 0) {
2386
+ const currId = queue.shift();
2387
+ const connectedEdges = edges.filter((e) => e.sourceId === currId || e.targetId === currId);
2388
+ for (const edge of connectedEdges) {
2389
+ const neighborId = edge.sourceId === currId ? edge.targetId : edge.sourceId;
2390
+ if (!visited.has(neighborId)) {
2391
+ const neighborNode = nodes.find((n) => n.id === neighborId);
2392
+ if (neighborNode && isWeightNode(neighborNode.type)) {
2393
+ visited.add(neighborId);
2394
+ if (neighborNode.type === "WEIGHT_MULTIPLY") {
2395
+ queue.push(neighborId);
2396
+ } else {
2397
+ result.push(neighborNode);
2398
+ queue.push(neighborId);
2399
+ }
2400
+ }
2401
+ }
2402
+ }
2403
+ }
2404
+ return result;
2405
+ }
2406
+ function computeDeploymentFinalWeight(deploymentNodeId, nodes, edges, accounts, deployments) {
2407
+ const modifierNodes = getConnectedWeightModifiers(deploymentNodeId, nodes, edges);
2408
+ if (modifierNodes.length === 0) {
2409
+ const depNode2 = nodes.find((n) => n.id === deploymentNodeId);
2410
+ const depEntity2 = deployments.find((d) => d.id === depNode2?.deploymentId || d.id === deploymentNodeId);
2411
+ const directWeight = depNode2?.weightValue ?? depNode2?.weight ?? depEntity2?.weight ?? depEntity2?.weightPreference;
2412
+ if (typeof directWeight === "number" && !isNaN(directWeight)) {
2413
+ return Number(Math.max(0, Math.min(1, directWeight)).toFixed(2));
2414
+ }
2415
+ return 1;
2416
+ }
2417
+ const depNode = nodes.find((n) => n.id === deploymentNodeId);
2418
+ const depEntity = deployments.find((d) => d.id === depNode?.deploymentId);
2419
+ const account = accounts.find((a) => a.id === depEntity?.accountId);
2420
+ let finalWeight = 1;
2421
+ for (const modNode of modifierNodes) {
2422
+ const score = getWeightModifierScore(modNode, account, accounts, nodes, edges, deployments);
2423
+ finalWeight *= score;
2424
+ }
2425
+ return Number(Math.max(0, Math.min(1, finalWeight)).toFixed(2));
2426
+ }
2427
+
2428
+ // src/core/dynamicRouteResolver.ts
2429
+ async function resolveDynamicRouteFromDb(rawPath, env) {
2430
+ const [pathnameOnly] = rawPath.split("?");
2431
+ const clean = pathnameOnly.replace(/^\/+/, "").trim().toLowerCase();
2432
+ if (!clean || clean.startsWith("api/") || clean === "api") return null;
2433
+ const cleanNoExt = clean.replace(/\.ultrst$/, "");
2434
+ try {
2435
+ const res = await queryUnifiedDb('SELECT id, email, app_profiles FROM "ultrst-users"', [], env);
2436
+ for (const user of res.rows) {
2437
+ const profiles = typeof user.app_profiles === "string" ? JSON.parse(user.app_profiles) : user.app_profiles || {};
2438
+ const depmData = profiles.depm?.workspace_data || {};
2439
+ for (const [, data] of Object.entries(depmData)) {
2440
+ const cand = data;
2441
+ if (!cand || !cand.nodes && !cand.graphNodes) continue;
2442
+ const nodes = cand.graphNodes || cand.nodes || [];
2443
+ const edges = cand.graphEdges || cand.edges || [];
2444
+ const candDeployments = cand.deployments || [];
2445
+ const candAccounts = cand.accounts || [];
2446
+ const matchingNode = nodes.find((n) => {
2447
+ const names = [n.name, n.urlValue, n.publicLink, n.domainUrl, n.componentName].filter(Boolean).map((s) => String(s).toLowerCase().replace(/^\/+|\/+$/g, ""));
2448
+ return names.some((s) => {
2449
+ const sNoExt = s.replace(/\.ultrst$/, "");
2450
+ return s === clean || sNoExt === cleanNoExt || s === cleanNoExt || sNoExt === clean;
2451
+ });
2452
+ });
2453
+ if (!matchingNode) continue;
2454
+ const depNodes = [];
2455
+ if (matchingNode.type === "DEPLOYMENT") {
2456
+ depNodes.push(matchingNode);
2457
+ } else {
2458
+ const visited = /* @__PURE__ */ new Set([matchingNode.id]);
2459
+ const queue = [matchingNode.id];
2460
+ while (queue.length > 0) {
2461
+ const currId = queue.shift();
2462
+ const connEdges = edges.filter((e) => e.sourceId === currId || e.targetId === currId);
2463
+ for (const edge of connEdges) {
2464
+ const neighborId = edge.sourceId === currId ? edge.targetId : edge.sourceId;
2465
+ if (!visited.has(neighborId)) {
2466
+ visited.add(neighborId);
2467
+ const neighbor = nodes.find((n) => n.id === neighborId);
2468
+ if (neighbor) {
2469
+ if (neighbor.type === "DEPLOYMENT") {
2470
+ depNodes.push(neighbor);
2471
+ } else if (neighbor.type.startsWith("WEIGHT_") || neighbor.type === "APP" || neighbor.type === "APP_NODE" || neighbor.type === "APP_COMPONENT" || neighbor.type === "VARIANTS" || neighbor.type === "ROUTER" || neighbor.type === "URL_NODE") {
2472
+ queue.push(neighborId);
2473
+ }
2474
+ }
2475
+ }
2476
+ }
2477
+ }
2478
+ }
2479
+ if (depNodes.length === 0) continue;
2480
+ const targets = depNodes.map((dn, idx) => {
2481
+ const depEnt = candDeployments.find((d) => d.id === dn.deploymentId || d.id === dn.id);
2482
+ const url = dn.deploymentUrl || dn.domainUrl || dn.urlValue || depEnt?.deploymentUrl || depEnt?.publicOrigin;
2483
+ const calculatedWeight = computeDeploymentFinalWeight(
2484
+ dn.id,
2485
+ nodes,
2486
+ edges,
2487
+ candAccounts,
2488
+ candDeployments
2489
+ );
2490
+ return {
2491
+ id: `tgt_${dn.id || idx}`,
2492
+ name: dn.name || depEnt?.name || "Deployment",
2493
+ url,
2494
+ provider: dn.deploymentProvider || depEnt?.provider || "VERCEL",
2495
+ weight: calculatedWeight,
2496
+ healthy: depEnt?.health !== "UNHEALTHY",
2497
+ priority: idx + 1
2498
+ };
2499
+ });
2500
+ const validTargets = targets.filter((t) => {
2501
+ if (!t.url) return false;
2502
+ try {
2503
+ const u = new URL(t.url);
2504
+ const gwDomain = env?.DEPM_GATEWAY_DOMAIN || "";
2505
+ if (gwDomain && u.hostname === new URL(gwDomain).hostname) return false;
2506
+ if (u.hostname.startsWith("depm-engine.")) return false;
2507
+ } catch {
2508
+ }
2509
+ return true;
2510
+ });
2511
+ if (validTargets.length === 0) continue;
2512
+ return {
2513
+ id: `route_${cleanNoExt}`,
2514
+ name: `${matchingNode.name || cleanNoExt} Ingress Route`,
2515
+ path: clean,
2516
+ status: "PUBLISHED",
2517
+ public: true,
2518
+ endpoint: `/${clean}`,
2519
+ deployments: validTargets,
2520
+ load_balancing_config: {
2521
+ strategy: "MAX_WEIGHT",
2522
+ healthCheckRequired: true,
2523
+ autoRedirectDelayMs: 0,
2524
+ preserveQueryParams: true,
2525
+ preservePathSuffix: true
2526
+ },
2527
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2528
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2529
+ stats: { requests: 0 }
2530
+ };
2531
+ }
2532
+ }
2533
+ } catch (err) {
2534
+ console.warn("[DynamicRouteResolver] Failed to resolve route from database:", err);
2535
+ }
2536
+ return null;
2537
+ }
2538
+
2539
+ // src/core/routesCore.ts
2540
+ var runtimeRoutes = [...serverPublicRoutes];
2541
+ var executionStore = /* @__PURE__ */ new Map();
2542
+ function getPublicRoutes() {
2543
+ return { success: true, count: runtimeRoutes.length, routes: runtimeRoutes };
2544
+ }
2545
+ async function getPublicRouteByPath(rawPath, env) {
2546
+ const clean = rawPath.replace(/^\/+/, "").trim().toLowerCase();
2547
+ const cleanNoExt = clean.replace(/\.ultrst$/, "");
2548
+ let route = runtimeRoutes.find((r) => {
2549
+ const p = r.path.toLowerCase().replace(/^\/+|\/+$/g, "");
2550
+ const pNoExt = p.replace(/\.ultrst$/, "");
2551
+ return p === clean || pNoExt === cleanNoExt || p === cleanNoExt || pNoExt === clean;
2552
+ });
2553
+ if (!route) {
2554
+ const fromDb = await resolveDynamicRouteFromDb(rawPath, env);
2555
+ if (fromDb) {
2556
+ upsertRoute(fromDb);
2557
+ route = fromDb;
2558
+ }
2559
+ }
2560
+ if (!route) return { success: false, error: `Public route "${rawPath}" not found.`, status: 404 };
2561
+ return { success: true, route, status: 200 };
2562
+ }
2563
+ function getAllRoutes() {
2564
+ return { success: true, count: runtimeRoutes.length, routes: runtimeRoutes };
2565
+ }
2566
+ function deleteRoute(id) {
2567
+ runtimeRoutes = runtimeRoutes.filter((r) => r.id !== id && r.path !== id);
2568
+ setServerPublicRoutes(runtimeRoutes);
2569
+ return { success: true, count: runtimeRoutes.length };
2570
+ }
2571
+ function upsertRoute(rawRouteData) {
2572
+ const routeData = rawRouteData?.route || rawRouteData;
2573
+ if (!routeData || !routeData.path) return { success: false, error: "Route path is required.", status: 400 };
2574
+ const id = routeData.id || `route_${Date.now()}`;
2575
+ const idx = runtimeRoutes.findIndex((r) => r.id === id || r.path === routeData.path);
2576
+ const updated = {
2577
+ ...routeData,
2578
+ id,
2579
+ path: routeData.path.replace(/^\/+/, ""),
2580
+ endpoint: `/${routeData.path.replace(/^\/+/, "")}`,
2581
+ status: routeData.status || "PUBLISHED",
2582
+ public: routeData.public !== void 0 ? Boolean(routeData.public) : true,
2583
+ deployments: Array.isArray(routeData.deployments) ? routeData.deployments : [],
2584
+ load_balancing_config: routeData.load_balancing_config || { strategy: "MAX_WEIGHT" },
2585
+ createdAt: routeData.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
2586
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2587
+ stats: routeData.stats || { requests: 0 }
2588
+ };
2589
+ if (idx >= 0) runtimeRoutes[idx] = updated;
2590
+ else runtimeRoutes.push(updated);
2591
+ setServerPublicRoutes(runtimeRoutes);
2592
+ return { success: true, route: updated, status: 200 };
2593
+ }
2594
+ function getGatewayConfig(env) {
2595
+ const rawDomain = env?.DEPM_GATEWAY_DOMAIN || (typeof process !== "undefined" ? process.env?.DEPM_GATEWAY_DOMAIN : "") || "";
2596
+ const domain = rawDomain.trim();
2597
+ return {
2598
+ success: true,
2599
+ domain,
2600
+ gatewayDomain: domain,
2601
+ isConfigured: Boolean(domain),
2602
+ mode: "EDGE_WORKER_PROXY",
2603
+ registeredRoutesCount: runtimeRoutes.length,
2604
+ activeRoutes: runtimeRoutes.map((r) => ({ id: r.id, path: r.path, deploymentsCount: r.deployments.length }))
2605
+ };
2606
+ }
2607
+ function getExecutions(appId) {
2608
+ const list = executionStore.get(appId) || [];
2609
+ return { success: true, appId, count: list.length, executions: list };
2610
+ }
2611
+ function recordExecution(appId, data) {
2612
+ const current = executionStore.get(appId) || [];
2613
+ const rec = { id: `exec_${Date.now()}`, appId, timestamp: (/* @__PURE__ */ new Date()).toISOString(), durationMs: data.durationMs || 10, status: data.status || "SUCCESS", details: data };
2614
+ current.unshift(rec);
2615
+ if (current.length > 50) current.pop();
2616
+ executionStore.set(appId, current);
2617
+ return { success: true, execution: rec };
2618
+ }
2619
+ function matchGatewayIngress(urlPath, search) {
2620
+ const [pathnameOnly, internalQuery] = urlPath.split("?");
2621
+ const clean = pathnameOnly.replace(/^\/+/, "").trim().toLowerCase();
2622
+ if (!clean || clean.startsWith("api/") || clean === "api") return null;
2623
+ const cleanNoExt = clean.replace(/\.ultrst$/, "");
2624
+ const finalSearch = search || (internalQuery ? `?${internalQuery}` : void 0);
2625
+ const match = runtimeRoutes.find((r) => {
2626
+ const p = r.path.toLowerCase().replace(/^\/+|\/+$/g, "");
2627
+ const pNoExt = p.replace(/\.ultrst$/, "");
2628
+ return p === clean || pNoExt === cleanNoExt || p === cleanNoExt || pNoExt === clean;
2629
+ });
2630
+ if (!match || !match.deployments || match.deployments.length === 0) return null;
2631
+ const healthy = match.deployments.filter((d) => d.healthy !== false);
2632
+ const candidates = healthy.length > 0 ? healthy : match.deployments;
2633
+ if (candidates.length === 0) return null;
2634
+ const config = match.load_balancing_config || { strategy: "MAX_WEIGHT" };
2635
+ const strategy = config.strategy || "MAX_WEIGHT";
2636
+ let selected = candidates[0];
2637
+ if (strategy === "MAX_WEIGHT") {
2638
+ let maxWeight = -Infinity;
2639
+ for (const cand of candidates) {
2640
+ const w = typeof cand.weight === "number" ? cand.weight : 1;
2641
+ if (w > maxWeight) {
2642
+ maxWeight = w;
2643
+ selected = cand;
2644
+ }
2645
+ }
2646
+ } else if (strategy === "WEIGHTED") {
2647
+ const totalWeight = candidates.reduce((sum, c) => sum + Math.max(0.01, typeof c.weight === "number" ? c.weight : 1), 0);
2648
+ let rand = Math.random() * totalWeight;
2649
+ for (const cand of candidates) {
2650
+ rand -= Math.max(0.01, typeof cand.weight === "number" ? cand.weight : 1);
2651
+ if (rand <= 0) {
2652
+ selected = cand;
2653
+ break;
2654
+ }
2655
+ }
2656
+ } else if (strategy === "FAILOVER_PRIORITY") {
2657
+ const sorted = [...candidates].sort((a, b) => (a.priority ?? 999) - (b.priority ?? 999));
2658
+ selected = sorted[0] || candidates[0];
2659
+ } else if (strategy === "ROUND_ROBIN") {
2660
+ selected = candidates[(match.stats?.requests || 0) % candidates.length];
2661
+ } else {
2662
+ selected = candidates[Math.floor(Math.random() * candidates.length)];
2663
+ }
2664
+ if (!selected || !selected.url) return null;
2665
+ let dest = selected.url;
2666
+ if (!/^https?:\/\//i.test(dest)) dest = `https://${dest}`;
2667
+ if (config.preserveQueryParams && finalSearch) {
2668
+ const cleanSearch = finalSearch.startsWith("?") ? finalSearch.slice(1) : finalSearch;
2669
+ if (cleanSearch) {
2670
+ const sep = dest.includes("?") ? "&" : "?";
2671
+ dest = `${dest}${sep}${cleanSearch}`;
2672
+ }
2673
+ }
2674
+ return { destinationUrl: dest };
2675
+ }
2676
+ async function matchGatewayIngressAsync(urlPath, search, env) {
2677
+ const syncMatch = matchGatewayIngress(urlPath, search);
2678
+ if (syncMatch) return syncMatch;
2679
+ const resolved = await resolveDynamicRouteFromDb(urlPath, env);
2680
+ if (resolved) {
2681
+ upsertRoute(resolved);
2682
+ return matchGatewayIngress(urlPath, search);
2683
+ }
2684
+ return null;
2685
+ }
2686
+
2687
+ // src/core/workspaceBucketSyncCore.ts
2688
+ function resolveWorkspaceConnection(profiles, workspace) {
2689
+ const depm = profiles.depm || {};
2690
+ const conns = depm.storage_connections?.length ? depm.storage_connections : profiles.srkl?.storage_connections || profiles.docustack?.storage_connections || [];
2691
+ return conns.find((c) => c.id === workspace.storageConnectionId) || conns.find((c) => c.role === "primary") || conns[0] || null;
2692
+ }
2693
+ async function syncWorkspaceCreateToBucket(workspace, connection, env) {
2694
+ if (!connection || connection.provider !== "backblaze_b2" && connection.provider !== "BACKBLAZE_B2") return;
2695
+ try {
2696
+ const wsMeta = {
2697
+ id: workspace.id,
2698
+ name: workspace.name,
2699
+ createdAt: workspace.createdAt,
2700
+ updatedAt: workspace.updatedAt,
2701
+ storageProvider: "backblaze_b2",
2702
+ bucket: connection.b2BucketName || "ultrst-apps"
2703
+ };
2704
+ await writeWorkspaceFile(workspace, connection, ".bzEmpty", new Uint8Array(0), "application/x-empty");
2705
+ await writeWorkspaceFile(workspace, connection, "workspace.json", JSON.stringify(wsMeta, null, 2), "application/json");
2706
+ await writeWorkspaceFile(
2707
+ workspace,
2708
+ connection,
2709
+ "ecosystem.json",
2710
+ JSON.stringify({ apps: [], nodes: [], edges: [], ecosystems: [] }, null, 2),
2711
+ "application/json"
2712
+ );
2713
+ } catch (err) {
2714
+ console.warn("[WorkspaceBucketSync] Create sync error:", err.message);
2715
+ }
2716
+ }
2717
+ async function syncWorkspaceDeleteToBucket(workspace, connection, env) {
2718
+ if (!connection || connection.provider !== "backblaze_b2" && connection.provider !== "BACKBLAZE_B2") return;
2719
+ try {
2720
+ await deleteWorkspaceFolder(workspace, connection);
2721
+ } catch (err) {
2722
+ console.warn("[WorkspaceBucketSync] Delete sync error:", err.message);
2723
+ }
2724
+ }
2725
+ async function syncWorkspaceDataToBucket(workspace, connection, data, env) {
2726
+ if (!connection || connection.provider !== "backblaze_b2" && connection.provider !== "BACKBLAZE_B2") return;
2727
+ try {
2728
+ await writeWorkspaceFile(workspace, connection, "ecosystem.json", JSON.stringify(data, null, 2), "application/json");
2729
+ } catch (err) {
2730
+ console.warn("[WorkspaceBucketSync] Data sync error:", err.message);
2731
+ }
2732
+ }
2733
+
2734
+ // src/core/workspaceDataCore.ts
2735
+ async function resolveUserRow(userId, env) {
2736
+ if (userId) {
2737
+ const res = await queryUnifiedDb(`SELECT * FROM "ultrst-users" WHERE id = $1 OR email = $1 LIMIT 1`, [userId], env);
2738
+ if (res.rows.length > 0) return res.rows[0];
2739
+ }
2740
+ return null;
2741
+ }
2742
+ async function loadWorkspaceData(userId, workspaceId, env) {
2743
+ if (!workspaceId) return { success: false, error: "workspaceId required", workspace: null };
2744
+ const user = await resolveUserRow(userId, env);
2745
+ if (!user) return { success: false, error: "User not found", workspace: null };
2746
+ const profiles = typeof user.app_profiles === "string" ? JSON.parse(user.app_profiles) : user.app_profiles || {};
2747
+ const depm = profiles.depm || {};
2748
+ const workspaceDataMap = depm.workspace_data || {};
2749
+ let data = workspaceDataMap[workspaceId];
2750
+ const isEmptyData = !data || (!data.nodes || data.nodes.length === 0) && (!data.apps || data.apps.length === 0);
2751
+ if (isEmptyData) {
2752
+ const ws = (depm.workspaces || []).find((w) => w.id === workspaceId);
2753
+ if (ws) {
2754
+ const conn = resolveWorkspaceConnection(profiles, ws);
2755
+ if (conn) {
2756
+ try {
2757
+ const fileBuf = await readWorkspaceFile(ws, conn, "ecosystem.json");
2758
+ if (fileBuf && fileBuf.length > 0) {
2759
+ const parsed = JSON.parse(new TextDecoder().decode(fileBuf));
2760
+ if (parsed.nodes && parsed.nodes.length > 0 || parsed.apps && parsed.apps.length > 0) {
2761
+ if (!profiles.depm) profiles.depm = {};
2762
+ if (!profiles.depm.workspace_data) profiles.depm.workspace_data = {};
2763
+ profiles.depm.workspace_data[workspaceId] = parsed;
2764
+ data = parsed;
2765
+ await queryUnifiedDb(
2766
+ `UPDATE "ultrst-users" SET app_profiles = $1, updated_at = NOW() WHERE id = $2`,
2767
+ [JSON.stringify(profiles), user.id],
2768
+ env
2769
+ );
2770
+ return { success: true, workspace: data, workspaceId };
2771
+ }
2772
+ }
2773
+ } catch (err) {
2774
+ console.warn("[WorkspaceDataCore] Storage recovery check:", err.message);
2775
+ }
2776
+ }
2777
+ }
2778
+ }
2779
+ if (!data) {
2780
+ const ws = (depm.workspaces || []).find((w) => w.id === workspaceId);
2781
+ const ecoName = ws?.ecosystemName || `${ws?.name || "Workspace"} Ecosystem`;
2782
+ const fallback = {
2783
+ apps: [],
2784
+ accounts: [],
2785
+ deployments: [],
2786
+ nodes: [],
2787
+ edges: [],
2788
+ ecosystems: [
2789
+ {
2790
+ id: `eco_${workspaceId}`,
2791
+ name: ecoName,
2792
+ icon: "Globe",
2793
+ health: "HEALTHY",
2794
+ status: "SYSTEM ONLINE",
2795
+ connectedAppsCount: 0,
2796
+ meshLinkCount: 0,
2797
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2798
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2799
+ }
2800
+ ],
2801
+ ecosystemName: ecoName,
2802
+ ecosystemNodes: [
2803
+ {
2804
+ id: `node_eco_root_${workspaceId}`,
2805
+ type: "ECOSYSTEM",
2806
+ name: ecoName,
2807
+ x: 0,
2808
+ y: 0,
2809
+ appId: "__ecosystem__",
2810
+ icon: "Globe",
2811
+ ecosystemData: { connectedAppsCount: 0, health: "HEALTHY", status: "SYSTEM ONLINE" }
2812
+ }
2813
+ ],
2814
+ ecosystemEdges: []
2815
+ };
2816
+ return { success: true, workspace: fallback, workspaceId };
2817
+ }
2818
+ return { success: true, workspace: data, workspaceId };
2819
+ }
2820
+ async function saveWorkspaceData(userId, workspaceId, data, env) {
2821
+ if (!workspaceId) return { success: false, error: "workspaceId required" };
2822
+ const user = await resolveUserRow(userId, env);
2823
+ if (!user) return { success: false, error: "User not found" };
2824
+ const profiles = typeof user.app_profiles === "string" ? JSON.parse(user.app_profiles) : user.app_profiles || {};
2825
+ if (!profiles.depm) profiles.depm = {};
2826
+ if (!profiles.depm.workspace_data) profiles.depm.workspace_data = {};
2827
+ const existing = profiles.depm.workspace_data[workspaceId];
2828
+ const incomingEmpty = (!data.nodes || data.nodes.length === 0) && (!data.apps || data.apps.length === 0);
2829
+ const existingHasData = existing && (existing.nodes && existing.nodes.length > 0 || existing.apps && existing.apps.length > 0);
2830
+ if (incomingEmpty && existingHasData && !data._forceEmptySave) {
2831
+ console.warn(`[WorkspaceDataCore] Blocked empty overwrite for workspace ${workspaceId}.`);
2832
+ return { success: true, workspaceId, workspace: existing, protected: true };
2833
+ }
2834
+ profiles.depm.workspace_data[workspaceId] = data;
2835
+ if (Array.isArray(profiles.depm.workspaces)) {
2836
+ const ws2 = profiles.depm.workspaces.find((w) => w.id === workspaceId);
2837
+ if (ws2) {
2838
+ ws2.appsCount = Array.isArray(data.apps) ? data.apps.length : 0;
2839
+ ws2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2840
+ }
2841
+ }
2842
+ await queryUnifiedDb(
2843
+ `UPDATE "ultrst-users" SET app_profiles = $1, updated_at = NOW() WHERE id = $2`,
2844
+ [JSON.stringify(profiles), user.id],
2845
+ env
2846
+ );
2847
+ const ws = Array.isArray(profiles.depm?.workspaces) ? profiles.depm.workspaces.find((w) => w.id === workspaceId) : null;
2848
+ if (ws) {
2849
+ const conn = resolveWorkspaceConnection(profiles, ws);
2850
+ await syncWorkspaceDataToBucket(ws, conn, data, env);
2851
+ }
2852
+ return { success: true, workspaceId, workspace: data };
2853
+ }
2854
+ async function renameAppInWorkspaceData(userId, workspaceId, appId, newName, env) {
2855
+ if (!workspaceId || !appId || !newName) {
2856
+ return { success: false, error: "workspaceId, appId and newName required" };
2857
+ }
2858
+ const loadRes = await loadWorkspaceData(userId, workspaceId, env);
2859
+ if (!loadRes.success || !loadRes.workspace) return loadRes;
2860
+ const wsData = loadRes.workspace;
2861
+ const trimmed = newName.trim();
2862
+ const folderName = trimmed.replace(/[/\\?%*:|"<>]/g, "_");
2863
+ if (Array.isArray(wsData.apps)) {
2864
+ const targetApp = wsData.apps.find((a) => a.id === appId);
2865
+ if (targetApp) {
2866
+ targetApp.name = trimmed;
2867
+ targetApp.folderName = folderName;
2868
+ targetApp.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1151
2869
  }
1152
- return { success: true, buckets: [] };
1153
- } catch (err) {
1154
- return { success: false, error: err.message || "Failed to list Backblaze buckets" };
1155
2870
  }
2871
+ if (Array.isArray(wsData.nodes)) {
2872
+ wsData.nodes = wsData.nodes.map((n) => {
2873
+ if (n.appId === appId && (n.type === "APP" || n.type === "APP_NODE" || n.type === "APP_GRAPH_NODE")) {
2874
+ return { ...n, name: trimmed, appData: n.appData ? { ...n.appData, name: trimmed, folderName } : void 0 };
2875
+ }
2876
+ return n;
2877
+ });
2878
+ }
2879
+ return await saveWorkspaceData(userId, workspaceId, wsData, env);
1156
2880
  }
1157
- var srklListBackblazeBuckets2 = (keyId, applicationKey, bucketId, bucketName, userId) => listBackblazeBuckets({ keyId, applicationKey, bucketId, bucketName }, userId);
1158
- async function getSrklStatus(env) {
1159
- const srklUrl = await getPrimarySrklDeploymentUrl2(env);
1160
- const start = Date.now();
2881
+
2882
+ // src/core/workspaceStorageDetailsCore.ts
2883
+ async function getWorkspaceStorageDetails(workspaceId, env) {
2884
+ if (!workspaceId) return { success: false, error: "workspaceId is required" };
2885
+ const { workspace, connection } = await findUserWorkspace(workspaceId, env);
2886
+ const folderPrefix = workspace?.localFolderKey || workspace?.localFolderName || (workspace?.workspacePath ? workspace.workspacePath.replace(/^\.\//, "") : workspaceId);
2887
+ if (!connection || connection.provider !== "backblaze_b2" && connection.provider !== "BACKBLAZE_B2") {
2888
+ return {
2889
+ success: true,
2890
+ workspaceId,
2891
+ storageProvider: workspace?.storageProvider || "local_fs",
2892
+ connection: connection || null,
2893
+ folderPrefix,
2894
+ localStats: { exists: false, appsCount: 0, ecosystemsCount: 0 },
2895
+ bucket: null,
2896
+ files: []
2897
+ };
2898
+ }
2899
+ const keyId = connection.b2KeyId || connection.keyId;
2900
+ const applicationKey = connection.b2ApplicationKey || connection.appKey;
2901
+ const bucketName = connection.b2BucketName || workspace.b2BucketName || "ultrst-apps";
1161
2902
  try {
1162
- const resp = await fetch(`${srklUrl}/api/ecosystem/health`, { signal: AbortSignal.timeout(5e3) });
1163
- const latencyMs = Date.now() - start;
1164
- if (resp.ok) {
1165
- const data = await resp.json().catch(() => ({}));
1166
- return { success: true, deploymentUrl: srklUrl, online: true, latencyMs, details: data };
2903
+ const buckets = await b2ListBuckets2(keyId, applicationKey, void 0, bucketName);
2904
+ const bucketObj = buckets.find((b) => b.bucketName === bucketName);
2905
+ if (!bucketObj) {
2906
+ return {
2907
+ success: true,
2908
+ workspaceId,
2909
+ storageProvider: "backblaze_b2",
2910
+ connection: { id: connection.id, label: connection.label, provider: connection.provider },
2911
+ folderPrefix,
2912
+ bucket: { name: bucketName, status: "BUCKET_NOT_FOUND" },
2913
+ files: []
2914
+ };
1167
2915
  }
1168
- return { success: false, deploymentUrl: srklUrl, online: false, status: resp.status, latencyMs };
2916
+ const rawFiles = await b2ListFileNames2(keyId, applicationKey, bucketName, `${folderPrefix}/`);
2917
+ return {
2918
+ success: true,
2919
+ workspaceId,
2920
+ storageProvider: "backblaze_b2",
2921
+ connection: { id: connection.id, label: connection.label, provider: connection.provider },
2922
+ folderPrefix,
2923
+ bucket: {
2924
+ id: bucketObj.bucketId,
2925
+ name: bucketObj.bucketName,
2926
+ type: bucketObj.bucketType,
2927
+ endpoint: connection.b2Endpoint || "s3.eu-central-003.backblazeb2.com",
2928
+ region: connection.b2Region || "eu-central-003",
2929
+ status: "CONNECTED"
2930
+ },
2931
+ fileCount: rawFiles.length,
2932
+ files: rawFiles.map((f) => ({
2933
+ fileName: f.fileName,
2934
+ sizeBytes: f.contentLength,
2935
+ uploadTimestamp: f.uploadTimestamp
2936
+ }))
2937
+ };
1169
2938
  } catch (err) {
1170
- return { success: false, deploymentUrl: srklUrl, online: false, error: err.message, latencyMs: Date.now() - start };
2939
+ return {
2940
+ success: false,
2941
+ workspaceId,
2942
+ storageProvider: "backblaze_b2",
2943
+ error: err.message,
2944
+ folderPrefix
2945
+ };
1171
2946
  }
1172
2947
  }
1173
2948
 
1174
- // src/core/workspaceCloneSyncCore.ts
1175
- import fs3 from "fs";
1176
- import path3 from "path";
1177
- var ACCOUNTS_FILE3 = path3.resolve(".skipo/accounts.json");
1178
- async function saveWorkspaceClonesState(userId, workspaceId, clones, autoSync) {
1179
- try {
1180
- const uRes = await queryUnifiedDb(`SELECT app_profiles FROM "ultrst-users" WHERE id = $1 LIMIT 1`, [userId]);
1181
- if (uRes.rows.length > 0) {
1182
- const p = typeof uRes.rows[0].app_profiles === "string" ? JSON.parse(uRes.rows[0].app_profiles) : uRes.rows[0].app_profiles || {};
1183
- if (p.qwnsr?.workspaces) {
1184
- const ws = p.qwnsr.workspaces.find((w) => w.id === workspaceId);
1185
- if (ws) {
1186
- ws.secondaryClones = clones;
1187
- if (autoSync !== void 0) ws.autoSyncClones = autoSync;
1188
- ws.updatedAt = Date.now();
1189
- await queryUnifiedDb(`UPDATE "ultrst-users" SET app_profiles = $1, updated_at = NOW() WHERE id = $2`, [JSON.stringify(p), userId]);
1190
- }
2949
+ // src/core/workspaceCore.ts
2950
+ async function findUserWorkspace(workspaceId, env) {
2951
+ const res = await queryUnifiedDb(`SELECT id, email, username, app_profiles FROM "ultrst-users"`, [], env);
2952
+ for (const user of res.rows) {
2953
+ const profiles = typeof user.app_profiles === "string" ? JSON.parse(user.app_profiles) : user.app_profiles || {};
2954
+ const depm = profiles.depm || {};
2955
+ const workspaces = Array.isArray(depm.workspaces) ? depm.workspaces : [];
2956
+ const ws = workspaces.find((w) => w.id === workspaceId);
2957
+ if (ws) {
2958
+ const conns = depm.storage_connections || profiles.srkl?.storage_connections || profiles.docustack?.storage_connections || [];
2959
+ const conn = conns.find((c) => c.id === ws.storageConnectionId) || conns.find((c) => c.role === "primary") || conns[0] || null;
2960
+ return { user, workspace: ws, connection: conn };
2961
+ }
2962
+ }
2963
+ return { user: null, workspace: null, connection: null };
2964
+ }
2965
+ async function getWorkspaces(userId, env) {
2966
+ if (!userId) {
2967
+ return { success: true, workspaces: [], activeWorkspaceId: null, storage_connections: [] };
2968
+ }
2969
+ const res = await queryUnifiedDb(`SELECT * FROM "ultrst-users" WHERE id = $1 OR email = $1 LIMIT 1`, [userId], env);
2970
+ const userRow = res.rows[0];
2971
+ if (!userRow) {
2972
+ return { success: true, workspaces: [], activeWorkspaceId: null, storage_connections: [] };
2973
+ }
2974
+ const profiles = typeof userRow.app_profiles === "string" ? JSON.parse(userRow.app_profiles) : userRow.app_profiles || {};
2975
+ const depm = profiles.depm || {};
2976
+ const workspaces = Array.isArray(depm.workspaces) ? depm.workspaces : [];
2977
+ const activeWorkspaceId = depm.activeWorkspaceId || null;
2978
+ const conns = depm.storage_connections?.length ? depm.storage_connections : profiles.srkl?.storage_connections || [];
2979
+ return { success: true, workspaces, activeWorkspaceId, storage_connections: conns };
2980
+ }
2981
+ async function createWorkspace(userId, wsData, env) {
2982
+ const uRes = await queryUnifiedDb(`SELECT * FROM "ultrst-users" WHERE id = $1`, [userId], env);
2983
+ const user = uRes.rows[0];
2984
+ if (!user) throw new Error("User account not found.");
2985
+ const profiles = typeof user.app_profiles === "string" ? JSON.parse(user.app_profiles) : user.app_profiles || {};
2986
+ if (!profiles.depm) profiles.depm = {};
2987
+ const workspaces = Array.isArray(profiles.depm.workspaces) ? profiles.depm.workspaces : [];
2988
+ const wsName = (wsData.name || "New Workspace").trim();
2989
+ const wsId = wsData.id || `ws-depm-${Date.now()}`;
2990
+ const userName = user.username || user.name || "user";
2991
+ const folderKey = `ultrst_depm_${userName.toLowerCase().replace(/[^a-z0-9]/g, "_")}_${wsName.toLowerCase().replace(/[^a-z0-9]/g, "_")}`;
2992
+ const newWs = {
2993
+ ...wsData,
2994
+ id: wsId,
2995
+ name: wsName,
2996
+ localFolderName: folderKey,
2997
+ localFolderKey: folderKey,
2998
+ workspacePath: `./${folderKey}`,
2999
+ storageProvider: wsData.storageProvider || "backblaze_b2",
3000
+ b2BucketName: wsData.b2BucketName || "ultrst-apps",
3001
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3002
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3003
+ };
3004
+ workspaces.push(newWs);
3005
+ profiles.depm.workspaces = workspaces;
3006
+ profiles.depm.activeWorkspaceId = wsId;
3007
+ await queryUnifiedDb(`UPDATE "ultrst-users" SET app_profiles = $1, updated_at = NOW() WHERE id = $2`, [JSON.stringify(profiles), user.id], env);
3008
+ const conn = resolveWorkspaceConnection(profiles, newWs);
3009
+ await syncWorkspaceCreateToBucket(newWs, conn, env);
3010
+ return { success: true, workspace: newWs, workspaces };
3011
+ }
3012
+ async function updateWorkspace(userId, params, env) {
3013
+ const uRes = await queryUnifiedDb(`SELECT * FROM "ultrst-users" WHERE id = $1`, [userId], env);
3014
+ const user = uRes.rows[0];
3015
+ if (!user) throw new Error("User not found.");
3016
+ const profiles = typeof user.app_profiles === "string" ? JSON.parse(user.app_profiles) : user.app_profiles || {};
3017
+ if (!profiles.depm) profiles.depm = {};
3018
+ const workspaces = Array.isArray(profiles.depm.workspaces) ? profiles.depm.workspaces : [];
3019
+ const { action, workspaceId, updates } = params;
3020
+ const targetId = workspaceId || updates?.id;
3021
+ const match = workspaces.find((w) => w.id === targetId);
3022
+ if (!match) throw new Error("Workspace not found.");
3023
+ if (action === "switch") {
3024
+ profiles.depm.activeWorkspaceId = targetId;
3025
+ } else {
3026
+ const safeUpdates = { ...updates || params };
3027
+ delete safeUpdates.storageProvider;
3028
+ delete safeUpdates.storageConnectionId;
3029
+ delete safeUpdates.b2BucketName;
3030
+ delete safeUpdates.b2Endpoint;
3031
+ delete safeUpdates.b2Region;
3032
+ delete safeUpdates.localFolderName;
3033
+ delete safeUpdates.localFolderKey;
3034
+ delete safeUpdates.workspacePath;
3035
+ delete safeUpdates.primaryStorage;
3036
+ delete safeUpdates.id;
3037
+ delete safeUpdates.action;
3038
+ delete safeUpdates.workspaceId;
3039
+ Object.assign(match, safeUpdates, { updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
3040
+ }
3041
+ await queryUnifiedDb(`UPDATE "ultrst-users" SET app_profiles = $1, updated_at = NOW() WHERE id = $2`, [JSON.stringify(profiles), user.id], env);
3042
+ return { success: true, workspace: match, workspaces };
3043
+ }
3044
+ async function deleteWorkspace(userId, wsId, env) {
3045
+ const uRes = await queryUnifiedDb(`SELECT * FROM "ultrst-users" WHERE id = $1`, [userId], env);
3046
+ const user = uRes.rows[0];
3047
+ if (!user) throw new Error("User not found.");
3048
+ const profiles = typeof user.app_profiles === "string" ? JSON.parse(user.app_profiles) : user.app_profiles || {};
3049
+ if (!profiles.depm?.workspaces) return { success: true, workspaces: [] };
3050
+ const targetWs = profiles.depm.workspaces.find((w) => w.id === wsId);
3051
+ const conn = targetWs ? resolveWorkspaceConnection(profiles, targetWs) : null;
3052
+ profiles.depm.workspaces = profiles.depm.workspaces.filter((w) => w.id !== wsId);
3053
+ if (profiles.depm.activeWorkspaceId === wsId) {
3054
+ profiles.depm.activeWorkspaceId = profiles.depm.workspaces[0]?.id || null;
3055
+ }
3056
+ if (profiles.depm.workspace_data?.[wsId]) {
3057
+ delete profiles.depm.workspace_data[wsId];
3058
+ }
3059
+ await queryUnifiedDb(`UPDATE "ultrst-users" SET app_profiles = $1, updated_at = NOW() WHERE id = $2`, [JSON.stringify(profiles), user.id], env);
3060
+ if (targetWs && conn) {
3061
+ await syncWorkspaceDeleteToBucket(targetWs, conn, env);
3062
+ }
3063
+ return { success: true, workspaces: profiles.depm.workspaces };
3064
+ }
3065
+
3066
+ // src/core/cloudflareIntegrationCore.ts
3067
+ var cfFetcher = new CloudflareResourceFetcher();
3068
+ function buildHeaders(token, authKey, authEmail) {
3069
+ const headers = { "Content-Type": "application/json", "User-Agent": "DEPM-Engine/1.0" };
3070
+ if (token) headers["Authorization"] = `Bearer ${token.trim()}`;
3071
+ else if (authKey && authEmail) {
3072
+ headers["X-Auth-Key"] = authKey.trim();
3073
+ headers["X-Auth-Email"] = authEmail.trim();
3074
+ }
3075
+ return headers;
3076
+ }
3077
+ async function fetchCloudflareData(payload, rawUserId, env) {
3078
+ const userId = rawUserId || payload?.userId || "";
3079
+ const creds = payload?.credentials || {};
3080
+ let token = (payload?.token || payload?.apiToken || payload?.accessToken || creds.token || creds.apiToken || creds.accessToken || "").trim();
3081
+ const isCfHex = (v) => Boolean(v && /^[a-f0-9]{32}$/i.test(v.trim()));
3082
+ let accountTag = [
3083
+ payload?.accountTag,
3084
+ payload?.cfAccountId,
3085
+ creds.accountId,
3086
+ creds.cfAccountId,
3087
+ creds.accountTag,
3088
+ payload?.accountId
3089
+ ].map((v) => (v || "").trim()).find((v) => isCfHex(v)) || "";
3090
+ let authKey = (payload?.authKey || payload?.globalApiKey || creds.authKey || creds.globalApiKey || "").trim();
3091
+ let authEmail = (payload?.authEmail || payload?.email || creds.authEmail || creds.email || "").trim();
3092
+ let accountName = payload?.accountName || creds.accountName || "";
3093
+ if (!token && !authKey && (payload?.accountId || payload?.id) && userId) {
3094
+ try {
3095
+ const srkl = await getSrklAccounts2(userId, "cloudflare", env);
3096
+ const matched = srkl.accounts?.find((a) => a.id === (payload.accountId || payload.id));
3097
+ const mCreds = matched?.credentials || matched?.metadata?.credentials || {};
3098
+ token = (mCreds.token || mCreds.apiToken || mCreds.accessToken || token).trim();
3099
+ if (!accountTag) {
3100
+ accountTag = [mCreds.accountId, mCreds.cfAccountId, mCreds.accountTag].map((v) => (v || "").trim()).find((v) => isCfHex(v)) || "";
1191
3101
  }
3102
+ authKey = (mCreds.authKey || mCreds.globalApiKey || authKey).trim();
3103
+ authEmail = (mCreds.authEmail || mCreds.email || authEmail).trim();
3104
+ if (!accountName && (matched?.name || mCreds.accountName)) accountName = matched?.name || mCreds.accountName;
3105
+ } catch (err) {
3106
+ console.warn("[CloudflareIntegration] SRKL account lookup fallback:", err.message);
1192
3107
  }
1193
- } catch (err) {
1194
- console.warn("[workspaceCloneSyncCore] DB save warning:", err.message);
1195
3108
  }
3109
+ if (!token && !authKey) {
3110
+ return { success: false, error: "Cloudflare API Token or Global API Key required.", projects: [], projectObservability: [] };
3111
+ }
3112
+ const headers = buildHeaders(token, authKey, authEmail);
1196
3113
  try {
1197
- if (fs3.existsSync(ACCOUNTS_FILE3)) {
1198
- const accounts = JSON.parse(await fs3.promises.readFile(ACCOUNTS_FILE3, "utf-8"));
1199
- for (const u of Object.values(accounts)) {
1200
- const ws = u.workspaces?.find((w) => w.id === workspaceId);
1201
- if (ws) {
1202
- ws.secondaryClones = clones;
1203
- if (autoSync !== void 0) ws.autoSyncClones = autoSync;
1204
- await fs3.promises.writeFile(ACCOUNTS_FILE3, JSON.stringify(accounts, null, 2), "utf-8");
1205
- break;
3114
+ if (!accountTag) {
3115
+ const userRes = await fetch("https://api.cloudflare.com/client/v4/accounts?page=1&per_page=10", { headers, signal: AbortSignal.timeout(6e3) });
3116
+ if (userRes.ok) {
3117
+ const uData = await userRes.json();
3118
+ const first = uData?.result?.[0];
3119
+ if (first?.id) {
3120
+ accountTag = first.id;
3121
+ if (first.name && (!accountName || accountName.toLowerCase() === "cloudflare")) accountName = first.name;
1206
3122
  }
1207
3123
  }
1208
- }
1209
- } catch {
1210
- }
1211
- }
1212
- async function syncPrimaryToClones(workspaceId, targetCloneIds) {
1213
- const match = await findWorkspaceRecord(workspaceId);
1214
- if (!match) return { success: false, workspaceId, syncedClones: [], totalFilesSynced: 0, error: "Workspace not found" };
1215
- const { workspace, user, connection } = match;
1216
- const folderKey = getWorkspaceFolderKey(workspace, user);
1217
- const clones = workspace.secondaryClones || [];
1218
- const targetClones = clones.filter((c) => !targetCloneIds || targetCloneIds.length === 0 || targetCloneIds.includes(c.id));
1219
- if (targetClones.length === 0) {
1220
- return { success: true, workspaceId, syncedClones: [], totalFilesSynced: 0 };
1221
- }
1222
- const filesToSync = [];
1223
- const isPrimaryB2 = workspace.storageProvider === "backblaze_b2" || workspace.storageConfig?.type === "b2";
1224
- if (isPrimaryB2) {
1225
- const creds = workspace.storageConfig || connection?.credentials || connection || {};
1226
- const keyId = creds.keyId || creds.b2KeyId;
1227
- const appKey = creds.applicationKey || creds.b2ApplicationKey || creds.appKey;
1228
- const bucket = creds.bucketName || creds.b2BucketName || workspace.b2BucketName || "ultrst-apps";
1229
- if (keyId && appKey) {
1230
- const b2Prefix = `${folderKey}/`;
1231
- const remoteList = await b2ListFileNames2(keyId, appKey, bucket, b2Prefix);
1232
- for (const rf of remoteList) {
1233
- const rel = rf.fileName.slice(b2Prefix.length);
1234
- if (!rel) continue;
1235
- const data = await b2DownloadFile2(keyId, appKey, bucket, rf.fileName);
1236
- if (data) filesToSync.push({ relPath: rel, content: data, contentType: rf.contentType || "application/json" });
1237
- }
1238
- }
1239
- }
1240
- if (filesToSync.length === 0) {
1241
- const workingDir = getWorkingWorkspaceDir();
1242
- if (fs3.existsSync(workingDir)) {
1243
- async function walk(d, base) {
1244
- for (const entry of await fs3.promises.readdir(d, { withFileTypes: true })) {
1245
- if (entry.name.startsWith(".") && entry.name !== ".bzEmpty") continue;
1246
- const full = path3.join(d, entry.name);
1247
- if (entry.isDirectory()) await walk(full, base);
1248
- else if (entry.isFile()) {
1249
- const rel = path3.relative(base, full).replace(/\\/g, "/");
1250
- const buf = await fs3.promises.readFile(full);
1251
- filesToSync.push({ relPath: rel, content: new Uint8Array(buf), contentType: "application/json" });
1252
- }
3124
+ } else if (!accountName || accountName.toLowerCase() === "cloudflare") {
3125
+ try {
3126
+ const aRes = await fetch(`https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(accountTag)}`, { headers, signal: AbortSignal.timeout(4e3) });
3127
+ if (aRes.ok) {
3128
+ const aData = await aRes.json();
3129
+ if (aData?.result?.name) accountName = aData.result.name;
1253
3130
  }
3131
+ } catch {
1254
3132
  }
1255
- await walk(workingDir, workingDir);
1256
3133
  }
1257
- }
1258
- const results = [];
1259
- let totalFiles = 0;
1260
- const nowIso = (/* @__PURE__ */ new Date()).toISOString();
1261
- for (const clone of targetClones) {
3134
+ if (!accountTag) {
3135
+ return { success: false, error: "Missing Cloudflare Account ID. Please configure your Account ID or check API token permissions.", projects: [], projectObservability: [] };
3136
+ }
3137
+ let workerSubdomain = "";
1262
3138
  try {
1263
- let count = 0;
1264
- if (clone.provider === "backblaze_b2" || clone.b2BucketName) {
1265
- const keyId = clone.b2KeyId || connection?.b2KeyId;
1266
- const appKey = clone.b2ApplicationKey || connection?.b2ApplicationKey;
1267
- const bucket = clone.b2BucketName || "ultrst-apps";
1268
- if (!keyId || !appKey) throw new Error("Missing B2 credentials for clone");
1269
- for (const f of filesToSync) {
1270
- const fullKey = `${folderKey}/${f.relPath}`;
1271
- await b2UploadFile2(keyId, appKey, bucket, fullKey, f.content, f.contentType);
1272
- count++;
1273
- }
1274
- } else {
1275
- const cloneBase = path3.resolve(process.cwd(), clone.localFolderKey || clone.localFolderName || `${folderKey}_clone`);
1276
- for (const f of filesToSync) {
1277
- const dest = path3.join(cloneBase, f.relPath);
1278
- await fs3.promises.mkdir(path3.dirname(dest), { recursive: true });
1279
- await fs3.promises.writeFile(dest, f.content);
1280
- count++;
3139
+ const subRes = await fetch(`https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(accountTag)}/workers/subdomain`, { headers, signal: AbortSignal.timeout(6e3) });
3140
+ const subData = await subRes.json().catch(() => ({}));
3141
+ if (subData.result?.subdomain) workerSubdomain = subData.result.subdomain;
3142
+ } catch {
3143
+ }
3144
+ const [workersRes, pagesRes] = await Promise.allSettled([
3145
+ fetch(`https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(accountTag)}/workers/scripts`, { headers, signal: AbortSignal.timeout(8e3) }).then((r) => r.json()),
3146
+ fetch(`https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(accountTag)}/pages/projects`, { headers, signal: AbortSignal.timeout(8e3) }).then((r) => r.json())
3147
+ ]);
3148
+ const rawWorkers = workersRes.status === "fulfilled" && Array.isArray(workersRes.value?.result) ? workersRes.value.result : [];
3149
+ const rawPages = pagesRes.status === "fulfilled" && Array.isArray(pagesRes.value?.result) ? pagesRes.value.result : [];
3150
+ const workersList = rawWorkers.map((w) => {
3151
+ const wName = w.id || w.name;
3152
+ const host = workerSubdomain ? `${wName}.${workerSubdomain}.workers.dev` : `${wName}.workers.dev`;
3153
+ const url = `https://${host}`;
3154
+ const routes = Array.isArray(w.routes) ? w.routes : w.routes ? [String(w.routes)] : [];
3155
+ return {
3156
+ id: w.id || w.name,
3157
+ name: w.name || w.id,
3158
+ type: "WORKER",
3159
+ status: "ACTIVE",
3160
+ framework: "Cloudflare Worker",
3161
+ domain: host,
3162
+ domains: routes.length > 0 ? routes : [host],
3163
+ url,
3164
+ edgeRequestsFormatted: "0",
3165
+ fastDataTransferFormatted: "0 MB",
3166
+ updatedAt: w.modified_on || w.created_on || (/* @__PURE__ */ new Date()).toISOString()
3167
+ };
3168
+ });
3169
+ const pagesList = rawPages.map((p) => {
3170
+ const pDomain = p.subdomain ? `${p.subdomain}.pages.dev` : `${p.name}.pages.dev`;
3171
+ const rawUrl = p.canonical_deployment?.url || `https://${pDomain}`;
3172
+ const url = rawUrl.startsWith("http") ? rawUrl : `https://${rawUrl}`;
3173
+ const domains = Array.isArray(p.domains) && p.domains.length > 0 ? p.domains : [pDomain];
3174
+ return {
3175
+ id: p.name || p.id,
3176
+ name: p.name,
3177
+ type: "PAGES_PROJECT",
3178
+ status: p.latest_deployment?.status || "READY",
3179
+ framework: "Cloudflare Pages",
3180
+ domain: pDomain,
3181
+ domains,
3182
+ url,
3183
+ edgeRequestsFormatted: "0",
3184
+ fastDataTransferFormatted: "0 MB",
3185
+ updatedAt: p.latest_deployment?.created_on || p.created_on || (/* @__PURE__ */ new Date()).toISOString()
3186
+ };
3187
+ });
3188
+ const allProjects = [...workersList, ...pagesList];
3189
+ const projectObservability = allProjects.map((p) => ({
3190
+ projectId: p.id,
3191
+ projectName: p.name,
3192
+ status: p.status,
3193
+ framework: p.framework,
3194
+ domain: p.domain,
3195
+ domains: p.domains,
3196
+ deploymentUrl: p.url,
3197
+ latestDeploymentUrl: p.url,
3198
+ edgeRequestsFormatted: p.edgeRequestsFormatted,
3199
+ fastDataTransferFormatted: p.fastDataTransferFormatted,
3200
+ type: p.type,
3201
+ updatedAt: p.updatedAt
3202
+ }));
3203
+ let rawUsage = null;
3204
+ let unifiedMetrics = null;
3205
+ try {
3206
+ rawUsage = await cfFetcher.fetchRawUsage({ apiToken: token, accountId: accountTag, authKey, authEmail });
3207
+ unifiedMetrics = cfFetcher.transformMetrics(rawUsage, payload.tier || "free");
3208
+ } catch {
3209
+ }
3210
+ return {
3211
+ success: true,
3212
+ provider: "CLOUDFLARE",
3213
+ tier: unifiedMetrics?.tier || "free",
3214
+ isFree: unifiedMetrics?.isFreeTier ?? true,
3215
+ accountName: accountName || void 0,
3216
+ accountId: accountTag,
3217
+ projectsCount: allProjects.length,
3218
+ projects: allProjects,
3219
+ projectObservability,
3220
+ workers: workersList,
3221
+ pages: pagesList,
3222
+ usageMetrics: unifiedMetrics,
3223
+ unifiedMetrics
3224
+ };
3225
+ } catch (err) {
3226
+ try {
3227
+ const srklUrl = await getPrimarySrklDeploymentUrl2(env);
3228
+ const srklRes = await fetch(`${srklUrl}/api/resources?serviceId=cloudflare&token=${encodeURIComponent(token)}&cfAccountId=${encodeURIComponent(accountTag)}`, { signal: AbortSignal.timeout(6e3) });
3229
+ if (srklRes.ok) {
3230
+ const sData = await srklRes.json();
3231
+ if (sData.success && Array.isArray(sData.resources)) {
3232
+ const projs = sData.resources.map((r) => ({
3233
+ id: r.id || r.name,
3234
+ name: r.name,
3235
+ type: r.type,
3236
+ status: r.status || "ONLINE",
3237
+ framework: r.type === "PAGES_PROJECT" ? "Cloudflare Pages" : "Cloudflare Worker",
3238
+ domain: r.subdomain ? `${r.subdomain}.pages.dev` : r.url ? r.url.replace(/^https?:\/\//, "") : `${r.name}.workers.dev`,
3239
+ domains: r.routes || [r.url || `${r.name}.workers.dev`],
3240
+ url: r.url || `https://${r.name}.workers.dev`,
3241
+ edgeRequestsFormatted: "0",
3242
+ fastDataTransferFormatted: "0 MB",
3243
+ updatedAt: r.updatedAt
3244
+ }));
3245
+ return { success: true, provider: "CLOUDFLARE", tier: "free", isFree: true, accountName: accountName || void 0, accountId: accountTag, projectsCount: projs.length, projects: projs, projectObservability: projs.map((p) => ({ ...p, projectId: p.id, projectName: p.name, deploymentUrl: p.url, latestDeploymentUrl: p.url })), workers: projs.filter((p) => p.type === "WORKER"), pages: projs.filter((p) => p.type === "PAGES_PROJECT") };
1281
3246
  }
1282
3247
  }
1283
- clone.lastSyncedAt = nowIso;
1284
- clone.filesSynced = count;
1285
- clone.status = "SYNCED";
1286
- results.push({ id: clone.id, label: clone.label, provider: clone.provider, filesSynced: count, status: "SYNCED" });
1287
- totalFiles += count;
1288
- } catch (err) {
1289
- console.warn(`[workspaceCloneSyncCore] Clone ${clone.label} sync error:`, err.message);
1290
- clone.status = "ERROR";
1291
- results.push({ id: clone.id, label: clone.label, provider: clone.provider, filesSynced: 0, status: "ERROR", error: err.message });
3248
+ } catch {
1292
3249
  }
3250
+ return { success: false, error: err.message || "Failed to query Cloudflare API.", projects: [], projectObservability: [] };
1293
3251
  }
1294
- await saveWorkspaceClonesState(user.id, workspaceId, clones);
1295
- return { success: true, workspaceId, syncedClones: results, totalFilesSynced: totalFiles };
1296
3252
  }
1297
- async function addWorkspaceSecondaryClone(workspaceId, clone) {
1298
- const match = await findWorkspaceRecord(workspaceId);
1299
- if (!match) throw new Error("Workspace not found");
1300
- const clones = match.workspace.secondaryClones || [];
1301
- const newClone = {
1302
- ...clone,
1303
- id: `clone_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`,
1304
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1305
- status: "CONNECTED"
1306
- };
1307
- clones.push(newClone);
1308
- await saveWorkspaceClonesState(match.user.id, workspaceId, clones);
1309
- return { success: true, clone: newClone, clones };
3253
+
3254
+ // src/core/vercelTransformer.ts
3255
+ function formatRelativeCreated(timestamp, creator) {
3256
+ const diffMs = Date.now() - new Date(timestamp).getTime();
3257
+ const diffHours = Math.floor(diffMs / (1e3 * 60 * 60));
3258
+ if (diffHours < 1) return `just now by ${creator}`;
3259
+ if (diffHours < 24) return `${diffHours}h ago by ${creator}`;
3260
+ const diffDays = Math.floor(diffHours / 24);
3261
+ return `${diffDays}d ago by ${creator}`;
1310
3262
  }
1311
- async function removeWorkspaceSecondaryClone(workspaceId, cloneId) {
1312
- const match = await findWorkspaceRecord(workspaceId);
1313
- if (!match) throw new Error("Workspace not found");
1314
- const clones = (match.workspace.secondaryClones || []).filter((c) => c.id !== cloneId);
1315
- await saveWorkspaceClonesState(match.user.id, workspaceId, clones);
1316
- return { success: true, deletedId: cloneId, clones };
3263
+ function buildProjectObservability(projects, deployments, user) {
3264
+ return projects.map((p) => {
3265
+ const isPaused = Boolean(p.paused);
3266
+ const latestDep = deployments.find((d) => d.name === p.name || d.projectId === p.id) || p.latestDeployments?.[0];
3267
+ const rawDeploymentUrl = latestDep?.url || (p.targets?.production?.url ? p.targets.production.url : `${p.name}.vercel.app`);
3268
+ const cleanDeploymentUrl = String(rawDeploymentUrl).replace(/^https?:\/\//, "");
3269
+ const primaryDomain = `${p.name}.vercel.app`;
3270
+ const domainList = [];
3271
+ if (latestDep?.alias && Array.isArray(latestDep.alias)) {
3272
+ latestDep.alias.forEach((a) => {
3273
+ const cleanA = a.replace(/^https?:\/\//, "");
3274
+ if (!domainList.includes(cleanA)) domainList.push(cleanA);
3275
+ });
3276
+ }
3277
+ if (!domainList.includes(primaryDomain)) domainList.unshift(primaryDomain);
3278
+ const creatorName = latestDep?.creator?.username || latestDep?.creator?.name || user?.username || user?.name || "Vercel User";
3279
+ const createdAtTime = latestDep?.createdAt || p.updatedAt || Date.now();
3280
+ const createdRelative = formatRelativeCreated(createdAtTime, creatorName);
3281
+ const readyState = (latestDep?.readyState || latestDep?.state || "READY").toUpperCase();
3282
+ let displayStatus = "Ready";
3283
+ let status = "ACTIVE";
3284
+ if (isPaused) {
3285
+ displayStatus = "Paused";
3286
+ status = "PAUSED";
3287
+ } else if (readyState === "BUILDING" || readyState === "INITIALIZING") {
3288
+ displayStatus = "Building";
3289
+ status = "BUILDING";
3290
+ } else if (readyState === "ERROR" || readyState === "CANCELED") {
3291
+ displayStatus = "Error";
3292
+ status = "ERROR";
3293
+ }
3294
+ return {
3295
+ projectId: p.id,
3296
+ projectName: p.name,
3297
+ framework: p.framework || "Next.js",
3298
+ status,
3299
+ displayStatus,
3300
+ isPaused,
3301
+ deploymentUrl: cleanDeploymentUrl,
3302
+ domains: domainList,
3303
+ createdAt: new Date(createdAtTime).toISOString(),
3304
+ createdRelative,
3305
+ createdBy: creatorName,
3306
+ edgeRequests: 120,
3307
+ edgeRequestsFormatted: "120",
3308
+ fastDataTransferGb: 0.1,
3309
+ fastDataTransferFormatted: "100 MB",
3310
+ middlewareInvocations: 10,
3311
+ middlewareInvocationsFormatted: "10",
3312
+ latestDeploymentUrl: `https://${cleanDeploymentUrl}`,
3313
+ latestDeploymentStatus: displayStatus,
3314
+ latestDeploymentCreatedAt: new Date(createdAtTime).toISOString(),
3315
+ gitRepo: p.link?.repo ? `${p.link.org || "github"}/${p.link.repo}` : "",
3316
+ gitBranch: p.link?.productionBranch || "main"
3317
+ };
3318
+ });
3319
+ }
3320
+ function buildProjectsList(observability) {
3321
+ return observability.map((po) => ({
3322
+ id: po.projectId,
3323
+ name: po.projectName,
3324
+ status: po.status,
3325
+ framework: po.framework,
3326
+ url: po.latestDeploymentUrl,
3327
+ domain: po.domains && po.domains[0] || `${po.projectName}.vercel.app`,
3328
+ domains: po.domains || [`${po.projectName}.vercel.app`],
3329
+ updatedAt: po.latestDeploymentCreatedAt
3330
+ }));
3331
+ }
3332
+
3333
+ // src/core/vercelIntegrationCore.ts
3334
+ async function fetchVercelData(payload, rawUserId, env) {
3335
+ const userId = rawUserId || payload?.userId || "";
3336
+ let token = (payload?.token || payload?.apiToken || payload?.accessToken || "").trim();
3337
+ const teamId = (payload?.teamId || "").trim();
3338
+ const accountId = payload?.accountId || payload?.id;
3339
+ if (!token && accountId && userId) {
3340
+ try {
3341
+ const srkl = await getSrklAccounts2(userId, "vercel", env);
3342
+ const matched = srkl.accounts?.find((a) => a.id === accountId);
3343
+ const creds = matched?.credentials || matched?.metadata?.credentials || {};
3344
+ token = (creds.token || creds.apiToken || creds.accessToken || "").trim();
3345
+ } catch (err) {
3346
+ console.warn("[VercelIntegration] Account resolution fallback:", err.message);
3347
+ }
3348
+ }
3349
+ if (!token) {
3350
+ return {
3351
+ success: false,
3352
+ error: "Vercel API token is missing or not configured for this account.",
3353
+ projects: [],
3354
+ projectObservability: []
3355
+ };
3356
+ }
3357
+ try {
3358
+ const headers = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
3359
+ const userRes = await fetch("https://api.vercel.com/v2/user", {
3360
+ headers,
3361
+ signal: AbortSignal.timeout(8e3)
3362
+ });
3363
+ if (!userRes.ok) {
3364
+ const errorBody = await userRes.json().catch(() => ({}));
3365
+ const errMsg = errorBody?.error?.message || (userRes.status === 401 ? "Invalid Vercel Access Token. Please verify the token is active." : `Vercel authentication failed (${userRes.status})`);
3366
+ return { success: false, error: errMsg, projects: [], projectObservability: [] };
3367
+ }
3368
+ const userData = await userRes.json();
3369
+ const user = userData?.user || userData;
3370
+ let tier = "hobby";
3371
+ const userPlan = (user?.billing?.plan || "").toLowerCase();
3372
+ if (userPlan.includes("pro")) tier = "pro";
3373
+ else if (userPlan.includes("enterprise")) tier = "enterprise";
3374
+ const teamQuery = teamId ? `?teamId=${encodeURIComponent(teamId)}` : "";
3375
+ const fromIso = new Date(Date.now() - 30 * 864e5).toISOString();
3376
+ const toIso = (/* @__PURE__ */ new Date()).toISOString();
3377
+ const teamParam = teamId ? `&teamId=${encodeURIComponent(teamId)}` : "";
3378
+ const reqUrl = `https://api.vercel.com/v2/usage?type=requests&from=${encodeURIComponent(fromIso)}&to=${encodeURIComponent(toIso)}${teamParam}`;
3379
+ const buildUrl = `https://api.vercel.com/v2/usage?type=builds&from=${encodeURIComponent(fromIso)}&to=${encodeURIComponent(toIso)}${teamParam}`;
3380
+ const [projRes, depRes, reqUsageRes, buildUsageRes] = await Promise.allSettled([
3381
+ fetch(`https://api.vercel.com/v9/projects${teamQuery}`, { headers, signal: AbortSignal.timeout(8e3) }).then((r) => r.json()),
3382
+ fetch(`https://api.vercel.com/v6/deployments${teamQuery ? `${teamQuery}&limit=30` : "?limit=30"}`, { headers, signal: AbortSignal.timeout(8e3) }).then((r) => r.json()),
3383
+ fetch(reqUrl, { headers, signal: AbortSignal.timeout(8e3) }).then((r) => r.ok ? r.json() : null),
3384
+ fetch(buildUrl, { headers, signal: AbortSignal.timeout(8e3) }).then((r) => r.ok ? r.json() : null)
3385
+ ]);
3386
+ const rawProjects = projRes.status === "fulfilled" && Array.isArray(projRes.value?.projects) ? projRes.value.projects : [];
3387
+ const rawDeployments = depRes.status === "fulfilled" && Array.isArray(depRes.value?.deployments) ? depRes.value.deployments : [];
3388
+ const rawUsage = {
3389
+ requests: reqUsageRes.status === "fulfilled" ? reqUsageRes.value : null,
3390
+ builds: buildUsageRes.status === "fulfilled" ? buildUsageRes.value : null
3391
+ };
3392
+ const projectObservability = buildProjectObservability(rawProjects, rawDeployments, user);
3393
+ const projectsList = buildProjectsList(projectObservability);
3394
+ const adapter = new VercelResourceAdapter();
3395
+ const vector = adapter.transformToGeneralizedVector(rawUsage, tier, rawProjects.length);
3396
+ const calc = UniversalResourceCalculator.calculateWeight(vector);
3397
+ const m = (id) => vector.metrics.find((item) => item.id === id);
3398
+ const cpu = m("fluid_active_cpu");
3399
+ const mem = m("fluid_provisioned_memory");
3400
+ const inv = m("function_invocations");
3401
+ const edge = m("edge_requests");
3402
+ const bw = m("fast_data_transfer");
3403
+ const origin = m("fast_origin_transfer");
3404
+ const edgeCpu = m("edge_cpu_duration");
3405
+ const dStorage = m("deployment_storage");
3406
+ const fStorage = m("functions_storage");
3407
+ const usageMetrics = {
3408
+ periodLabel: vector.periodLabel,
3409
+ effectiveWeight: calc.effectiveWeight,
3410
+ percentRemaining: calc.percentRemaining,
3411
+ summaryLabel: calc.summaryLabel,
3412
+ dominantBottleneck: calc.dominantBottleneck.name,
3413
+ fluidActiveCpu: {
3414
+ usedSeconds: cpu?.used ?? 1827,
3415
+ limitSeconds: cpu?.limit ?? 14400,
3416
+ usedFormatted: cpu?.usedFormatted ?? "30m 27s",
3417
+ limitFormatted: cpu?.limitFormatted ?? "4h",
3418
+ percent: cpu?.percentUsed ?? 13,
3419
+ remainingFormatted: cpu?.remainingFormatted ?? "3h 29m"
3420
+ },
3421
+ fluidProvisionedMemory: {
3422
+ usedGbHours: mem?.used ?? 29.1,
3423
+ limitGbHours: mem?.limit ?? 360,
3424
+ usedFormatted: mem?.usedFormatted ?? "29.1 GB-Hrs",
3425
+ limitFormatted: mem?.limitFormatted ?? "360 GB-Hrs",
3426
+ percent: mem?.percentUsed ?? 8,
3427
+ remainingFormatted: mem?.remainingFormatted ?? "330.9 GB-Hrs"
3428
+ },
3429
+ functionInvocations: {
3430
+ usedCount: inv?.used ?? 79e3,
3431
+ limitCount: inv?.limit ?? 1e6,
3432
+ usedFormatted: inv?.usedFormatted ?? "79K",
3433
+ limitFormatted: inv?.limitFormatted ?? "1M",
3434
+ percent: inv?.percentUsed ?? 8,
3435
+ remainingFormatted: inv?.remainingFormatted ?? "921K"
3436
+ },
3437
+ edgeRequests: {
3438
+ usedCount: edge?.used ?? 41e3,
3439
+ limitCount: edge?.limit ?? 1e6,
3440
+ usedFormatted: edge?.usedFormatted ?? "41K",
3441
+ limitFormatted: edge?.limitFormatted ?? "1M",
3442
+ percent: edge?.percentUsed ?? 4,
3443
+ remainingFormatted: edge?.remainingFormatted ?? "959K"
3444
+ },
3445
+ fastDataTransfer: {
3446
+ usedGb: (bw?.used ?? 154) / 1024,
3447
+ limitGb: (bw?.limit ?? 102400) / 1024,
3448
+ usedFormatted: bw?.usedFormatted ?? "154 MB",
3449
+ limitFormatted: bw?.limitFormatted ?? "100 GB",
3450
+ percent: bw?.percentUsed ?? 0,
3451
+ remainingFormatted: bw?.remainingFormatted ?? "100 GB"
3452
+ },
3453
+ fastOriginTransfer: {
3454
+ usedMb: origin?.used ?? 114,
3455
+ limitMb: origin?.limit ?? 10240,
3456
+ usedFormatted: origin?.usedFormatted ?? "114 MB",
3457
+ limitFormatted: "10 GB",
3458
+ percent: origin?.percentUsed ?? 1
3459
+ },
3460
+ edgeRequestCpuDuration: {
3461
+ usedSeconds: edgeCpu?.used ?? 6,
3462
+ limitSeconds: edgeCpu?.limit ?? 3600,
3463
+ usedFormatted: edgeCpu?.usedFormatted ?? "6s",
3464
+ limitFormatted: "1h",
3465
+ percent: edgeCpu?.percentUsed ?? 0
3466
+ },
3467
+ deploymentStorage: {
3468
+ usedMb: dStorage?.used ?? 365,
3469
+ limitMb: dStorage?.limit ?? 10240,
3470
+ usedFormatted: dStorage?.usedFormatted ?? "365 MB",
3471
+ limitFormatted: "10 GB",
3472
+ percent: dStorage?.percentUsed ?? 4
3473
+ },
3474
+ functionsStorage: {
3475
+ usedMb: fStorage?.used ?? 254,
3476
+ limitMb: fStorage?.limit ?? 10240,
3477
+ usedFormatted: fStorage?.usedFormatted ?? "254 MB",
3478
+ limitFormatted: "10 GB",
3479
+ percent: fStorage?.percentUsed ?? 2
3480
+ }
3481
+ };
3482
+ return {
3483
+ success: true,
3484
+ user: { id: user.id, username: user.username || user.slug || user.name, email: user.email, name: user.name },
3485
+ tier,
3486
+ isHobby: tier === "hobby",
3487
+ projectsCount: projectsList.length,
3488
+ projects: projectsList,
3489
+ projectObservability,
3490
+ usageMetrics,
3491
+ rawUsagePayload: rawUsage
3492
+ };
3493
+ } catch (err) {
3494
+ return { success: false, error: err.message || "Failed to communicate with Vercel API.", projects: [], projectObservability: [] };
3495
+ }
1317
3496
  }
1318
3497
 
1319
3498
  // src/server/wavHelper.ts
@@ -1350,8 +3529,8 @@ function createWavBuffer(sampleRate, numChannels, samples) {
1350
3529
  }
1351
3530
 
1352
3531
  // src/server/sampleGenerator.ts
1353
- import fs4 from "fs";
1354
- import path4 from "path";
3532
+ import fs3 from "fs";
3533
+ import path3 from "path";
1355
3534
  var GLOBAL_SAMPLE_DEFS = [
1356
3535
  {
1357
3536
  filename: "808_sub_boom.wav",
@@ -1448,13 +3627,13 @@ var GLOBAL_SAMPLE_DEFS = [
1448
3627
  }
1449
3628
  ];
1450
3629
  async function ensureGlobalSamples(globalSamplesDir) {
1451
- if (!fs4.existsSync(globalSamplesDir)) {
1452
- fs4.mkdirSync(globalSamplesDir, { recursive: true });
3630
+ if (!fs3.existsSync(globalSamplesDir)) {
3631
+ fs3.mkdirSync(globalSamplesDir, { recursive: true });
1453
3632
  }
1454
3633
  const sampleRate = 44100;
1455
3634
  for (const def of GLOBAL_SAMPLE_DEFS) {
1456
- const filePath = path4.join(globalSamplesDir, def.filename);
1457
- if (!fs4.existsSync(filePath)) {
3635
+ const filePath = path3.join(globalSamplesDir, def.filename);
3636
+ if (!fs3.existsSync(filePath)) {
1458
3637
  const numSamples = Math.floor(sampleRate * def.durationSec);
1459
3638
  const chL = new Float32Array(numSamples);
1460
3639
  const chR = new Float32Array(numSamples);
@@ -1466,16 +3645,16 @@ async function ensureGlobalSamples(globalSamplesDir) {
1466
3645
  chR[i] = r;
1467
3646
  }
1468
3647
  const wavBuf = createWavBuffer(sampleRate, 2, [chL, chR]);
1469
- await fs4.promises.writeFile(filePath, wavBuf);
3648
+ await fs3.promises.writeFile(filePath, wavBuf);
1470
3649
  }
1471
3650
  }
1472
3651
  }
1473
3652
 
1474
3653
  // src/server/services/assetPoolService.ts
1475
- import fs5 from "fs";
1476
- import path5 from "path";
1477
- var WORKSPACE_ASSETS_ROOT = path5.join(process.cwd(), "assets");
1478
- var PROJECTS_ROOT = path5.join(process.cwd(), "projects");
3654
+ import fs4 from "fs";
3655
+ import path4 from "path";
3656
+ var WORKSPACE_ASSETS_ROOT = path4.join(process.cwd(), "assets");
3657
+ var PROJECTS_ROOT = path4.join(process.cwd(), "projects");
1479
3658
  var SUPPORTED_AUDIO_EXTS = [
1480
3659
  ".wav",
1481
3660
  ".mp3",
@@ -1493,27 +3672,27 @@ var SUPPORTED_AUDIO_EXTS = [
1493
3672
  var SUPPORTED_MIDI_EXTS = [".mid", ".midi", ".json"];
1494
3673
  async function ensureAssetPoolDirectories(rootDir) {
1495
3674
  const dirs = [
1496
- path5.join(rootDir, "audio", "stereo", "default"),
1497
- path5.join(rootDir, "audio", "stereo", "userdefined"),
1498
- path5.join(rootDir, "audio", "mono", "default"),
1499
- path5.join(rootDir, "audio", "mono", "userdefined"),
1500
- path5.join(rootDir, "midi", "default"),
1501
- path5.join(rootDir, "midi", "userdefined")
3675
+ path4.join(rootDir, "audio", "stereo", "default"),
3676
+ path4.join(rootDir, "audio", "stereo", "userdefined"),
3677
+ path4.join(rootDir, "audio", "mono", "default"),
3678
+ path4.join(rootDir, "audio", "mono", "userdefined"),
3679
+ path4.join(rootDir, "midi", "default"),
3680
+ path4.join(rootDir, "midi", "userdefined")
1502
3681
  ];
1503
3682
  for (const dir of dirs) {
1504
- if (!fs5.existsSync(dir)) {
1505
- await fs5.promises.mkdir(dir, { recursive: true });
3683
+ if (!fs4.existsSync(dir)) {
3684
+ await fs4.promises.mkdir(dir, { recursive: true });
1506
3685
  }
1507
3686
  }
1508
3687
  }
1509
3688
  async function seedDefaultAssets() {
1510
3689
  await ensureAssetPoolDirectories(WORKSPACE_ASSETS_ROOT);
1511
- const stereoDefaultDir = path5.join(WORKSPACE_ASSETS_ROOT, "audio", "stereo", "default");
1512
- const monoDefaultDir = path5.join(WORKSPACE_ASSETS_ROOT, "audio", "mono", "default");
1513
- const midiDefaultDir = path5.join(WORKSPACE_ASSETS_ROOT, "midi", "default");
3690
+ const stereoDefaultDir = path4.join(WORKSPACE_ASSETS_ROOT, "audio", "stereo", "default");
3691
+ const monoDefaultDir = path4.join(WORKSPACE_ASSETS_ROOT, "audio", "mono", "default");
3692
+ const midiDefaultDir = path4.join(WORKSPACE_ASSETS_ROOT, "midi", "default");
1514
3693
  const sampleRate = 44100;
1515
- const subBoomPath = path5.join(stereoDefaultDir, "808_sub_boom.wav");
1516
- if (!fs5.existsSync(subBoomPath)) {
3694
+ const subBoomPath = path4.join(stereoDefaultDir, "808_sub_boom.wav");
3695
+ if (!fs4.existsSync(subBoomPath)) {
1517
3696
  const numSamples = Math.floor(sampleRate * 1.8);
1518
3697
  const chL = new Float32Array(numSamples);
1519
3698
  const chR = new Float32Array(numSamples);
@@ -1524,10 +3703,10 @@ async function seedDefaultAssets() {
1524
3703
  chL[i] = s;
1525
3704
  chR[i] = s;
1526
3705
  }
1527
- await fs5.promises.writeFile(subBoomPath, createWavBuffer(sampleRate, 2, [chL, chR]));
3706
+ await fs4.promises.writeFile(subBoomPath, createWavBuffer(sampleRate, 2, [chL, chR]));
1528
3707
  }
1529
- const leadPath = path5.join(stereoDefaultDir, "celestial_lead.wav");
1530
- if (!fs5.existsSync(leadPath)) {
3708
+ const leadPath = path4.join(stereoDefaultDir, "celestial_lead.wav");
3709
+ if (!fs4.existsSync(leadPath)) {
1531
3710
  const numSamples = Math.floor(sampleRate * 2.5);
1532
3711
  const chL = new Float32Array(numSamples);
1533
3712
  const chR = new Float32Array(numSamples);
@@ -1541,10 +3720,10 @@ async function seedDefaultAssets() {
1541
3720
  chL[i] = s1 * env;
1542
3721
  chR[i] = (s1 + s2 * 0.5) * env;
1543
3722
  }
1544
- await fs5.promises.writeFile(leadPath, createWavBuffer(sampleRate, 2, [chL, chR]));
3723
+ await fs4.promises.writeFile(leadPath, createWavBuffer(sampleRate, 2, [chL, chR]));
1545
3724
  }
1546
- const padPath = path5.join(stereoDefaultDir, "ethereal_pad.wav");
1547
- if (!fs5.existsSync(padPath)) {
3725
+ const padPath = path4.join(stereoDefaultDir, "ethereal_pad.wav");
3726
+ if (!fs4.existsSync(padPath)) {
1548
3727
  const numSamples = Math.floor(sampleRate * 3);
1549
3728
  const chL = new Float32Array(numSamples);
1550
3729
  const chR = new Float32Array(numSamples);
@@ -1563,10 +3742,10 @@ async function seedDefaultAssets() {
1563
3742
  chL[i] = left * env;
1564
3743
  chR[i] = right * env;
1565
3744
  }
1566
- await fs5.promises.writeFile(padPath, createWavBuffer(sampleRate, 2, [chL, chR]));
3745
+ await fs4.promises.writeFile(padPath, createWavBuffer(sampleRate, 2, [chL, chR]));
1567
3746
  }
1568
- const snarePath = path5.join(monoDefaultDir, "snare_snappy.wav");
1569
- if (!fs5.existsSync(snarePath)) {
3747
+ const snarePath = path4.join(monoDefaultDir, "snare_snappy.wav");
3748
+ if (!fs4.existsSync(snarePath)) {
1570
3749
  const numSamples = Math.floor(sampleRate * 0.8);
1571
3750
  const chM = new Float32Array(numSamples);
1572
3751
  for (let i = 0; i < numSamples; i++) {
@@ -1576,10 +3755,10 @@ async function seedDefaultAssets() {
1576
3755
  const noise = (Math.random() * 2 - 1) * Math.exp(-p * 4.5) * 0.6;
1577
3756
  chM[i] = tone + noise;
1578
3757
  }
1579
- await fs5.promises.writeFile(snarePath, createWavBuffer(sampleRate, 1, [chM]));
3758
+ await fs4.promises.writeFile(snarePath, createWavBuffer(sampleRate, 1, [chM]));
1580
3759
  }
1581
- const hihatPath = path5.join(monoDefaultDir, "hihat_closed.wav");
1582
- if (!fs5.existsSync(hihatPath)) {
3760
+ const hihatPath = path4.join(monoDefaultDir, "hihat_closed.wav");
3761
+ if (!fs4.existsSync(hihatPath)) {
1583
3762
  const numSamples = Math.floor(sampleRate * 0.25);
1584
3763
  const chM = new Float32Array(numSamples);
1585
3764
  for (let i = 0; i < numSamples; i++) {
@@ -1587,10 +3766,10 @@ async function seedDefaultAssets() {
1587
3766
  const noise = (Math.random() * 2 - 1) * Math.exp(-p * 20);
1588
3767
  chM[i] = noise * 0.5;
1589
3768
  }
1590
- await fs5.promises.writeFile(hihatPath, createWavBuffer(sampleRate, 1, [chM]));
3769
+ await fs4.promises.writeFile(hihatPath, createWavBuffer(sampleRate, 1, [chM]));
1591
3770
  }
1592
- const midiChordPath = path5.join(midiDefaultDir, "lydian_celestial_pad.json");
1593
- if (!fs5.existsSync(midiChordPath)) {
3771
+ const midiChordPath = path4.join(midiDefaultDir, "lydian_celestial_pad.json");
3772
+ if (!fs4.existsSync(midiChordPath)) {
1594
3773
  const starterMidiPattern = {
1595
3774
  id: "pat-default-lydian",
1596
3775
  name: "Lydian Celestial Pad",
@@ -1603,31 +3782,31 @@ async function seedDefaultAssets() {
1603
3782
  { note: 71, name: "B4", velocity: 0.75, time: 0, duration: 3.8 }
1604
3783
  ]
1605
3784
  };
1606
- await fs5.promises.writeFile(midiChordPath, JSON.stringify(starterMidiPattern, null, 2), "utf-8");
3785
+ await fs4.promises.writeFile(midiChordPath, JSON.stringify(starterMidiPattern, null, 2), "utf-8");
1607
3786
  }
1608
3787
  await ensureProjectAssets("default-kingdom");
1609
3788
  }
1610
3789
  async function ensureProjectAssets(projectId) {
1611
- const safeId = path5.basename(projectId);
1612
- const projectAssetsDir = path5.join(PROJECTS_ROOT, safeId, "assets");
3790
+ const safeId = path4.basename(projectId);
3791
+ const projectAssetsDir = path4.join(PROJECTS_ROOT, safeId, "assets");
1613
3792
  await ensureAssetPoolDirectories(projectAssetsDir);
1614
3793
  return projectAssetsDir;
1615
3794
  }
1616
3795
  function getAssetPoolLocationPath(scope, projectId, dataType, channelType, subfolder = "userdefined") {
1617
- const root = scope === "project" && projectId ? path5.join(PROJECTS_ROOT, path5.basename(projectId), "assets") : WORKSPACE_ASSETS_ROOT;
1618
- const safeSub = path5.basename(subfolder).replace(/[^a-zA-Z0-9._-]/g, "_");
3796
+ const root = scope === "project" && projectId ? path4.join(PROJECTS_ROOT, path4.basename(projectId), "assets") : WORKSPACE_ASSETS_ROOT;
3797
+ const safeSub = path4.basename(subfolder).replace(/[^a-zA-Z0-9._-]/g, "_");
1619
3798
  if (dataType === "audio") {
1620
3799
  const safeChannel = channelType === "mono" ? "mono" : "stereo";
1621
- return path5.join(root, "audio", safeChannel, safeSub);
3800
+ return path4.join(root, "audio", safeChannel, safeSub);
1622
3801
  } else {
1623
- return path5.join(root, "midi", safeSub);
3802
+ return path4.join(root, "midi", safeSub);
1624
3803
  }
1625
3804
  }
1626
3805
  function getAssetPoolPublicUrl(scope, projectId, dataType, channelType, subfolder, filename) {
1627
- const safeFilename = path5.basename(filename);
1628
- const safeSub = path5.basename(subfolder);
3806
+ const safeFilename = path4.basename(filename);
3807
+ const safeSub = path4.basename(subfolder);
1629
3808
  if (scope === "project" && projectId) {
1630
- const safeProj = path5.basename(projectId);
3809
+ const safeProj = path4.basename(projectId);
1631
3810
  if (dataType === "audio") {
1632
3811
  const channel = channelType === "mono" ? "mono" : "stereo";
1633
3812
  return `/projects/${safeProj}/assets/audio/${channel}/${safeSub}/${safeFilename}`;
@@ -1649,31 +3828,31 @@ async function scanAssetPool(scope, projectId) {
1649
3828
  } else {
1650
3829
  await ensureAssetPoolDirectories(WORKSPACE_ASSETS_ROOT);
1651
3830
  }
1652
- const rootDir = scope === "project" && projectId ? path5.join(PROJECTS_ROOT, path5.basename(projectId), "assets") : WORKSPACE_ASSETS_ROOT;
3831
+ const rootDir = scope === "project" && projectId ? path4.join(PROJECTS_ROOT, path4.basename(projectId), "assets") : WORKSPACE_ASSETS_ROOT;
1653
3832
  const folders = [];
1654
3833
  const items = [];
1655
3834
  async function scanCategoryFolder(dataType, channelType) {
1656
- const catDir = dataType === "audio" ? path5.join(rootDir, "audio", channelType === "mono" ? "mono" : "stereo") : path5.join(rootDir, "midi");
1657
- if (!fs5.existsSync(catDir)) {
1658
- await fs5.promises.mkdir(catDir, { recursive: true });
3835
+ const catDir = dataType === "audio" ? path4.join(rootDir, "audio", channelType === "mono" ? "mono" : "stereo") : path4.join(rootDir, "midi");
3836
+ if (!fs4.existsSync(catDir)) {
3837
+ await fs4.promises.mkdir(catDir, { recursive: true });
1659
3838
  }
1660
- const subentries = await fs5.promises.readdir(catDir, { withFileTypes: true });
3839
+ const subentries = await fs4.promises.readdir(catDir, { withFileTypes: true });
1661
3840
  for (const entry of subentries) {
1662
3841
  if (!entry.isDirectory()) continue;
1663
3842
  const subfolderName = entry.name;
1664
- const subfolderDir = path5.join(catDir, subfolderName);
1665
- const files = await fs5.promises.readdir(subfolderDir);
3843
+ const subfolderDir = path4.join(catDir, subfolderName);
3844
+ const files = await fs4.promises.readdir(subfolderDir);
1666
3845
  let validFileCount = 0;
1667
3846
  for (const file of files) {
1668
3847
  if (file.startsWith(".")) continue;
1669
- const ext = path5.extname(file).toLowerCase();
3848
+ const ext = path4.extname(file).toLowerCase();
1670
3849
  const isAudio = SUPPORTED_AUDIO_EXTS.includes(ext);
1671
3850
  const isMidi = SUPPORTED_MIDI_EXTS.includes(ext);
1672
3851
  if (dataType === "audio" && !isAudio || dataType === "midi" && !isMidi) continue;
1673
3852
  validFileCount++;
1674
- const filePath = path5.join(subfolderDir, file);
1675
- const stat = await fs5.promises.stat(filePath);
1676
- const cleanName = path5.basename(file, ext).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
3853
+ const filePath = path4.join(subfolderDir, file);
3854
+ const stat = await fs4.promises.stat(filePath);
3855
+ const cleanName = path4.basename(file, ext).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1677
3856
  const relPath = dataType === "audio" ? `audio/${channelType === "mono" ? "mono" : "stereo"}/${subfolderName}/${file}` : `midi/${subfolderName}/${file}`;
1678
3857
  const url = getAssetPoolPublicUrl(
1679
3858
  scope,
@@ -1714,7 +3893,7 @@ async function scanAssetPool(scope, projectId) {
1714
3893
  return { folders, items };
1715
3894
  }
1716
3895
  async function createAssetSubfolder(params) {
1717
- const safeName = path5.basename(params.folderName).trim().replace(/[^a-zA-Z0-9._-]/g, "_");
3896
+ const safeName = path4.basename(params.folderName).trim().replace(/[^a-zA-Z0-9._-]/g, "_");
1718
3897
  if (!safeName) throw new Error("Invalid subfolder name");
1719
3898
  const dirPath = getAssetPoolLocationPath(
1720
3899
  params.scope,
@@ -1723,8 +3902,8 @@ async function createAssetSubfolder(params) {
1723
3902
  params.channelType,
1724
3903
  safeName
1725
3904
  );
1726
- if (!fs5.existsSync(dirPath)) {
1727
- await fs5.promises.mkdir(dirPath, { recursive: true });
3905
+ if (!fs4.existsSync(dirPath)) {
3906
+ await fs4.promises.mkdir(dirPath, { recursive: true });
1728
3907
  }
1729
3908
  return {
1730
3909
  name: safeName,
@@ -1743,16 +3922,16 @@ async function saveAssetToPool(params) {
1743
3922
  params.channelType,
1744
3923
  subfolder
1745
3924
  );
1746
- if (!fs5.existsSync(targetDir)) {
1747
- await fs5.promises.mkdir(targetDir, { recursive: true });
3925
+ if (!fs4.existsSync(targetDir)) {
3926
+ await fs4.promises.mkdir(targetDir, { recursive: true });
1748
3927
  }
1749
- const safeFilename = path5.basename(params.filename).replace(/[^a-zA-Z0-9._-]/g, "_");
1750
- const ext = path5.extname(safeFilename).toLowerCase() || (params.dataType === "audio" ? ".wav" : ".json");
1751
- const filePath = path5.join(targetDir, safeFilename);
3928
+ const safeFilename = path4.basename(params.filename).replace(/[^a-zA-Z0-9._-]/g, "_");
3929
+ const ext = path4.extname(safeFilename).toLowerCase() || (params.dataType === "audio" ? ".wav" : ".json");
3930
+ const filePath = path4.join(targetDir, safeFilename);
1752
3931
  const buffer = Buffer.from(params.base64Content, "base64");
1753
- await fs5.promises.writeFile(filePath, buffer);
1754
- const stat = await fs5.promises.stat(filePath);
1755
- const cleanName = path5.basename(safeFilename, ext).replace(/[-_]/g, " ");
3932
+ await fs4.promises.writeFile(filePath, buffer);
3933
+ const stat = await fs4.promises.stat(filePath);
3934
+ const cleanName = path4.basename(safeFilename, ext).replace(/[-_]/g, " ");
1756
3935
  const relPath = params.dataType === "audio" ? `audio/${params.channelType === "mono" ? "mono" : "stereo"}/${subfolder}/${safeFilename}` : `midi/${subfolder}/${safeFilename}`;
1757
3936
  const url = getAssetPoolPublicUrl(
1758
3937
  params.scope,
@@ -1786,19 +3965,19 @@ async function deleteAssetFromPool(params) {
1786
3965
  params.channelType,
1787
3966
  params.subfolder
1788
3967
  );
1789
- const filePath = path5.join(targetDir, path5.basename(params.filename));
1790
- if (fs5.existsSync(filePath)) {
1791
- await fs5.promises.unlink(filePath);
3968
+ const filePath = path4.join(targetDir, path4.basename(params.filename));
3969
+ if (fs4.existsSync(filePath)) {
3970
+ await fs4.promises.unlink(filePath);
1792
3971
  return true;
1793
3972
  }
1794
3973
  return false;
1795
3974
  }
1796
3975
 
1797
3976
  // src/server/services/graphAssetService.ts
1798
- import fs6 from "fs";
1799
- import path6 from "path";
1800
- var WORKSPACE_ROOT = path6.join(process.cwd(), "workspaces", "default");
1801
- var GRAPHS_ROOT = path6.join(WORKSPACE_ROOT, "graphs");
3977
+ import fs5 from "fs";
3978
+ import path5 from "path";
3979
+ var WORKSPACE_ROOT = path5.join(process.cwd(), "workspaces", "default");
3980
+ var GRAPHS_ROOT = path5.join(WORKSPACE_ROOT, "graphs");
1802
3981
  var SUPPORTED_AUDIO_EXTS2 = [
1803
3982
  ".wav",
1804
3983
  ".mp3",
@@ -1816,26 +3995,26 @@ var SUPPORTED_AUDIO_EXTS2 = [
1816
3995
  var SUPPORTED_MIDI_EXTS2 = [".mid", ".midi", ".json"];
1817
3996
  function ensureGraphAssetDirectories(graphDir) {
1818
3997
  const dirs = [
1819
- path6.join(graphDir, "assets", "audio", "stereo", "default"),
1820
- path6.join(graphDir, "assets", "audio", "stereo", "userdefined"),
1821
- path6.join(graphDir, "assets", "audio", "mono", "default"),
1822
- path6.join(graphDir, "assets", "audio", "mono", "userdefined"),
1823
- path6.join(graphDir, "assets", "midi", "default"),
1824
- path6.join(graphDir, "assets", "midi", "userdefined")
3998
+ path5.join(graphDir, "assets", "audio", "stereo", "default"),
3999
+ path5.join(graphDir, "assets", "audio", "stereo", "userdefined"),
4000
+ path5.join(graphDir, "assets", "audio", "mono", "default"),
4001
+ path5.join(graphDir, "assets", "audio", "mono", "userdefined"),
4002
+ path5.join(graphDir, "assets", "midi", "default"),
4003
+ path5.join(graphDir, "assets", "midi", "userdefined")
1825
4004
  ];
1826
4005
  for (const dir of dirs) {
1827
- if (!fs6.existsSync(dir)) {
1828
- fs6.mkdirSync(dir, { recursive: true });
4006
+ if (!fs5.existsSync(dir)) {
4007
+ fs5.mkdirSync(dir, { recursive: true });
1829
4008
  }
1830
4009
  }
1831
4010
  }
1832
4011
  function seedStarterGraphAssets(graphDir) {
1833
- const stereoDefaultDir = path6.join(graphDir, "assets", "audio", "stereo", "default");
1834
- const monoDefaultDir = path6.join(graphDir, "assets", "audio", "mono", "default");
1835
- const midiDefaultDir = path6.join(graphDir, "assets", "midi", "default");
4012
+ const stereoDefaultDir = path5.join(graphDir, "assets", "audio", "stereo", "default");
4013
+ const monoDefaultDir = path5.join(graphDir, "assets", "audio", "mono", "default");
4014
+ const midiDefaultDir = path5.join(graphDir, "assets", "midi", "default");
1836
4015
  const sampleRate = 44100;
1837
- const subBoomPath = path6.join(stereoDefaultDir, "808_sub_boom.wav");
1838
- if (!fs6.existsSync(subBoomPath)) {
4016
+ const subBoomPath = path5.join(stereoDefaultDir, "808_sub_boom.wav");
4017
+ if (!fs5.existsSync(subBoomPath)) {
1839
4018
  const numSamples = Math.floor(sampleRate * 1.8);
1840
4019
  const chL = new Float32Array(numSamples);
1841
4020
  const chR = new Float32Array(numSamples);
@@ -1846,10 +4025,10 @@ function seedStarterGraphAssets(graphDir) {
1846
4025
  chL[i] = s;
1847
4026
  chR[i] = s;
1848
4027
  }
1849
- fs6.writeFileSync(subBoomPath, createWavBuffer(sampleRate, 2, [chL, chR]));
4028
+ fs5.writeFileSync(subBoomPath, createWavBuffer(sampleRate, 2, [chL, chR]));
1850
4029
  }
1851
- const leadPath = path6.join(stereoDefaultDir, "celestial_lead.wav");
1852
- if (!fs6.existsSync(leadPath)) {
4030
+ const leadPath = path5.join(stereoDefaultDir, "celestial_lead.wav");
4031
+ if (!fs5.existsSync(leadPath)) {
1853
4032
  const numSamples = Math.floor(sampleRate * 2.5);
1854
4033
  const chL = new Float32Array(numSamples);
1855
4034
  const chR = new Float32Array(numSamples);
@@ -1863,10 +4042,10 @@ function seedStarterGraphAssets(graphDir) {
1863
4042
  chL[i] = s1 * env;
1864
4043
  chR[i] = (s1 + s2 * 0.5) * env;
1865
4044
  }
1866
- fs6.writeFileSync(leadPath, createWavBuffer(sampleRate, 2, [chL, chR]));
4045
+ fs5.writeFileSync(leadPath, createWavBuffer(sampleRate, 2, [chL, chR]));
1867
4046
  }
1868
- const padPath = path6.join(stereoDefaultDir, "ethereal_pad.wav");
1869
- if (!fs6.existsSync(padPath)) {
4047
+ const padPath = path5.join(stereoDefaultDir, "ethereal_pad.wav");
4048
+ if (!fs5.existsSync(padPath)) {
1870
4049
  const numSamples = Math.floor(sampleRate * 3);
1871
4050
  const chL = new Float32Array(numSamples);
1872
4051
  const chR = new Float32Array(numSamples);
@@ -1885,10 +4064,10 @@ function seedStarterGraphAssets(graphDir) {
1885
4064
  chL[i] = left * env;
1886
4065
  chR[i] = right * env;
1887
4066
  }
1888
- fs6.writeFileSync(padPath, createWavBuffer(sampleRate, 2, [chL, chR]));
4067
+ fs5.writeFileSync(padPath, createWavBuffer(sampleRate, 2, [chL, chR]));
1889
4068
  }
1890
- const snarePath = path6.join(monoDefaultDir, "snare_snappy.wav");
1891
- if (!fs6.existsSync(snarePath)) {
4069
+ const snarePath = path5.join(monoDefaultDir, "snare_snappy.wav");
4070
+ if (!fs5.existsSync(snarePath)) {
1892
4071
  const numSamples = Math.floor(sampleRate * 0.8);
1893
4072
  const chM = new Float32Array(numSamples);
1894
4073
  for (let i = 0; i < numSamples; i++) {
@@ -1898,10 +4077,10 @@ function seedStarterGraphAssets(graphDir) {
1898
4077
  const noise = (Math.random() * 2 - 1) * Math.exp(-p * 4.5) * 0.6;
1899
4078
  chM[i] = tone + noise;
1900
4079
  }
1901
- fs6.writeFileSync(snarePath, createWavBuffer(sampleRate, 1, [chM]));
4080
+ fs5.writeFileSync(snarePath, createWavBuffer(sampleRate, 1, [chM]));
1902
4081
  }
1903
- const hihatPath = path6.join(monoDefaultDir, "hihat_closed.wav");
1904
- if (!fs6.existsSync(hihatPath)) {
4082
+ const hihatPath = path5.join(monoDefaultDir, "hihat_closed.wav");
4083
+ if (!fs5.existsSync(hihatPath)) {
1905
4084
  const numSamples = Math.floor(sampleRate * 0.25);
1906
4085
  const chM = new Float32Array(numSamples);
1907
4086
  for (let i = 0; i < numSamples; i++) {
@@ -1909,10 +4088,10 @@ function seedStarterGraphAssets(graphDir) {
1909
4088
  const noise = (Math.random() * 2 - 1) * Math.exp(-p * 20);
1910
4089
  chM[i] = noise * 0.5;
1911
4090
  }
1912
- fs6.writeFileSync(hihatPath, createWavBuffer(sampleRate, 1, [chM]));
4091
+ fs5.writeFileSync(hihatPath, createWavBuffer(sampleRate, 1, [chM]));
1913
4092
  }
1914
- const midiChordPath = path6.join(midiDefaultDir, "lydian_celestial_pad.json");
1915
- if (!fs6.existsSync(midiChordPath)) {
4093
+ const midiChordPath = path5.join(midiDefaultDir, "lydian_celestial_pad.json");
4094
+ if (!fs5.existsSync(midiChordPath)) {
1916
4095
  const starterMidiPattern = {
1917
4096
  id: "pat-default-lydian",
1918
4097
  name: "Lydian Celestial Pad",
@@ -1925,43 +4104,43 @@ function seedStarterGraphAssets(graphDir) {
1925
4104
  { note: 71, name: "B4", velocity: 0.75, time: 0, duration: 3.8 }
1926
4105
  ]
1927
4106
  };
1928
- fs6.writeFileSync(midiChordPath, JSON.stringify(starterMidiPattern, null, 2), "utf-8");
4107
+ fs5.writeFileSync(midiChordPath, JSON.stringify(starterMidiPattern, null, 2), "utf-8");
1929
4108
  }
1930
4109
  }
1931
4110
  async function scanGraphAssets(graphId, includeImported = true) {
1932
- const safeGraphId = path6.basename(graphId);
1933
- const graphDir = path6.join(GRAPHS_ROOT, safeGraphId);
1934
- if (!fs6.existsSync(graphDir)) {
1935
- await fs6.promises.mkdir(graphDir, { recursive: true });
4111
+ const safeGraphId = path5.basename(graphId);
4112
+ const graphDir = path5.join(GRAPHS_ROOT, safeGraphId);
4113
+ if (!fs5.existsSync(graphDir)) {
4114
+ await fs5.promises.mkdir(graphDir, { recursive: true });
1936
4115
  }
1937
4116
  ensureGraphAssetDirectories(graphDir);
1938
4117
  const folders = [];
1939
4118
  const items = [];
1940
4119
  const importedItems = [];
1941
4120
  async function scanSingleGraph(gId, isImported, ownerName) {
1942
- const currentGraphDir = path6.join(GRAPHS_ROOT, path6.basename(gId));
1943
- const assetsDir = path6.join(currentGraphDir, "assets");
1944
- if (!fs6.existsSync(assetsDir)) return;
4121
+ const currentGraphDir = path5.join(GRAPHS_ROOT, path5.basename(gId));
4122
+ const assetsDir = path5.join(currentGraphDir, "assets");
4123
+ if (!fs5.existsSync(assetsDir)) return;
1945
4124
  async function scanCategoryFolder(dataType, channelType) {
1946
- const catDir = dataType === "audio" ? path6.join(assetsDir, "audio", channelType === "mono" ? "mono" : "stereo") : path6.join(assetsDir, "midi");
1947
- if (!fs6.existsSync(catDir)) return;
1948
- const subentries = await fs6.promises.readdir(catDir, { withFileTypes: true });
4125
+ const catDir = dataType === "audio" ? path5.join(assetsDir, "audio", channelType === "mono" ? "mono" : "stereo") : path5.join(assetsDir, "midi");
4126
+ if (!fs5.existsSync(catDir)) return;
4127
+ const subentries = await fs5.promises.readdir(catDir, { withFileTypes: true });
1949
4128
  for (const entry of subentries) {
1950
4129
  if (!entry.isDirectory()) continue;
1951
4130
  const subfolderName = entry.name;
1952
- const subfolderDir = path6.join(catDir, subfolderName);
1953
- const files = await fs6.promises.readdir(subfolderDir);
4131
+ const subfolderDir = path5.join(catDir, subfolderName);
4132
+ const files = await fs5.promises.readdir(subfolderDir);
1954
4133
  let validFileCount = 0;
1955
4134
  for (const file of files) {
1956
4135
  if (file.startsWith(".")) continue;
1957
- const ext = path6.extname(file).toLowerCase();
4136
+ const ext = path5.extname(file).toLowerCase();
1958
4137
  const isAudio = SUPPORTED_AUDIO_EXTS2.includes(ext);
1959
4138
  const isMidi = SUPPORTED_MIDI_EXTS2.includes(ext);
1960
4139
  if (dataType === "audio" && !isAudio || dataType === "midi" && !isMidi) continue;
1961
4140
  validFileCount++;
1962
- const filePath = path6.join(subfolderDir, file);
1963
- const stat = await fs6.promises.stat(filePath);
1964
- const cleanName = path6.basename(file, ext).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
4141
+ const filePath = path5.join(subfolderDir, file);
4142
+ const stat = await fs5.promises.stat(filePath);
4143
+ const cleanName = path5.basename(file, ext).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1965
4144
  const relPath = dataType === "audio" ? `audio/${channelType === "mono" ? "mono" : "stereo"}/${subfolderName}/${file}` : `midi/${subfolderName}/${file}`;
1966
4145
  const url = dataType === "audio" ? `/graphs/${gId}/assets/audio/${channelType === "mono" ? "mono" : "stereo"}/${subfolderName}/${file}` : `/graphs/${gId}/assets/midi/${subfolderName}/${file}`;
1967
4146
  const item = {
@@ -2004,18 +4183,18 @@ async function scanGraphAssets(graphId, includeImported = true) {
2004
4183
  }
2005
4184
  await scanSingleGraph(safeGraphId, false);
2006
4185
  if (includeImported) {
2007
- const qmsFile = path6.join(graphDir, "graph.qms");
2008
- if (fs6.existsSync(qmsFile)) {
4186
+ const qmsFile = path5.join(graphDir, "graph.qms");
4187
+ if (fs5.existsSync(qmsFile)) {
2009
4188
  try {
2010
- const raw = await fs6.promises.readFile(qmsFile, "utf-8");
4189
+ const raw = await fs5.promises.readFile(qmsFile, "utf-8");
2011
4190
  const pkg = JSON.parse(raw);
2012
4191
  const importedIds = pkg?.metadata?.importedGraphIds || [];
2013
4192
  for (const impId of importedIds) {
2014
- const impQms = path6.join(GRAPHS_ROOT, impId, "graph.qms");
4193
+ const impQms = path5.join(GRAPHS_ROOT, impId, "graph.qms");
2015
4194
  let impName = impId;
2016
- if (fs6.existsSync(impQms)) {
4195
+ if (fs5.existsSync(impQms)) {
2017
4196
  try {
2018
- const impRaw = await fs6.promises.readFile(impQms, "utf-8");
4197
+ const impRaw = await fs5.promises.readFile(impQms, "utf-8");
2019
4198
  const impPkg = JSON.parse(impRaw);
2020
4199
  impName = impPkg?.metadata?.name || impId;
2021
4200
  } catch {
@@ -2031,14 +4210,14 @@ async function scanGraphAssets(graphId, includeImported = true) {
2031
4210
  return { folders, items, importedItems };
2032
4211
  }
2033
4212
  async function createGraphAssetFolder(params) {
2034
- const safeGraphId = path6.basename(params.graphId);
2035
- const safeFolderName = path6.basename(params.folderName).trim().replace(/[^a-zA-Z0-9._-]/g, "_");
4213
+ const safeGraphId = path5.basename(params.graphId);
4214
+ const safeFolderName = path5.basename(params.folderName).trim().replace(/[^a-zA-Z0-9._-]/g, "_");
2036
4215
  if (!safeFolderName) throw new Error("Invalid subfolder name");
2037
- const graphDir = path6.join(GRAPHS_ROOT, safeGraphId);
4216
+ const graphDir = path5.join(GRAPHS_ROOT, safeGraphId);
2038
4217
  ensureGraphAssetDirectories(graphDir);
2039
- const targetDir = params.dataType === "audio" ? path6.join(graphDir, "assets", "audio", params.channelType === "mono" ? "mono" : "stereo", safeFolderName) : path6.join(graphDir, "assets", "midi", safeFolderName);
2040
- if (!fs6.existsSync(targetDir)) {
2041
- await fs6.promises.mkdir(targetDir, { recursive: true });
4218
+ const targetDir = params.dataType === "audio" ? path5.join(graphDir, "assets", "audio", params.channelType === "mono" ? "mono" : "stereo", safeFolderName) : path5.join(graphDir, "assets", "midi", safeFolderName);
4219
+ if (!fs5.existsSync(targetDir)) {
4220
+ await fs5.promises.mkdir(targetDir, { recursive: true });
2042
4221
  }
2043
4222
  return {
2044
4223
  name: safeFolderName,
@@ -2050,21 +4229,21 @@ async function createGraphAssetFolder(params) {
2050
4229
  };
2051
4230
  }
2052
4231
  async function saveAssetToGraph(params) {
2053
- const safeGraphId = path6.basename(params.graphId);
4232
+ const safeGraphId = path5.basename(params.graphId);
2054
4233
  const subfolder = params.subfolder || "userdefined";
2055
- const graphDir = path6.join(GRAPHS_ROOT, safeGraphId);
4234
+ const graphDir = path5.join(GRAPHS_ROOT, safeGraphId);
2056
4235
  ensureGraphAssetDirectories(graphDir);
2057
- const targetDir = params.dataType === "audio" ? path6.join(graphDir, "assets", "audio", params.channelType === "mono" ? "mono" : "stereo", subfolder) : path6.join(graphDir, "assets", "midi", subfolder);
2058
- if (!fs6.existsSync(targetDir)) {
2059
- await fs6.promises.mkdir(targetDir, { recursive: true });
4236
+ const targetDir = params.dataType === "audio" ? path5.join(graphDir, "assets", "audio", params.channelType === "mono" ? "mono" : "stereo", subfolder) : path5.join(graphDir, "assets", "midi", subfolder);
4237
+ if (!fs5.existsSync(targetDir)) {
4238
+ await fs5.promises.mkdir(targetDir, { recursive: true });
2060
4239
  }
2061
- const safeFilename = path6.basename(params.filename).replace(/[^a-zA-Z0-9._-]/g, "_");
2062
- const ext = path6.extname(safeFilename).toLowerCase() || (params.dataType === "audio" ? ".wav" : ".json");
2063
- const filePath = path6.join(targetDir, safeFilename);
4240
+ const safeFilename = path5.basename(params.filename).replace(/[^a-zA-Z0-9._-]/g, "_");
4241
+ const ext = path5.extname(safeFilename).toLowerCase() || (params.dataType === "audio" ? ".wav" : ".json");
4242
+ const filePath = path5.join(targetDir, safeFilename);
2064
4243
  const buffer = Buffer.from(params.base64Content, "base64");
2065
- await fs6.promises.writeFile(filePath, buffer);
2066
- const stat = await fs6.promises.stat(filePath);
2067
- const cleanName = path6.basename(safeFilename, ext).replace(/[-_]/g, " ");
4244
+ await fs5.promises.writeFile(filePath, buffer);
4245
+ const stat = await fs5.promises.stat(filePath);
4246
+ const cleanName = path5.basename(safeFilename, ext).replace(/[-_]/g, " ");
2068
4247
  const relPath = params.dataType === "audio" ? `audio/${params.channelType === "mono" ? "mono" : "stereo"}/${subfolder}/${safeFilename}` : `midi/${subfolder}/${safeFilename}`;
2069
4248
  const url = params.dataType === "audio" ? `/graphs/${safeGraphId}/assets/audio/${params.channelType === "mono" ? "mono" : "stereo"}/${subfolder}/${safeFilename}` : `/graphs/${safeGraphId}/assets/midi/${subfolder}/${safeFilename}`;
2070
4249
  return {
@@ -2083,32 +4262,32 @@ async function saveAssetToGraph(params) {
2083
4262
  };
2084
4263
  }
2085
4264
  async function deleteAssetFromGraph(params) {
2086
- const safeGraphId = path6.basename(params.graphId);
2087
- const graphDir = path6.join(GRAPHS_ROOT, safeGraphId);
2088
- const targetDir = params.dataType === "audio" ? path6.join(graphDir, "assets", "audio", params.channelType === "mono" ? "mono" : "stereo", params.subfolder) : path6.join(graphDir, "assets", "midi", params.subfolder);
2089
- const filePath = path6.join(targetDir, path6.basename(params.filename));
2090
- if (fs6.existsSync(filePath)) {
2091
- await fs6.promises.unlink(filePath);
4265
+ const safeGraphId = path5.basename(params.graphId);
4266
+ const graphDir = path5.join(GRAPHS_ROOT, safeGraphId);
4267
+ const targetDir = params.dataType === "audio" ? path5.join(graphDir, "assets", "audio", params.channelType === "mono" ? "mono" : "stereo", params.subfolder) : path5.join(graphDir, "assets", "midi", params.subfolder);
4268
+ const filePath = path5.join(targetDir, path5.basename(params.filename));
4269
+ if (fs5.existsSync(filePath)) {
4270
+ await fs5.promises.unlink(filePath);
2092
4271
  return true;
2093
4272
  }
2094
4273
  return false;
2095
4274
  }
2096
4275
 
2097
4276
  // src/server/services/graphPackageService.ts
2098
- import fs7 from "fs";
2099
- import path7 from "path";
2100
- import { validateGraphImport } from "@ultrst-armada/kloks/state-graph";
2101
- var WORKSPACE_ROOT2 = path7.join(process.cwd(), "workspaces", "default");
2102
- var GRAPHS_ROOT2 = path7.join(WORKSPACE_ROOT2, "graphs");
2103
- var LEGACY_PROJECTS_ROOT = path7.join(process.cwd(), "projects");
4277
+ import fs6 from "fs";
4278
+ import path6 from "path";
4279
+ import { validateGraphImport } from "@kloks/state-graph";
4280
+ var WORKSPACE_ROOT2 = path6.join(process.cwd(), "workspaces", "default");
4281
+ var GRAPHS_ROOT2 = path6.join(WORKSPACE_ROOT2, "graphs");
4282
+ var LEGACY_PROJECTS_ROOT = path6.join(process.cwd(), "projects");
2104
4283
  function ensureWorkspaceStructure() {
2105
- if (!fs7.existsSync(GRAPHS_ROOT2)) {
2106
- fs7.mkdirSync(GRAPHS_ROOT2, { recursive: true });
4284
+ if (!fs6.existsSync(GRAPHS_ROOT2)) {
4285
+ fs6.mkdirSync(GRAPHS_ROOT2, { recursive: true });
2107
4286
  }
2108
4287
  seedStarterGraphsIfEmpty();
2109
4288
  }
2110
4289
  function seedStarterGraphsIfEmpty() {
2111
- const existing = fs7.existsSync(GRAPHS_ROOT2) ? fs7.readdirSync(GRAPHS_ROOT2) : [];
4290
+ const existing = fs6.existsSync(GRAPHS_ROOT2) ? fs6.readdirSync(GRAPHS_ROOT2) : [];
2112
4291
  if (existing.length > 0) return;
2113
4292
  const introId = "intro-motif-starter";
2114
4293
  createInitialGraphPackageSync(
@@ -2377,9 +4556,9 @@ function seedStarterGraphsIfEmpty() {
2377
4556
  );
2378
4557
  }
2379
4558
  function createInitialGraphPackageSync(id, name, description, type, bpm, masterVolume, currentBiome, nodes, connections, importedGraphIds = []) {
2380
- const graphDir = path7.join(GRAPHS_ROOT2, id);
2381
- if (!fs7.existsSync(graphDir)) {
2382
- fs7.mkdirSync(graphDir, { recursive: true });
4559
+ const graphDir = path6.join(GRAPHS_ROOT2, id);
4560
+ if (!fs6.existsSync(graphDir)) {
4561
+ fs6.mkdirSync(graphDir, { recursive: true });
2383
4562
  }
2384
4563
  ensureGraphAssetDirectories(graphDir);
2385
4564
  seedStarterGraphAssets(graphDir);
@@ -2412,29 +4591,29 @@ function createInitialGraphPackageSync(id, name, description, type, bpm, masterV
2412
4591
  connections
2413
4592
  }
2414
4593
  };
2415
- fs7.writeFileSync(path7.join(graphDir, "graph.qms"), JSON.stringify(graphPackage, null, 2), "utf-8");
4594
+ fs6.writeFileSync(path6.join(graphDir, "graph.qms"), JSON.stringify(graphPackage, null, 2), "utf-8");
2416
4595
  }
2417
4596
  async function listAllGraphPackages() {
2418
4597
  ensureWorkspaceStructure();
2419
- const dirs = await fs7.promises.readdir(GRAPHS_ROOT2);
4598
+ const dirs = await fs6.promises.readdir(GRAPHS_ROOT2);
2420
4599
  const packages = [];
2421
4600
  for (const dir of dirs) {
2422
- const graphDir = path7.join(GRAPHS_ROOT2, dir);
2423
- const stat = await fs7.promises.stat(graphDir);
4601
+ const graphDir = path6.join(GRAPHS_ROOT2, dir);
4602
+ const stat = await fs6.promises.stat(graphDir);
2424
4603
  if (!stat.isDirectory()) continue;
2425
- const qmsFile = path7.join(graphDir, "graph.qms");
2426
- const legacyProjectFile = path7.join(graphDir, "project.json");
4604
+ const qmsFile = path6.join(graphDir, "graph.qms");
4605
+ const legacyProjectFile = path6.join(graphDir, "project.json");
2427
4606
  let packageData = null;
2428
- if (fs7.existsSync(qmsFile)) {
4607
+ if (fs6.existsSync(qmsFile)) {
2429
4608
  try {
2430
- const raw = await fs7.promises.readFile(qmsFile, "utf-8");
4609
+ const raw = await fs6.promises.readFile(qmsFile, "utf-8");
2431
4610
  packageData = JSON.parse(raw);
2432
4611
  } catch (err) {
2433
4612
  console.warn(`Corrupted graph.qms in ${dir}:`, err);
2434
4613
  }
2435
- } else if (fs7.existsSync(legacyProjectFile)) {
4614
+ } else if (fs6.existsSync(legacyProjectFile)) {
2436
4615
  try {
2437
- const raw = await fs7.promises.readFile(legacyProjectFile, "utf-8");
4616
+ const raw = await fs6.promises.readFile(legacyProjectFile, "utf-8");
2438
4617
  const legacy = JSON.parse(raw);
2439
4618
  packageData = {
2440
4619
  format: "qms_graph_package",
@@ -2464,7 +4643,7 @@ async function listAllGraphPackages() {
2464
4643
  mapIoConfig: legacy.mapIoConfig
2465
4644
  }
2466
4645
  };
2467
- await fs7.promises.writeFile(qmsFile, JSON.stringify(packageData, null, 2), "utf-8");
4646
+ await fs6.promises.writeFile(qmsFile, JSON.stringify(packageData, null, 2), "utf-8");
2468
4647
  } catch (err) {
2469
4648
  console.warn(`Failed to auto-migrate legacy project ${dir}:`, err);
2470
4649
  }
@@ -2486,13 +4665,13 @@ async function listAllGraphPackages() {
2486
4665
  }
2487
4666
  async function getGraphPackageById(id) {
2488
4667
  ensureWorkspaceStructure();
2489
- const safeId = path7.basename(id);
2490
- const graphDir = path7.join(GRAPHS_ROOT2, safeId);
2491
- const qmsFile = path7.join(graphDir, "graph.qms");
2492
- if (!fs7.existsSync(qmsFile)) {
2493
- const legacyProject = path7.join(LEGACY_PROJECTS_ROOT, safeId, "project.json");
2494
- if (fs7.existsSync(legacyProject)) {
2495
- const raw2 = await fs7.promises.readFile(legacyProject, "utf-8");
4668
+ const safeId = path6.basename(id);
4669
+ const graphDir = path6.join(GRAPHS_ROOT2, safeId);
4670
+ const qmsFile = path6.join(graphDir, "graph.qms");
4671
+ if (!fs6.existsSync(qmsFile)) {
4672
+ const legacyProject = path6.join(LEGACY_PROJECTS_ROOT, safeId, "project.json");
4673
+ if (fs6.existsSync(legacyProject)) {
4674
+ const raw2 = await fs6.promises.readFile(legacyProject, "utf-8");
2496
4675
  const legacy = JSON.parse(raw2);
2497
4676
  return {
2498
4677
  format: "qms_graph_package",
@@ -2525,7 +4704,7 @@ async function getGraphPackageById(id) {
2525
4704
  }
2526
4705
  return null;
2527
4706
  }
2528
- const raw = await fs7.promises.readFile(qmsFile, "utf-8");
4707
+ const raw = await fs6.promises.readFile(qmsFile, "utf-8");
2529
4708
  return JSON.parse(raw);
2530
4709
  }
2531
4710
  async function createGraphPackage(params) {
@@ -2533,8 +4712,8 @@ async function createGraphPackage(params) {
2533
4712
  const name = params.name.trim();
2534
4713
  const safeSlug = name.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-");
2535
4714
  const id = `${safeSlug}-${Date.now().toString(36)}`;
2536
- const graphDir = path7.join(GRAPHS_ROOT2, id);
2537
- await fs7.promises.mkdir(graphDir, { recursive: true });
4715
+ const graphDir = path6.join(GRAPHS_ROOT2, id);
4716
+ await fs6.promises.mkdir(graphDir, { recursive: true });
2538
4717
  await ensureGraphAssetDirectories(graphDir);
2539
4718
  seedStarterGraphAssets(graphDir);
2540
4719
  const now = Date.now();
@@ -2569,8 +4748,8 @@ async function createGraphPackage(params) {
2569
4748
  connections: params.connections || []
2570
4749
  }
2571
4750
  };
2572
- await fs7.promises.writeFile(
2573
- path7.join(graphDir, "graph.qms"),
4751
+ await fs6.promises.writeFile(
4752
+ path6.join(graphDir, "graph.qms"),
2574
4753
  JSON.stringify(graphPackage, null, 2),
2575
4754
  "utf-8"
2576
4755
  );
@@ -2578,10 +4757,10 @@ async function createGraphPackage(params) {
2578
4757
  }
2579
4758
  async function saveGraphPackage(id, payload) {
2580
4759
  ensureWorkspaceStructure();
2581
- const safeId = path7.basename(id);
2582
- const graphDir = path7.join(GRAPHS_ROOT2, safeId);
2583
- if (!fs7.existsSync(graphDir)) {
2584
- await fs7.promises.mkdir(graphDir, { recursive: true });
4760
+ const safeId = path6.basename(id);
4761
+ const graphDir = path6.join(GRAPHS_ROOT2, safeId);
4762
+ if (!fs6.existsSync(graphDir)) {
4763
+ await fs6.promises.mkdir(graphDir, { recursive: true });
2585
4764
  await ensureGraphAssetDirectories(graphDir);
2586
4765
  }
2587
4766
  const existing = await getGraphPackageById(safeId);
@@ -2616,18 +4795,18 @@ async function saveGraphPackage(id, payload) {
2616
4795
  mapIoConfig: payload.mapIoConfig || existing?.graph?.mapIoConfig
2617
4796
  }
2618
4797
  };
2619
- await fs7.promises.writeFile(
2620
- path7.join(graphDir, "graph.qms"),
4798
+ await fs6.promises.writeFile(
4799
+ path6.join(graphDir, "graph.qms"),
2621
4800
  JSON.stringify(graphPackage, null, 2),
2622
4801
  "utf-8"
2623
4802
  );
2624
4803
  return graphPackage;
2625
4804
  }
2626
4805
  async function deleteGraphPackage(id) {
2627
- const safeId = path7.basename(id);
2628
- const graphDir = path7.join(GRAPHS_ROOT2, safeId);
2629
- if (!fs7.existsSync(graphDir)) return false;
2630
- await fs7.promises.rm(graphDir, { recursive: true, force: true });
4806
+ const safeId = path6.basename(id);
4807
+ const graphDir = path6.join(GRAPHS_ROOT2, safeId);
4808
+ if (!fs6.existsSync(graphDir)) return false;
4809
+ await fs6.promises.rm(graphDir, { recursive: true, force: true });
2631
4810
  return true;
2632
4811
  }
2633
4812
  async function importGraphDependency(targetGraphId, candidateImportId) {
@@ -2669,26 +4848,26 @@ async function removeGraphDependency(targetGraphId, importIdToRemove) {
2669
4848
  }
2670
4849
 
2671
4850
  // src/server/services/projectService.ts
2672
- import fs8 from "fs";
2673
- import path8 from "path";
2674
- var PROJECTS_ROOT2 = path8.join(process.cwd(), "projects");
4851
+ import fs7 from "fs";
4852
+ import path7 from "path";
4853
+ var PROJECTS_ROOT2 = path7.join(process.cwd(), "projects");
2675
4854
  var customStarterProjectTemplate = null;
2676
4855
  function configureStarterProjectTemplate(template) {
2677
4856
  customStarterProjectTemplate = template;
2678
4857
  }
2679
4858
  function ensureProjectsRoot(starterTemplate) {
2680
- if (!fs8.existsSync(PROJECTS_ROOT2)) {
2681
- fs8.mkdirSync(PROJECTS_ROOT2, { recursive: true });
4859
+ if (!fs7.existsSync(PROJECTS_ROOT2)) {
4860
+ fs7.mkdirSync(PROJECTS_ROOT2, { recursive: true });
2682
4861
  }
2683
4862
  const template = starterTemplate || customStarterProjectTemplate;
2684
4863
  if (template && template.id) {
2685
- const defaultProjDir = path8.join(PROJECTS_ROOT2, String(template.id));
2686
- if (!fs8.existsSync(defaultProjDir)) {
2687
- fs8.mkdirSync(defaultProjDir, { recursive: true });
2688
- fs8.mkdirSync(path8.join(defaultProjDir, "samples"), { recursive: true });
2689
- fs8.mkdirSync(path8.join(defaultProjDir, "assets"), { recursive: true });
2690
- fs8.writeFileSync(
2691
- path8.join(defaultProjDir, "project.json"),
4864
+ const defaultProjDir = path7.join(PROJECTS_ROOT2, String(template.id));
4865
+ if (!fs7.existsSync(defaultProjDir)) {
4866
+ fs7.mkdirSync(defaultProjDir, { recursive: true });
4867
+ fs7.mkdirSync(path7.join(defaultProjDir, "samples"), { recursive: true });
4868
+ fs7.mkdirSync(path7.join(defaultProjDir, "assets"), { recursive: true });
4869
+ fs7.writeFileSync(
4870
+ path7.join(defaultProjDir, "project.json"),
2692
4871
  JSON.stringify({ ...template, createdAt: Date.now(), updatedAt: Date.now() }, null, 2),
2693
4872
  "utf-8"
2694
4873
  );
@@ -2697,22 +4876,22 @@ function ensureProjectsRoot(starterTemplate) {
2697
4876
  }
2698
4877
  async function listAllProjects() {
2699
4878
  ensureProjectsRoot();
2700
- const dirs = await fs8.promises.readdir(PROJECTS_ROOT2);
4879
+ const dirs = await fs7.promises.readdir(PROJECTS_ROOT2);
2701
4880
  const projects = [];
2702
4881
  for (const dir of dirs) {
2703
- const projDir = path8.join(PROJECTS_ROOT2, dir);
2704
- const stat = await fs8.promises.stat(projDir);
4882
+ const projDir = path7.join(PROJECTS_ROOT2, dir);
4883
+ const stat = await fs7.promises.stat(projDir);
2705
4884
  if (!stat.isDirectory()) continue;
2706
- const projectFile = path8.join(projDir, "project.json");
2707
- const samplesDir = path8.join(projDir, "samples");
4885
+ const projectFile = path7.join(projDir, "project.json");
4886
+ const samplesDir = path7.join(projDir, "samples");
2708
4887
  let sampleCount = 0;
2709
- if (fs8.existsSync(samplesDir)) {
2710
- const samples = await fs8.promises.readdir(samplesDir);
4888
+ if (fs7.existsSync(samplesDir)) {
4889
+ const samples = await fs7.promises.readdir(samplesDir);
2711
4890
  sampleCount = samples.filter((f) => !f.startsWith(".")).length;
2712
4891
  }
2713
- if (fs8.existsSync(projectFile)) {
4892
+ if (fs7.existsSync(projectFile)) {
2714
4893
  try {
2715
- const raw = await fs8.promises.readFile(projectFile, "utf-8");
4894
+ const raw = await fs7.promises.readFile(projectFile, "utf-8");
2716
4895
  const data = JSON.parse(raw);
2717
4896
  projects.push({
2718
4897
  id: data.id || dir,
@@ -2732,24 +4911,24 @@ async function listAllProjects() {
2732
4911
  return projects.sort((a, b) => b.updatedAt - a.updatedAt);
2733
4912
  }
2734
4913
  async function getProjectById(id) {
2735
- const safeId = path8.basename(id);
2736
- const projectFile = path8.join(PROJECTS_ROOT2, safeId, "project.json");
2737
- if (!fs8.existsSync(projectFile)) return null;
2738
- const raw = await fs8.promises.readFile(projectFile, "utf-8");
4914
+ const safeId = path7.basename(id);
4915
+ const projectFile = path7.join(PROJECTS_ROOT2, safeId, "project.json");
4916
+ if (!fs7.existsSync(projectFile)) return null;
4917
+ const raw = await fs7.promises.readFile(projectFile, "utf-8");
2739
4918
  return JSON.parse(raw);
2740
4919
  }
2741
4920
  async function saveProject(id, payload) {
2742
- const safeId = path8.basename(id);
2743
- const projDir = path8.join(PROJECTS_ROOT2, safeId);
2744
- const projectFile = path8.join(projDir, "project.json");
2745
- if (!fs8.existsSync(projDir)) {
2746
- await fs8.promises.mkdir(projDir, { recursive: true });
2747
- await fs8.promises.mkdir(path8.join(projDir, "samples"), { recursive: true });
2748
- await fs8.promises.mkdir(path8.join(projDir, "assets"), { recursive: true });
4921
+ const safeId = path7.basename(id);
4922
+ const projDir = path7.join(PROJECTS_ROOT2, safeId);
4923
+ const projectFile = path7.join(projDir, "project.json");
4924
+ if (!fs7.existsSync(projDir)) {
4925
+ await fs7.promises.mkdir(projDir, { recursive: true });
4926
+ await fs7.promises.mkdir(path7.join(projDir, "samples"), { recursive: true });
4927
+ await fs7.promises.mkdir(path7.join(projDir, "assets"), { recursive: true });
2749
4928
  }
2750
4929
  let existingData = {};
2751
- if (fs8.existsSync(projectFile)) {
2752
- const raw = await fs8.promises.readFile(projectFile, "utf-8");
4930
+ if (fs7.existsSync(projectFile)) {
4931
+ const raw = await fs7.promises.readFile(projectFile, "utf-8");
2753
4932
  existingData = JSON.parse(raw);
2754
4933
  }
2755
4934
  const updatedData = {
@@ -2758,22 +4937,22 @@ async function saveProject(id, payload) {
2758
4937
  id: safeId,
2759
4938
  updatedAt: Date.now()
2760
4939
  };
2761
- await fs8.promises.writeFile(projectFile, JSON.stringify(updatedData, null, 2), "utf-8");
4940
+ await fs7.promises.writeFile(projectFile, JSON.stringify(updatedData, null, 2), "utf-8");
2762
4941
  return updatedData;
2763
4942
  }
2764
4943
  async function deleteProject(id) {
2765
- const safeId = path8.basename(id);
2766
- const projDir = path8.join(PROJECTS_ROOT2, safeId);
2767
- if (!fs8.existsSync(projDir)) return false;
2768
- await fs8.promises.rm(projDir, { recursive: true, force: true });
4944
+ const safeId = path7.basename(id);
4945
+ const projDir = path7.join(PROJECTS_ROOT2, safeId);
4946
+ if (!fs7.existsSync(projDir)) return false;
4947
+ await fs7.promises.rm(projDir, { recursive: true, force: true });
2769
4948
  return true;
2770
4949
  }
2771
4950
 
2772
4951
  // src/server/services/sampleService.ts
2773
- import fs9 from "fs";
2774
- import path9 from "path";
2775
- var SAMPLES_GLOBAL_ROOT = path9.join(process.cwd(), "samples", "global");
2776
- var PROJECTS_ROOT3 = path9.join(process.cwd(), "projects");
4952
+ import fs8 from "fs";
4953
+ import path8 from "path";
4954
+ var SAMPLES_GLOBAL_ROOT = path8.join(process.cwd(), "samples", "global");
4955
+ var PROJECTS_ROOT3 = path8.join(process.cwd(), "projects");
2777
4956
  var SUPPORTED_AUDIO_EXTS3 = [".wav", ".mp3", ".ogg", ".flac", ".m4a", ".webm", ".weba", ".aac", ".aiff", ".aif", ".opus", ".mp4"];
2778
4957
  function initGlobalSamplesDirectory() {
2779
4958
  ensureGlobalSamples(SAMPLES_GLOBAL_ROOT).catch((err) => {
@@ -2781,17 +4960,17 @@ function initGlobalSamplesDirectory() {
2781
4960
  });
2782
4961
  }
2783
4962
  async function listGlobalSamples() {
2784
- if (!fs9.existsSync(SAMPLES_GLOBAL_ROOT)) {
2785
- await fs9.promises.mkdir(SAMPLES_GLOBAL_ROOT, { recursive: true });
4963
+ if (!fs8.existsSync(SAMPLES_GLOBAL_ROOT)) {
4964
+ await fs8.promises.mkdir(SAMPLES_GLOBAL_ROOT, { recursive: true });
2786
4965
  }
2787
- const files = await fs9.promises.readdir(SAMPLES_GLOBAL_ROOT);
4966
+ const files = await fs8.promises.readdir(SAMPLES_GLOBAL_ROOT);
2788
4967
  const samples = [];
2789
4968
  for (const file of files) {
2790
- const ext = path9.extname(file).toLowerCase();
4969
+ const ext = path8.extname(file).toLowerCase();
2791
4970
  if (!SUPPORTED_AUDIO_EXTS3.includes(ext)) continue;
2792
- const filePath = path9.join(SAMPLES_GLOBAL_ROOT, file);
2793
- const stat = await fs9.promises.stat(filePath);
2794
- const baseName = path9.basename(file, ext);
4971
+ const filePath = path8.join(SAMPLES_GLOBAL_ROOT, file);
4972
+ const stat = await fs8.promises.stat(filePath);
4973
+ const baseName = path8.basename(file, ext);
2795
4974
  const cleanName = baseName.replace(/[-_]/g, " ");
2796
4975
  let category = "synth";
2797
4976
  if (/808|kick|snare|hihat|drum|perc/i.test(baseName)) category = "drums";
@@ -2811,15 +4990,15 @@ async function listGlobalSamples() {
2811
4990
  return samples;
2812
4991
  }
2813
4992
  async function saveGlobalSample(filename, base64Content, category) {
2814
- const safeFilename = path9.basename(filename).replace(/[^a-zA-Z0-9._-]/g, "_");
2815
- const ext = path9.extname(safeFilename).toLowerCase() || ".wav";
2816
- const filePath = path9.join(SAMPLES_GLOBAL_ROOT, safeFilename);
4993
+ const safeFilename = path8.basename(filename).replace(/[^a-zA-Z0-9._-]/g, "_");
4994
+ const ext = path8.extname(safeFilename).toLowerCase() || ".wav";
4995
+ const filePath = path8.join(SAMPLES_GLOBAL_ROOT, safeFilename);
2817
4996
  const buffer = Buffer.from(base64Content, "base64");
2818
- await fs9.promises.writeFile(filePath, buffer);
2819
- const stat = await fs9.promises.stat(filePath);
4997
+ await fs8.promises.writeFile(filePath, buffer);
4998
+ const stat = await fs8.promises.stat(filePath);
2820
4999
  return {
2821
5000
  filename: safeFilename,
2822
- name: path9.basename(safeFilename, ext).replace(/[-_]/g, " "),
5001
+ name: path8.basename(safeFilename, ext).replace(/[-_]/g, " "),
2823
5002
  category: category || "custom",
2824
5003
  format: ext.replace(".", "").toUpperCase(),
2825
5004
  url: `/samples/global/${safeFilename}`,
@@ -2829,28 +5008,28 @@ async function saveGlobalSample(filename, base64Content, category) {
2829
5008
  };
2830
5009
  }
2831
5010
  async function deleteGlobalSample(filename) {
2832
- const safeFilename = path9.basename(filename);
2833
- const filePath = path9.join(SAMPLES_GLOBAL_ROOT, safeFilename);
2834
- if (fs9.existsSync(filePath)) {
2835
- await fs9.promises.unlink(filePath);
5011
+ const safeFilename = path8.basename(filename);
5012
+ const filePath = path8.join(SAMPLES_GLOBAL_ROOT, safeFilename);
5013
+ if (fs8.existsSync(filePath)) {
5014
+ await fs8.promises.unlink(filePath);
2836
5015
  return true;
2837
5016
  }
2838
5017
  return false;
2839
5018
  }
2840
5019
  async function listProjectSamples(projectId) {
2841
- const safeId = path9.basename(projectId);
2842
- const samplesDir = path9.join(PROJECTS_ROOT3, safeId, "samples");
2843
- if (!fs9.existsSync(samplesDir)) {
2844
- await fs9.promises.mkdir(samplesDir, { recursive: true });
5020
+ const safeId = path8.basename(projectId);
5021
+ const samplesDir = path8.join(PROJECTS_ROOT3, safeId, "samples");
5022
+ if (!fs8.existsSync(samplesDir)) {
5023
+ await fs8.promises.mkdir(samplesDir, { recursive: true });
2845
5024
  }
2846
- const files = await fs9.promises.readdir(samplesDir);
5025
+ const files = await fs8.promises.readdir(samplesDir);
2847
5026
  const samples = [];
2848
5027
  for (const file of files) {
2849
- const ext = path9.extname(file).toLowerCase();
5028
+ const ext = path8.extname(file).toLowerCase();
2850
5029
  if (!SUPPORTED_AUDIO_EXTS3.includes(ext)) continue;
2851
- const filePath = path9.join(samplesDir, file);
2852
- const stat = await fs9.promises.stat(filePath);
2853
- const baseName = path9.basename(file, ext);
5030
+ const filePath = path8.join(samplesDir, file);
5031
+ const stat = await fs8.promises.stat(filePath);
5032
+ const baseName = path8.basename(file, ext);
2854
5033
  samples.push({
2855
5034
  filename: file,
2856
5035
  name: baseName.replace(/[-_]/g, " "),
@@ -2866,21 +5045,21 @@ async function listProjectSamples(projectId) {
2866
5045
  return samples;
2867
5046
  }
2868
5047
  async function saveProjectSample(projectId, filename, base64Content, isRecording) {
2869
- const safeId = path9.basename(projectId);
2870
- const samplesDir = path9.join(PROJECTS_ROOT3, safeId, "samples");
2871
- if (!fs9.existsSync(samplesDir)) {
2872
- await fs9.promises.mkdir(samplesDir, { recursive: true });
5048
+ const safeId = path8.basename(projectId);
5049
+ const samplesDir = path8.join(PROJECTS_ROOT3, safeId, "samples");
5050
+ if (!fs8.existsSync(samplesDir)) {
5051
+ await fs8.promises.mkdir(samplesDir, { recursive: true });
2873
5052
  }
2874
5053
  const defaultName = isRecording ? `recording_take_${Date.now()}.wav` : `clip_${Date.now()}.wav`;
2875
- const targetFilename = filename ? path9.basename(filename).replace(/[^a-zA-Z0-9._-]/g, "_") : defaultName;
2876
- const ext = path9.extname(targetFilename).toLowerCase() || ".wav";
2877
- const filePath = path9.join(samplesDir, targetFilename);
5054
+ const targetFilename = filename ? path8.basename(filename).replace(/[^a-zA-Z0-9._-]/g, "_") : defaultName;
5055
+ const ext = path8.extname(targetFilename).toLowerCase() || ".wav";
5056
+ const filePath = path8.join(samplesDir, targetFilename);
2878
5057
  const buffer = Buffer.from(base64Content, "base64");
2879
- await fs9.promises.writeFile(filePath, buffer);
2880
- const stat = await fs9.promises.stat(filePath);
5058
+ await fs8.promises.writeFile(filePath, buffer);
5059
+ const stat = await fs8.promises.stat(filePath);
2881
5060
  return {
2882
5061
  filename: targetFilename,
2883
- name: path9.basename(targetFilename, ext).replace(/[-_]/g, " "),
5062
+ name: path8.basename(targetFilename, ext).replace(/[-_]/g, " "),
2884
5063
  category: isRecording ? "recording" : "import",
2885
5064
  format: ext.replace(".", "").toUpperCase(),
2886
5065
  url: `/projects/${safeId}/samples/${targetFilename}`,
@@ -2891,26 +5070,26 @@ async function saveProjectSample(projectId, filename, base64Content, isRecording
2891
5070
  };
2892
5071
  }
2893
5072
  async function deleteProjectSample(projectId, filename) {
2894
- const safeId = path9.basename(projectId);
2895
- const safeFilename = path9.basename(filename);
2896
- const filePath = path9.join(PROJECTS_ROOT3, safeId, "samples", safeFilename);
2897
- if (fs9.existsSync(filePath)) {
2898
- await fs9.promises.unlink(filePath);
5073
+ const safeId = path8.basename(projectId);
5074
+ const safeFilename = path8.basename(filename);
5075
+ const filePath = path8.join(PROJECTS_ROOT3, safeId, "samples", safeFilename);
5076
+ if (fs8.existsSync(filePath)) {
5077
+ await fs8.promises.unlink(filePath);
2899
5078
  return true;
2900
5079
  }
2901
5080
  return false;
2902
5081
  }
2903
5082
 
2904
5083
  // src/server/services/workspaceGraphService.ts
2905
- import fs10 from "fs";
2906
- import path10 from "path";
2907
- var AUTH_FILE_PATH = path10.resolve(".skipo/accounts.json");
5084
+ import fs9 from "fs";
5085
+ import path9 from "path";
5086
+ var AUTH_FILE_PATH = path9.resolve(".skipo/accounts.json");
2908
5087
  async function resolveWorkspaceDir(workspaceIdOrSlug) {
2909
5088
  const workingDir = getWorkingWorkspaceDir();
2910
5089
  let targetWs = null;
2911
5090
  try {
2912
- if (fs10.existsSync(AUTH_FILE_PATH)) {
2913
- const raw = await fs10.promises.readFile(AUTH_FILE_PATH, "utf-8");
5091
+ if (fs9.existsSync(AUTH_FILE_PATH)) {
5092
+ const raw = await fs9.promises.readFile(AUTH_FILE_PATH, "utf-8");
2914
5093
  const accounts = JSON.parse(raw);
2915
5094
  for (const user of Object.values(accounts)) {
2916
5095
  if (Array.isArray(user.workspaces)) {
@@ -2930,35 +5109,35 @@ async function resolveWorkspaceDir(workspaceIdOrSlug) {
2930
5109
  if (targetWs) {
2931
5110
  const rawFolderName = targetWs.localFolderName || targetWs.name.replace(/[/\\?%*:|"<>]/g, "_");
2932
5111
  const folderName = rawFolderName.trim();
2933
- await fs10.promises.mkdir(workingDir, { recursive: true });
5112
+ await fs9.promises.mkdir(workingDir, { recursive: true });
2934
5113
  return { wsDir: workingDir, wsName: targetWs.name, wsFolder: folderName, wsId: targetWs.id };
2935
5114
  }
2936
- await fs10.promises.mkdir(workingDir, { recursive: true });
5115
+ await fs9.promises.mkdir(workingDir, { recursive: true });
2937
5116
  return { wsDir: workingDir, wsName: "Active Workspace", wsFolder: "workspace", wsId: workspaceIdOrSlug || "default" };
2938
5117
  }
2939
5118
  async function listWorkspaceGraphs(workspaceIdOrSlug) {
2940
5119
  const { wsDir, wsFolder } = await resolveWorkspaceDir(workspaceIdOrSlug);
2941
5120
  const graphs = [];
2942
5121
  const inspectDirForGraph = async (dirPath, folderName) => {
2943
- const projectJson = path10.join(dirPath, "project.json");
2944
- const graphQms = path10.join(dirPath, "graph.qms");
5122
+ const projectJson = path9.join(dirPath, "project.json");
5123
+ const graphQms = path9.join(dirPath, "graph.qms");
2945
5124
  let data = null;
2946
- if (fs10.existsSync(projectJson)) {
5125
+ if (fs9.existsSync(projectJson)) {
2947
5126
  try {
2948
- const raw = await fs10.promises.readFile(projectJson, "utf-8");
5127
+ const raw = await fs9.promises.readFile(projectJson, "utf-8");
2949
5128
  data = JSON.parse(raw);
2950
5129
  } catch {
2951
5130
  }
2952
- } else if (fs10.existsSync(graphQms)) {
5131
+ } else if (fs9.existsSync(graphQms)) {
2953
5132
  try {
2954
- const raw = await fs10.promises.readFile(graphQms, "utf-8");
5133
+ const raw = await fs9.promises.readFile(graphQms, "utf-8");
2955
5134
  const pkg = JSON.parse(raw);
2956
5135
  data = { ...pkg.metadata || {}, ...pkg.graph || {} };
2957
5136
  } catch {
2958
5137
  }
2959
5138
  }
2960
5139
  if (!data) return null;
2961
- const stat = await fs10.promises.stat(dirPath);
5140
+ const stat = await fs9.promises.stat(dirPath);
2962
5141
  const nodeCount = Array.isArray(data.nodes) ? data.nodes.length : 0;
2963
5142
  const connectionCount = Array.isArray(data.connections) ? data.connections.length : 0;
2964
5143
  return {
@@ -2966,7 +5145,7 @@ async function listWorkspaceGraphs(workspaceIdOrSlug) {
2966
5145
  name: data.name || folderName,
2967
5146
  description: data.description || "",
2968
5147
  folderName,
2969
- folderPath: path10.relative(process.cwd(), dirPath).replace(/\\/g, "/"),
5148
+ folderPath: path9.relative(process.cwd(), dirPath).replace(/\\/g, "/"),
2970
5149
  createdAt: data.createdAt || stat.birthtimeMs,
2971
5150
  updatedAt: data.updatedAt || stat.mtimeMs,
2972
5151
  bpm: data.bpm || 120,
@@ -2979,18 +5158,18 @@ async function listWorkspaceGraphs(workspaceIdOrSlug) {
2979
5158
  connectionCount
2980
5159
  };
2981
5160
  };
2982
- const entries = await fs10.promises.readdir(wsDir);
5161
+ const entries = await fs9.promises.readdir(wsDir);
2983
5162
  for (const entry of entries) {
2984
5163
  if (entry.startsWith(".") || entry === "samples" || entry === "assets") continue;
2985
- const fullPath = path10.join(wsDir, entry);
2986
- const stat = await fs10.promises.stat(fullPath);
5164
+ const fullPath = path9.join(wsDir, entry);
5165
+ const stat = await fs9.promises.stat(fullPath);
2987
5166
  if (!stat.isDirectory()) continue;
2988
5167
  if (entry === "graphs") {
2989
- const subEntries = await fs10.promises.readdir(fullPath);
5168
+ const subEntries = await fs9.promises.readdir(fullPath);
2990
5169
  for (const sub of subEntries) {
2991
5170
  if (sub.startsWith(".")) continue;
2992
- const subPath = path10.join(fullPath, sub);
2993
- const subStat = await fs10.promises.stat(subPath);
5171
+ const subPath = path9.join(fullPath, sub);
5172
+ const subStat = await fs9.promises.stat(subPath);
2994
5173
  if (subStat.isDirectory()) {
2995
5174
  const g2 = await inspectDirForGraph(subPath, sub);
2996
5175
  if (g2) graphs.push(g2);
@@ -3007,10 +5186,10 @@ async function createWorkspaceGraph(workspaceIdOrSlug, params) {
3007
5186
  const { wsDir, wsFolder, wsId } = await resolveWorkspaceDir(workspaceIdOrSlug);
3008
5187
  const rawName = params.name.trim();
3009
5188
  const folderName = rawName.replace(/[/\\?%*:|"<>]/g, "_");
3010
- const graphDir = path10.join(wsDir, folderName);
3011
- await fs10.promises.mkdir(graphDir, { recursive: true });
3012
- await fs10.promises.mkdir(path10.join(graphDir, "samples"), { recursive: true });
3013
- await fs10.promises.mkdir(path10.join(graphDir, "assets"), { recursive: true });
5189
+ const graphDir = path9.join(wsDir, folderName);
5190
+ await fs9.promises.mkdir(graphDir, { recursive: true });
5191
+ await fs9.promises.mkdir(path9.join(graphDir, "samples"), { recursive: true });
5192
+ await fs9.promises.mkdir(path9.join(graphDir, "assets"), { recursive: true });
3014
5193
  const now = Date.now();
3015
5194
  const nodes = params.templateData?.nodes || [];
3016
5195
  const connections = params.templateData?.connections || [];
@@ -3021,7 +5200,7 @@ async function createWorkspaceGraph(workspaceIdOrSlug, params) {
3021
5200
  id: folderName,
3022
5201
  name: rawName,
3023
5202
  description: params.description || `Workspace graph in ${wsFolder}`,
3024
- folderPath: path10.relative(process.cwd(), graphDir).replace(/\\/g, "/"),
5203
+ folderPath: path9.relative(process.cwd(), graphDir).replace(/\\/g, "/"),
3025
5204
  createdAt: now,
3026
5205
  updatedAt: now,
3027
5206
  bpm,
@@ -3030,7 +5209,7 @@ async function createWorkspaceGraph(workspaceIdOrSlug, params) {
3030
5209
  nodes,
3031
5210
  connections
3032
5211
  };
3033
- await fs10.promises.writeFile(path10.join(graphDir, "project.json"), JSON.stringify(projectData, null, 2), "utf-8");
5212
+ await fs9.promises.writeFile(path9.join(graphDir, "project.json"), JSON.stringify(projectData, null, 2), "utf-8");
3034
5213
  const graphPackage = {
3035
5214
  format: "qms_graph_package",
3036
5215
  version: "2.0.0",
@@ -3048,27 +5227,27 @@ async function createWorkspaceGraph(workspaceIdOrSlug, params) {
3048
5227
  },
3049
5228
  graph: { bpm, masterVolume, currentBiome, nodes, connections }
3050
5229
  };
3051
- await fs10.promises.writeFile(path10.join(graphDir, "graph.qms"), JSON.stringify(graphPackage, null, 2), "utf-8");
5230
+ await fs9.promises.writeFile(path9.join(graphDir, "graph.qms"), JSON.stringify(graphPackage, null, 2), "utf-8");
3052
5231
  return projectData;
3053
5232
  }
3054
5233
  async function getWorkspaceGraph(workspaceIdOrSlug, graphIdOrName) {
3055
5234
  const { wsDir } = await resolveWorkspaceDir(workspaceIdOrSlug);
3056
5235
  const safeTarget = graphIdOrName.replace(/[/\\?%*:|"<>]/g, "_");
3057
5236
  const candidates = [
3058
- path10.join(wsDir, safeTarget),
3059
- path10.join(wsDir, graphIdOrName),
3060
- path10.join(wsDir, "graphs", safeTarget)
5237
+ path9.join(wsDir, safeTarget),
5238
+ path9.join(wsDir, graphIdOrName),
5239
+ path9.join(wsDir, "graphs", safeTarget)
3061
5240
  ];
3062
5241
  for (const cand of candidates) {
3063
- if (fs10.existsSync(cand)) {
3064
- const pJson = path10.join(cand, "project.json");
3065
- if (fs10.existsSync(pJson)) {
3066
- const raw = await fs10.promises.readFile(pJson, "utf-8");
5242
+ if (fs9.existsSync(cand)) {
5243
+ const pJson = path9.join(cand, "project.json");
5244
+ if (fs9.existsSync(pJson)) {
5245
+ const raw = await fs9.promises.readFile(pJson, "utf-8");
3067
5246
  return JSON.parse(raw);
3068
5247
  }
3069
- const gQms = path10.join(cand, "graph.qms");
3070
- if (fs10.existsSync(gQms)) {
3071
- const raw = await fs10.promises.readFile(gQms, "utf-8");
5248
+ const gQms = path9.join(cand, "graph.qms");
5249
+ if (fs9.existsSync(gQms)) {
5250
+ const raw = await fs9.promises.readFile(gQms, "utf-8");
3072
5251
  const pkg = JSON.parse(raw);
3073
5252
  return { ...pkg.metadata || {}, ...pkg.graph || {} };
3074
5253
  }
@@ -3079,39 +5258,39 @@ async function getWorkspaceGraph(workspaceIdOrSlug, graphIdOrName) {
3079
5258
  async function saveWorkspaceGraph(workspaceIdOrSlug, graphIdOrName, payload) {
3080
5259
  const { wsDir } = await resolveWorkspaceDir(workspaceIdOrSlug);
3081
5260
  const safeTarget = graphIdOrName.replace(/[/\\?%*:|"<>]/g, "_");
3082
- let targetDir = path10.join(wsDir, safeTarget);
3083
- if (!fs10.existsSync(targetDir) && fs10.existsSync(path10.join(wsDir, "graphs", safeTarget))) {
3084
- targetDir = path10.join(wsDir, "graphs", safeTarget);
5261
+ let targetDir = path9.join(wsDir, safeTarget);
5262
+ if (!fs9.existsSync(targetDir) && fs9.existsSync(path9.join(wsDir, "graphs", safeTarget))) {
5263
+ targetDir = path9.join(wsDir, "graphs", safeTarget);
3085
5264
  }
3086
- await fs10.promises.mkdir(targetDir, { recursive: true });
3087
- const pJson = path10.join(targetDir, "project.json");
5265
+ await fs9.promises.mkdir(targetDir, { recursive: true });
5266
+ const pJson = path9.join(targetDir, "project.json");
3088
5267
  let existing = {};
3089
- if (fs10.existsSync(pJson)) {
5268
+ if (fs9.existsSync(pJson)) {
3090
5269
  try {
3091
- existing = JSON.parse(await fs10.promises.readFile(pJson, "utf-8"));
5270
+ existing = JSON.parse(await fs9.promises.readFile(pJson, "utf-8"));
3092
5271
  } catch {
3093
5272
  }
3094
5273
  }
3095
5274
  const updated = {
3096
5275
  ...existing,
3097
5276
  ...payload,
3098
- id: existing.id || path10.basename(targetDir),
5277
+ id: existing.id || path9.basename(targetDir),
3099
5278
  updatedAt: Date.now()
3100
5279
  };
3101
- await fs10.promises.writeFile(pJson, JSON.stringify(updated, null, 2), "utf-8");
5280
+ await fs9.promises.writeFile(pJson, JSON.stringify(updated, null, 2), "utf-8");
3102
5281
  return updated;
3103
5282
  }
3104
5283
  async function deleteWorkspaceGraph(workspaceIdOrSlug, graphIdOrName) {
3105
5284
  const { wsDir } = await resolveWorkspaceDir(workspaceIdOrSlug);
3106
5285
  const safeTarget = graphIdOrName.replace(/[/\\?%*:|"<>]/g, "_");
3107
5286
  const candidates = [
3108
- path10.join(wsDir, safeTarget),
3109
- path10.join(wsDir, graphIdOrName),
3110
- path10.join(wsDir, "graphs", safeTarget)
5287
+ path9.join(wsDir, safeTarget),
5288
+ path9.join(wsDir, graphIdOrName),
5289
+ path9.join(wsDir, "graphs", safeTarget)
3111
5290
  ];
3112
5291
  for (const cand of candidates) {
3113
- if (fs10.existsSync(cand)) {
3114
- await fs10.promises.rm(cand, { recursive: true, force: true });
5292
+ if (fs9.existsSync(cand)) {
5293
+ await fs9.promises.rm(cand, { recursive: true, force: true });
3115
5294
  return true;
3116
5295
  }
3117
5296
  }
@@ -3130,9 +5309,11 @@ export {
3130
5309
  BaseAccountProvider,
3131
5310
  BaseApiRoute,
3132
5311
  BaseDataProvider,
5312
+ BaseResourceFetcher,
3133
5313
  BaseStorageAdapter,
3134
5314
  CANONICAL_SRKL_URL,
3135
5315
  CallbackRegistry,
5316
+ CloudflareResourceFetcher,
3136
5317
  CompositeBackend,
3137
5318
  ConcurrencyConflictError,
3138
5319
  ConcurrencyManager,
@@ -3167,6 +5348,7 @@ export {
3167
5348
  PasswordHasher,
3168
5349
  RateLimiter,
3169
5350
  RemoteExecutor,
5351
+ ResourceFetcherRegistry,
3170
5352
  ResultFlowBridge,
3171
5353
  S3Adapter,
3172
5354
  SAMPLES_GLOBAL_ROOT,
@@ -3182,6 +5364,7 @@ export {
3182
5364
  StorageServiceFacade,
3183
5365
  SupabaseStorageAdapter,
3184
5366
  SyncEngine,
5367
+ VercelResourceFetcher,
3185
5368
  WORKSPACE_ASSETS_ROOT,
3186
5369
  WORKSPACE_ROOT,
3187
5370
  WebSocketTransport,
@@ -3190,7 +5373,6 @@ export {
3190
5373
  WorkspaceFS,
3191
5374
  WorkspaceServiceDefinition,
3192
5375
  WorkspaceServiceFacade,
3193
- addWorkspaceSecondaryClone,
3194
5376
  authorizeB2,
3195
5377
  b2DeleteFileVersion,
3196
5378
  b2DeleteFolder,
@@ -3198,12 +5380,15 @@ export {
3198
5380
  b2ListBuckets,
3199
5381
  b2ListFileNames,
3200
5382
  b2UploadFile,
5383
+ buildProjectObservability,
5384
+ buildProjectsList,
3201
5385
  cleanDbUrl,
3202
5386
  configureStarterProjectTemplate,
3203
5387
  createAssetSubfolder,
3204
5388
  createBackend,
3205
5389
  createBackendClient,
3206
5390
  createCompositeService,
5391
+ createCredential,
3207
5392
  createEmptyContract,
3208
5393
  createGraphAssetFolder,
3209
5394
  createGraphAssetFolder as createGraphAssetSubfolder,
@@ -3214,15 +5399,23 @@ export {
3214
5399
  createSSEResponse,
3215
5400
  createSrklClient,
3216
5401
  createWavBuffer,
5402
+ createWorkspace,
3217
5403
  createWorkspaceGraph,
5404
+ decryptSecretFromBlob,
3218
5405
  deleteAssetFromGraph,
3219
5406
  deleteAssetFromPool,
5407
+ deleteCredential,
3220
5408
  deleteGlobalSample,
3221
5409
  deleteGraphPackage,
3222
5410
  deleteProject,
3223
5411
  deleteProjectSample,
5412
+ deleteRoute,
5413
+ deleteWorkspace,
5414
+ deleteWorkspaceFile,
5415
+ deleteWorkspaceFolder,
3224
5416
  deleteWorkspaceGraph,
3225
5417
  detectDatabaseEngine,
5418
+ encryptSecretToBlob,
3226
5419
  ensureAssetPoolDirectories,
3227
5420
  ensureGlobalSamples,
3228
5421
  ensureGraphAssetDirectories,
@@ -3230,16 +5423,27 @@ export {
3230
5423
  ensureProjectsRoot,
3231
5424
  ensureS3DirectoryMarker,
3232
5425
  ensureWorkspaceStructure,
5426
+ exportEncryptedCredentials,
5427
+ fetchCloudflareData,
5428
+ fetchVercelData,
5429
+ findUserWorkspace,
3233
5430
  findWorkspaceRecord,
3234
5431
  formatCanonicalWorkspaceFolderName,
5432
+ formatRelativeCreated,
5433
+ getAllRoutes,
3235
5434
  getAssetPoolLocationPath,
3236
5435
  getAssetPoolPublicUrl,
3237
5436
  getB2BucketId,
5437
+ getCredential,
3238
5438
  getDefaultWorkingWorkspaceDir,
5439
+ getExecutions,
5440
+ getGatewayConfig,
3239
5441
  getGraphPackageById,
3240
5442
  getPrimarySrklDeploymentUrl2 as getPrimarySrklCoreDeploymentUrl,
3241
5443
  getPrimarySrklDeploymentUrl,
3242
5444
  getProjectById,
5445
+ getPublicRouteByPath,
5446
+ getPublicRoutes,
3243
5447
  getSrklAccounts,
3244
5448
  getSrklAccounts2 as getSrklCoreAccounts,
3245
5449
  getSrklStatus,
@@ -3249,51 +5453,75 @@ export {
3249
5453
  getWorkingWorkspaceConfig,
3250
5454
  getWorkingWorkspaceDir,
3251
5455
  getWorkspaceFolderKey,
5456
+ getWorkspaceFolderPrefix,
3252
5457
  getWorkspaceGraph,
5458
+ getWorkspaceStorageDetails,
5459
+ getWorkspaces,
5460
+ importEncryptedCredentials,
3253
5461
  importGraphDependency,
3254
5462
  initGlobalSamplesDirectory,
5463
+ isB2Workspace,
3255
5464
  isSecretMasked,
3256
5465
  listAllGraphPackages,
3257
5466
  listAllProjects,
3258
5467
  listBackblazeBuckets,
5468
+ listCredentials,
3259
5469
  listGlobalSamples,
3260
5470
  listProjectSamples,
5471
+ listWorkspaceFiles,
3261
5472
  listWorkspaceGraphs,
5473
+ loadCredentialManifest,
5474
+ loadWorkspaceData,
3262
5475
  loginSystemUser,
3263
5476
  loginUser,
5477
+ matchGatewayIngress,
5478
+ matchGatewayIngressAsync,
3264
5479
  normalizeBackblazeCredentials,
3265
5480
  permanentDeleteS3Folder,
3266
5481
  permanentDeleteS3Object,
3267
5482
  queryUnifiedDb,
5483
+ readWorkspaceFile,
5484
+ recordExecution,
3268
5485
  removeGraphDependency,
3269
- removeWorkspaceSecondaryClone,
5486
+ renameAppInWorkspaceData,
3270
5487
  renameS3Folder,
3271
5488
  resolveBackblazeEndpoint,
5489
+ resolveDynamicRouteFromDb,
3272
5490
  resolveSystemDatabaseClusterNodes,
3273
5491
  resolveUserFromToken,
5492
+ resolveWorkspaceConnection,
3274
5493
  resolveWorkspaceDir,
3275
5494
  sanitizeWorkspaceName,
3276
5495
  saveAssetToGraph,
3277
5496
  saveAssetToPool,
5497
+ saveCredentialManifest,
3278
5498
  saveGlobalSample,
3279
5499
  saveGraphPackage,
3280
5500
  saveProject,
3281
5501
  saveProjectSample,
3282
- saveWorkspaceClonesState,
5502
+ saveWorkspaceData,
3283
5503
  saveWorkspaceGraph,
3284
5504
  scanAssetPool,
3285
5505
  scanGraphAssets,
3286
5506
  seedDefaultAssets,
3287
5507
  seedStarterGraphAssets,
5508
+ serverPublicRoutes,
5509
+ setServerPublicRoutes,
3288
5510
  setWorkingWorkspaceConfig,
3289
5511
  srklListBackblazeBuckets2 as srklCoreListBackblazeBuckets,
3290
5512
  srklListBackblazeBuckets,
3291
- syncPrimaryToClones,
3292
5513
  syncWorkspace,
5514
+ syncWorkspaceCreateToBucket,
5515
+ syncWorkspaceDataToBucket,
5516
+ syncWorkspaceDeleteToBucket,
5517
+ updateCredential,
5518
+ updateWorkspace,
5519
+ upsertRoute,
3293
5520
  useBackend,
3294
5521
  useBackendClient,
3295
5522
  useSrklServices,
3296
5523
  validateServiceDefinition,
3297
5524
  withAuth,
3298
- withCors
5525
+ withCors,
5526
+ writeWorkspaceFile
3299
5527
  };