@specific.dev/cli 0.1.142 → 0.1.146

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.
Files changed (115) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.!KGRlZmF1bHQp.__PAGE__.txt +4 -4
  4. package/dist/admin/__next.!KGRlZmF1bHQp.txt +7 -7
  5. package/dist/admin/__next._full.txt +16 -16
  6. package/dist/admin/__next._head.txt +4 -4
  7. package/dist/admin/__next._index.txt +6 -6
  8. package/dist/admin/__next._tree.txt +2 -2
  9. package/dist/admin/_next/static/chunks/4894dd6189acd52e.js +1 -0
  10. package/dist/admin/_next/static/chunks/48cbaceebda5df57.js +1 -0
  11. package/dist/admin/_next/static/chunks/48f1de3af928e439.js +1 -0
  12. package/dist/admin/_next/static/chunks/63473a6cb811ed42.js +5 -0
  13. package/dist/admin/_next/static/chunks/6d4c1852135da048.js +5 -0
  14. package/dist/admin/_next/static/chunks/{a308451471d4cb39.js → 7c2bbd09e9922443.js} +1 -1
  15. package/dist/admin/_next/static/chunks/7e5c5b91b30c68a0.js +7 -0
  16. package/dist/admin/_next/static/chunks/9835cc4e14c5aee3.js +1 -0
  17. package/dist/admin/_next/static/chunks/{00ed768604f00dc4.js → 9fe0755b78ed504a.js} +1 -1
  18. package/dist/admin/_next/static/chunks/b44581875c51fcd8.js +1 -0
  19. package/dist/admin/_next/static/chunks/b6749b7e46ea7447.css +4 -0
  20. package/dist/admin/_next/static/chunks/b69f189d24b42cc4.js +1 -0
  21. package/dist/admin/_next/static/chunks/c52b2ebc474a0535.js +1 -0
  22. package/dist/admin/_next/static/chunks/c82a52b3c62e7d18.js +1 -0
  23. package/dist/admin/_next/static/chunks/dc5c264e3262a84a.js +1 -0
  24. package/dist/admin/_next/static/chunks/turbopack-2830c15bd3d8f58b.js +4 -0
  25. package/dist/admin/_not-found/__next._full.txt +11 -11
  26. package/dist/admin/_not-found/__next._head.txt +4 -4
  27. package/dist/admin/_not-found/__next._index.txt +6 -6
  28. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +2 -2
  29. package/dist/admin/_not-found/__next._not-found.txt +3 -3
  30. package/dist/admin/_not-found/__next._tree.txt +2 -2
  31. package/dist/admin/_not-found/index.html +1 -1
  32. package/dist/admin/_not-found/index.txt +11 -11
  33. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +4 -4
  34. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +3 -3
  35. package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +7 -7
  36. package/dist/admin/databases/__next._full.txt +16 -16
  37. package/dist/admin/databases/__next._head.txt +4 -4
  38. package/dist/admin/databases/__next._index.txt +6 -6
  39. package/dist/admin/databases/__next._tree.txt +2 -2
  40. package/dist/admin/databases/index.html +1 -1
  41. package/dist/admin/databases/index.txt +16 -16
  42. package/dist/admin/fullscreen/__next._full.txt +12 -12
  43. package/dist/admin/fullscreen/__next._head.txt +4 -4
  44. package/dist/admin/fullscreen/__next._index.txt +6 -6
  45. package/dist/admin/fullscreen/__next._tree.txt +2 -2
  46. package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +4 -4
  47. package/dist/admin/fullscreen/__next.fullscreen.txt +3 -3
  48. package/dist/admin/fullscreen/databases/__next._full.txt +12 -12
  49. package/dist/admin/fullscreen/databases/__next._head.txt +4 -4
  50. package/dist/admin/fullscreen/databases/__next._index.txt +6 -6
  51. package/dist/admin/fullscreen/databases/__next._tree.txt +2 -2
  52. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +4 -4
  53. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +3 -3
  54. package/dist/admin/fullscreen/databases/__next.fullscreen.txt +3 -3
  55. package/dist/admin/fullscreen/databases/index.html +1 -1
  56. package/dist/admin/fullscreen/databases/index.txt +12 -12
  57. package/dist/admin/fullscreen/index.html +1 -1
  58. package/dist/admin/fullscreen/index.txt +12 -12
  59. package/dist/admin/index.html +1 -1
  60. package/dist/admin/index.txt +16 -16
  61. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +4 -4
  62. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +3 -3
  63. package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +7 -7
  64. package/dist/admin/mail/__next._full.txt +16 -16
  65. package/dist/admin/mail/__next._head.txt +4 -4
  66. package/dist/admin/mail/__next._index.txt +6 -6
  67. package/dist/admin/mail/__next._tree.txt +2 -2
  68. package/dist/admin/mail/index.html +1 -1
  69. package/dist/admin/mail/index.txt +16 -16
  70. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.__PAGE__.txt +4 -4
  71. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.txt +3 -3
  72. package/dist/admin/services/__next.!KGRlZmF1bHQp.txt +7 -7
  73. package/dist/admin/services/__next._full.txt +16 -16
  74. package/dist/admin/services/__next._head.txt +4 -4
  75. package/dist/admin/services/__next._index.txt +6 -6
  76. package/dist/admin/services/__next._tree.txt +2 -2
  77. package/dist/admin/services/index.html +1 -1
  78. package/dist/admin/services/index.txt +16 -16
  79. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.__PAGE__.txt +9 -0
  80. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.txt +4 -0
  81. package/dist/admin/storage/__next.!KGRlZmF1bHQp.txt +8 -0
  82. package/dist/admin/storage/__next._full.txt +27 -0
  83. package/dist/admin/storage/__next._head.txt +6 -0
  84. package/dist/admin/storage/__next._index.txt +7 -0
  85. package/dist/admin/storage/__next._tree.txt +5 -0
  86. package/dist/admin/storage/index.html +1 -0
  87. package/dist/admin/storage/index.txt +27 -0
  88. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +7 -7
  89. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +4 -4
  90. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +3 -3
  91. package/dist/admin/workflows/__next._full.txt +16 -16
  92. package/dist/admin/workflows/__next._head.txt +4 -4
  93. package/dist/admin/workflows/__next._index.txt +6 -6
  94. package/dist/admin/workflows/__next._tree.txt +2 -2
  95. package/dist/admin/workflows/index.html +1 -1
  96. package/dist/admin/workflows/index.txt +16 -16
  97. package/dist/cli.js +515 -24
  98. package/dist/docs/redis.md +1 -1
  99. package/package.json +2 -1
  100. package/dist/admin/_next/static/chunks/051b91c534cf9f23.js +0 -1
  101. package/dist/admin/_next/static/chunks/05dca3efccd1ccf3.js +0 -1
  102. package/dist/admin/_next/static/chunks/1437c65d19a610ec.js +0 -5
  103. package/dist/admin/_next/static/chunks/16e0c1b704eededd.js +0 -2
  104. package/dist/admin/_next/static/chunks/4bd3607000957431.js +0 -1
  105. package/dist/admin/_next/static/chunks/5953d56a33f0b7ea.js +0 -4
  106. package/dist/admin/_next/static/chunks/65ce370ab86b6df6.js +0 -1
  107. package/dist/admin/_next/static/chunks/99a055acecd950e9.js +0 -1
  108. package/dist/admin/_next/static/chunks/a088825647b58cc9.css +0 -4
  109. package/dist/admin/_next/static/chunks/ab7b22d870fdc67c.js +0 -1
  110. package/dist/admin/_next/static/chunks/b42765249c4a529a.js +0 -1
  111. package/dist/admin/_next/static/chunks/db49f609ddd8888b.js +0 -1
  112. package/dist/admin/_next/static/chunks/turbopack-ea5c59f39d53507e.js +0 -4
  113. /package/dist/admin/_next/static/{0m_Df0WwF7T1e25BA6Bff → g6SjZi_LuWK7FrmmMr5PE}/_buildManifest.js +0 -0
  114. /package/dist/admin/_next/static/{0m_Df0WwF7T1e25BA6Bff → g6SjZi_LuWK7FrmmMr5PE}/_clientMiddlewareManifest.json +0 -0
  115. /package/dist/admin/_next/static/{0m_Df0WwF7T1e25BA6Bff → g6SjZi_LuWK7FrmmMr5PE}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -185198,6 +185198,16 @@ import * as http from "http";
185198
185198
  import * as fs7 from "fs";
185199
185199
  import * as path7 from "path";
185200
185200
  import { fileURLToPath } from "url";
185201
+ import {
185202
+ CopyObjectCommand,
185203
+ DeleteObjectCommand,
185204
+ DeleteObjectsCommand,
185205
+ GetObjectCommand,
185206
+ HeadObjectCommand,
185207
+ ListObjectsV2Command,
185208
+ PutObjectCommand,
185209
+ S3Client
185210
+ } from "@aws-sdk/client-s3";
185201
185211
  import * as net3 from "net";
185202
185212
  import * as fs8 from "fs";
185203
185213
  import * as path9 from "path";
@@ -369113,9 +369123,9 @@ var postgresBinary = {
369113
369123
  };
369114
369124
  var redisBinary = {
369115
369125
  name: "redis",
369116
- versions: ["8.4.0"],
369126
+ versions: ["8.1.7"],
369117
369127
  // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
369118
- urlTemplate: "https://binaries.specific.dev/redis/{version}/{arch}.tar.gz",
369128
+ urlTemplate: "https://binaries.specific.dev/valkey/{version}/{arch}.tar.gz",
369119
369129
  platformMapping: {
369120
369130
  darwin: {
369121
369131
  arm64: { platform: "macos", arch: "macos_arm" },
@@ -369128,8 +369138,7 @@ var redisBinary = {
369128
369138
  },
369129
369139
  // Binaries are at the root of the archive
369130
369140
  stripComponents: 0,
369131
- // Core Redis executable
369132
- executables: ["redis-server"]
369141
+ executables: ["valkey-server"]
369133
369142
  };
369134
369143
  var electricBinary = {
369135
369144
  name: "electric",
@@ -369449,16 +369458,35 @@ async function startPostgres(pg, port, dataDir, onProgress) {
369449
369458
  }
369450
369459
  };
369451
369460
  }
369452
- async function startRedis(redis, port, onProgress) {
369461
+ async function startRedis(redis, port, dataDir, onProgress) {
369453
369462
  const binary = await ensureBinary(redisBinary, void 0, onProgress);
369454
369463
  const host = "127.0.0.1";
369464
+ const redisDataPath = path5.join(process.cwd(), dataDir, "redis", redis.name);
369465
+ fs5.mkdirSync(redisDataPath, { recursive: true });
369466
+ const stalePath = path5.join(redisDataPath, "dump.rdb");
369467
+ if (fs5.existsSync(stalePath)) {
369468
+ fs5.rmSync(stalePath, { force: true });
369469
+ }
369455
369470
  if (await isPortInUse(host, port)) {
369456
369471
  await reclaimSpecificOrphanOnPort(port);
369457
369472
  await waitForPortFree(host, port, 1e3);
369458
369473
  }
369459
369474
  const redisProc = spawn(
369460
- binary.executables["redis-server"],
369461
- ["--port", String(port), "--bind", host, "--daemonize", "no"],
369475
+ binary.executables["valkey-server"],
369476
+ [
369477
+ "--port",
369478
+ String(port),
369479
+ "--bind",
369480
+ host,
369481
+ "--daemonize",
369482
+ "no",
369483
+ "--dir",
369484
+ redisDataPath,
369485
+ "--save",
369486
+ "",
369487
+ "--appendonly",
369488
+ "no"
369489
+ ],
369462
369490
  {
369463
369491
  stdio: ["ignore", "pipe", "pipe"],
369464
369492
  detached: true
@@ -370448,6 +370476,417 @@ function killTrackedProcess(pid, detached, isProcessRunning) {
370448
370476
  } catch {
370449
370477
  }
370450
370478
  }
370479
+ var MAX_KEY_LENGTH = 1024;
370480
+ var PROXY_URL_EXPIRES_IN = 3600;
370481
+ function validateKey(key) {
370482
+ if (!key || key.length > MAX_KEY_LENGTH) return "Invalid key length";
370483
+ if (key.includes("..")) return "Key must not contain '..'";
370484
+ if (key.includes("\0")) return "Key must not contain null bytes";
370485
+ if (key.startsWith("/")) return "Key must not start with '/'";
370486
+ return null;
370487
+ }
370488
+ var clientCache = /* @__PURE__ */ new Map();
370489
+ function getClient(info) {
370490
+ const cacheKey = `${info.host}:${info.port}:${info.accessKey}`;
370491
+ const cached2 = clientCache.get(cacheKey);
370492
+ if (cached2) return cached2;
370493
+ const client2 = new S3Client({
370494
+ endpoint: `http://${info.host}:${info.port}`,
370495
+ region: "us-east-1",
370496
+ credentials: {
370497
+ accessKeyId: info.accessKey,
370498
+ secretAccessKey: info.secretKey
370499
+ },
370500
+ forcePathStyle: true
370501
+ });
370502
+ clientCache.set(cacheKey, client2);
370503
+ return client2;
370504
+ }
370505
+ function sendJson(res, status, body) {
370506
+ res.setHeader("Content-Type", "application/json");
370507
+ res.writeHead(status);
370508
+ res.end(JSON.stringify(body));
370509
+ }
370510
+ function sendError(res, status, message) {
370511
+ sendJson(res, status, { error: message });
370512
+ }
370513
+ async function readJsonBody(req) {
370514
+ const chunks = [];
370515
+ for await (const chunk of req) {
370516
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
370517
+ }
370518
+ const text = Buffer.concat(chunks).toString("utf8");
370519
+ return text.length > 0 ? JSON.parse(text) : {};
370520
+ }
370521
+ async function handleListObjects(client2, bucket, url, res) {
370522
+ const prefix = url.searchParams.get("prefix") ?? "";
370523
+ const cursor = url.searchParams.get("cursor") ?? void 0;
370524
+ const maxKeys = Math.min(
370525
+ Math.max(parseInt(url.searchParams.get("maxKeys") ?? "100", 10) || 100, 1),
370526
+ 1e3
370527
+ );
370528
+ const response = await client2.send(
370529
+ new ListObjectsV2Command({
370530
+ Bucket: bucket,
370531
+ Prefix: prefix,
370532
+ Delimiter: "/",
370533
+ MaxKeys: maxKeys,
370534
+ ContinuationToken: cursor || void 0
370535
+ })
370536
+ );
370537
+ sendJson(res, 200, {
370538
+ objects: (response.Contents ?? []).map((obj) => ({
370539
+ key: obj.Key,
370540
+ size: obj.Size ?? 0,
370541
+ lastModified: obj.LastModified?.toISOString() ?? "",
370542
+ etag: obj.ETag
370543
+ })),
370544
+ prefixes: (response.CommonPrefixes ?? []).map((p) => p.Prefix),
370545
+ nextCursor: response.NextContinuationToken ?? null,
370546
+ prefix
370547
+ });
370548
+ }
370549
+ async function handleMetrics(client2, bucket, res) {
370550
+ let totalObjects = 0;
370551
+ let totalSize = 0;
370552
+ let continuationToken;
370553
+ let pages = 0;
370554
+ const MAX_PAGES = 100;
370555
+ do {
370556
+ const response = await client2.send(
370557
+ new ListObjectsV2Command({
370558
+ Bucket: bucket,
370559
+ ContinuationToken: continuationToken
370560
+ })
370561
+ );
370562
+ totalObjects += response.KeyCount ?? 0;
370563
+ for (const obj of response.Contents ?? []) {
370564
+ totalSize += obj.Size ?? 0;
370565
+ }
370566
+ continuationToken = response.NextContinuationToken;
370567
+ pages++;
370568
+ } while (continuationToken && pages < MAX_PAGES);
370569
+ sendJson(res, 200, {
370570
+ totalObjects,
370571
+ totalSize,
370572
+ bucketName: bucket,
370573
+ ...pages >= MAX_PAGES ? { truncated: true } : {}
370574
+ });
370575
+ }
370576
+ async function handleGetObject(client2, bucket, key, res) {
370577
+ try {
370578
+ const response = await client2.send(
370579
+ new HeadObjectCommand({ Bucket: bucket, Key: key })
370580
+ );
370581
+ sendJson(res, 200, {
370582
+ key,
370583
+ size: response.ContentLength ?? 0,
370584
+ lastModified: response.LastModified?.toISOString(),
370585
+ contentType: response.ContentType,
370586
+ etag: response.ETag,
370587
+ metadata: response.Metadata
370588
+ });
370589
+ } catch (error) {
370590
+ const name = error.name;
370591
+ if (name === "NotFound" || name === "NoSuchKey") {
370592
+ sendError(res, 404, `Object '${key}' not found`);
370593
+ return;
370594
+ }
370595
+ throw error;
370596
+ }
370597
+ }
370598
+ async function handleDeleteObject(client2, bucket, key, res) {
370599
+ if (key.endsWith("/")) {
370600
+ const allKeys = [];
370601
+ let continuationToken;
370602
+ do {
370603
+ const listResponse = await client2.send(
370604
+ new ListObjectsV2Command({
370605
+ Bucket: bucket,
370606
+ Prefix: key,
370607
+ ContinuationToken: continuationToken
370608
+ })
370609
+ );
370610
+ for (const obj of listResponse.Contents ?? []) {
370611
+ if (obj.Key) allKeys.push(obj.Key);
370612
+ }
370613
+ continuationToken = listResponse.NextContinuationToken;
370614
+ } while (continuationToken);
370615
+ const batches = [];
370616
+ for (let i = 0; i < allKeys.length; i += 1e3) {
370617
+ batches.push(
370618
+ client2.send(
370619
+ new DeleteObjectsCommand({
370620
+ Bucket: bucket,
370621
+ Delete: {
370622
+ Objects: allKeys.slice(i, i + 1e3).map((k) => ({ Key: k })),
370623
+ Quiet: true
370624
+ }
370625
+ })
370626
+ )
370627
+ );
370628
+ }
370629
+ await Promise.all(batches);
370630
+ } else {
370631
+ await client2.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
370632
+ }
370633
+ sendJson(res, 200, { success: true });
370634
+ }
370635
+ async function handleCreateFolder(client2, bucket, body, res) {
370636
+ const path172 = body.path;
370637
+ if (typeof path172 !== "string") {
370638
+ sendError(res, 400, "Missing path");
370639
+ return;
370640
+ }
370641
+ const folderKey = path172.endsWith("/") ? path172 : `${path172}/`;
370642
+ const keyError = validateKey(folderKey);
370643
+ if (keyError) {
370644
+ sendError(res, 400, keyError);
370645
+ return;
370646
+ }
370647
+ await client2.send(
370648
+ new PutObjectCommand({ Bucket: bucket, Key: folderKey, Body: "" })
370649
+ );
370650
+ sendJson(res, 200, { key: folderKey });
370651
+ }
370652
+ async function handleRename(client2, bucket, body, res) {
370653
+ const { sourceKey, destinationKey } = body;
370654
+ if (typeof sourceKey !== "string" || typeof destinationKey !== "string") {
370655
+ sendError(res, 400, "Missing sourceKey or destinationKey");
370656
+ return;
370657
+ }
370658
+ for (const k of [sourceKey, destinationKey]) {
370659
+ const err = validateKey(k);
370660
+ if (err) {
370661
+ sendError(res, 400, err);
370662
+ return;
370663
+ }
370664
+ }
370665
+ if (sourceKey.endsWith("/")) {
370666
+ const allKeys = [];
370667
+ let continuationToken;
370668
+ do {
370669
+ const listResponse = await client2.send(
370670
+ new ListObjectsV2Command({
370671
+ Bucket: bucket,
370672
+ Prefix: sourceKey,
370673
+ ContinuationToken: continuationToken
370674
+ })
370675
+ );
370676
+ for (const obj of listResponse.Contents ?? []) {
370677
+ if (obj.Key) allKeys.push(obj.Key);
370678
+ }
370679
+ continuationToken = listResponse.NextContinuationToken;
370680
+ } while (continuationToken);
370681
+ if (allKeys.length === 0) {
370682
+ sendError(res, 404, "Folder is empty or does not exist");
370683
+ return;
370684
+ }
370685
+ await Promise.all(
370686
+ allKeys.map(
370687
+ (k) => client2.send(
370688
+ new CopyObjectCommand({
370689
+ Bucket: bucket,
370690
+ CopySource: `${bucket}/${encodeURI(k)}`,
370691
+ Key: destinationKey + k.slice(sourceKey.length)
370692
+ })
370693
+ )
370694
+ )
370695
+ );
370696
+ const deleteBatches = [];
370697
+ for (let i = 0; i < allKeys.length; i += 1e3) {
370698
+ deleteBatches.push(
370699
+ client2.send(
370700
+ new DeleteObjectsCommand({
370701
+ Bucket: bucket,
370702
+ Delete: {
370703
+ Objects: allKeys.slice(i, i + 1e3).map((k) => ({ Key: k })),
370704
+ Quiet: true
370705
+ }
370706
+ })
370707
+ )
370708
+ );
370709
+ }
370710
+ await Promise.all(deleteBatches);
370711
+ } else {
370712
+ await client2.send(
370713
+ new CopyObjectCommand({
370714
+ Bucket: bucket,
370715
+ CopySource: `${bucket}/${encodeURI(sourceKey)}`,
370716
+ Key: destinationKey
370717
+ })
370718
+ );
370719
+ await client2.send(
370720
+ new DeleteObjectCommand({ Bucket: bucket, Key: sourceKey })
370721
+ );
370722
+ }
370723
+ sendJson(res, 200, { key: destinationKey });
370724
+ }
370725
+ async function handleUploadData(client2, bucket, key, req, res) {
370726
+ const chunks = [];
370727
+ for await (const chunk of req) {
370728
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
370729
+ }
370730
+ const body = Buffer.concat(chunks);
370731
+ const contentType = req.headers["content-type"];
370732
+ await client2.send(
370733
+ new PutObjectCommand({
370734
+ Bucket: bucket,
370735
+ Key: key,
370736
+ Body: body,
370737
+ ContentType: typeof contentType === "string" ? contentType : void 0
370738
+ })
370739
+ );
370740
+ res.writeHead(200);
370741
+ res.end();
370742
+ }
370743
+ async function handleDownloadData(client2, bucket, key, res) {
370744
+ try {
370745
+ const response = await client2.send(
370746
+ new GetObjectCommand({ Bucket: bucket, Key: key })
370747
+ );
370748
+ if (response.ContentType) {
370749
+ res.setHeader("Content-Type", response.ContentType);
370750
+ }
370751
+ if (response.ContentLength !== void 0) {
370752
+ res.setHeader("Content-Length", String(response.ContentLength));
370753
+ }
370754
+ res.writeHead(200);
370755
+ const body = response.Body;
370756
+ if (!body) {
370757
+ res.end();
370758
+ return;
370759
+ }
370760
+ if (typeof body.pipe === "function") {
370761
+ body.pipe(res);
370762
+ } else {
370763
+ const stream = body.transformToWebStream();
370764
+ const reader = stream.getReader();
370765
+ while (true) {
370766
+ const { done, value } = await reader.read();
370767
+ if (done) break;
370768
+ if (value) res.write(Buffer.from(value));
370769
+ }
370770
+ res.end();
370771
+ }
370772
+ } catch (error) {
370773
+ const name = error.name;
370774
+ if (name === "NotFound" || name === "NoSuchKey") {
370775
+ if (!res.headersSent) {
370776
+ sendError(res, 404, `Object '${key}' not found`);
370777
+ } else {
370778
+ res.end();
370779
+ }
370780
+ return;
370781
+ }
370782
+ throw error;
370783
+ }
370784
+ }
370785
+ function handlePresigned(storageName, body, res) {
370786
+ const { key, action, expiresIn } = body;
370787
+ if (typeof key !== "string" || action !== "upload" && action !== "download") {
370788
+ sendError(res, 400, "Missing key or invalid action");
370789
+ return;
370790
+ }
370791
+ const keyError = validateKey(key);
370792
+ if (keyError) {
370793
+ sendError(res, 400, keyError);
370794
+ return;
370795
+ }
370796
+ const url = `/api/storage/${encodeURIComponent(storageName)}/data?key=${encodeURIComponent(key)}`;
370797
+ sendJson(res, 200, {
370798
+ url,
370799
+ expiresIn: typeof expiresIn === "number" ? expiresIn : PROXY_URL_EXPIRES_IN
370800
+ });
370801
+ }
370802
+ async function handleStorageRequest(req, res, pathname, url, getStorageInstance) {
370803
+ const match = /^\/api\/storage\/([^/]+)(\/.*)?$/.exec(pathname);
370804
+ if (!match) return false;
370805
+ const storageName = decodeURIComponent(match[1]);
370806
+ const sub = match[2] ?? "";
370807
+ const info = getStorageInstance(storageName);
370808
+ if (!info) {
370809
+ sendError(res, 404, `Storage '${storageName}' not found`);
370810
+ return true;
370811
+ }
370812
+ const client2 = getClient(info);
370813
+ const bucket = info.bucket;
370814
+ try {
370815
+ if (sub === "/objects" && req.method === "GET") {
370816
+ await handleListObjects(client2, bucket, url, res);
370817
+ return true;
370818
+ }
370819
+ if (sub === "/metrics" && req.method === "GET") {
370820
+ await handleMetrics(client2, bucket, res);
370821
+ return true;
370822
+ }
370823
+ if (sub === "/object") {
370824
+ const key = url.searchParams.get("key");
370825
+ if (!key) {
370826
+ sendError(res, 400, "Missing key");
370827
+ return true;
370828
+ }
370829
+ const keyError = validateKey(key);
370830
+ if (keyError) {
370831
+ sendError(res, 400, keyError);
370832
+ return true;
370833
+ }
370834
+ if (req.method === "GET") {
370835
+ await handleGetObject(client2, bucket, key, res);
370836
+ return true;
370837
+ }
370838
+ if (req.method === "DELETE") {
370839
+ await handleDeleteObject(client2, bucket, key, res);
370840
+ return true;
370841
+ }
370842
+ }
370843
+ if (sub === "/data") {
370844
+ const key = url.searchParams.get("key");
370845
+ if (!key) {
370846
+ sendError(res, 400, "Missing key");
370847
+ return true;
370848
+ }
370849
+ const keyError = validateKey(key);
370850
+ if (keyError) {
370851
+ sendError(res, 400, keyError);
370852
+ return true;
370853
+ }
370854
+ if (req.method === "PUT") {
370855
+ await handleUploadData(client2, bucket, key, req, res);
370856
+ return true;
370857
+ }
370858
+ if (req.method === "GET") {
370859
+ await handleDownloadData(client2, bucket, key, res);
370860
+ return true;
370861
+ }
370862
+ }
370863
+ if (sub === "/folder" && req.method === "POST") {
370864
+ const body = await readJsonBody(req);
370865
+ await handleCreateFolder(client2, bucket, body, res);
370866
+ return true;
370867
+ }
370868
+ if (sub === "/rename" && req.method === "POST") {
370869
+ const body = await readJsonBody(req);
370870
+ await handleRename(client2, bucket, body, res);
370871
+ return true;
370872
+ }
370873
+ if (sub === "/presigned" && req.method === "POST") {
370874
+ const body = await readJsonBody(req);
370875
+ handlePresigned(storageName, body, res);
370876
+ return true;
370877
+ }
370878
+ sendError(res, 404, "Not found");
370879
+ return true;
370880
+ } catch (error) {
370881
+ writeLog("admin:storage", `Storage handler error: ${error}`);
370882
+ if (!res.headersSent) {
370883
+ sendError(res, 500, error instanceof Error ? error.message : "Internal error");
370884
+ } else {
370885
+ res.end();
370886
+ }
370887
+ return true;
370888
+ }
370889
+ }
370451
370890
  var __dirname = path7.dirname(fileURLToPath(import.meta.url));
370452
370891
  var adminDir = path7.join(__dirname, "admin");
370453
370892
  var _embeddedAdmin = null;
@@ -370515,6 +370954,19 @@ function serveEmbeddedFile(res, pathname) {
370515
370954
  res.end(content);
370516
370955
  return;
370517
370956
  }
370957
+ if (!path7.extname(relativePath)) {
370958
+ const parts = relativePath.split("/").filter(Boolean);
370959
+ while (parts.length > 0) {
370960
+ parts.pop();
370961
+ const candidate = parts.length === 0 ? "index.html" : parts.join("/") + "/index.html";
370962
+ const fallback = _embeddedAdmin.get(candidate);
370963
+ if (fallback) {
370964
+ res.writeHead(200, { "Content-Type": "text/html" });
370965
+ res.end(fallback);
370966
+ return;
370967
+ }
370968
+ }
370969
+ }
370518
370970
  const notFound = _embeddedAdmin.get("404.html");
370519
370971
  if (notFound) {
370520
370972
  res.writeHead(404, { "Content-Type": "text/html" });
@@ -370556,6 +371008,20 @@ function serveFilesystemFile(res, pathname) {
370556
371008
  if (fs7.existsSync(indexPath)) {
370557
371009
  return serveFile(res, indexPath);
370558
371010
  }
371011
+ if (!path7.extname(relativePath)) {
371012
+ let dir = path7.dirname(resolvedPath);
371013
+ while (dir.startsWith(resolvedAdminDir) && dir !== resolvedAdminDir) {
371014
+ const fallback = path7.join(dir, "index.html");
371015
+ if (fs7.existsSync(fallback)) {
371016
+ return serveFile(res, fallback);
371017
+ }
371018
+ dir = path7.dirname(dir);
371019
+ }
371020
+ const rootIndex = path7.join(resolvedAdminDir, "index.html");
371021
+ if (fs7.existsSync(rootIndex)) {
371022
+ return serveFile(res, rootIndex);
371023
+ }
371024
+ }
370559
371025
  const notFoundPath = path7.join(adminDir, "404.html");
370560
371026
  if (fs7.existsSync(notFoundPath)) {
370561
371027
  return serveFileContent(res, notFoundPath, "text/html", 404);
@@ -370609,12 +371075,15 @@ function proxyRequest(req, res, targetPort) {
370609
371075
  });
370610
371076
  req.pipe(proxyReq, { end: true });
370611
371077
  }
370612
- async function startAdminServer(getState, listenPort = 0, getLogsDir = () => null) {
371078
+ async function startAdminServer(getState, listenPort = 0, getLogsDir = () => null, getStorageInstance = () => void 0) {
370613
371079
  return new Promise((resolve62, reject) => {
370614
371080
  const server = http.createServer((req, res) => {
370615
371081
  const url = new URL(req.url || "/", "http://localhost");
370616
371082
  res.setHeader("Access-Control-Allow-Origin", "*");
370617
- res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
371083
+ res.setHeader(
371084
+ "Access-Control-Allow-Methods",
371085
+ "GET, POST, PUT, DELETE, OPTIONS"
371086
+ );
370618
371087
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
370619
371088
  if (req.method === "OPTIONS") {
370620
371089
  res.writeHead(204);
@@ -370688,6 +371157,10 @@ async function startAdminServer(getState, listenPort = 0, getLogsDir = () => nul
370688
371157
  res.end(JSON.stringify(body));
370689
371158
  return;
370690
371159
  }
371160
+ if (pathname.startsWith("/api/storage/")) {
371161
+ void handleStorageRequest(req, res, pathname, url, getStorageInstance);
371162
+ return;
371163
+ }
370691
371164
  if (pathname.startsWith("/temporal-ui")) {
370692
371165
  const state = getState();
370693
371166
  if (state.temporalUiPort) {
@@ -371686,7 +372159,7 @@ async function startResources(options2) {
371686
372159
  const port = portAllocator.allocate(`redis:${redis.name}`);
371687
372160
  log(`Starting redis "${redis.name}" on port ${port}`);
371688
372161
  callbacks.onResourceStarting?.(redis.name, "redis");
371689
- const instance = await startRedis(redis, port, (progress) => {
372162
+ const instance = await startRedis(redis, port, dataDir, (progress) => {
371690
372163
  callbacks.onResourceProgress?.(redis.name, progress);
371691
372164
  });
371692
372165
  resources.set(redis.name, instance);
@@ -373315,7 +373788,20 @@ Add them to the config block in specific.local`
373315
373788
  const adminServer = await startAdminServer(
373316
373789
  getState,
373317
373790
  adminPort,
373318
- () => this.serviceLogFiles?.logsDir ?? null
373791
+ () => this.serviceLogFiles?.logsDir ?? null,
373792
+ (name) => {
373793
+ const r = this.resources.get(name);
373794
+ if (!r || r.type !== "storage" || !r.bucket || !r.accessKey || !r.secretKey) {
373795
+ return void 0;
373796
+ }
373797
+ return {
373798
+ host: r.host,
373799
+ port: r.port,
373800
+ bucket: r.bucket,
373801
+ accessKey: r.accessKey,
373802
+ secretKey: r.secretKey
373803
+ };
373804
+ }
373319
373805
  );
373320
373806
  this.adminServer = adminServer;
373321
373807
  this.systemLog(
@@ -373905,7 +374391,7 @@ function getProjectId() {
373905
374391
  }
373906
374392
  return void 0;
373907
374393
  }
373908
- function getClient() {
374394
+ function getClient2() {
373909
374395
  if (!isEnabled()) return null;
373910
374396
  if (!client) {
373911
374397
  client = new PostHog("phc_qNQCEUXh6ErdciQRRmeG2xlVvwFjkcW6A5bnOFJ8vXZ", {
@@ -373918,7 +374404,7 @@ function getClient() {
373918
374404
  return client;
373919
374405
  }
373920
374406
  function trackEvent(event, properties) {
373921
- const ph = getClient();
374407
+ const ph = getClient2();
373922
374408
  if (!ph) return;
373923
374409
  const userId = getUserId();
373924
374410
  if (userId && !identified) {
@@ -373939,7 +374425,7 @@ function trackEvent(event, properties) {
373939
374425
  event,
373940
374426
  properties: {
373941
374427
  ...properties,
373942
- cli_version: "0.1.142",
374428
+ cli_version: "0.1.146",
373943
374429
  platform: process.platform,
373944
374430
  node_version: process.version,
373945
374431
  project_id: getProjectId()
@@ -376182,6 +376668,7 @@ function DeployUI({ envFlag, preview, config }) {
376182
376668
  setState((s) => ({
376183
376669
  ...s,
376184
376670
  phase: "creating-tarball",
376671
+ environmentId: match.id,
376185
376672
  environmentName: match.name,
376186
376673
  environments
376187
376674
  }));
@@ -376195,6 +376682,7 @@ function DeployUI({ envFlag, preview, config }) {
376195
376682
  setState((s) => ({
376196
376683
  ...s,
376197
376684
  phase: "creating-tarball",
376685
+ environmentId: match.id,
376198
376686
  environmentName: match.name,
376199
376687
  environments
376200
376688
  }));
@@ -376206,6 +376694,7 @@ function DeployUI({ envFlag, preview, config }) {
376206
376694
  setState((s) => ({
376207
376695
  ...s,
376208
376696
  phase: "creating-tarball",
376697
+ environmentId: environments[0].id,
376209
376698
  environmentName: environments[0].name,
376210
376699
  environments
376211
376700
  }));
@@ -376254,6 +376743,7 @@ function DeployUI({ envFlag, preview, config }) {
376254
376743
  setState((s) => ({
376255
376744
  ...s,
376256
376745
  phase: "creating-tarball",
376746
+ environmentId: previewEnv.id,
376257
376747
  environmentName: previewEnv.name
376258
376748
  }));
376259
376749
  } catch (err) {
@@ -376275,6 +376765,7 @@ function DeployUI({ envFlag, preview, config }) {
376275
376765
  setState((s) => ({
376276
376766
  ...s,
376277
376767
  phase: "creating-tarball",
376768
+ environmentId: env2.id,
376278
376769
  environmentName: env2.name
376279
376770
  }));
376280
376771
  },
@@ -376426,7 +376917,7 @@ function DeployUI({ envFlag, preview, config }) {
376426
376917
  }, [state]);
376427
376918
  const environment = state.environmentName || "prod";
376428
376919
  useEffect6(() => {
376429
- if (state.phase !== "creating-tarball" || !state.projectId || !state.environmentName) return;
376920
+ if (state.phase !== "creating-tarball" || !state.projectId || !state.environmentId || !state.environmentName) return;
376430
376921
  let cancelled = false;
376431
376922
  async function runDeploy() {
376432
376923
  const projectDir = process.cwd();
@@ -376462,7 +376953,7 @@ function DeployUI({ envFlag, preview, config }) {
376462
376953
  try {
376463
376954
  writeLog("deploy", `Creating deployment for project ${state.projectId}`);
376464
376955
  const gitInfo = getGitInfo(projectDir);
376465
- deployment2 = await client2.createDeployment(state.projectId, state.environmentName, {
376956
+ deployment2 = await client2.createDeployment(state.projectId, state.environmentId, {
376466
376957
  triggeredBy: "cli",
376467
376958
  ...gitInfo && {
376468
376959
  gitCommitSha: gitInfo.commitSha,
@@ -376927,7 +377418,7 @@ async function runDeployPipeline(options2) {
376927
377418
  const projects = await client2.listProjects();
376928
377419
  const project = projects.find((p) => p.id === projectId);
376929
377420
  const environments = project?.environments ?? [];
376930
- let environmentName;
377421
+ let environmentId;
376931
377422
  if (options2.environment) {
376932
377423
  const match = findEnvironmentByNameOrId(environments, options2.environment);
376933
377424
  if (!match) {
@@ -376937,15 +377428,15 @@ async function runDeployPipeline(options2) {
376937
377428
  );
376938
377429
  process.exit(1);
376939
377430
  }
376940
- environmentName = match.name;
377431
+ environmentId = match.id;
376941
377432
  writeEnvironmentId(match.id);
376942
377433
  } else if (hasEnvironmentId(projectDir)) {
376943
377434
  const savedEnvId = readEnvironmentId(projectDir);
376944
377435
  const env2 = environments.find((e) => e.id === savedEnvId);
376945
377436
  if (env2) {
376946
- environmentName = env2.name;
377437
+ environmentId = env2.id;
376947
377438
  } else if (environments.length === 1) {
376948
- environmentName = environments[0].name;
377439
+ environmentId = environments[0].id;
376949
377440
  writeEnvironmentId(environments[0].id);
376950
377441
  } else if (environments.length === 0) {
376951
377442
  console.error("Error: No environments found for this project");
@@ -376960,7 +377451,7 @@ async function runDeployPipeline(options2) {
376960
377451
  }
376961
377452
  } else {
376962
377453
  if (environments.length === 1) {
376963
- environmentName = environments[0].name;
377454
+ environmentId = environments[0].id;
376964
377455
  writeEnvironmentId(environments[0].id);
376965
377456
  } else if (environments.length === 0) {
376966
377457
  console.error("Error: No environments found for this project");
@@ -377007,7 +377498,7 @@ async function runDeployPipeline(options2) {
377007
377498
  let deployment;
377008
377499
  try {
377009
377500
  const gitInfo = getGitInfo(projectDir);
377010
- deployment = await client2.createDeployment(projectId, environmentName, {
377501
+ deployment = await client2.createDeployment(projectId, environmentId, {
377011
377502
  triggeredBy: "cli",
377012
377503
  ...gitInfo && {
377013
377504
  gitCommitSha: gitInfo.commitSha,
@@ -378050,7 +378541,7 @@ function compareVersions(a, b) {
378050
378541
  return 0;
378051
378542
  }
378052
378543
  async function checkForUpdate() {
378053
- const currentVersion = "0.1.142";
378544
+ const currentVersion = "0.1.146";
378054
378545
  const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
378055
378546
  if (!response.ok) {
378056
378547
  throw new Error(`Failed to check for updates: HTTP ${response.status}`);
@@ -378565,7 +379056,7 @@ function friendlyErrorMessage(raw) {
378565
379056
  var program = new Command();
378566
379057
  var env = "production";
378567
379058
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
378568
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.142").enablePositionalOptions();
379059
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.146").enablePositionalOptions();
378569
379060
  program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
378570
379061
  Examples:
378571
379062
  $ specific init