@specific.dev/cli 0.1.37 → 0.1.39

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 (173) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.__PAGE__.txt +2 -2
  4. package/dist/admin/__next._full.txt +3 -3
  5. package/dist/admin/__next._head.txt +1 -1
  6. package/dist/admin/__next._index.txt +2 -2
  7. package/dist/admin/__next._tree.txt +2 -2
  8. package/dist/admin/_next/static/chunks/{de6af6d8adf8b50a.js → 0476153b08658d87.js} +2 -2
  9. package/dist/admin/_next/static/chunks/{4ab079bdcb131778.js → 1de437acc5206b1f.js} +2 -2
  10. package/dist/admin/_next/static/chunks/5ff94899b8b7a03a.css +3 -0
  11. package/dist/admin/_next/static/chunks/{895a6f91f0b479fb.js → 6aae09c9429b9f24.js} +2 -2
  12. package/dist/admin/_next/static/chunks/b71388016463cab2.js +1 -0
  13. package/dist/admin/_not-found/__next._full.txt +2 -2
  14. package/dist/admin/_not-found/__next._head.txt +1 -1
  15. package/dist/admin/_not-found/__next._index.txt +2 -2
  16. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  17. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  18. package/dist/admin/_not-found/__next._tree.txt +2 -2
  19. package/dist/admin/_not-found/index.html +1 -1
  20. package/dist/admin/_not-found/index.txt +2 -2
  21. package/dist/admin/databases/__next._full.txt +3 -3
  22. package/dist/admin/databases/__next._head.txt +1 -1
  23. package/dist/admin/databases/__next._index.txt +2 -2
  24. package/dist/admin/databases/__next._tree.txt +2 -2
  25. package/dist/admin/databases/__next.databases.__PAGE__.txt +2 -2
  26. package/dist/admin/databases/__next.databases.txt +1 -1
  27. package/dist/admin/databases/index.html +1 -1
  28. package/dist/admin/databases/index.txt +3 -3
  29. package/dist/admin/index.html +1 -1
  30. package/dist/admin/index.txt +3 -3
  31. package/dist/cli.js +1527 -719
  32. package/package.json +3 -2
  33. package/dist/admin/_next/static/1o2O2QPOKhuZyxcNaCjq8/_buildManifest.js +0 -11
  34. package/dist/admin/_next/static/1o2O2QPOKhuZyxcNaCjq8/_clientMiddlewareManifest.json +0 -1
  35. package/dist/admin/_next/static/1o2O2QPOKhuZyxcNaCjq8/_ssgManifest.js +0 -1
  36. package/dist/admin/_next/static/2ZtqtFX2EkuM82mTM7NWT/_buildManifest.js +0 -11
  37. package/dist/admin/_next/static/2ZtqtFX2EkuM82mTM7NWT/_clientMiddlewareManifest.json +0 -1
  38. package/dist/admin/_next/static/2ZtqtFX2EkuM82mTM7NWT/_ssgManifest.js +0 -1
  39. package/dist/admin/_next/static/2vLF5bo4ZtUfhs8bdQBoF/_buildManifest.js +0 -11
  40. package/dist/admin/_next/static/2vLF5bo4ZtUfhs8bdQBoF/_clientMiddlewareManifest.json +0 -1
  41. package/dist/admin/_next/static/2vLF5bo4ZtUfhs8bdQBoF/_ssgManifest.js +0 -1
  42. package/dist/admin/_next/static/8_8s51m7RbLVvbIechG-b/_buildManifest.js +0 -11
  43. package/dist/admin/_next/static/8_8s51m7RbLVvbIechG-b/_clientMiddlewareManifest.json +0 -1
  44. package/dist/admin/_next/static/8_8s51m7RbLVvbIechG-b/_ssgManifest.js +0 -1
  45. package/dist/admin/_next/static/C4Y44KtLnP684m57gN3Ga/_buildManifest.js +0 -11
  46. package/dist/admin/_next/static/C4Y44KtLnP684m57gN3Ga/_clientMiddlewareManifest.json +0 -1
  47. package/dist/admin/_next/static/C4Y44KtLnP684m57gN3Ga/_ssgManifest.js +0 -1
  48. package/dist/admin/_next/static/DBeh36kAJnZBeBMJSPBW8/_buildManifest.js +0 -11
  49. package/dist/admin/_next/static/DBeh36kAJnZBeBMJSPBW8/_clientMiddlewareManifest.json +0 -1
  50. package/dist/admin/_next/static/DBeh36kAJnZBeBMJSPBW8/_ssgManifest.js +0 -1
  51. package/dist/admin/_next/static/REKEUu6DP2t99jKSAqNOu/_buildManifest.js +0 -11
  52. package/dist/admin/_next/static/REKEUu6DP2t99jKSAqNOu/_clientMiddlewareManifest.json +0 -1
  53. package/dist/admin/_next/static/REKEUu6DP2t99jKSAqNOu/_ssgManifest.js +0 -1
  54. package/dist/admin/_next/static/TDGT3mkKtTe0w_CxujLxI/_buildManifest.js +0 -11
  55. package/dist/admin/_next/static/TDGT3mkKtTe0w_CxujLxI/_clientMiddlewareManifest.json +0 -1
  56. package/dist/admin/_next/static/TDGT3mkKtTe0w_CxujLxI/_ssgManifest.js +0 -1
  57. package/dist/admin/_next/static/ZHMJ-g7mAlAMt_2uCXeEk/_buildManifest.js +0 -11
  58. package/dist/admin/_next/static/ZHMJ-g7mAlAMt_2uCXeEk/_clientMiddlewareManifest.json +0 -1
  59. package/dist/admin/_next/static/ZHMJ-g7mAlAMt_2uCXeEk/_ssgManifest.js +0 -1
  60. package/dist/admin/_next/static/a6JDAB-CdvWPvS4sBgJji/_buildManifest.js +0 -11
  61. package/dist/admin/_next/static/a6JDAB-CdvWPvS4sBgJji/_clientMiddlewareManifest.json +0 -1
  62. package/dist/admin/_next/static/a6JDAB-CdvWPvS4sBgJji/_ssgManifest.js +0 -1
  63. package/dist/admin/_next/static/abFUeBpymhlx-IxygVnM9/_buildManifest.js +0 -11
  64. package/dist/admin/_next/static/abFUeBpymhlx-IxygVnM9/_clientMiddlewareManifest.json +0 -1
  65. package/dist/admin/_next/static/abFUeBpymhlx-IxygVnM9/_ssgManifest.js +0 -1
  66. package/dist/admin/_next/static/chunks/1584f10ea1cebcb2.js +0 -4
  67. package/dist/admin/_next/static/chunks/195bbec70cfcd241.js +0 -2
  68. package/dist/admin/_next/static/chunks/2583656ea9ac4ad6.js +0 -5
  69. package/dist/admin/_next/static/chunks/605800ff25160d05.js +0 -1
  70. package/dist/admin/_next/static/chunks/656b870f0567ed5f.js +0 -1
  71. package/dist/admin/_next/static/chunks/71098a6cd6181738.css +0 -3
  72. package/dist/admin/_next/static/chunks/9032f4a1aac1ca5d.css +0 -3
  73. package/dist/admin/_next/static/chunks/a28af2dc6f5fbaad.js +0 -1
  74. package/dist/admin/_next/static/chunks/b4205fc2f84bda68.css +0 -3
  75. package/dist/admin/_next/static/chunks/c1a750c25bc8d092.js +0 -1
  76. package/dist/admin/_next/static/chunks/c3d30f6f144dca51.js +0 -2
  77. package/dist/admin/_next/static/chunks/cbf55ce8731457ae.js +0 -2
  78. package/dist/admin/_next/static/chunks/f0c001244d275aab.js +0 -5
  79. package/dist/admin/_next/static/chunks/fde89fd76ad6a3d0.css +0 -3
  80. package/dist/admin/_next/static/chunks/turbopack-a3d691c83d4b1778.js +0 -4
  81. package/dist/admin/_next/static/dcjrfD44GuB6g8bZ6BcFm/_buildManifest.js +0 -11
  82. package/dist/admin/_next/static/dcjrfD44GuB6g8bZ6BcFm/_clientMiddlewareManifest.json +0 -1
  83. package/dist/admin/_next/static/dcjrfD44GuB6g8bZ6BcFm/_ssgManifest.js +0 -1
  84. package/dist/admin/_next/static/fTEa8Scx922n-dMuqN3Vc/_buildManifest.js +0 -11
  85. package/dist/admin/_next/static/fTEa8Scx922n-dMuqN3Vc/_clientMiddlewareManifest.json +0 -1
  86. package/dist/admin/_next/static/fTEa8Scx922n-dMuqN3Vc/_ssgManifest.js +0 -1
  87. package/dist/admin/_next/static/kZNwyhHft01wPtJ_AvqQT/_buildManifest.js +0 -11
  88. package/dist/admin/_next/static/kZNwyhHft01wPtJ_AvqQT/_clientMiddlewareManifest.json +0 -1
  89. package/dist/admin/_next/static/kZNwyhHft01wPtJ_AvqQT/_ssgManifest.js +0 -1
  90. package/dist/admin/_next/static/kyVInC6N3DY6NWnjkLQ4J/_buildManifest.js +0 -11
  91. package/dist/admin/_next/static/kyVInC6N3DY6NWnjkLQ4J/_clientMiddlewareManifest.json +0 -1
  92. package/dist/admin/_next/static/kyVInC6N3DY6NWnjkLQ4J/_ssgManifest.js +0 -1
  93. package/dist/admin/_next/static/twedXBdhyxpjS68UQoNAc/_buildManifest.js +0 -11
  94. package/dist/admin/_next/static/twedXBdhyxpjS68UQoNAc/_clientMiddlewareManifest.json +0 -1
  95. package/dist/admin/_next/static/twedXBdhyxpjS68UQoNAc/_ssgManifest.js +0 -1
  96. package/dist/cli.d.ts +0 -3
  97. package/dist/cli.d.ts.map +0 -1
  98. package/dist/cli.js.map +0 -1
  99. package/dist/commands/check.d.ts +0 -2
  100. package/dist/commands/check.d.ts.map +0 -1
  101. package/dist/commands/check.js +0 -104
  102. package/dist/commands/check.js.map +0 -1
  103. package/dist/commands/clean.d.ts +0 -2
  104. package/dist/commands/clean.d.ts.map +0 -1
  105. package/dist/commands/clean.js +0 -70
  106. package/dist/commands/clean.js.map +0 -1
  107. package/dist/commands/deploy.d.ts +0 -2
  108. package/dist/commands/deploy.d.ts.map +0 -1
  109. package/dist/commands/deploy.js +0 -11
  110. package/dist/commands/deploy.js.map +0 -1
  111. package/dist/commands/dev.d.ts +0 -2
  112. package/dist/commands/dev.d.ts.map +0 -1
  113. package/dist/commands/dev.js +0 -398
  114. package/dist/commands/dev.js.map +0 -1
  115. package/dist/commands/docs.d.ts +0 -2
  116. package/dist/commands/docs.d.ts.map +0 -1
  117. package/dist/commands/docs.js +0 -32
  118. package/dist/commands/docs.js.map +0 -1
  119. package/dist/commands/exec.d.ts +0 -2
  120. package/dist/commands/exec.d.ts.map +0 -1
  121. package/dist/commands/exec.js +0 -178
  122. package/dist/commands/exec.js.map +0 -1
  123. package/dist/commands/init.d.ts +0 -2
  124. package/dist/commands/init.d.ts.map +0 -1
  125. package/dist/commands/init.js +0 -339
  126. package/dist/commands/init.js.map +0 -1
  127. package/dist/commands/psql.d.ts +0 -2
  128. package/dist/commands/psql.d.ts.map +0 -1
  129. package/dist/commands/psql.js +0 -53
  130. package/dist/commands/psql.js.map +0 -1
  131. package/dist/commands/secrets.d.ts +0 -3
  132. package/dist/commands/secrets.d.ts.map +0 -1
  133. package/dist/commands/secrets.js +0 -136
  134. package/dist/commands/secrets.js.map +0 -1
  135. package/dist/lib/dev/database-manager.d.ts +0 -17
  136. package/dist/lib/dev/database-manager.d.ts.map +0 -1
  137. package/dist/lib/dev/database-manager.js +0 -114
  138. package/dist/lib/dev/database-manager.js.map +0 -1
  139. package/dist/lib/dev/env-resolver.d.ts +0 -14
  140. package/dist/lib/dev/env-resolver.d.ts.map +0 -1
  141. package/dist/lib/dev/env-resolver.js +0 -109
  142. package/dist/lib/dev/env-resolver.js.map +0 -1
  143. package/dist/lib/dev/http-proxy.d.ts +0 -11
  144. package/dist/lib/dev/http-proxy.d.ts.map +0 -1
  145. package/dist/lib/dev/http-proxy.js +0 -165
  146. package/dist/lib/dev/http-proxy.js.map +0 -1
  147. package/dist/lib/dev/index.d.ts +0 -11
  148. package/dist/lib/dev/index.d.ts.map +0 -1
  149. package/dist/lib/dev/index.js +0 -7
  150. package/dist/lib/dev/index.js.map +0 -1
  151. package/dist/lib/dev/instance-state.d.ts +0 -45
  152. package/dist/lib/dev/instance-state.d.ts.map +0 -1
  153. package/dist/lib/dev/instance-state.js +0 -213
  154. package/dist/lib/dev/instance-state.js.map +0 -1
  155. package/dist/lib/dev/port-allocator.d.ts +0 -5
  156. package/dist/lib/dev/port-allocator.d.ts.map +0 -1
  157. package/dist/lib/dev/port-allocator.js +0 -21
  158. package/dist/lib/dev/port-allocator.js.map +0 -1
  159. package/dist/lib/dev/service-runner.d.ts +0 -16
  160. package/dist/lib/dev/service-runner.d.ts.map +0 -1
  161. package/dist/lib/dev/service-runner.js +0 -61
  162. package/dist/lib/dev/service-runner.js.map +0 -1
  163. package/dist/lib/secrets/index.d.ts +0 -2
  164. package/dist/lib/secrets/index.d.ts.map +0 -1
  165. package/dist/lib/secrets/index.js +0 -2
  166. package/dist/lib/secrets/index.js.map +0 -1
  167. package/dist/lib/secrets/parser.d.ts +0 -37
  168. package/dist/lib/secrets/parser.d.ts.map +0 -1
  169. package/dist/lib/secrets/parser.js +0 -116
  170. package/dist/lib/secrets/parser.js.map +0 -1
  171. /package/dist/admin/_next/static/{0zkv3YeV6IWOWVWE1S1A1 → cNL40bpv360RCjyOls7vD}/_buildManifest.js +0 -0
  172. /package/dist/admin/_next/static/{0zkv3YeV6IWOWVWE1S1A1 → cNL40bpv360RCjyOls7vD}/_clientMiddlewareManifest.json +0 -0
  173. /package/dist/admin/_next/static/{0zkv3YeV6IWOWVWE1S1A1 → cNL40bpv360RCjyOls7vD}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -41,8 +41,8 @@ var require_dist = __commonJS({
41
41
  var $global, $module, $NaN = NaN;
42
42
  if ("undefined" != typeof window ? $global = window : "undefined" != typeof self ? $global = self : "undefined" != typeof global ? ($global = global).require = __require : $global = this, void 0 === $global || void 0 === $global.Array) throw new Error("no global object found");
43
43
  if ("undefined" != typeof module && ($module = module), !$global.fs && $global.require) try {
44
- var fs25 = $global.require("fs");
45
- "object" == typeof fs25 && null !== fs25 && 0 !== Object.keys(fs25).length && ($global.fs = fs25);
44
+ var fs26 = $global.require("fs");
45
+ "object" == typeof fs26 && null !== fs26 && 0 !== Object.keys(fs26).length && ($global.fs = fs26);
46
46
  } catch (e) {
47
47
  }
48
48
  if (!$global.fs) {
@@ -182236,8 +182236,8 @@ import { Command } from "commander";
182236
182236
  import React, { useState, useEffect } from "react";
182237
182237
  import { render, Text, Box, useInput, useApp } from "ink";
182238
182238
  import "ink-spinner";
182239
- import * as fs2 from "fs";
182240
- import * as path2 from "path";
182239
+ import * as fs3 from "fs";
182240
+ import * as path3 from "path";
182241
182241
 
182242
182242
  // src/lib/dev/local-ca.ts
182243
182243
  import * as fs from "fs";
@@ -182368,40 +182368,143 @@ function installCAToTrustStore(certPath) {
182368
182368
  throw new Error(`Unsupported platform: ${platform5}`);
182369
182369
  }
182370
182370
  }
182371
- function generateCertificate(domain) {
182371
+ function generateCertificate(domain, keys = []) {
182372
182372
  const caDir = getCADir();
182373
182373
  const caCertPem = fs.readFileSync(path.join(caDir, "ca.crt"), "utf-8");
182374
182374
  const caKeyPem = fs.readFileSync(path.join(caDir, "ca.key"), "utf-8");
182375
182375
  const caCert = forge.pki.certificateFromPem(caCertPem);
182376
182376
  const caKey = forge.pki.privateKeyFromPem(caKeyPem);
182377
- const keys = forge.pki.rsa.generateKeyPair(2048);
182377
+ const certKeys = forge.pki.rsa.generateKeyPair(2048);
182378
182378
  const cert = forge.pki.createCertificate();
182379
- cert.publicKey = keys.publicKey;
182379
+ cert.publicKey = certKeys.publicKey;
182380
182380
  cert.serialNumber = forge.util.bytesToHex(forge.random.getBytesSync(16));
182381
182381
  cert.validity.notBefore = /* @__PURE__ */ new Date();
182382
182382
  cert.validity.notAfter = /* @__PURE__ */ new Date();
182383
182383
  cert.validity.notAfter.setDate(cert.validity.notBefore.getDate() + 7);
182384
182384
  cert.setSubject([{ name: "commonName", value: domain }]);
182385
182385
  cert.setIssuer(caCert.subject.attributes);
182386
+ const altNames = [
182387
+ { type: 2, value: domain },
182388
+ { type: 2, value: `*.${domain}` }
182389
+ ];
182390
+ for (const key of keys) {
182391
+ if (key !== "default") {
182392
+ altNames.push({ type: 2, value: `*.${key}.${domain}` });
182393
+ }
182394
+ }
182386
182395
  cert.setExtensions([
182387
182396
  { name: "basicConstraints", cA: false },
182388
182397
  { name: "keyUsage", digitalSignature: true, keyEncipherment: true },
182389
182398
  { name: "extKeyUsage", serverAuth: true },
182390
- {
182391
- name: "subjectAltName",
182392
- altNames: [
182393
- { type: 2, value: domain },
182394
- { type: 2, value: `*.${domain}` }
182395
- ]
182396
- }
182399
+ { name: "subjectAltName", altNames }
182397
182400
  ]);
182398
182401
  cert.sign(caKey, forge.md.sha256.create());
182399
182402
  return {
182400
- key: Buffer.from(forge.pki.privateKeyToPem(keys.privateKey)),
182403
+ key: Buffer.from(forge.pki.privateKeyToPem(certKeys.privateKey)),
182401
182404
  cert: Buffer.from(forge.pki.certificateToPem(cert))
182402
182405
  };
182403
182406
  }
182404
182407
 
182408
+ // src/lib/analytics/index.ts
182409
+ import { PostHog } from "posthog-node";
182410
+ import * as os2 from "os";
182411
+ import * as crypto from "crypto";
182412
+
182413
+ // src/lib/project/config.ts
182414
+ import * as fs2 from "fs";
182415
+ import * as path2 from "path";
182416
+ var PROJECT_ID_FILE = ".specific/project_id";
182417
+ var ProjectNotLinkedError = class extends Error {
182418
+ constructor() {
182419
+ super(
182420
+ `Project not linked to Specific.
182421
+
182422
+ The file ${PROJECT_ID_FILE} does not exist.
182423
+
182424
+ Run: specific deploy`
182425
+ );
182426
+ this.name = "ProjectNotLinkedError";
182427
+ }
182428
+ };
182429
+ function readProjectId(projectDir = process.cwd()) {
182430
+ const projectIdPath = path2.join(projectDir, PROJECT_ID_FILE);
182431
+ if (!fs2.existsSync(projectIdPath)) {
182432
+ throw new ProjectNotLinkedError();
182433
+ }
182434
+ const projectId = fs2.readFileSync(projectIdPath, "utf-8").trim();
182435
+ if (!projectId) {
182436
+ throw new Error(`${PROJECT_ID_FILE} is empty`);
182437
+ }
182438
+ return projectId;
182439
+ }
182440
+ function hasProjectId(projectDir = process.cwd()) {
182441
+ const projectIdPath = path2.join(projectDir, PROJECT_ID_FILE);
182442
+ return fs2.existsSync(projectIdPath);
182443
+ }
182444
+ function writeProjectId(projectId, projectDir = process.cwd()) {
182445
+ const specificDir = path2.join(projectDir, ".specific");
182446
+ if (!fs2.existsSync(specificDir)) {
182447
+ fs2.mkdirSync(specificDir, { recursive: true });
182448
+ }
182449
+ fs2.writeFileSync(path2.join(specificDir, "project_id"), projectId + "\n");
182450
+ }
182451
+
182452
+ // src/lib/analytics/index.ts
182453
+ var POSTHOG_HOST = "https://eu.i.posthog.com";
182454
+ var client = null;
182455
+ var anonymousId = null;
182456
+ function isEnabled() {
182457
+ return true;
182458
+ }
182459
+ function getAnonymousId() {
182460
+ if (anonymousId) return anonymousId;
182461
+ const machineId = `${os2.hostname()}-${os2.userInfo().username}`;
182462
+ anonymousId = crypto.createHash("sha256").update(machineId).digest("hex").slice(0, 16);
182463
+ return anonymousId;
182464
+ }
182465
+ function getProjectId() {
182466
+ try {
182467
+ if (hasProjectId()) {
182468
+ return readProjectId();
182469
+ }
182470
+ } catch {
182471
+ }
182472
+ return void 0;
182473
+ }
182474
+ function getClient() {
182475
+ if (!isEnabled()) return null;
182476
+ if (!client) {
182477
+ client = new PostHog("phc_qNQCEUXh6ErdciQRRmeG2xlVvwFjkcW6A5bnOFJ8vXZ", {
182478
+ host: POSTHOG_HOST,
182479
+ flushAt: 1,
182480
+ // Flush immediately for CLI
182481
+ flushInterval: 0
182482
+ });
182483
+ }
182484
+ return client;
182485
+ }
182486
+ function trackEvent(event, properties) {
182487
+ const ph = getClient();
182488
+ if (!ph) return;
182489
+ ph.capture({
182490
+ distinctId: getAnonymousId(),
182491
+ event,
182492
+ properties: {
182493
+ ...properties,
182494
+ cli_version: "0.1.39",
182495
+ platform: process.platform,
182496
+ node_version: process.version,
182497
+ project_id: getProjectId()
182498
+ }
182499
+ });
182500
+ }
182501
+ async function shutdown() {
182502
+ if (client) {
182503
+ await client.shutdown();
182504
+ client = null;
182505
+ }
182506
+ }
182507
+
182405
182508
  // src/commands/init.tsx
182406
182509
  var SPECIFIC_INSTRUCTIONS = `Use Specific to handle all infrastructure and development environments: like deployments, databases, real-time sync, services, secrets, configuration and environment variables. Run \`specific docs\` to learn how to use Specific. ALWAYS run \`specific check\` after making changes to the Specific configuration.`;
182407
182510
  var CURSOR_MDC_CONTENT = `---
@@ -182419,67 +182522,67 @@ var options = [
182419
182522
  { id: "other", label: "Other / Manual" }
182420
182523
  ];
182421
182524
  function isGitProject() {
182422
- const gitPath = path2.join(process.cwd(), ".git");
182423
- return fs2.existsSync(gitPath);
182525
+ const gitPath = path3.join(process.cwd(), ".git");
182526
+ return fs3.existsSync(gitPath);
182424
182527
  }
182425
182528
  function detectExistingAgents() {
182426
182529
  const detected = {};
182427
- const cursorDir = path2.join(process.cwd(), ".cursor");
182428
- if (fs2.existsSync(cursorDir)) {
182530
+ const cursorDir = path3.join(process.cwd(), ".cursor");
182531
+ if (fs3.existsSync(cursorDir)) {
182429
182532
  detected["cursor"] = true;
182430
182533
  }
182431
- const claudeDir = path2.join(process.cwd(), ".claude");
182432
- const claudeMd = path2.join(process.cwd(), "CLAUDE.md");
182433
- if (fs2.existsSync(claudeDir) || fs2.existsSync(claudeMd)) {
182534
+ const claudeDir = path3.join(process.cwd(), ".claude");
182535
+ const claudeMd = path3.join(process.cwd(), "CLAUDE.md");
182536
+ if (fs3.existsSync(claudeDir) || fs3.existsSync(claudeMd)) {
182434
182537
  detected["claude"] = true;
182435
182538
  }
182436
- const agentsMd = path2.join(process.cwd(), "AGENTS.md");
182437
- if (fs2.existsSync(agentsMd)) {
182539
+ const agentsMd = path3.join(process.cwd(), "AGENTS.md");
182540
+ if (fs3.existsSync(agentsMd)) {
182438
182541
  detected["codex"] = true;
182439
182542
  }
182440
182543
  return detected;
182441
182544
  }
182442
182545
  function appendOrCreateFile(filePath, content) {
182443
- if (fs2.existsSync(filePath)) {
182444
- const existing = fs2.readFileSync(filePath, "utf-8");
182546
+ if (fs3.existsSync(filePath)) {
182547
+ const existing = fs3.readFileSync(filePath, "utf-8");
182445
182548
  if (existing.includes("specific docs") || existing.includes("specific check")) {
182446
182549
  return "unchanged";
182447
182550
  }
182448
182551
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
182449
- fs2.writeFileSync(filePath, existing + separator + content + "\n");
182552
+ fs3.writeFileSync(filePath, existing + separator + content + "\n");
182450
182553
  return "modified";
182451
182554
  } else {
182452
- fs2.writeFileSync(filePath, content + "\n");
182555
+ fs3.writeFileSync(filePath, content + "\n");
182453
182556
  return "created";
182454
182557
  }
182455
182558
  }
182456
182559
  function addToGitignore() {
182457
- const gitignorePath = path2.join(process.cwd(), ".gitignore");
182560
+ const gitignorePath = path3.join(process.cwd(), ".gitignore");
182458
182561
  const entries = [".specific", "specific.secrets"];
182459
- if (fs2.existsSync(gitignorePath)) {
182460
- const existing = fs2.readFileSync(gitignorePath, "utf-8");
182562
+ if (fs3.existsSync(gitignorePath)) {
182563
+ const existing = fs3.readFileSync(gitignorePath, "utf-8");
182461
182564
  const lines = existing.split("\n").map((l) => l.trim());
182462
182565
  const missingEntries = entries.filter((entry) => !lines.includes(entry));
182463
182566
  if (missingEntries.length === 0) {
182464
182567
  return "unchanged";
182465
182568
  }
182466
182569
  const separator = existing.endsWith("\n") ? "" : "\n";
182467
- fs2.writeFileSync(
182570
+ fs3.writeFileSync(
182468
182571
  gitignorePath,
182469
182572
  existing + separator + missingEntries.join("\n") + "\n"
182470
182573
  );
182471
182574
  return "modified";
182472
182575
  } else {
182473
- fs2.writeFileSync(gitignorePath, entries.join("\n") + "\n");
182576
+ fs3.writeFileSync(gitignorePath, entries.join("\n") + "\n");
182474
182577
  return "created";
182475
182578
  }
182476
182579
  }
182477
182580
  function configureClaudeCodePermissions() {
182478
- const claudeDir = path2.join(process.cwd(), ".claude");
182479
- const settingsPath = path2.join(claudeDir, "settings.local.json");
182581
+ const claudeDir = path3.join(process.cwd(), ".claude");
182582
+ const settingsPath = path3.join(claudeDir, "settings.local.json");
182480
182583
  const permissions = ["Bash(specific docs:*)", "Bash(specific check:*)"];
182481
- if (fs2.existsSync(settingsPath)) {
182482
- const existing = JSON.parse(fs2.readFileSync(settingsPath, "utf-8"));
182584
+ if (fs3.existsSync(settingsPath)) {
182585
+ const existing = JSON.parse(fs3.readFileSync(settingsPath, "utf-8"));
182483
182586
  const allowList = existing?.permissions?.allow || [];
182484
182587
  const missingPermissions = permissions.filter(
182485
182588
  (p) => !allowList.includes(p)
@@ -182494,39 +182597,39 @@ function configureClaudeCodePermissions() {
182494
182597
  existing.permissions.allow = [];
182495
182598
  }
182496
182599
  existing.permissions.allow.push(...missingPermissions);
182497
- fs2.writeFileSync(settingsPath, JSON.stringify(existing, null, 2) + "\n");
182600
+ fs3.writeFileSync(settingsPath, JSON.stringify(existing, null, 2) + "\n");
182498
182601
  return "modified";
182499
182602
  }
182500
- if (!fs2.existsSync(claudeDir)) {
182501
- fs2.mkdirSync(claudeDir);
182603
+ if (!fs3.existsSync(claudeDir)) {
182604
+ fs3.mkdirSync(claudeDir);
182502
182605
  }
182503
182606
  const settings = {
182504
182607
  permissions: {
182505
182608
  allow: permissions
182506
182609
  }
182507
182610
  };
182508
- fs2.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
182611
+ fs3.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
182509
182612
  return "created";
182510
182613
  }
182511
182614
  function createCursorRule() {
182512
- const cursorDir = path2.join(process.cwd(), ".cursor");
182513
- const rulesDir = path2.join(cursorDir, "rules");
182514
- const mdcPath = path2.join(rulesDir, "specific.mdc");
182515
- if (fs2.existsSync(mdcPath)) {
182516
- const existing = fs2.readFileSync(mdcPath, "utf-8");
182615
+ const cursorDir = path3.join(process.cwd(), ".cursor");
182616
+ const rulesDir = path3.join(cursorDir, "rules");
182617
+ const mdcPath = path3.join(rulesDir, "specific.mdc");
182618
+ if (fs3.existsSync(mdcPath)) {
182619
+ const existing = fs3.readFileSync(mdcPath, "utf-8");
182517
182620
  if (existing.includes("specific docs") || existing.includes("specific check")) {
182518
182621
  return "unchanged";
182519
182622
  }
182520
- fs2.writeFileSync(mdcPath, CURSOR_MDC_CONTENT);
182623
+ fs3.writeFileSync(mdcPath, CURSOR_MDC_CONTENT);
182521
182624
  return "modified";
182522
182625
  }
182523
- if (!fs2.existsSync(cursorDir)) {
182524
- fs2.mkdirSync(cursorDir);
182626
+ if (!fs3.existsSync(cursorDir)) {
182627
+ fs3.mkdirSync(cursorDir);
182525
182628
  }
182526
- if (!fs2.existsSync(rulesDir)) {
182527
- fs2.mkdirSync(rulesDir);
182629
+ if (!fs3.existsSync(rulesDir)) {
182630
+ fs3.mkdirSync(rulesDir);
182528
182631
  }
182529
- fs2.writeFileSync(mdcPath, CURSOR_MDC_CONTENT);
182632
+ fs3.writeFileSync(mdcPath, CURSOR_MDC_CONTENT);
182530
182633
  return "created";
182531
182634
  }
182532
182635
  function configureAgents(checked) {
@@ -182542,7 +182645,7 @@ function configureAgents(checked) {
182542
182645
  agents.filesModified.push(".cursor/rules/specific.mdc");
182543
182646
  }
182544
182647
  if (checked["claude"]) {
182545
- const claudeMdPath = path2.join(process.cwd(), "CLAUDE.md");
182648
+ const claudeMdPath = path3.join(process.cwd(), "CLAUDE.md");
182546
182649
  const status = appendOrCreateFile(claudeMdPath, SPECIFIC_INSTRUCTIONS);
182547
182650
  if (status === "created") agents.filesCreated.push("CLAUDE.md");
182548
182651
  else if (status === "modified") agents.filesModified.push("CLAUDE.md");
@@ -182553,7 +182656,7 @@ function configureAgents(checked) {
182553
182656
  agents.filesModified.push(".claude/settings.local.json");
182554
182657
  }
182555
182658
  if (checked["codex"]) {
182556
- const agentsMdPath = path2.join(process.cwd(), "AGENTS.md");
182659
+ const agentsMdPath = path3.join(process.cwd(), "AGENTS.md");
182557
182660
  const status = appendOrCreateFile(agentsMdPath, SPECIFIC_INSTRUCTIONS);
182558
182661
  if (status === "created") agents.filesCreated.push("AGENTS.md");
182559
182662
  else if (status === "modified") agents.filesModified.push("AGENTS.md");
@@ -182629,6 +182732,9 @@ function InitUI() {
182629
182732
  } else if ((key.return || input === " ") && isSubmitFocused) {
182630
182733
  const configResult = configureAgents(checked);
182631
182734
  setResult(configResult);
182735
+ trackEvent("project_initialized", {
182736
+ agents: Object.keys(checked).filter((k) => checked[k])
182737
+ });
182632
182738
  setPhase("done");
182633
182739
  }
182634
182740
  });
@@ -182672,35 +182778,35 @@ function initCommand() {
182672
182778
  }
182673
182779
 
182674
182780
  // src/commands/docs.tsx
182675
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
182676
- import { join as join3, dirname } from "path";
182781
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
182782
+ import { join as join4, dirname } from "path";
182677
182783
  import { fileURLToPath } from "url";
182678
182784
  var __dirname = dirname(fileURLToPath(import.meta.url));
182679
- var docsDir = join3(__dirname, "docs");
182680
- function docsCommand(path20) {
182681
- const docPath = resolveDocPath(path20);
182785
+ var docsDir = join4(__dirname, "docs");
182786
+ function docsCommand(path22) {
182787
+ const docPath = resolveDocPath(path22);
182682
182788
  if (!docPath) {
182683
182789
  console.error(
182684
- `Documentation not found: ${path20 || "index"}
182790
+ `Documentation not found: ${path22 || "index"}
182685
182791
 
182686
182792
  Run 'specific docs' to see available topics.`
182687
182793
  );
182688
182794
  process.exit(1);
182689
182795
  }
182690
- const content = readFileSync3(docPath, "utf-8");
182796
+ const content = readFileSync4(docPath, "utf-8");
182691
182797
  console.log(content);
182692
182798
  }
182693
- function resolveDocPath(path20) {
182694
- if (!path20) {
182695
- const indexPath2 = join3(docsDir, "index.md");
182696
- return existsSync3(indexPath2) ? indexPath2 : null;
182799
+ function resolveDocPath(path22) {
182800
+ if (!path22) {
182801
+ const indexPath2 = join4(docsDir, "index.md");
182802
+ return existsSync4(indexPath2) ? indexPath2 : null;
182697
182803
  }
182698
- const directPath = join3(docsDir, `${path20}.md`);
182699
- if (existsSync3(directPath)) {
182804
+ const directPath = join4(docsDir, `${path22}.md`);
182805
+ if (existsSync4(directPath)) {
182700
182806
  return directPath;
182701
182807
  }
182702
- const indexPath = join3(docsDir, path20, "index.md");
182703
- if (existsSync3(indexPath)) {
182808
+ const indexPath = join4(docsDir, path22, "index.md");
182809
+ if (existsSync4(indexPath)) {
182704
182810
  return indexPath;
182705
182811
  }
182706
182812
  return null;
@@ -182710,8 +182816,8 @@ function resolveDocPath(path20) {
182710
182816
  import React2, { useState as useState2, useEffect as useEffect2 } from "react";
182711
182817
  import { render as render2, Text as Text2, Box as Box2 } from "ink";
182712
182818
  import Spinner2 from "ink-spinner";
182713
- import * as fs3 from "fs";
182714
- import * as path3 from "path";
182819
+ import * as fs4 from "fs";
182820
+ import * as path4 from "path";
182715
182821
 
182716
182822
  // ../config/dist/parser.js
182717
182823
  var import_hcl2_json_parser = __toESM(require_dist(), 1);
@@ -183193,8 +183299,8 @@ function CheckUI() {
183193
183299
  const [state, setState] = useState2({ status: "loading" });
183194
183300
  useEffect2(() => {
183195
183301
  async function load() {
183196
- const configPath = path3.join(process.cwd(), "specific.hcl");
183197
- if (!fs3.existsSync(configPath)) {
183302
+ const configPath = path4.join(process.cwd(), "specific.hcl");
183303
+ if (!fs4.existsSync(configPath)) {
183198
183304
  setState({
183199
183305
  status: "error",
183200
183306
  error: "No specific.hcl found in current directory"
@@ -183202,7 +183308,7 @@ function CheckUI() {
183202
183308
  return;
183203
183309
  }
183204
183310
  try {
183205
- const hcl = fs3.readFileSync(configPath, "utf-8");
183311
+ const hcl = fs4.readFileSync(configPath, "utf-8");
183206
183312
  const config2 = await parseConfig(hcl);
183207
183313
  setState({ status: "success", config: config2 });
183208
183314
  } catch (err) {
@@ -183234,8 +183340,8 @@ function checkCommand() {
183234
183340
  import React3, { useState as useState3, useEffect as useEffect3, useRef } from "react";
183235
183341
  import { render as render3, Text as Text3, Box as Box3, useApp as useApp2, Static } from "ink";
183236
183342
  import Spinner3 from "ink-spinner";
183237
- import * as fs11 from "fs";
183238
- import * as path12 from "path";
183343
+ import * as fs13 from "fs";
183344
+ import * as path14 from "path";
183239
183345
 
183240
183346
  // node_modules/chokidar/index.js
183241
183347
  import { EventEmitter } from "node:events";
@@ -183327,7 +183433,7 @@ var ReaddirpStream = class extends Readable {
183327
183433
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
183328
183434
  const statMethod = opts.lstat ? lstat : stat;
183329
183435
  if (wantBigintFsStats) {
183330
- this._stat = (path20) => statMethod(path20, { bigint: true });
183436
+ this._stat = (path22) => statMethod(path22, { bigint: true });
183331
183437
  } else {
183332
183438
  this._stat = statMethod;
183333
183439
  }
@@ -183352,8 +183458,8 @@ var ReaddirpStream = class extends Readable {
183352
183458
  const par = this.parent;
183353
183459
  const fil = par && par.files;
183354
183460
  if (fil && fil.length > 0) {
183355
- const { path: path20, depth } = par;
183356
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path20));
183461
+ const { path: path22, depth } = par;
183462
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path22));
183357
183463
  const awaited = await Promise.all(slice);
183358
183464
  for (const entry of awaited) {
183359
183465
  if (!entry)
@@ -183393,21 +183499,21 @@ var ReaddirpStream = class extends Readable {
183393
183499
  this.reading = false;
183394
183500
  }
183395
183501
  }
183396
- async _exploreDir(path20, depth) {
183502
+ async _exploreDir(path22, depth) {
183397
183503
  let files;
183398
183504
  try {
183399
- files = await readdir(path20, this._rdOptions);
183505
+ files = await readdir(path22, this._rdOptions);
183400
183506
  } catch (error) {
183401
183507
  this._onError(error);
183402
183508
  }
183403
- return { files, depth, path: path20 };
183509
+ return { files, depth, path: path22 };
183404
183510
  }
183405
- async _formatEntry(dirent, path20) {
183511
+ async _formatEntry(dirent, path22) {
183406
183512
  let entry;
183407
- const basename4 = this._isDirent ? dirent.name : dirent;
183513
+ const basename5 = this._isDirent ? dirent.name : dirent;
183408
183514
  try {
183409
- const fullPath = presolve(pjoin(path20, basename4));
183410
- entry = { path: prelative(this._root, fullPath), fullPath, basename: basename4 };
183515
+ const fullPath = presolve(pjoin(path22, basename5));
183516
+ entry = { path: prelative(this._root, fullPath), fullPath, basename: basename5 };
183411
183517
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
183412
183518
  } catch (err) {
183413
183519
  this._onError(err);
@@ -183806,16 +183912,16 @@ var delFromSet = (main, prop, item) => {
183806
183912
  };
183807
183913
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
183808
183914
  var FsWatchInstances = /* @__PURE__ */ new Map();
183809
- function createFsWatchInstance(path20, options2, listener, errHandler, emitRaw) {
183915
+ function createFsWatchInstance(path22, options2, listener, errHandler, emitRaw) {
183810
183916
  const handleEvent = (rawEvent, evPath) => {
183811
- listener(path20);
183812
- emitRaw(rawEvent, evPath, { watchedPath: path20 });
183813
- if (evPath && path20 !== evPath) {
183814
- fsWatchBroadcast(sp.resolve(path20, evPath), KEY_LISTENERS, sp.join(path20, evPath));
183917
+ listener(path22);
183918
+ emitRaw(rawEvent, evPath, { watchedPath: path22 });
183919
+ if (evPath && path22 !== evPath) {
183920
+ fsWatchBroadcast(sp.resolve(path22, evPath), KEY_LISTENERS, sp.join(path22, evPath));
183815
183921
  }
183816
183922
  };
183817
183923
  try {
183818
- return fs_watch(path20, {
183924
+ return fs_watch(path22, {
183819
183925
  persistent: options2.persistent
183820
183926
  }, handleEvent);
183821
183927
  } catch (error) {
@@ -183831,12 +183937,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
183831
183937
  listener(val1, val2, val3);
183832
183938
  });
183833
183939
  };
183834
- var setFsWatchListener = (path20, fullPath, options2, handlers) => {
183940
+ var setFsWatchListener = (path22, fullPath, options2, handlers) => {
183835
183941
  const { listener, errHandler, rawEmitter } = handlers;
183836
183942
  let cont = FsWatchInstances.get(fullPath);
183837
183943
  let watcher;
183838
183944
  if (!options2.persistent) {
183839
- watcher = createFsWatchInstance(path20, options2, listener, errHandler, rawEmitter);
183945
+ watcher = createFsWatchInstance(path22, options2, listener, errHandler, rawEmitter);
183840
183946
  if (!watcher)
183841
183947
  return;
183842
183948
  return watcher.close.bind(watcher);
@@ -183847,7 +183953,7 @@ var setFsWatchListener = (path20, fullPath, options2, handlers) => {
183847
183953
  addAndConvert(cont, KEY_RAW, rawEmitter);
183848
183954
  } else {
183849
183955
  watcher = createFsWatchInstance(
183850
- path20,
183956
+ path22,
183851
183957
  options2,
183852
183958
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
183853
183959
  errHandler,
@@ -183862,7 +183968,7 @@ var setFsWatchListener = (path20, fullPath, options2, handlers) => {
183862
183968
  cont.watcherUnusable = true;
183863
183969
  if (isWindows && error.code === "EPERM") {
183864
183970
  try {
183865
- const fd = await open(path20, "r");
183971
+ const fd = await open(path22, "r");
183866
183972
  await fd.close();
183867
183973
  broadcastErr(error);
183868
183974
  } catch (err) {
@@ -183893,7 +183999,7 @@ var setFsWatchListener = (path20, fullPath, options2, handlers) => {
183893
183999
  };
183894
184000
  };
183895
184001
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
183896
- var setFsWatchFileListener = (path20, fullPath, options2, handlers) => {
184002
+ var setFsWatchFileListener = (path22, fullPath, options2, handlers) => {
183897
184003
  const { listener, rawEmitter } = handlers;
183898
184004
  let cont = FsWatchFileInstances.get(fullPath);
183899
184005
  const copts = cont && cont.options;
@@ -183915,7 +184021,7 @@ var setFsWatchFileListener = (path20, fullPath, options2, handlers) => {
183915
184021
  });
183916
184022
  const currmtime = curr.mtimeMs;
183917
184023
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
183918
- foreach(cont.listeners, (listener2) => listener2(path20, curr));
184024
+ foreach(cont.listeners, (listener2) => listener2(path22, curr));
183919
184025
  }
183920
184026
  })
183921
184027
  };
@@ -183945,13 +184051,13 @@ var NodeFsHandler = class {
183945
184051
  * @param listener on fs change
183946
184052
  * @returns closer for the watcher instance
183947
184053
  */
183948
- _watchWithNodeFs(path20, listener) {
184054
+ _watchWithNodeFs(path22, listener) {
183949
184055
  const opts = this.fsw.options;
183950
- const directory = sp.dirname(path20);
183951
- const basename4 = sp.basename(path20);
184056
+ const directory = sp.dirname(path22);
184057
+ const basename5 = sp.basename(path22);
183952
184058
  const parent = this.fsw._getWatchedDir(directory);
183953
- parent.add(basename4);
183954
- const absolutePath = sp.resolve(path20);
184059
+ parent.add(basename5);
184060
+ const absolutePath = sp.resolve(path22);
183955
184061
  const options2 = {
183956
184062
  persistent: opts.persistent
183957
184063
  };
@@ -183960,13 +184066,13 @@ var NodeFsHandler = class {
183960
184066
  let closer;
183961
184067
  if (opts.usePolling) {
183962
184068
  const enableBin = opts.interval !== opts.binaryInterval;
183963
- options2.interval = enableBin && isBinaryPath(basename4) ? opts.binaryInterval : opts.interval;
183964
- closer = setFsWatchFileListener(path20, absolutePath, options2, {
184069
+ options2.interval = enableBin && isBinaryPath(basename5) ? opts.binaryInterval : opts.interval;
184070
+ closer = setFsWatchFileListener(path22, absolutePath, options2, {
183965
184071
  listener,
183966
184072
  rawEmitter: this.fsw._emitRaw
183967
184073
  });
183968
184074
  } else {
183969
- closer = setFsWatchListener(path20, absolutePath, options2, {
184075
+ closer = setFsWatchListener(path22, absolutePath, options2, {
183970
184076
  listener,
183971
184077
  errHandler: this._boundHandleError,
183972
184078
  rawEmitter: this.fsw._emitRaw
@@ -183983,12 +184089,12 @@ var NodeFsHandler = class {
183983
184089
  return;
183984
184090
  }
183985
184091
  const dirname8 = sp.dirname(file);
183986
- const basename4 = sp.basename(file);
184092
+ const basename5 = sp.basename(file);
183987
184093
  const parent = this.fsw._getWatchedDir(dirname8);
183988
184094
  let prevStats = stats;
183989
- if (parent.has(basename4))
184095
+ if (parent.has(basename5))
183990
184096
  return;
183991
- const listener = async (path20, newStats) => {
184097
+ const listener = async (path22, newStats) => {
183992
184098
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
183993
184099
  return;
183994
184100
  if (!newStats || newStats.mtimeMs === 0) {
@@ -184002,18 +184108,18 @@ var NodeFsHandler = class {
184002
184108
  this.fsw._emit(EV.CHANGE, file, newStats2);
184003
184109
  }
184004
184110
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
184005
- this.fsw._closeFile(path20);
184111
+ this.fsw._closeFile(path22);
184006
184112
  prevStats = newStats2;
184007
184113
  const closer2 = this._watchWithNodeFs(file, listener);
184008
184114
  if (closer2)
184009
- this.fsw._addPathCloser(path20, closer2);
184115
+ this.fsw._addPathCloser(path22, closer2);
184010
184116
  } else {
184011
184117
  prevStats = newStats2;
184012
184118
  }
184013
184119
  } catch (error) {
184014
- this.fsw._remove(dirname8, basename4);
184120
+ this.fsw._remove(dirname8, basename5);
184015
184121
  }
184016
- } else if (parent.has(basename4)) {
184122
+ } else if (parent.has(basename5)) {
184017
184123
  const at = newStats.atimeMs;
184018
184124
  const mt = newStats.mtimeMs;
184019
184125
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -184038,7 +184144,7 @@ var NodeFsHandler = class {
184038
184144
  * @param item basename of this item
184039
184145
  * @returns true if no more processing is needed for this entry.
184040
184146
  */
184041
- async _handleSymlink(entry, directory, path20, item) {
184147
+ async _handleSymlink(entry, directory, path22, item) {
184042
184148
  if (this.fsw.closed) {
184043
184149
  return;
184044
184150
  }
@@ -184048,7 +184154,7 @@ var NodeFsHandler = class {
184048
184154
  this.fsw._incrReadyCount();
184049
184155
  let linkPath;
184050
184156
  try {
184051
- linkPath = await fsrealpath(path20);
184157
+ linkPath = await fsrealpath(path22);
184052
184158
  } catch (e) {
184053
184159
  this.fsw._emitReady();
184054
184160
  return true;
@@ -184058,12 +184164,12 @@ var NodeFsHandler = class {
184058
184164
  if (dir.has(item)) {
184059
184165
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
184060
184166
  this.fsw._symlinkPaths.set(full, linkPath);
184061
- this.fsw._emit(EV.CHANGE, path20, entry.stats);
184167
+ this.fsw._emit(EV.CHANGE, path22, entry.stats);
184062
184168
  }
184063
184169
  } else {
184064
184170
  dir.add(item);
184065
184171
  this.fsw._symlinkPaths.set(full, linkPath);
184066
- this.fsw._emit(EV.ADD, path20, entry.stats);
184172
+ this.fsw._emit(EV.ADD, path22, entry.stats);
184067
184173
  }
184068
184174
  this.fsw._emitReady();
184069
184175
  return true;
@@ -184093,9 +184199,9 @@ var NodeFsHandler = class {
184093
184199
  return;
184094
184200
  }
184095
184201
  const item = entry.path;
184096
- let path20 = sp.join(directory, item);
184202
+ let path22 = sp.join(directory, item);
184097
184203
  current.add(item);
184098
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path20, item)) {
184204
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path22, item)) {
184099
184205
  return;
184100
184206
  }
184101
184207
  if (this.fsw.closed) {
@@ -184104,11 +184210,11 @@ var NodeFsHandler = class {
184104
184210
  }
184105
184211
  if (item === target || !target && !previous.has(item)) {
184106
184212
  this.fsw._incrReadyCount();
184107
- path20 = sp.join(dir, sp.relative(dir, path20));
184108
- this._addToNodeFs(path20, initialAdd, wh, depth + 1);
184213
+ path22 = sp.join(dir, sp.relative(dir, path22));
184214
+ this._addToNodeFs(path22, initialAdd, wh, depth + 1);
184109
184215
  }
184110
184216
  }).on(EV.ERROR, this._boundHandleError);
184111
- return new Promise((resolve4, reject) => {
184217
+ return new Promise((resolve5, reject) => {
184112
184218
  if (!stream)
184113
184219
  return reject();
184114
184220
  stream.once(STR_END, () => {
@@ -184117,7 +184223,7 @@ var NodeFsHandler = class {
184117
184223
  return;
184118
184224
  }
184119
184225
  const wasThrottled = throttler ? throttler.clear() : false;
184120
- resolve4(void 0);
184226
+ resolve5(void 0);
184121
184227
  previous.getChildren().filter((item) => {
184122
184228
  return item !== directory && !current.has(item);
184123
184229
  }).forEach((item) => {
@@ -184174,13 +184280,13 @@ var NodeFsHandler = class {
184174
184280
  * @param depth Child path actually targeted for watch
184175
184281
  * @param target Child path actually targeted for watch
184176
184282
  */
184177
- async _addToNodeFs(path20, initialAdd, priorWh, depth, target) {
184283
+ async _addToNodeFs(path22, initialAdd, priorWh, depth, target) {
184178
184284
  const ready = this.fsw._emitReady;
184179
- if (this.fsw._isIgnored(path20) || this.fsw.closed) {
184285
+ if (this.fsw._isIgnored(path22) || this.fsw.closed) {
184180
184286
  ready();
184181
184287
  return false;
184182
184288
  }
184183
- const wh = this.fsw._getWatchHelpers(path20);
184289
+ const wh = this.fsw._getWatchHelpers(path22);
184184
184290
  if (priorWh) {
184185
184291
  wh.filterPath = (entry) => priorWh.filterPath(entry);
184186
184292
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -184196,8 +184302,8 @@ var NodeFsHandler = class {
184196
184302
  const follow = this.fsw.options.followSymlinks;
184197
184303
  let closer;
184198
184304
  if (stats.isDirectory()) {
184199
- const absPath = sp.resolve(path20);
184200
- const targetPath = follow ? await fsrealpath(path20) : path20;
184305
+ const absPath = sp.resolve(path22);
184306
+ const targetPath = follow ? await fsrealpath(path22) : path22;
184201
184307
  if (this.fsw.closed)
184202
184308
  return;
184203
184309
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -184207,29 +184313,29 @@ var NodeFsHandler = class {
184207
184313
  this.fsw._symlinkPaths.set(absPath, targetPath);
184208
184314
  }
184209
184315
  } else if (stats.isSymbolicLink()) {
184210
- const targetPath = follow ? await fsrealpath(path20) : path20;
184316
+ const targetPath = follow ? await fsrealpath(path22) : path22;
184211
184317
  if (this.fsw.closed)
184212
184318
  return;
184213
184319
  const parent = sp.dirname(wh.watchPath);
184214
184320
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
184215
184321
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
184216
- closer = await this._handleDir(parent, stats, initialAdd, depth, path20, wh, targetPath);
184322
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path22, wh, targetPath);
184217
184323
  if (this.fsw.closed)
184218
184324
  return;
184219
184325
  if (targetPath !== void 0) {
184220
- this.fsw._symlinkPaths.set(sp.resolve(path20), targetPath);
184326
+ this.fsw._symlinkPaths.set(sp.resolve(path22), targetPath);
184221
184327
  }
184222
184328
  } else {
184223
184329
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
184224
184330
  }
184225
184331
  ready();
184226
184332
  if (closer)
184227
- this.fsw._addPathCloser(path20, closer);
184333
+ this.fsw._addPathCloser(path22, closer);
184228
184334
  return false;
184229
184335
  } catch (error) {
184230
184336
  if (this.fsw._handleError(error)) {
184231
184337
  ready();
184232
- return path20;
184338
+ return path22;
184233
184339
  }
184234
184340
  }
184235
184341
  }
@@ -184272,24 +184378,24 @@ function createPattern(matcher) {
184272
184378
  }
184273
184379
  return () => false;
184274
184380
  }
184275
- function normalizePath(path20) {
184276
- if (typeof path20 !== "string")
184381
+ function normalizePath(path22) {
184382
+ if (typeof path22 !== "string")
184277
184383
  throw new Error("string expected");
184278
- path20 = sp2.normalize(path20);
184279
- path20 = path20.replace(/\\/g, "/");
184384
+ path22 = sp2.normalize(path22);
184385
+ path22 = path22.replace(/\\/g, "/");
184280
184386
  let prepend = false;
184281
- if (path20.startsWith("//"))
184387
+ if (path22.startsWith("//"))
184282
184388
  prepend = true;
184283
- path20 = path20.replace(DOUBLE_SLASH_RE, "/");
184389
+ path22 = path22.replace(DOUBLE_SLASH_RE, "/");
184284
184390
  if (prepend)
184285
- path20 = "/" + path20;
184286
- return path20;
184391
+ path22 = "/" + path22;
184392
+ return path22;
184287
184393
  }
184288
184394
  function matchPatterns(patterns, testString, stats) {
184289
- const path20 = normalizePath(testString);
184395
+ const path22 = normalizePath(testString);
184290
184396
  for (let index = 0; index < patterns.length; index++) {
184291
184397
  const pattern = patterns[index];
184292
- if (pattern(path20, stats)) {
184398
+ if (pattern(path22, stats)) {
184293
184399
  return true;
184294
184400
  }
184295
184401
  }
@@ -184327,19 +184433,19 @@ var toUnix = (string) => {
184327
184433
  }
184328
184434
  return str;
184329
184435
  };
184330
- var normalizePathToUnix = (path20) => toUnix(sp2.normalize(toUnix(path20)));
184331
- var normalizeIgnored = (cwd = "") => (path20) => {
184332
- if (typeof path20 === "string") {
184333
- return normalizePathToUnix(sp2.isAbsolute(path20) ? path20 : sp2.join(cwd, path20));
184436
+ var normalizePathToUnix = (path22) => toUnix(sp2.normalize(toUnix(path22)));
184437
+ var normalizeIgnored = (cwd = "") => (path22) => {
184438
+ if (typeof path22 === "string") {
184439
+ return normalizePathToUnix(sp2.isAbsolute(path22) ? path22 : sp2.join(cwd, path22));
184334
184440
  } else {
184335
- return path20;
184441
+ return path22;
184336
184442
  }
184337
184443
  };
184338
- var getAbsolutePath = (path20, cwd) => {
184339
- if (sp2.isAbsolute(path20)) {
184340
- return path20;
184444
+ var getAbsolutePath = (path22, cwd) => {
184445
+ if (sp2.isAbsolute(path22)) {
184446
+ return path22;
184341
184447
  }
184342
- return sp2.join(cwd, path20);
184448
+ return sp2.join(cwd, path22);
184343
184449
  };
184344
184450
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
184345
184451
  var DirEntry = class {
@@ -184404,10 +184510,10 @@ var WatchHelper = class {
184404
184510
  dirParts;
184405
184511
  followSymlinks;
184406
184512
  statMethod;
184407
- constructor(path20, follow, fsw) {
184513
+ constructor(path22, follow, fsw) {
184408
184514
  this.fsw = fsw;
184409
- const watchPath = path20;
184410
- this.path = path20 = path20.replace(REPLACER_RE, "");
184515
+ const watchPath = path22;
184516
+ this.path = path22 = path22.replace(REPLACER_RE, "");
184411
184517
  this.watchPath = watchPath;
184412
184518
  this.fullWatchPath = sp2.resolve(watchPath);
184413
184519
  this.dirParts = [];
@@ -184547,20 +184653,20 @@ var FSWatcher = class extends EventEmitter {
184547
184653
  this._closePromise = void 0;
184548
184654
  let paths = unifyPaths(paths_);
184549
184655
  if (cwd) {
184550
- paths = paths.map((path20) => {
184551
- const absPath = getAbsolutePath(path20, cwd);
184656
+ paths = paths.map((path22) => {
184657
+ const absPath = getAbsolutePath(path22, cwd);
184552
184658
  return absPath;
184553
184659
  });
184554
184660
  }
184555
- paths.forEach((path20) => {
184556
- this._removeIgnoredPath(path20);
184661
+ paths.forEach((path22) => {
184662
+ this._removeIgnoredPath(path22);
184557
184663
  });
184558
184664
  this._userIgnored = void 0;
184559
184665
  if (!this._readyCount)
184560
184666
  this._readyCount = 0;
184561
184667
  this._readyCount += paths.length;
184562
- Promise.all(paths.map(async (path20) => {
184563
- const res = await this._nodeFsHandler._addToNodeFs(path20, !_internal, void 0, 0, _origAdd);
184668
+ Promise.all(paths.map(async (path22) => {
184669
+ const res = await this._nodeFsHandler._addToNodeFs(path22, !_internal, void 0, 0, _origAdd);
184564
184670
  if (res)
184565
184671
  this._emitReady();
184566
184672
  return res;
@@ -184582,17 +184688,17 @@ var FSWatcher = class extends EventEmitter {
184582
184688
  return this;
184583
184689
  const paths = unifyPaths(paths_);
184584
184690
  const { cwd } = this.options;
184585
- paths.forEach((path20) => {
184586
- if (!sp2.isAbsolute(path20) && !this._closers.has(path20)) {
184691
+ paths.forEach((path22) => {
184692
+ if (!sp2.isAbsolute(path22) && !this._closers.has(path22)) {
184587
184693
  if (cwd)
184588
- path20 = sp2.join(cwd, path20);
184589
- path20 = sp2.resolve(path20);
184694
+ path22 = sp2.join(cwd, path22);
184695
+ path22 = sp2.resolve(path22);
184590
184696
  }
184591
- this._closePath(path20);
184592
- this._addIgnoredPath(path20);
184593
- if (this._watched.has(path20)) {
184697
+ this._closePath(path22);
184698
+ this._addIgnoredPath(path22);
184699
+ if (this._watched.has(path22)) {
184594
184700
  this._addIgnoredPath({
184595
- path: path20,
184701
+ path: path22,
184596
184702
  recursive: true
184597
184703
  });
184598
184704
  }
@@ -184656,38 +184762,38 @@ var FSWatcher = class extends EventEmitter {
184656
184762
  * @param stats arguments to be passed with event
184657
184763
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
184658
184764
  */
184659
- async _emit(event, path20, stats) {
184765
+ async _emit(event, path22, stats) {
184660
184766
  if (this.closed)
184661
184767
  return;
184662
184768
  const opts = this.options;
184663
184769
  if (isWindows)
184664
- path20 = sp2.normalize(path20);
184770
+ path22 = sp2.normalize(path22);
184665
184771
  if (opts.cwd)
184666
- path20 = sp2.relative(opts.cwd, path20);
184667
- const args = [path20];
184772
+ path22 = sp2.relative(opts.cwd, path22);
184773
+ const args = [path22];
184668
184774
  if (stats != null)
184669
184775
  args.push(stats);
184670
184776
  const awf = opts.awaitWriteFinish;
184671
184777
  let pw;
184672
- if (awf && (pw = this._pendingWrites.get(path20))) {
184778
+ if (awf && (pw = this._pendingWrites.get(path22))) {
184673
184779
  pw.lastChange = /* @__PURE__ */ new Date();
184674
184780
  return this;
184675
184781
  }
184676
184782
  if (opts.atomic) {
184677
184783
  if (event === EVENTS.UNLINK) {
184678
- this._pendingUnlinks.set(path20, [event, ...args]);
184784
+ this._pendingUnlinks.set(path22, [event, ...args]);
184679
184785
  setTimeout(() => {
184680
- this._pendingUnlinks.forEach((entry, path21) => {
184786
+ this._pendingUnlinks.forEach((entry, path23) => {
184681
184787
  this.emit(...entry);
184682
184788
  this.emit(EVENTS.ALL, ...entry);
184683
- this._pendingUnlinks.delete(path21);
184789
+ this._pendingUnlinks.delete(path23);
184684
184790
  });
184685
184791
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
184686
184792
  return this;
184687
184793
  }
184688
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path20)) {
184794
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path22)) {
184689
184795
  event = EVENTS.CHANGE;
184690
- this._pendingUnlinks.delete(path20);
184796
+ this._pendingUnlinks.delete(path22);
184691
184797
  }
184692
184798
  }
184693
184799
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -184705,16 +184811,16 @@ var FSWatcher = class extends EventEmitter {
184705
184811
  this.emitWithAll(event, args);
184706
184812
  }
184707
184813
  };
184708
- this._awaitWriteFinish(path20, awf.stabilityThreshold, event, awfEmit);
184814
+ this._awaitWriteFinish(path22, awf.stabilityThreshold, event, awfEmit);
184709
184815
  return this;
184710
184816
  }
184711
184817
  if (event === EVENTS.CHANGE) {
184712
- const isThrottled = !this._throttle(EVENTS.CHANGE, path20, 50);
184818
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path22, 50);
184713
184819
  if (isThrottled)
184714
184820
  return this;
184715
184821
  }
184716
184822
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
184717
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path20) : path20;
184823
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path22) : path22;
184718
184824
  let stats2;
184719
184825
  try {
184720
184826
  stats2 = await stat3(fullPath);
@@ -184745,23 +184851,23 @@ var FSWatcher = class extends EventEmitter {
184745
184851
  * @param timeout duration of time to suppress duplicate actions
184746
184852
  * @returns tracking object or false if action should be suppressed
184747
184853
  */
184748
- _throttle(actionType, path20, timeout) {
184854
+ _throttle(actionType, path22, timeout) {
184749
184855
  if (!this._throttled.has(actionType)) {
184750
184856
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
184751
184857
  }
184752
184858
  const action = this._throttled.get(actionType);
184753
184859
  if (!action)
184754
184860
  throw new Error("invalid throttle");
184755
- const actionPath = action.get(path20);
184861
+ const actionPath = action.get(path22);
184756
184862
  if (actionPath) {
184757
184863
  actionPath.count++;
184758
184864
  return false;
184759
184865
  }
184760
184866
  let timeoutObject;
184761
184867
  const clear = () => {
184762
- const item = action.get(path20);
184868
+ const item = action.get(path22);
184763
184869
  const count = item ? item.count : 0;
184764
- action.delete(path20);
184870
+ action.delete(path22);
184765
184871
  clearTimeout(timeoutObject);
184766
184872
  if (item)
184767
184873
  clearTimeout(item.timeoutObject);
@@ -184769,7 +184875,7 @@ var FSWatcher = class extends EventEmitter {
184769
184875
  };
184770
184876
  timeoutObject = setTimeout(clear, timeout);
184771
184877
  const thr = { timeoutObject, clear, count: 0 };
184772
- action.set(path20, thr);
184878
+ action.set(path22, thr);
184773
184879
  return thr;
184774
184880
  }
184775
184881
  _incrReadyCount() {
@@ -184783,44 +184889,44 @@ var FSWatcher = class extends EventEmitter {
184783
184889
  * @param event
184784
184890
  * @param awfEmit Callback to be called when ready for event to be emitted.
184785
184891
  */
184786
- _awaitWriteFinish(path20, threshold, event, awfEmit) {
184892
+ _awaitWriteFinish(path22, threshold, event, awfEmit) {
184787
184893
  const awf = this.options.awaitWriteFinish;
184788
184894
  if (typeof awf !== "object")
184789
184895
  return;
184790
184896
  const pollInterval = awf.pollInterval;
184791
184897
  let timeoutHandler;
184792
- let fullPath = path20;
184793
- if (this.options.cwd && !sp2.isAbsolute(path20)) {
184794
- fullPath = sp2.join(this.options.cwd, path20);
184898
+ let fullPath = path22;
184899
+ if (this.options.cwd && !sp2.isAbsolute(path22)) {
184900
+ fullPath = sp2.join(this.options.cwd, path22);
184795
184901
  }
184796
184902
  const now = /* @__PURE__ */ new Date();
184797
184903
  const writes = this._pendingWrites;
184798
184904
  function awaitWriteFinishFn(prevStat) {
184799
184905
  statcb(fullPath, (err, curStat) => {
184800
- if (err || !writes.has(path20)) {
184906
+ if (err || !writes.has(path22)) {
184801
184907
  if (err && err.code !== "ENOENT")
184802
184908
  awfEmit(err);
184803
184909
  return;
184804
184910
  }
184805
184911
  const now2 = Number(/* @__PURE__ */ new Date());
184806
184912
  if (prevStat && curStat.size !== prevStat.size) {
184807
- writes.get(path20).lastChange = now2;
184913
+ writes.get(path22).lastChange = now2;
184808
184914
  }
184809
- const pw = writes.get(path20);
184915
+ const pw = writes.get(path22);
184810
184916
  const df = now2 - pw.lastChange;
184811
184917
  if (df >= threshold) {
184812
- writes.delete(path20);
184918
+ writes.delete(path22);
184813
184919
  awfEmit(void 0, curStat);
184814
184920
  } else {
184815
184921
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
184816
184922
  }
184817
184923
  });
184818
184924
  }
184819
- if (!writes.has(path20)) {
184820
- writes.set(path20, {
184925
+ if (!writes.has(path22)) {
184926
+ writes.set(path22, {
184821
184927
  lastChange: now,
184822
184928
  cancelWait: () => {
184823
- writes.delete(path20);
184929
+ writes.delete(path22);
184824
184930
  clearTimeout(timeoutHandler);
184825
184931
  return event;
184826
184932
  }
@@ -184831,8 +184937,8 @@ var FSWatcher = class extends EventEmitter {
184831
184937
  /**
184832
184938
  * Determines whether user has asked to ignore this path.
184833
184939
  */
184834
- _isIgnored(path20, stats) {
184835
- if (this.options.atomic && DOT_RE.test(path20))
184940
+ _isIgnored(path22, stats) {
184941
+ if (this.options.atomic && DOT_RE.test(path22))
184836
184942
  return true;
184837
184943
  if (!this._userIgnored) {
184838
184944
  const { cwd } = this.options;
@@ -184842,17 +184948,17 @@ var FSWatcher = class extends EventEmitter {
184842
184948
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
184843
184949
  this._userIgnored = anymatch(list, void 0);
184844
184950
  }
184845
- return this._userIgnored(path20, stats);
184951
+ return this._userIgnored(path22, stats);
184846
184952
  }
184847
- _isntIgnored(path20, stat4) {
184848
- return !this._isIgnored(path20, stat4);
184953
+ _isntIgnored(path22, stat4) {
184954
+ return !this._isIgnored(path22, stat4);
184849
184955
  }
184850
184956
  /**
184851
184957
  * Provides a set of common helpers and properties relating to symlink handling.
184852
184958
  * @param path file or directory pattern being watched
184853
184959
  */
184854
- _getWatchHelpers(path20) {
184855
- return new WatchHelper(path20, this.options.followSymlinks, this);
184960
+ _getWatchHelpers(path22) {
184961
+ return new WatchHelper(path22, this.options.followSymlinks, this);
184856
184962
  }
184857
184963
  // Directory helpers
184858
184964
  // -----------------
@@ -184884,63 +184990,63 @@ var FSWatcher = class extends EventEmitter {
184884
184990
  * @param item base path of item/directory
184885
184991
  */
184886
184992
  _remove(directory, item, isDirectory) {
184887
- const path20 = sp2.join(directory, item);
184888
- const fullPath = sp2.resolve(path20);
184889
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path20) || this._watched.has(fullPath);
184890
- if (!this._throttle("remove", path20, 100))
184993
+ const path22 = sp2.join(directory, item);
184994
+ const fullPath = sp2.resolve(path22);
184995
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path22) || this._watched.has(fullPath);
184996
+ if (!this._throttle("remove", path22, 100))
184891
184997
  return;
184892
184998
  if (!isDirectory && this._watched.size === 1) {
184893
184999
  this.add(directory, item, true);
184894
185000
  }
184895
- const wp = this._getWatchedDir(path20);
185001
+ const wp = this._getWatchedDir(path22);
184896
185002
  const nestedDirectoryChildren = wp.getChildren();
184897
- nestedDirectoryChildren.forEach((nested) => this._remove(path20, nested));
185003
+ nestedDirectoryChildren.forEach((nested) => this._remove(path22, nested));
184898
185004
  const parent = this._getWatchedDir(directory);
184899
185005
  const wasTracked = parent.has(item);
184900
185006
  parent.remove(item);
184901
185007
  if (this._symlinkPaths.has(fullPath)) {
184902
185008
  this._symlinkPaths.delete(fullPath);
184903
185009
  }
184904
- let relPath = path20;
185010
+ let relPath = path22;
184905
185011
  if (this.options.cwd)
184906
- relPath = sp2.relative(this.options.cwd, path20);
185012
+ relPath = sp2.relative(this.options.cwd, path22);
184907
185013
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
184908
185014
  const event = this._pendingWrites.get(relPath).cancelWait();
184909
185015
  if (event === EVENTS.ADD)
184910
185016
  return;
184911
185017
  }
184912
- this._watched.delete(path20);
185018
+ this._watched.delete(path22);
184913
185019
  this._watched.delete(fullPath);
184914
185020
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
184915
- if (wasTracked && !this._isIgnored(path20))
184916
- this._emit(eventName, path20);
184917
- this._closePath(path20);
185021
+ if (wasTracked && !this._isIgnored(path22))
185022
+ this._emit(eventName, path22);
185023
+ this._closePath(path22);
184918
185024
  }
184919
185025
  /**
184920
185026
  * Closes all watchers for a path
184921
185027
  */
184922
- _closePath(path20) {
184923
- this._closeFile(path20);
184924
- const dir = sp2.dirname(path20);
184925
- this._getWatchedDir(dir).remove(sp2.basename(path20));
185028
+ _closePath(path22) {
185029
+ this._closeFile(path22);
185030
+ const dir = sp2.dirname(path22);
185031
+ this._getWatchedDir(dir).remove(sp2.basename(path22));
184926
185032
  }
184927
185033
  /**
184928
185034
  * Closes only file-specific watchers
184929
185035
  */
184930
- _closeFile(path20) {
184931
- const closers = this._closers.get(path20);
185036
+ _closeFile(path22) {
185037
+ const closers = this._closers.get(path22);
184932
185038
  if (!closers)
184933
185039
  return;
184934
185040
  closers.forEach((closer) => closer());
184935
- this._closers.delete(path20);
185041
+ this._closers.delete(path22);
184936
185042
  }
184937
- _addPathCloser(path20, closer) {
185043
+ _addPathCloser(path22, closer) {
184938
185044
  if (!closer)
184939
185045
  return;
184940
- let list = this._closers.get(path20);
185046
+ let list = this._closers.get(path22);
184941
185047
  if (!list) {
184942
185048
  list = [];
184943
- this._closers.set(path20, list);
185049
+ this._closers.set(path22, list);
184944
185050
  }
184945
185051
  list.push(closer);
184946
185052
  }
@@ -184989,8 +185095,8 @@ var PortAllocator = class {
184989
185095
  };
184990
185096
 
184991
185097
  // src/lib/dev/stable-port-allocator.ts
184992
- import * as fs4 from "fs";
184993
- import * as path4 from "path";
185098
+ import * as fs5 from "fs";
185099
+ import * as path5 from "path";
184994
185100
  var PORT_RANGE_START2 = 4e4;
184995
185101
  var PORT_RANGE_END2 = 49999;
184996
185102
  var StablePortAllocator = class {
@@ -184998,17 +185104,17 @@ var StablePortAllocator = class {
184998
185104
  portsFilePath;
184999
185105
  savedPorts = {};
185000
185106
  usedPorts = /* @__PURE__ */ new Set();
185001
- constructor(projectRoot) {
185002
- this.portsDir = path4.join(projectRoot, ".specific");
185003
- this.portsFilePath = path4.join(this.portsDir, "ports.json");
185107
+ constructor(projectRoot, key = "default") {
185108
+ this.portsDir = path5.join(projectRoot, ".specific", "keys", key);
185109
+ this.portsFilePath = path5.join(this.portsDir, "ports.json");
185004
185110
  this.loadPorts();
185005
185111
  }
185006
185112
  loadPorts() {
185007
- if (!fs4.existsSync(this.portsFilePath)) {
185113
+ if (!fs5.existsSync(this.portsFilePath)) {
185008
185114
  return;
185009
185115
  }
185010
185116
  try {
185011
- const content = fs4.readFileSync(this.portsFilePath, "utf-8");
185117
+ const content = fs5.readFileSync(this.portsFilePath, "utf-8");
185012
185118
  const data = JSON.parse(content);
185013
185119
  if (data.version === 1 && data.ports) {
185014
185120
  this.savedPorts = data.ports;
@@ -185021,14 +185127,14 @@ var StablePortAllocator = class {
185021
185127
  }
185022
185128
  }
185023
185129
  savePorts() {
185024
- if (!fs4.existsSync(this.portsDir)) {
185025
- fs4.mkdirSync(this.portsDir, { recursive: true });
185130
+ if (!fs5.existsSync(this.portsDir)) {
185131
+ fs5.mkdirSync(this.portsDir, { recursive: true });
185026
185132
  }
185027
185133
  const data = {
185028
185134
  version: 1,
185029
185135
  ports: this.savedPorts
185030
185136
  };
185031
- fs4.writeFileSync(this.portsFilePath, JSON.stringify(data, null, 2));
185137
+ fs5.writeFileSync(this.portsFilePath, JSON.stringify(data, null, 2));
185032
185138
  }
185033
185139
  allocateRandom() {
185034
185140
  const rangeSize = PORT_RANGE_END2 - PORT_RANGE_START2 + 1;
@@ -185054,10 +185160,10 @@ var StablePortAllocator = class {
185054
185160
  };
185055
185161
 
185056
185162
  // src/lib/dev/database-manager.ts
185057
- import * as fs7 from "fs";
185058
- import * as path7 from "path";
185163
+ import * as fs8 from "fs";
185164
+ import * as path8 from "path";
185059
185165
  import * as net from "net";
185060
- import * as os3 from "os";
185166
+ import * as os4 from "os";
185061
185167
  import { spawn } from "child_process";
185062
185168
 
185063
185169
  // src/lib/bin/types.ts
@@ -185095,17 +185201,17 @@ var ExtractionError = class extends Error {
185095
185201
  };
185096
185202
 
185097
185203
  // src/lib/bin/manager.ts
185098
- import * as fs5 from "fs";
185099
- import * as path5 from "path";
185100
- import * as os2 from "os";
185204
+ import * as fs6 from "fs";
185205
+ import * as path6 from "path";
185206
+ import * as os3 from "os";
185101
185207
  import { createReadStream } from "fs";
185102
185208
  import { createTarExtractor, extractTo } from "tar-vern";
185103
185209
  function getBinBaseDir() {
185104
- return path5.join(os2.homedir(), ".specific", "bin");
185210
+ return path6.join(os3.homedir(), ".specific", "bin");
185105
185211
  }
185106
185212
  function getPlatformInfo() {
185107
- const platform5 = os2.platform();
185108
- const arch3 = os2.arch();
185213
+ const platform5 = os3.platform();
185214
+ const arch3 = os3.arch();
185109
185215
  if (platform5 !== "darwin" && platform5 !== "linux") {
185110
185216
  throw new Error(
185111
185217
  `Unsupported platform: ${platform5}. Only macOS and Linux are supported.`
@@ -185124,7 +185230,7 @@ function getPlatformInfo() {
185124
185230
  return { platform: platform5, arch: mappedArch };
185125
185231
  }
185126
185232
  function getBinaryDir(definition, version, platformInfo) {
185127
- return path5.join(
185233
+ return path6.join(
185128
185234
  getBinBaseDir(),
185129
185235
  definition.name,
185130
185236
  version,
@@ -185134,8 +185240,8 @@ function getBinaryDir(definition, version, platformInfo) {
185134
185240
  function isBinaryInstalled(definition, version, platformInfo) {
185135
185241
  const binDir = getBinaryDir(definition, version, platformInfo);
185136
185242
  for (const execPath of definition.executables) {
185137
- const fullPath = path5.join(binDir, execPath);
185138
- if (!fs5.existsSync(fullPath)) {
185243
+ const fullPath = path6.join(binDir, execPath);
185244
+ if (!fs6.existsSync(fullPath)) {
185139
185245
  return false;
185140
185246
  }
185141
185247
  }
@@ -185163,12 +185269,12 @@ async function downloadFile(url, destPath, onProgress) {
185163
185269
  10
185164
185270
  );
185165
185271
  let bytesDownloaded = 0;
185166
- const parentDir = path5.dirname(destPath);
185167
- if (!fs5.existsSync(parentDir)) {
185168
- fs5.mkdirSync(parentDir, { recursive: true });
185272
+ const parentDir = path6.dirname(destPath);
185273
+ if (!fs6.existsSync(parentDir)) {
185274
+ fs6.mkdirSync(parentDir, { recursive: true });
185169
185275
  }
185170
185276
  const partPath = destPath + ".part";
185171
- const fileStream = fs5.createWriteStream(partPath);
185277
+ const fileStream = fs6.createWriteStream(partPath);
185172
185278
  try {
185173
185279
  const reader = response.body.getReader();
185174
185280
  while (true) {
@@ -185185,18 +185291,18 @@ async function downloadFile(url, destPath, onProgress) {
185185
185291
  });
185186
185292
  }
185187
185293
  }
185188
- await new Promise((resolve4, reject) => {
185294
+ await new Promise((resolve5, reject) => {
185189
185295
  fileStream.end((err) => {
185190
185296
  if (err) reject(err);
185191
- else resolve4();
185297
+ else resolve5();
185192
185298
  });
185193
185299
  });
185194
- fs5.renameSync(partPath, destPath);
185300
+ fs6.renameSync(partPath, destPath);
185195
185301
  } catch (error) {
185196
185302
  try {
185197
185303
  fileStream.close();
185198
- if (fs5.existsSync(partPath)) {
185199
- fs5.unlinkSync(partPath);
185304
+ if (fs6.existsSync(partPath)) {
185305
+ fs6.unlinkSync(partPath);
185200
185306
  }
185201
185307
  } catch {
185202
185308
  }
@@ -185205,8 +185311,8 @@ async function downloadFile(url, destPath, onProgress) {
185205
185311
  }
185206
185312
  async function extractTarball(archivePath, destDir, definition, onProgress) {
185207
185313
  onProgress?.({ phase: "extracting" });
185208
- if (!fs5.existsSync(destDir)) {
185209
- fs5.mkdirSync(destDir, { recursive: true });
185314
+ if (!fs6.existsSync(destDir)) {
185315
+ fs6.mkdirSync(destDir, { recursive: true });
185210
185316
  }
185211
185317
  try {
185212
185318
  const fileStream = createReadStream(archivePath);
@@ -185214,9 +185320,9 @@ async function extractTarball(archivePath, destDir, definition, onProgress) {
185214
185320
  await extractTo(entries, destDir);
185215
185321
  onProgress?.({ phase: "finalizing" });
185216
185322
  for (const execPath of definition.executables) {
185217
- const fullPath = path5.join(destDir, execPath);
185218
- if (fs5.existsSync(fullPath)) {
185219
- fs5.chmodSync(fullPath, 493);
185323
+ const fullPath = path6.join(destDir, execPath);
185324
+ if (fs6.existsSync(fullPath)) {
185325
+ fs6.chmodSync(fullPath, 493);
185220
185326
  }
185221
185327
  }
185222
185328
  } catch (error) {
@@ -185240,24 +185346,24 @@ async function ensureBinary(definition, version, onProgress) {
185240
185346
  `Binary type definitions must have exactly one executable, got ${definition.executables.length}`
185241
185347
  );
185242
185348
  }
185243
- if (!fs5.existsSync(binDir)) {
185244
- fs5.mkdirSync(binDir, { recursive: true });
185349
+ if (!fs6.existsSync(binDir)) {
185350
+ fs6.mkdirSync(binDir, { recursive: true });
185245
185351
  }
185246
- const execPath = path5.join(binDir, definition.executables[0]);
185352
+ const execPath = path6.join(binDir, definition.executables[0]);
185247
185353
  await downloadFile(url, execPath, onProgress);
185248
- fs5.chmodSync(execPath, 493);
185354
+ fs6.chmodSync(execPath, 493);
185249
185355
  onProgress?.({ phase: "finalizing" });
185250
185356
  } else {
185251
- const downloadDir = path5.join(getBinBaseDir(), "downloads");
185357
+ const downloadDir = path6.join(getBinBaseDir(), "downloads");
185252
185358
  const archiveName = `${definition.name}-${resolvedVersion}-${platformInfo.platform}-${platformInfo.arch}.tar.gz`;
185253
- const archivePath = path5.join(downloadDir, archiveName);
185359
+ const archivePath = path6.join(downloadDir, archiveName);
185254
185360
  try {
185255
185361
  await downloadFile(url, archivePath, onProgress);
185256
185362
  await extractTarball(archivePath, binDir, definition, onProgress);
185257
185363
  } finally {
185258
185364
  try {
185259
- if (fs5.existsSync(archivePath)) {
185260
- fs5.unlinkSync(archivePath);
185365
+ if (fs6.existsSync(archivePath)) {
185366
+ fs6.unlinkSync(archivePath);
185261
185367
  }
185262
185368
  } catch {
185263
185369
  }
@@ -185266,10 +185372,10 @@ async function ensureBinary(definition, version, onProgress) {
185266
185372
  }
185267
185373
  const executables = {};
185268
185374
  for (const execPath of definition.executables) {
185269
- const name = path5.basename(execPath);
185270
- executables[name] = path5.join(binDir, execPath);
185375
+ const name = path6.basename(execPath);
185376
+ executables[name] = path6.join(binDir, execPath);
185271
185377
  }
185272
- const libraryPath = definition.libraryDir ? path5.join(binDir, definition.libraryDir) : void 0;
185378
+ const libraryPath = definition.libraryDir ? path6.join(binDir, definition.libraryDir) : void 0;
185273
185379
  return {
185274
185380
  rootDir: binDir,
185275
185381
  version: resolvedVersion,
@@ -185369,17 +185475,17 @@ var drizzleGatewayBinary = {
185369
185475
  };
185370
185476
 
185371
185477
  // src/lib/dev/debug-logger.ts
185372
- import * as fs6 from "fs";
185373
- import * as path6 from "path";
185478
+ import * as fs7 from "fs";
185479
+ import * as path7 from "path";
185374
185480
  var DEBUG_LOG_PATH = ".specific/debug.log";
185375
185481
  var logStream = null;
185376
185482
  function getLogStream() {
185377
185483
  if (logStream) {
185378
185484
  return logStream;
185379
185485
  }
185380
- const logPath = path6.join(process.cwd(), DEBUG_LOG_PATH);
185381
- fs6.mkdirSync(path6.dirname(logPath), { recursive: true });
185382
- logStream = fs6.createWriteStream(logPath, { flags: "a" });
185486
+ const logPath = path7.join(process.cwd(), DEBUG_LOG_PATH);
185487
+ fs7.mkdirSync(path7.dirname(logPath), { recursive: true });
185488
+ logStream = fs7.createWriteStream(logPath, { flags: "a" });
185383
185489
  return logStream;
185384
185490
  }
185385
185491
  function writeLog(source, message) {
@@ -185422,7 +185528,7 @@ function getLibraryEnv(binary) {
185422
185528
  if (!binary.libraryPath) {
185423
185529
  return {};
185424
185530
  }
185425
- const platform5 = os3.platform();
185531
+ const platform5 = os4.platform();
185426
185532
  if (platform5 === "darwin") {
185427
185533
  return { DYLD_LIBRARY_PATH: binary.libraryPath };
185428
185534
  } else if (platform5 === "linux") {
@@ -185432,15 +185538,15 @@ function getLibraryEnv(binary) {
185432
185538
  }
185433
185539
  async function startPostgres(pg, port, dataDir, onProgress) {
185434
185540
  const binary = await ensureBinary(postgresBinary, void 0, onProgress);
185435
- const dbDataPath = path7.join(process.cwd(), dataDir, pg.name);
185541
+ const dbDataPath = path8.join(process.cwd(), dataDir, pg.name);
185436
185542
  const host = "127.0.0.1";
185437
185543
  const user = "postgres";
185438
185544
  const password = "postgres";
185439
185545
  const libraryEnv = getLibraryEnv(binary);
185440
185546
  const env2 = { ...process.env, ...libraryEnv };
185441
- const dataExists = fs7.existsSync(dbDataPath);
185547
+ const dataExists = fs8.existsSync(dbDataPath);
185442
185548
  if (!dataExists) {
185443
- fs7.mkdirSync(dbDataPath, { recursive: true });
185549
+ fs8.mkdirSync(dbDataPath, { recursive: true });
185444
185550
  await runCommand(
185445
185551
  binary.executables["initdb"],
185446
185552
  ["-D", dbDataPath, "-U", user, "--auth=trust", "--no-locale", "-E", "UTF8"],
@@ -185516,9 +185622,9 @@ async function startRedis(redis, port, onProgress) {
185516
185622
  }
185517
185623
  async function startStorage(storage, port, dataDir) {
185518
185624
  const S3rver = (await import("s3rver")).default;
185519
- const storageDataPath = path7.join(process.cwd(), dataDir, storage.name);
185520
- if (!fs7.existsSync(storageDataPath)) {
185521
- fs7.mkdirSync(storageDataPath, { recursive: true });
185625
+ const storageDataPath = path8.join(process.cwd(), dataDir, storage.name);
185626
+ if (!fs8.existsSync(storageDataPath)) {
185627
+ fs8.mkdirSync(storageDataPath, { recursive: true });
185522
185628
  }
185523
185629
  const host = "127.0.0.1";
185524
185630
  const accessKey = "S3RVER";
@@ -185552,7 +185658,7 @@ async function startStorage(storage, port, dataDir) {
185552
185658
  };
185553
185659
  }
185554
185660
  async function runCommand(command, args, env2) {
185555
- return new Promise((resolve4, reject) => {
185661
+ return new Promise((resolve5, reject) => {
185556
185662
  const proc = spawn(command, args, {
185557
185663
  stdio: ["ignore", "pipe", "pipe"],
185558
185664
  env: env2
@@ -185563,7 +185669,7 @@ async function runCommand(command, args, env2) {
185563
185669
  });
185564
185670
  proc.on("close", (code) => {
185565
185671
  if (code === 0) {
185566
- resolve4();
185672
+ resolve5();
185567
185673
  } else {
185568
185674
  reject(new Error(`Command failed with code ${code}: ${stderr}`));
185569
185675
  }
@@ -185572,7 +185678,7 @@ async function runCommand(command, args, env2) {
185572
185678
  });
185573
185679
  }
185574
185680
  async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
185575
- return new Promise((resolve4, reject) => {
185681
+ return new Promise((resolve5, reject) => {
185576
185682
  const proc = spawn(
185577
185683
  postgresPath,
185578
185684
  ["--single", "-D", dataDir, "postgres"],
@@ -185586,7 +185692,7 @@ async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
185586
185692
  stderr += data.toString();
185587
185693
  });
185588
185694
  proc.on("close", (code) => {
185589
- resolve4();
185695
+ resolve5();
185590
185696
  });
185591
185697
  proc.on("error", reject);
185592
185698
  proc.stdin?.write(`CREATE DATABASE "${dbName}";
@@ -185606,33 +185712,33 @@ async function waitForTcpPort(host, port, timeoutMs = 3e4) {
185606
185712
  throw new Error(`Port ${port} did not become available within timeout`);
185607
185713
  }
185608
185714
  function checkTcpPort(host, port) {
185609
- return new Promise((resolve4) => {
185715
+ return new Promise((resolve5) => {
185610
185716
  const socket = new net.Socket();
185611
185717
  socket.setTimeout(1e3);
185612
185718
  socket.on("connect", () => {
185613
185719
  socket.destroy();
185614
- resolve4(true);
185720
+ resolve5(true);
185615
185721
  });
185616
185722
  socket.on("timeout", () => {
185617
185723
  socket.destroy();
185618
- resolve4(false);
185724
+ resolve5(false);
185619
185725
  });
185620
185726
  socket.on("error", () => {
185621
185727
  socket.destroy();
185622
- resolve4(false);
185728
+ resolve5(false);
185623
185729
  });
185624
185730
  socket.connect(port, host);
185625
185731
  });
185626
185732
  }
185627
185733
  async function stopProcess(proc) {
185628
- return new Promise((resolve4) => {
185734
+ return new Promise((resolve5) => {
185629
185735
  if (proc.killed || proc.exitCode !== null) {
185630
- resolve4();
185736
+ resolve5();
185631
185737
  return;
185632
185738
  }
185633
185739
  proc.once("exit", () => {
185634
185740
  clearTimeout(forceKillTimeout);
185635
- resolve4();
185741
+ resolve5();
185636
185742
  });
185637
185743
  proc.kill("SIGTERM");
185638
185744
  const forceKillTimeout = setTimeout(() => {
@@ -185643,7 +185749,7 @@ async function stopProcess(proc) {
185643
185749
  });
185644
185750
  }
185645
185751
  function sleep(ms) {
185646
- return new Promise((resolve4) => setTimeout(resolve4, ms));
185752
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
185647
185753
  }
185648
185754
 
185649
185755
  // src/lib/dev/service-runner.ts
@@ -185652,9 +185758,9 @@ import { spawn as spawn2 } from "child_process";
185652
185758
  // src/lib/secrets/parser.ts
185653
185759
  var import_hcl2_json_parser2 = __toESM(require_dist(), 1);
185654
185760
  import { readFile, writeFile, mkdir } from "fs/promises";
185655
- import { existsSync as existsSync8 } from "fs";
185656
- import * as path8 from "path";
185657
- import * as crypto from "crypto";
185761
+ import { existsSync as existsSync9 } from "fs";
185762
+ import * as path9 from "path";
185763
+ import * as crypto2 from "crypto";
185658
185764
  var { parseToObject: parseToObject2 } = import_hcl2_json_parser2.default;
185659
185765
  var SECRETS_FILE = "specific.secrets";
185660
185766
  var GENERATED_SECRETS_FILE = ".specific/generated-secrets.json";
@@ -185672,7 +185778,7 @@ async function parseSecretsFile(content) {
185672
185778
  return secrets;
185673
185779
  }
185674
185780
  async function loadSecrets() {
185675
- if (!existsSync8(SECRETS_FILE)) {
185781
+ if (!existsSync9(SECRETS_FILE)) {
185676
185782
  return /* @__PURE__ */ new Map();
185677
185783
  }
185678
185784
  const content = await readFile(SECRETS_FILE, "utf-8");
@@ -185680,7 +185786,7 @@ async function loadSecrets() {
185680
185786
  }
185681
185787
  function generateRandomString(length = 64) {
185682
185788
  const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
185683
- const bytes = crypto.randomBytes(length);
185789
+ const bytes = crypto2.randomBytes(length);
185684
185790
  let result = "";
185685
185791
  for (let i = 0; i < length; i++) {
185686
185792
  result += chars[bytes[i] % chars.length];
@@ -185688,7 +185794,7 @@ function generateRandomString(length = 64) {
185688
185794
  return result;
185689
185795
  }
185690
185796
  async function loadGeneratedSecrets() {
185691
- if (!existsSync8(GENERATED_SECRETS_FILE)) {
185797
+ if (!existsSync9(GENERATED_SECRETS_FILE)) {
185692
185798
  return /* @__PURE__ */ new Map();
185693
185799
  }
185694
185800
  const content = await readFile(GENERATED_SECRETS_FILE, "utf-8");
@@ -185697,13 +185803,13 @@ async function loadGeneratedSecrets() {
185697
185803
  }
185698
185804
  async function saveGeneratedSecret(name, value) {
185699
185805
  let secrets = {};
185700
- if (existsSync8(GENERATED_SECRETS_FILE)) {
185806
+ if (existsSync9(GENERATED_SECRETS_FILE)) {
185701
185807
  const content = await readFile(GENERATED_SECRETS_FILE, "utf-8");
185702
185808
  secrets = JSON.parse(content);
185703
185809
  }
185704
185810
  secrets[name] = value;
185705
- const dir = path8.dirname(GENERATED_SECRETS_FILE);
185706
- if (!existsSync8(dir)) {
185811
+ const dir = path9.dirname(GENERATED_SECRETS_FILE);
185812
+ if (!existsSync9(dir)) {
185707
185813
  await mkdir(dir, { recursive: true });
185708
185814
  }
185709
185815
  await writeFile(GENERATED_SECRETS_FILE, JSON.stringify(secrets, null, 2) + "\n");
@@ -185733,7 +185839,7 @@ async function prepareSecrets(secretsConfig) {
185733
185839
  // src/lib/config/parser.ts
185734
185840
  var import_hcl2_json_parser3 = __toESM(require_dist(), 1);
185735
185841
  import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
185736
- import { existsSync as existsSync9 } from "fs";
185842
+ import { existsSync as existsSync10 } from "fs";
185737
185843
  var { parseToObject: parseToObject3 } = import_hcl2_json_parser3.default;
185738
185844
  var CONFIG_FILE = "specific.config";
185739
185845
  async function parseConfigFile(content) {
@@ -185750,7 +185856,7 @@ async function parseConfigFile(content) {
185750
185856
  return configs;
185751
185857
  }
185752
185858
  async function loadConfigs() {
185753
- if (!existsSync9(CONFIG_FILE)) {
185859
+ if (!existsSync10(CONFIG_FILE)) {
185754
185860
  return /* @__PURE__ */ new Map();
185755
185861
  }
185756
185862
  const content = await readFile2(CONFIG_FILE, "utf-8");
@@ -186041,14 +186147,14 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
186041
186147
  ports: endpointPorts,
186042
186148
  process: child,
186043
186149
  async stop() {
186044
- return new Promise((resolve4) => {
186150
+ return new Promise((resolve5) => {
186045
186151
  if (child.killed || child.exitCode !== null) {
186046
- resolve4();
186152
+ resolve5();
186047
186153
  return;
186048
186154
  }
186049
186155
  child.once("exit", () => {
186050
186156
  clearTimeout(forceKillTimeout);
186051
- resolve4();
186157
+ resolve5();
186052
186158
  });
186053
186159
  child.kill("SIGTERM");
186054
186160
  const forceKillTimeout = setTimeout(() => {
@@ -186062,21 +186168,26 @@ function startService(service, resources, secrets, configs, endpointPorts, servi
186062
186168
  }
186063
186169
 
186064
186170
  // src/lib/dev/instance-state.ts
186065
- import * as fs8 from "fs";
186066
- import * as path9 from "path";
186171
+ import * as fs9 from "fs";
186172
+ import * as path10 from "path";
186067
186173
  var InstanceStateManager = class {
186068
186174
  stateDir;
186069
186175
  statePath;
186070
186176
  lockPath;
186071
186177
  ownsInstances = false;
186072
- constructor(projectRoot) {
186073
- this.stateDir = path9.join(projectRoot, ".specific");
186074
- this.statePath = path9.join(this.stateDir, "state.json");
186075
- this.lockPath = path9.join(this.stateDir, "state.lock");
186178
+ key;
186179
+ constructor(projectRoot, key = "default") {
186180
+ this.key = key;
186181
+ this.stateDir = path10.join(projectRoot, ".specific", "keys", key);
186182
+ this.statePath = path10.join(this.stateDir, "state.json");
186183
+ this.lockPath = path10.join(this.stateDir, "state.lock");
186184
+ }
186185
+ getKey() {
186186
+ return this.key;
186076
186187
  }
186077
186188
  ensureStateDir() {
186078
- if (!fs8.existsSync(this.stateDir)) {
186079
- fs8.mkdirSync(this.stateDir, { recursive: true });
186189
+ if (!fs9.existsSync(this.stateDir)) {
186190
+ fs9.mkdirSync(this.stateDir, { recursive: true });
186080
186191
  }
186081
186192
  }
186082
186193
  isProcessRunning(pid) {
@@ -186093,15 +186204,15 @@ var InstanceStateManager = class {
186093
186204
  const startTime = Date.now();
186094
186205
  while (Date.now() - startTime < timeoutMs) {
186095
186206
  try {
186096
- const fd = fs8.openSync(
186207
+ const fd = fs9.openSync(
186097
186208
  this.lockPath,
186098
- fs8.constants.O_CREAT | fs8.constants.O_EXCL | fs8.constants.O_WRONLY
186209
+ fs9.constants.O_CREAT | fs9.constants.O_EXCL | fs9.constants.O_WRONLY
186099
186210
  );
186100
- fs8.writeSync(fd, String(process.pid));
186101
- fs8.closeSync(fd);
186211
+ fs9.writeSync(fd, String(process.pid));
186212
+ fs9.closeSync(fd);
186102
186213
  return () => {
186103
186214
  try {
186104
- fs8.unlinkSync(this.lockPath);
186215
+ fs9.unlinkSync(this.lockPath);
186105
186216
  } catch {
186106
186217
  }
186107
186218
  };
@@ -186110,21 +186221,21 @@ var InstanceStateManager = class {
186110
186221
  if (err.code === "EEXIST") {
186111
186222
  try {
186112
186223
  const lockPid = parseInt(
186113
- fs8.readFileSync(this.lockPath, "utf-8").trim(),
186224
+ fs9.readFileSync(this.lockPath, "utf-8").trim(),
186114
186225
  10
186115
186226
  );
186116
186227
  if (!this.isProcessRunning(lockPid)) {
186117
- fs8.unlinkSync(this.lockPath);
186228
+ fs9.unlinkSync(this.lockPath);
186118
186229
  continue;
186119
186230
  }
186120
186231
  } catch {
186121
186232
  try {
186122
- fs8.unlinkSync(this.lockPath);
186233
+ fs9.unlinkSync(this.lockPath);
186123
186234
  } catch {
186124
186235
  }
186125
186236
  continue;
186126
186237
  }
186127
- await new Promise((resolve4) => setTimeout(resolve4, 100));
186238
+ await new Promise((resolve5) => setTimeout(resolve5, 100));
186128
186239
  } else {
186129
186240
  throw e;
186130
186241
  }
@@ -186133,12 +186244,12 @@ var InstanceStateManager = class {
186133
186244
  throw new Error("Failed to acquire state lock (timeout)");
186134
186245
  }
186135
186246
  async getExistingInstances() {
186136
- if (!fs8.existsSync(this.statePath)) {
186247
+ if (!fs9.existsSync(this.statePath)) {
186137
186248
  return null;
186138
186249
  }
186139
186250
  const releaseLock = await this.acquireLock();
186140
186251
  try {
186141
- const content = fs8.readFileSync(this.statePath, "utf-8");
186252
+ const content = fs9.readFileSync(this.statePath, "utf-8");
186142
186253
  const state = JSON.parse(content);
186143
186254
  if (!this.isProcessRunning(state.owner.pid)) {
186144
186255
  return null;
@@ -186151,21 +186262,21 @@ var InstanceStateManager = class {
186151
186262
  }
186152
186263
  }
186153
186264
  async cleanStaleState() {
186154
- if (!fs8.existsSync(this.statePath)) {
186265
+ if (!fs9.existsSync(this.statePath)) {
186155
186266
  return false;
186156
186267
  }
186157
186268
  const releaseLock = await this.acquireLock();
186158
186269
  try {
186159
- const content = fs8.readFileSync(this.statePath, "utf-8");
186270
+ const content = fs9.readFileSync(this.statePath, "utf-8");
186160
186271
  const state = JSON.parse(content);
186161
186272
  if (!this.isProcessRunning(state.owner.pid)) {
186162
- fs8.unlinkSync(this.statePath);
186273
+ fs9.unlinkSync(this.statePath);
186163
186274
  return true;
186164
186275
  }
186165
186276
  return false;
186166
186277
  } catch {
186167
186278
  try {
186168
- fs8.unlinkSync(this.statePath);
186279
+ fs9.unlinkSync(this.statePath);
186169
186280
  return true;
186170
186281
  } catch {
186171
186282
  }
@@ -186177,8 +186288,8 @@ var InstanceStateManager = class {
186177
186288
  async claimOwnership(command) {
186178
186289
  const releaseLock = await this.acquireLock();
186179
186290
  try {
186180
- if (fs8.existsSync(this.statePath)) {
186181
- const content = fs8.readFileSync(this.statePath, "utf-8");
186291
+ if (fs9.existsSync(this.statePath)) {
186292
+ const content = fs9.readFileSync(this.statePath, "utf-8");
186182
186293
  const state2 = JSON.parse(content);
186183
186294
  if (this.isProcessRunning(state2.owner.pid)) {
186184
186295
  throw new Error(`Instances already owned by PID ${state2.owner.pid}`);
@@ -186247,8 +186358,8 @@ var InstanceStateManager = class {
186247
186358
  }
186248
186359
  const releaseLock = await this.acquireLock();
186249
186360
  try {
186250
- if (fs8.existsSync(this.statePath)) {
186251
- fs8.unlinkSync(this.statePath);
186361
+ if (fs9.existsSync(this.statePath)) {
186362
+ fs9.unlinkSync(this.statePath);
186252
186363
  }
186253
186364
  this.ownsInstances = false;
186254
186365
  } finally {
@@ -186256,31 +186367,31 @@ var InstanceStateManager = class {
186256
186367
  }
186257
186368
  }
186258
186369
  readState() {
186259
- const content = fs8.readFileSync(this.statePath, "utf-8");
186370
+ const content = fs9.readFileSync(this.statePath, "utf-8");
186260
186371
  return JSON.parse(content);
186261
186372
  }
186262
186373
  writeStateAtomic(state) {
186263
186374
  this.ensureStateDir();
186264
186375
  const tmpPath = this.statePath + ".tmp";
186265
- fs8.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
186266
- fs8.renameSync(tmpPath, this.statePath);
186376
+ fs9.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
186377
+ fs9.renameSync(tmpPath, this.statePath);
186267
186378
  }
186268
186379
  };
186269
186380
 
186270
186381
  // src/lib/dev/http-proxy.ts
186271
186382
  import * as http from "http";
186272
186383
  import * as https from "https";
186273
- import * as fs9 from "fs";
186274
- import * as path10 from "path";
186384
+ import * as fs10 from "fs";
186385
+ import * as path11 from "path";
186275
186386
  import { fileURLToPath as fileURLToPath2 } from "url";
186276
186387
  import httpProxy from "http-proxy";
186277
- var __dirname2 = path10.dirname(fileURLToPath2(import.meta.url));
186278
- var adminDir = path10.join(__dirname2, "admin");
186388
+ var __dirname2 = path11.dirname(fileURLToPath2(import.meta.url));
186389
+ var adminDir = path11.join(__dirname2, "admin");
186279
186390
  var HTTP_PORT = 80;
186280
186391
  var HTTPS_PORT = 443;
186281
186392
  var DOMAIN_SUFFIX = ".local.spcf.app";
186282
186393
  var ADMIN_DOMAIN = "local.spcf.app";
186283
- var DRIZZLE_GATEWAY_DOMAIN = "__drizzle_gateway.local.spcf.app";
186394
+ var DRIZZLE_GATEWAY_PREFIX = "__drizzle_gateway";
186284
186395
  var MIME_TYPES = {
186285
186396
  ".html": "text/html",
186286
186397
  ".css": "text/css",
@@ -186295,11 +186406,31 @@ var MIME_TYPES = {
186295
186406
  ".woff2": "font/woff2",
186296
186407
  ".txt": "text/plain"
186297
186408
  };
186298
- async function startHttpProxy(services, certificate, getState, drizzleGatewayPort) {
186409
+ async function startHttpProxy(services, certificate, getState, instanceKey = "default") {
186299
186410
  const serviceMap = /* @__PURE__ */ new Map();
186411
+ const adminPortMap = /* @__PURE__ */ new Map();
186412
+ const drizzleGatewayPortMap = /* @__PURE__ */ new Map();
186413
+ const buildMapKey = (serviceName, key) => {
186414
+ return key === "default" ? serviceName : `${serviceName}.${key}`;
186415
+ };
186300
186416
  for (const svc of services) {
186301
- serviceMap.set(svc.name, svc.port);
186417
+ serviceMap.set(buildMapKey(svc.name, instanceKey), svc.port);
186302
186418
  }
186419
+ const updateServices = (registryServices, keys) => {
186420
+ serviceMap.clear();
186421
+ adminPortMap.clear();
186422
+ drizzleGatewayPortMap.clear();
186423
+ for (const svc of registryServices) {
186424
+ serviceMap.set(buildMapKey(svc.serviceName, svc.key), svc.port);
186425
+ }
186426
+ for (const [key, registration] of Object.entries(keys)) {
186427
+ adminPortMap.set(key, registration.adminPort);
186428
+ if (registration.drizzleGatewayPort !== void 0) {
186429
+ drizzleGatewayPortMap.set(key, registration.drizzleGatewayPort);
186430
+ }
186431
+ }
186432
+ writeLog("proxy", `Updated service map: ${serviceMap.size} services, ${adminPortMap.size} admin instances, ${drizzleGatewayPortMap.size} drizzle gateways`);
186433
+ };
186303
186434
  const { key: defaultKey, cert: defaultCert } = certificate;
186304
186435
  const proxy = httpProxy.createProxyServer({});
186305
186436
  proxy.on("error", (err, req, res) => {
@@ -186312,39 +186443,65 @@ async function startHttpProxy(services, certificate, getState, drizzleGatewayPor
186312
186443
  });
186313
186444
  const handleRequest = (req, res) => {
186314
186445
  const host = req.headers.host || "";
186315
- const hostname = host.split(":")[0];
186316
- if (hostname === ADMIN_DOMAIN) {
186317
- return handleAdminRequest(req, res, proxy, getState);
186318
- }
186319
- if (hostname === DRIZZLE_GATEWAY_DOMAIN && drizzleGatewayPort) {
186320
- proxy.web(req, res, {
186321
- target: `http://127.0.0.1:${drizzleGatewayPort}`
186322
- });
186446
+ const hostname2 = host.split(":")[0];
186447
+ const drizzleKey = extractDrizzleGatewayKey(host);
186448
+ if (drizzleKey !== null) {
186449
+ const drizzlePort = drizzleGatewayPortMap.get(drizzleKey);
186450
+ if (drizzlePort) {
186451
+ proxy.web(req, res, {
186452
+ target: `http://127.0.0.1:${drizzlePort}`
186453
+ });
186454
+ return;
186455
+ }
186456
+ res.writeHead(503, { "Content-Type": "text/html" });
186457
+ res.end("<h1>Database Viewer</h1><p>No database viewer running for this instance.</p>");
186323
186458
  return;
186324
186459
  }
186325
- const serviceName = extractServiceName(host);
186326
- if (!serviceName) {
186327
- return sendNotFound(res, null, services);
186460
+ const adminKey = extractAdminKey(host);
186461
+ if (adminKey !== null) {
186462
+ const adminPort = adminPortMap.get(adminKey);
186463
+ if (adminPort) {
186464
+ const url = new URL(req.url || "/", "http://localhost");
186465
+ if (url.pathname.startsWith("/api/")) {
186466
+ proxy.web(req, res, { target: `http://127.0.0.1:${adminPort}` });
186467
+ return;
186468
+ }
186469
+ serveStaticFile(res, url.pathname);
186470
+ return;
186471
+ }
186472
+ if (adminKey === "default" && hostname2 === ADMIN_DOMAIN) {
186473
+ res.writeHead(503, { "Content-Type": "text/html" });
186474
+ res.end("<h1>Admin UI</h1><p>No dev instance running. Start <code>specific dev</code> first.</p>");
186475
+ return;
186476
+ }
186477
+ }
186478
+ const route = extractServiceAndKey(host);
186479
+ if (!route) {
186480
+ return sendNotFound(res, null, serviceMap);
186328
186481
  }
186329
- const targetPort = serviceMap.get(serviceName);
186482
+ const mapKey = buildMapKey(route.serviceName, route.key);
186483
+ const targetPort = serviceMap.get(mapKey);
186330
186484
  if (!targetPort) {
186331
- return sendNotFound(res, serviceName, services);
186485
+ return sendNotFound(res, route.serviceName, serviceMap);
186332
186486
  }
186333
186487
  proxy.web(req, res, { target: `http://127.0.0.1:${targetPort}` });
186334
186488
  };
186335
186489
  const handleUpgrade = (req, socket, head) => {
186336
186490
  const host = req.headers.host || "";
186337
- const hostname = host.split(":")[0];
186338
- if (hostname === ADMIN_DOMAIN) {
186339
- socket.end("HTTP/1.1 404 Not Found\r\n\r\n");
186340
- return;
186491
+ const adminKey = extractAdminKey(host);
186492
+ if (adminKey !== null) {
186493
+ if (adminPortMap.has(adminKey)) {
186494
+ socket.end("HTTP/1.1 404 Not Found\r\n\r\n");
186495
+ return;
186496
+ }
186341
186497
  }
186342
- const serviceName = extractServiceName(host);
186343
- if (!serviceName) {
186498
+ const route = extractServiceAndKey(host);
186499
+ if (!route) {
186344
186500
  socket.end("HTTP/1.1 404 Not Found\r\n\r\n");
186345
186501
  return;
186346
186502
  }
186347
- const targetPort = serviceMap.get(serviceName);
186503
+ const mapKey = buildMapKey(route.serviceName, route.key);
186504
+ const targetPort = serviceMap.get(mapKey);
186348
186505
  if (!targetPort) {
186349
186506
  socket.end("HTTP/1.1 404 Not Found\r\n\r\n");
186350
186507
  return;
@@ -186361,7 +186518,7 @@ async function startHttpProxy(services, certificate, getState, drizzleGatewayPor
186361
186518
  handleRequest
186362
186519
  );
186363
186520
  httpsServer.on("upgrade", handleUpgrade);
186364
- return new Promise((resolve4, reject) => {
186521
+ return new Promise((resolve5, reject) => {
186365
186522
  let httpStarted = false;
186366
186523
  let httpsStarted = false;
186367
186524
  let failed = false;
@@ -186372,17 +186529,25 @@ async function startHttpProxy(services, certificate, getState, drizzleGatewayPor
186372
186529
  "proxy",
186373
186530
  `HTTP/HTTPS proxy started on ports ${HTTP_PORT}/${HTTPS_PORT}`
186374
186531
  );
186375
- resolve4({
186532
+ resolve5({
186376
186533
  httpPort: HTTP_PORT,
186377
186534
  httpsPort: HTTPS_PORT,
186535
+ updateServices,
186536
+ updateCertificate(newCert) {
186537
+ httpsServer.setSecureContext({
186538
+ key: newCert.key,
186539
+ cert: newCert.cert
186540
+ });
186541
+ writeLog("proxy", "Certificate updated");
186542
+ },
186378
186543
  async stop() {
186379
- return new Promise((resolve5) => {
186544
+ return new Promise((resolve6) => {
186380
186545
  let closed = 0;
186381
186546
  const onClose = () => {
186382
186547
  closed++;
186383
186548
  if (closed === 2) {
186384
186549
  clearTimeout(forceCloseTimeout);
186385
- resolve5();
186550
+ resolve6();
186386
186551
  }
186387
186552
  };
186388
186553
  httpServer.close(onClose);
@@ -186390,7 +186555,7 @@ async function startHttpProxy(services, certificate, getState, drizzleGatewayPor
186390
186555
  const forceCloseTimeout = setTimeout(() => {
186391
186556
  httpServer.closeAllConnections?.();
186392
186557
  httpsServer.closeAllConnections?.();
186393
- resolve5();
186558
+ resolve6();
186394
186559
  }, 2e3);
186395
186560
  });
186396
186561
  }
@@ -186424,15 +186589,70 @@ async function startHttpProxy(services, certificate, getState, drizzleGatewayPor
186424
186589
  });
186425
186590
  });
186426
186591
  }
186427
- function extractServiceName(host) {
186428
- const hostname = host.split(":")[0];
186429
- if (!hostname || !hostname.endsWith(DOMAIN_SUFFIX)) {
186592
+ function extractServiceAndKey(host) {
186593
+ const hostname2 = host.split(":")[0];
186594
+ if (!hostname2 || !hostname2.endsWith(DOMAIN_SUFFIX)) {
186430
186595
  return null;
186431
186596
  }
186432
- return hostname.slice(0, -DOMAIN_SUFFIX.length);
186597
+ const prefix = hostname2.slice(0, -DOMAIN_SUFFIX.length);
186598
+ if (!prefix) {
186599
+ return null;
186600
+ }
186601
+ const parts = prefix.split(".");
186602
+ if (parts.length === 1) {
186603
+ return { serviceName: parts[0], key: "default" };
186604
+ } else if (parts.length === 2) {
186605
+ return { serviceName: parts[0], key: parts[1] };
186606
+ }
186607
+ return null;
186433
186608
  }
186434
- function sendNotFound(res, requestedService, availableServices) {
186435
- const serviceList = availableServices.map((s) => `<li>${s.name}.local.spcf.app</li>`).join("\n");
186609
+ function extractDrizzleGatewayKey(host) {
186610
+ const hostname2 = host.split(":")[0];
186611
+ if (!hostname2 || !hostname2.endsWith(DOMAIN_SUFFIX)) {
186612
+ return null;
186613
+ }
186614
+ const prefix = hostname2.slice(0, -DOMAIN_SUFFIX.length);
186615
+ if (!prefix) {
186616
+ return null;
186617
+ }
186618
+ const parts = prefix.split(".");
186619
+ if (parts.length === 1 && parts[0] === DRIZZLE_GATEWAY_PREFIX) {
186620
+ return "default";
186621
+ } else if (parts.length === 2 && parts[0] === DRIZZLE_GATEWAY_PREFIX) {
186622
+ return parts[1];
186623
+ }
186624
+ return null;
186625
+ }
186626
+ function extractAdminKey(host) {
186627
+ const hostname2 = host.split(":")[0];
186628
+ if (!hostname2) {
186629
+ return null;
186630
+ }
186631
+ if (hostname2 === ADMIN_DOMAIN) {
186632
+ return "default";
186633
+ }
186634
+ if (!hostname2.endsWith(DOMAIN_SUFFIX)) {
186635
+ return null;
186636
+ }
186637
+ const prefix = hostname2.slice(0, -DOMAIN_SUFFIX.length);
186638
+ if (!prefix) {
186639
+ return null;
186640
+ }
186641
+ const parts = prefix.split(".");
186642
+ if (parts.length === 1) {
186643
+ return parts[0];
186644
+ }
186645
+ return null;
186646
+ }
186647
+ function sendNotFound(res, requestedService, serviceMap) {
186648
+ const serviceList = Array.from(serviceMap.keys()).map((key) => {
186649
+ const parts = key.split(".");
186650
+ if (parts.length === 1) {
186651
+ return `<li>${parts[0]}.local.spcf.app</li>`;
186652
+ } else {
186653
+ return `<li>${key}.local.spcf.app</li>`;
186654
+ }
186655
+ }).join("\n");
186436
186656
  const message = requestedService ? `No service named "${requestedService}" is running.` : "Invalid host header.";
186437
186657
  res.writeHead(404, { "Content-Type": "text/html" });
186438
186658
  res.end(`<!DOCTYPE html>
@@ -186441,61 +186661,36 @@ function sendNotFound(res, requestedService, availableServices) {
186441
186661
  <body>
186442
186662
  <h1>Service Not Found</h1>
186443
186663
  <p>${message}</p>
186444
- ${availableServices.length > 0 ? `<p>Available services:</p>
186664
+ ${serviceMap.size > 0 ? `<p>Available services:</p>
186445
186665
  <ul>${serviceList}</ul>` : "<p>No services are currently exposed.</p>"}
186446
186666
  </body>
186447
186667
  </html>`);
186448
186668
  }
186449
- function handleAdminRequest(req, res, proxy, getState) {
186450
- const url = new URL(req.url || "/", "http://localhost");
186451
- if (url.pathname.startsWith("/api/")) {
186452
- return handleAdminApi(req, res, url, getState);
186453
- }
186454
- serveStaticFile(res, url.pathname);
186455
- }
186456
- function handleAdminApi(_req, res, url, getState) {
186457
- res.setHeader("Content-Type", "application/json");
186458
- res.setHeader("Access-Control-Allow-Origin", "*");
186459
- const pathname = url.pathname.endsWith("/") && url.pathname.length > 1 ? url.pathname.slice(0, -1) : url.pathname;
186460
- if (pathname === "/api/state") {
186461
- if (!getState) {
186462
- res.writeHead(500);
186463
- res.end(JSON.stringify({ error: "State not available" }));
186464
- return;
186465
- }
186466
- const state = getState();
186467
- res.writeHead(200);
186468
- res.end(JSON.stringify(state));
186469
- return;
186470
- }
186471
- res.writeHead(404);
186472
- res.end(JSON.stringify({ error: "Not found" }));
186473
- }
186474
186669
  function serveStaticFile(res, pathname) {
186475
186670
  let filePath = pathname;
186476
186671
  if (filePath === "/" || filePath.endsWith("/")) {
186477
186672
  filePath = filePath + "index.html";
186478
186673
  }
186479
186674
  const relativePath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
186480
- const fullPath = path10.join(adminDir, relativePath);
186481
- const resolvedPath = path10.resolve(fullPath);
186482
- const resolvedAdminDir = path10.resolve(adminDir);
186675
+ const fullPath = path11.join(adminDir, relativePath);
186676
+ const resolvedPath = path11.resolve(fullPath);
186677
+ const resolvedAdminDir = path11.resolve(adminDir);
186483
186678
  if (!resolvedPath.startsWith(resolvedAdminDir)) {
186484
186679
  res.writeHead(403, { "Content-Type": "text/html" });
186485
186680
  res.end("<h1>Forbidden</h1>");
186486
186681
  return;
186487
186682
  }
186488
- if (!fs9.existsSync(resolvedPath)) {
186683
+ if (!fs10.existsSync(resolvedPath)) {
186489
186684
  const htmlPath = resolvedPath + ".html";
186490
- if (fs9.existsSync(htmlPath)) {
186685
+ if (fs10.existsSync(htmlPath)) {
186491
186686
  return serveFile(res, htmlPath);
186492
186687
  }
186493
- const indexPath = path10.join(resolvedPath, "index.html");
186494
- if (fs9.existsSync(indexPath)) {
186688
+ const indexPath = path11.join(resolvedPath, "index.html");
186689
+ if (fs10.existsSync(indexPath)) {
186495
186690
  return serveFile(res, indexPath);
186496
186691
  }
186497
- const notFoundPath = path10.join(adminDir, "404.html");
186498
- if (fs9.existsSync(notFoundPath)) {
186692
+ const notFoundPath = path11.join(adminDir, "404.html");
186693
+ if (fs10.existsSync(notFoundPath)) {
186499
186694
  return serveFileContent(res, notFoundPath, "text/html", 404);
186500
186695
  }
186501
186696
  res.writeHead(404, { "Content-Type": "text/html" });
@@ -186505,13 +186700,13 @@ function serveStaticFile(res, pathname) {
186505
186700
  serveFile(res, resolvedPath);
186506
186701
  }
186507
186702
  function serveFile(res, filePath) {
186508
- const ext = path10.extname(filePath).toLowerCase();
186703
+ const ext = path11.extname(filePath).toLowerCase();
186509
186704
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
186510
186705
  serveFileContent(res, filePath, contentType, 200);
186511
186706
  }
186512
186707
  function serveFileContent(res, filePath, contentType, statusCode = 200) {
186513
186708
  try {
186514
- const content = fs9.readFileSync(filePath);
186709
+ const content = fs10.readFileSync(filePath);
186515
186710
  res.writeHead(statusCode, { "Content-Type": contentType });
186516
186711
  res.end(content);
186517
186712
  } catch (err) {
@@ -186522,6 +186717,48 @@ function serveFileContent(res, filePath, contentType, statusCode = 200) {
186522
186717
  res.end("<h1>Internal Server Error</h1>");
186523
186718
  }
186524
186719
  }
186720
+ async function startAdminServer(getState) {
186721
+ return new Promise((resolve5, reject) => {
186722
+ const server = http.createServer((req, res) => {
186723
+ const url = new URL(req.url || "/", "http://localhost");
186724
+ res.setHeader("Access-Control-Allow-Origin", "*");
186725
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
186726
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
186727
+ if (req.method === "OPTIONS") {
186728
+ res.writeHead(204);
186729
+ res.end();
186730
+ return;
186731
+ }
186732
+ const pathname = url.pathname.endsWith("/") && url.pathname.length > 1 ? url.pathname.slice(0, -1) : url.pathname;
186733
+ if (pathname === "/api/state") {
186734
+ res.setHeader("Content-Type", "application/json");
186735
+ const state = getState();
186736
+ res.writeHead(200);
186737
+ res.end(JSON.stringify(state));
186738
+ return;
186739
+ }
186740
+ res.setHeader("Content-Type", "application/json");
186741
+ res.writeHead(404);
186742
+ res.end(JSON.stringify({ error: "Not found" }));
186743
+ });
186744
+ server.on("error", reject);
186745
+ server.listen(0, "127.0.0.1", () => {
186746
+ const addr = server.address();
186747
+ if (!addr || typeof addr === "string") {
186748
+ reject(new Error("Failed to get server address"));
186749
+ return;
186750
+ }
186751
+ const port = addr.port;
186752
+ writeLog("admin", `Admin API server started on port ${port}`);
186753
+ resolve5({
186754
+ port,
186755
+ stop: () => new Promise((res, rej) => {
186756
+ server.close((err) => err ? rej(err) : res());
186757
+ })
186758
+ });
186759
+ });
186760
+ });
186761
+ }
186525
186762
 
186526
186763
  // src/lib/dev/electric-manager.ts
186527
186764
  import * as net2 from "net";
@@ -186579,33 +186816,33 @@ async function waitForTcpPort2(host, port, timeoutMs = 3e4) {
186579
186816
  throw new Error(`Electric port ${port} did not become available within timeout`);
186580
186817
  }
186581
186818
  function checkTcpPort2(host, port) {
186582
- return new Promise((resolve4) => {
186819
+ return new Promise((resolve5) => {
186583
186820
  const socket = new net2.Socket();
186584
186821
  socket.setTimeout(1e3);
186585
186822
  socket.on("connect", () => {
186586
186823
  socket.destroy();
186587
- resolve4(true);
186824
+ resolve5(true);
186588
186825
  });
186589
186826
  socket.on("timeout", () => {
186590
186827
  socket.destroy();
186591
- resolve4(false);
186828
+ resolve5(false);
186592
186829
  });
186593
186830
  socket.on("error", () => {
186594
186831
  socket.destroy();
186595
- resolve4(false);
186832
+ resolve5(false);
186596
186833
  });
186597
186834
  socket.connect(port, host);
186598
186835
  });
186599
186836
  }
186600
186837
  async function stopProcess2(proc) {
186601
- return new Promise((resolve4) => {
186838
+ return new Promise((resolve5) => {
186602
186839
  if (proc.killed || proc.exitCode !== null) {
186603
- resolve4();
186840
+ resolve5();
186604
186841
  return;
186605
186842
  }
186606
186843
  proc.once("exit", () => {
186607
186844
  clearTimeout(forceKillTimeout);
186608
- resolve4();
186845
+ resolve5();
186609
186846
  });
186610
186847
  proc.kill("SIGTERM");
186611
186848
  const forceKillTimeout = setTimeout(() => {
@@ -186616,13 +186853,13 @@ async function stopProcess2(proc) {
186616
186853
  });
186617
186854
  }
186618
186855
  function sleep2(ms) {
186619
- return new Promise((resolve4) => setTimeout(resolve4, ms));
186856
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
186620
186857
  }
186621
186858
 
186622
186859
  // src/lib/dev/drizzle-gateway-manager.ts
186623
186860
  import * as net3 from "net";
186624
- import * as fs10 from "fs";
186625
- import * as path11 from "path";
186861
+ import * as fs11 from "fs";
186862
+ import * as path12 from "path";
186626
186863
  import { spawn as spawn4 } from "child_process";
186627
186864
  import { randomUUID } from "crypto";
186628
186865
  function generateStoreJson(postgresInstances) {
@@ -186654,13 +186891,13 @@ async function startDrizzleGateway(postgresInstances, port, configDir, options2)
186654
186891
  options2?.onProgress
186655
186892
  );
186656
186893
  const host = "127.0.0.1";
186657
- const drizzleConfigDir = path11.join(configDir, "drizzle-gateway");
186658
- if (!fs10.existsSync(drizzleConfigDir)) {
186659
- fs10.mkdirSync(drizzleConfigDir, { recursive: true });
186894
+ const drizzleConfigDir = path12.join(configDir, "drizzle-gateway");
186895
+ if (!fs11.existsSync(drizzleConfigDir)) {
186896
+ fs11.mkdirSync(drizzleConfigDir, { recursive: true });
186660
186897
  }
186661
186898
  const storeJson = generateStoreJson(postgresInstances);
186662
- const storeJsonPath = path11.join(drizzleConfigDir, "store.json");
186663
- fs10.writeFileSync(storeJsonPath, JSON.stringify(storeJson, null, 2));
186899
+ const storeJsonPath = path12.join(drizzleConfigDir, "store.json");
186900
+ fs11.writeFileSync(storeJsonPath, JSON.stringify(storeJson, null, 2));
186664
186901
  writeLog("drizzle-gateway", `Starting Drizzle Gateway`);
186665
186902
  writeLog("drizzle-gateway", `STORE_PATH: ${drizzleConfigDir}`);
186666
186903
  writeLog("drizzle-gateway", `PORT: ${port}`);
@@ -186700,33 +186937,33 @@ async function waitForTcpPort3(host, port, timeoutMs = 3e4) {
186700
186937
  );
186701
186938
  }
186702
186939
  function checkTcpPort3(host, port) {
186703
- return new Promise((resolve4) => {
186940
+ return new Promise((resolve5) => {
186704
186941
  const socket = new net3.Socket();
186705
186942
  socket.setTimeout(1e3);
186706
186943
  socket.on("connect", () => {
186707
186944
  socket.destroy();
186708
- resolve4(true);
186945
+ resolve5(true);
186709
186946
  });
186710
186947
  socket.on("timeout", () => {
186711
186948
  socket.destroy();
186712
- resolve4(false);
186949
+ resolve5(false);
186713
186950
  });
186714
186951
  socket.on("error", () => {
186715
186952
  socket.destroy();
186716
- resolve4(false);
186953
+ resolve5(false);
186717
186954
  });
186718
186955
  socket.connect(port, host);
186719
186956
  });
186720
186957
  }
186721
186958
  async function stopProcess3(proc) {
186722
- return new Promise((resolve4) => {
186959
+ return new Promise((resolve5) => {
186723
186960
  if (proc.killed || proc.exitCode !== null) {
186724
- resolve4();
186961
+ resolve5();
186725
186962
  return;
186726
186963
  }
186727
186964
  proc.once("exit", () => {
186728
186965
  clearTimeout(forceKillTimeout);
186729
- resolve4();
186966
+ resolve5();
186730
186967
  });
186731
186968
  proc.kill("SIGTERM");
186732
186969
  const forceKillTimeout = setTimeout(() => {
@@ -186737,7 +186974,7 @@ async function stopProcess3(proc) {
186737
186974
  });
186738
186975
  }
186739
186976
  function sleep3(ms) {
186740
- return new Promise((resolve4) => setTimeout(resolve4, ms));
186977
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
186741
186978
  }
186742
186979
 
186743
186980
  // src/lib/dev/sync-detector.ts
@@ -186928,19 +187165,345 @@ function watchConfigFile(configPath, debounceMs, onChange) {
186928
187165
  };
186929
187166
  }
186930
187167
 
187168
+ // src/lib/dev/proxy-registry.ts
187169
+ import * as fs12 from "fs";
187170
+ import * as path13 from "path";
187171
+ import * as os5 from "os";
187172
+ var ProxyRegistryManager = class {
187173
+ proxyDir;
187174
+ ownerPath;
187175
+ registryPath;
187176
+ lockPath;
187177
+ isOwner = false;
187178
+ registryWatcher = null;
187179
+ constructor() {
187180
+ this.proxyDir = path13.join(os5.homedir(), ".specific", "proxy");
187181
+ this.ownerPath = path13.join(this.proxyDir, "owner.json");
187182
+ this.registryPath = path13.join(this.proxyDir, "registry.json");
187183
+ this.lockPath = path13.join(this.proxyDir, "registry.lock");
187184
+ }
187185
+ ensureProxyDir() {
187186
+ if (!fs12.existsSync(this.proxyDir)) {
187187
+ fs12.mkdirSync(this.proxyDir, { recursive: true });
187188
+ }
187189
+ }
187190
+ isProcessRunning(pid) {
187191
+ try {
187192
+ process.kill(pid, 0);
187193
+ return true;
187194
+ } catch (e) {
187195
+ const err = e;
187196
+ return err.code !== "ESRCH";
187197
+ }
187198
+ }
187199
+ async acquireLock(timeoutMs = 5e3) {
187200
+ this.ensureProxyDir();
187201
+ const startTime = Date.now();
187202
+ while (Date.now() - startTime < timeoutMs) {
187203
+ try {
187204
+ const fd = fs12.openSync(
187205
+ this.lockPath,
187206
+ fs12.constants.O_CREAT | fs12.constants.O_EXCL | fs12.constants.O_WRONLY
187207
+ );
187208
+ fs12.writeSync(fd, String(process.pid));
187209
+ fs12.closeSync(fd);
187210
+ return () => {
187211
+ try {
187212
+ fs12.unlinkSync(this.lockPath);
187213
+ } catch {
187214
+ }
187215
+ };
187216
+ } catch (e) {
187217
+ const err = e;
187218
+ if (err.code === "EEXIST") {
187219
+ try {
187220
+ const lockPid = parseInt(
187221
+ fs12.readFileSync(this.lockPath, "utf-8").trim(),
187222
+ 10
187223
+ );
187224
+ if (!this.isProcessRunning(lockPid)) {
187225
+ fs12.unlinkSync(this.lockPath);
187226
+ continue;
187227
+ }
187228
+ } catch {
187229
+ try {
187230
+ fs12.unlinkSync(this.lockPath);
187231
+ } catch {
187232
+ }
187233
+ continue;
187234
+ }
187235
+ await new Promise((resolve5) => setTimeout(resolve5, 100));
187236
+ } else {
187237
+ throw e;
187238
+ }
187239
+ }
187240
+ }
187241
+ throw new Error("Failed to acquire proxy registry lock (timeout)");
187242
+ }
187243
+ /**
187244
+ * Try to claim ownership of the proxy.
187245
+ * Returns true if this process is now the owner.
187246
+ */
187247
+ async claimProxyOwnership(key) {
187248
+ const releaseLock = await this.acquireLock();
187249
+ try {
187250
+ if (fs12.existsSync(this.ownerPath)) {
187251
+ const content = fs12.readFileSync(this.ownerPath, "utf-8");
187252
+ const ownerFile2 = JSON.parse(content);
187253
+ if (this.isProcessRunning(ownerFile2.owner.pid)) {
187254
+ return false;
187255
+ }
187256
+ }
187257
+ const ownerFile = {
187258
+ version: 1,
187259
+ owner: {
187260
+ pid: process.pid,
187261
+ key,
187262
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
187263
+ }
187264
+ };
187265
+ this.writeFileAtomic(this.ownerPath, ownerFile);
187266
+ this.isOwner = true;
187267
+ return true;
187268
+ } finally {
187269
+ releaseLock();
187270
+ }
187271
+ }
187272
+ /**
187273
+ * Release ownership of the proxy.
187274
+ */
187275
+ async releaseProxyOwnership() {
187276
+ if (!this.isOwner) {
187277
+ return;
187278
+ }
187279
+ const releaseLock = await this.acquireLock();
187280
+ try {
187281
+ if (fs12.existsSync(this.ownerPath)) {
187282
+ const content = fs12.readFileSync(this.ownerPath, "utf-8");
187283
+ const ownerFile = JSON.parse(content);
187284
+ if (ownerFile.owner.pid === process.pid) {
187285
+ fs12.unlinkSync(this.ownerPath);
187286
+ }
187287
+ }
187288
+ this.isOwner = false;
187289
+ } finally {
187290
+ releaseLock();
187291
+ }
187292
+ }
187293
+ /**
187294
+ * Get the current proxy owner.
187295
+ */
187296
+ async getProxyOwner() {
187297
+ if (!fs12.existsSync(this.ownerPath)) {
187298
+ return null;
187299
+ }
187300
+ const releaseLock = await this.acquireLock();
187301
+ try {
187302
+ const content = fs12.readFileSync(this.ownerPath, "utf-8");
187303
+ const ownerFile = JSON.parse(content);
187304
+ if (!this.isProcessRunning(ownerFile.owner.pid)) {
187305
+ return null;
187306
+ }
187307
+ return ownerFile.owner;
187308
+ } catch {
187309
+ return null;
187310
+ } finally {
187311
+ releaseLock();
187312
+ }
187313
+ }
187314
+ /**
187315
+ * Check if this process is the proxy owner.
187316
+ */
187317
+ isProxyOwner() {
187318
+ return this.isOwner;
187319
+ }
187320
+ /**
187321
+ * Register services for a key.
187322
+ */
187323
+ async registerServices(key, adminPort, services, drizzleGatewayPort) {
187324
+ const releaseLock = await this.acquireLock();
187325
+ try {
187326
+ const registry = this.readRegistry();
187327
+ const registration = {
187328
+ adminPort,
187329
+ services
187330
+ };
187331
+ if (drizzleGatewayPort !== void 0) {
187332
+ registration.drizzleGatewayPort = drizzleGatewayPort;
187333
+ }
187334
+ registry.keys[key] = registration;
187335
+ registry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
187336
+ this.writeFileAtomic(this.registryPath, registry);
187337
+ } finally {
187338
+ releaseLock();
187339
+ }
187340
+ }
187341
+ /**
187342
+ * Unregister all services for a key.
187343
+ */
187344
+ async unregisterServices(key) {
187345
+ const releaseLock = await this.acquireLock();
187346
+ try {
187347
+ const registry = this.readRegistry();
187348
+ delete registry.keys[key];
187349
+ registry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
187350
+ this.writeFileAtomic(this.registryPath, registry);
187351
+ } finally {
187352
+ releaseLock();
187353
+ }
187354
+ }
187355
+ /**
187356
+ * Get all registered services (flattened view for proxy routing).
187357
+ */
187358
+ async getAllServices() {
187359
+ const releaseLock = await this.acquireLock();
187360
+ try {
187361
+ const registry = this.readRegistry();
187362
+ const services = [];
187363
+ for (const [key, registration] of Object.entries(registry.keys)) {
187364
+ for (const svc of registration.services) {
187365
+ services.push({
187366
+ key,
187367
+ serviceName: svc.serviceName,
187368
+ port: svc.port
187369
+ });
187370
+ }
187371
+ }
187372
+ return services;
187373
+ } finally {
187374
+ releaseLock();
187375
+ }
187376
+ }
187377
+ /**
187378
+ * Get all registered keys and their admin ports.
187379
+ */
187380
+ async getAllKeyRegistrations() {
187381
+ const releaseLock = await this.acquireLock();
187382
+ try {
187383
+ const registry = this.readRegistry();
187384
+ return { ...registry.keys };
187385
+ } finally {
187386
+ releaseLock();
187387
+ }
187388
+ }
187389
+ /**
187390
+ * Watch the registry for changes. Returns a cleanup function.
187391
+ * Callback receives both the flattened services list and all key registrations (for admin ports).
187392
+ * Uses chokidar instead of fs.watch for reliable detection of atomic file writes on macOS.
187393
+ */
187394
+ watchRegistry(onChange) {
187395
+ this.ensureProxyDir();
187396
+ if (!fs12.existsSync(this.registryPath)) {
187397
+ const emptyRegistry = {
187398
+ version: 1,
187399
+ keys: {},
187400
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
187401
+ };
187402
+ this.writeFileAtomic(this.registryPath, emptyRegistry);
187403
+ }
187404
+ let debounceTimer = null;
187405
+ this.registryWatcher = chokidar_default.watch(this.registryPath, {
187406
+ persistent: true,
187407
+ awaitWriteFinish: {
187408
+ stabilityThreshold: 100,
187409
+ pollInterval: 50
187410
+ }
187411
+ });
187412
+ this.registryWatcher.on("change", async () => {
187413
+ if (debounceTimer) {
187414
+ clearTimeout(debounceTimer);
187415
+ }
187416
+ debounceTimer = setTimeout(async () => {
187417
+ try {
187418
+ const services = await this.getAllServices();
187419
+ const keys = await this.getAllKeyRegistrations();
187420
+ onChange(services, keys);
187421
+ } catch {
187422
+ }
187423
+ }, 50);
187424
+ });
187425
+ return () => {
187426
+ if (debounceTimer) {
187427
+ clearTimeout(debounceTimer);
187428
+ }
187429
+ this.registryWatcher?.close();
187430
+ this.registryWatcher = null;
187431
+ };
187432
+ }
187433
+ /**
187434
+ * Attempt to become the proxy owner (election).
187435
+ * This is called when the current owner is detected as dead.
187436
+ */
187437
+ async attemptElection(key) {
187438
+ const releaseLock = await this.acquireLock();
187439
+ try {
187440
+ if (fs12.existsSync(this.ownerPath)) {
187441
+ const content = fs12.readFileSync(this.ownerPath, "utf-8");
187442
+ const ownerFile2 = JSON.parse(content);
187443
+ if (this.isProcessRunning(ownerFile2.owner.pid)) {
187444
+ return false;
187445
+ }
187446
+ fs12.unlinkSync(this.ownerPath);
187447
+ }
187448
+ const ownerFile = {
187449
+ version: 1,
187450
+ owner: {
187451
+ pid: process.pid,
187452
+ key,
187453
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
187454
+ }
187455
+ };
187456
+ this.writeFileAtomic(this.ownerPath, ownerFile);
187457
+ this.isOwner = true;
187458
+ return true;
187459
+ } finally {
187460
+ releaseLock();
187461
+ }
187462
+ }
187463
+ readRegistry() {
187464
+ if (!fs12.existsSync(this.registryPath)) {
187465
+ return {
187466
+ version: 1,
187467
+ keys: {},
187468
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
187469
+ };
187470
+ }
187471
+ try {
187472
+ const content = fs12.readFileSync(this.registryPath, "utf-8");
187473
+ return JSON.parse(content);
187474
+ } catch {
187475
+ return {
187476
+ version: 1,
187477
+ keys: {},
187478
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
187479
+ };
187480
+ }
187481
+ }
187482
+ writeFileAtomic(filePath, data) {
187483
+ this.ensureProxyDir();
187484
+ const tmpPath = filePath + ".tmp";
187485
+ fs12.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
187486
+ fs12.renameSync(tmpPath, filePath);
187487
+ }
187488
+ };
187489
+
186931
187490
  // src/commands/dev.tsx
186932
187491
  var COLORS = ["cyan", "yellow", "green", "magenta", "blue"];
186933
- function DevUI() {
187492
+ function DevUI({ instanceKey }) {
186934
187493
  const { exit } = useApp2();
186935
- const [state, setState] = useState3(() => ({
186936
- status: caFilesExist() ? "loading" : "installing-ca",
186937
- caInstallPhase: caFilesExist() ? void 0 : "installing",
186938
- resources: /* @__PURE__ */ new Map(),
186939
- resourceStatus: /* @__PURE__ */ new Map(),
186940
- services: [],
186941
- output: [],
186942
- colorMap: /* @__PURE__ */ new Map()
186943
- }));
187494
+ const [state, setState] = useState3(() => {
187495
+ const caExists = caFilesExist();
187496
+ return {
187497
+ status: caExists ? "loading" : "installing-ca",
187498
+ ...caExists ? {} : { caInstallPhase: "installing" },
187499
+ resources: /* @__PURE__ */ new Map(),
187500
+ resourceStatus: /* @__PURE__ */ new Map(),
187501
+ services: [],
187502
+ output: [],
187503
+ colorMap: /* @__PURE__ */ new Map(),
187504
+ isProxyOwner: false
187505
+ };
187506
+ });
186944
187507
  useEffect3(() => {
186945
187508
  if (state.status === "installing-ca" && state.caInstallPhase === "installing") {
186946
187509
  installCA();
@@ -186963,22 +187526,39 @@ function DevUI() {
186963
187526
  }
186964
187527
  }
186965
187528
  const shuttingDown = useRef(false);
187529
+ const startTimeRef = useRef(null);
186966
187530
  const stateManagerRef = useRef(null);
187531
+ const proxyRegistryRef = useRef(null);
186967
187532
  const electricInstancesRef = useRef([]);
186968
187533
  const drizzleGatewayRef = useRef(null);
187534
+ const registryWatcherCleanupRef = useRef(null);
187535
+ const electionIntervalRef = useRef(null);
186969
187536
  const proxyRef = useRef(null);
187537
+ const adminServerRef = useRef(null);
186970
187538
  const servicesRef = useRef([]);
186971
187539
  const resourcesRef = useRef(/* @__PURE__ */ new Map());
186972
187540
  const [reloadTrigger, setReloadTrigger] = useState3(0);
186973
187541
  const [readyToStart, setReadyToStart] = useState3(() => caFilesExist());
186974
- const shutdown = async () => {
187542
+ const shutdown2 = async () => {
186975
187543
  if (shuttingDown.current) return;
186976
187544
  shuttingDown.current = true;
187545
+ const duration = startTimeRef.current ? Math.round((Date.now() - startTimeRef.current) / 1e3) : void 0;
187546
+ trackEvent("dev_stopped", { duration_seconds: duration });
186977
187547
  writeLog("system", "Shutting down");
186978
187548
  setState((s) => ({ ...s, status: "stopping" }));
187549
+ if (electionIntervalRef.current) {
187550
+ clearInterval(electionIntervalRef.current);
187551
+ electionIntervalRef.current = null;
187552
+ }
187553
+ if (registryWatcherCleanupRef.current) {
187554
+ registryWatcherCleanupRef.current();
187555
+ registryWatcherCleanupRef.current = null;
187556
+ }
186979
187557
  await Promise.all([
186980
187558
  // Stop proxy
186981
187559
  proxyRef.current?.stop(),
187560
+ // Stop admin server
187561
+ adminServerRef.current?.stop(),
186982
187562
  // Stop all services
186983
187563
  ...servicesRef.current.map((service) => service.stop()),
186984
187564
  // Stop all Electric instances
@@ -186988,6 +187568,10 @@ function DevUI() {
186988
187568
  // Stop all resources
186989
187569
  ...[...resourcesRef.current.values()].map((resource) => resource.stop())
186990
187570
  ]);
187571
+ if (proxyRegistryRef.current) {
187572
+ await proxyRegistryRef.current.unregisterServices(instanceKey);
187573
+ await proxyRegistryRef.current.releaseProxyOwnership();
187574
+ }
186991
187575
  if (stateManagerRef.current) {
186992
187576
  await stateManagerRef.current.releaseOwnership();
186993
187577
  }
@@ -186999,9 +187583,19 @@ function DevUI() {
186999
187583
  if (shuttingDown.current || state.status === "reloading") return;
187000
187584
  writeLog("system", "Reloading (config changed)");
187001
187585
  setState((s) => ({ ...s, status: "reloading", parseError: void 0 }));
187586
+ if (electionIntervalRef.current) {
187587
+ clearInterval(electionIntervalRef.current);
187588
+ electionIntervalRef.current = null;
187589
+ }
187590
+ if (registryWatcherCleanupRef.current) {
187591
+ registryWatcherCleanupRef.current();
187592
+ registryWatcherCleanupRef.current = null;
187593
+ }
187002
187594
  await Promise.all([
187003
187595
  // Stop proxy
187004
187596
  proxyRef.current?.stop(),
187597
+ // Stop admin server
187598
+ adminServerRef.current?.stop(),
187005
187599
  // Stop all services
187006
187600
  ...servicesRef.current.map((service) => service.stop()),
187007
187601
  // Stop all Electric instances
@@ -187014,8 +187608,13 @@ function DevUI() {
187014
187608
  electricInstancesRef.current = [];
187015
187609
  drizzleGatewayRef.current = null;
187016
187610
  proxyRef.current = null;
187611
+ adminServerRef.current = null;
187017
187612
  servicesRef.current = [];
187018
187613
  resourcesRef.current = /* @__PURE__ */ new Map();
187614
+ if (proxyRegistryRef.current) {
187615
+ await proxyRegistryRef.current.unregisterServices(instanceKey);
187616
+ await proxyRegistryRef.current.releaseProxyOwnership();
187617
+ }
187019
187618
  if (stateManagerRef.current) {
187020
187619
  await stateManagerRef.current.releaseOwnership();
187021
187620
  }
@@ -187026,7 +187625,8 @@ function DevUI() {
187026
187625
  resources: /* @__PURE__ */ new Map(),
187027
187626
  resourceStatus: /* @__PURE__ */ new Map(),
187028
187627
  services: [],
187029
- proxy: void 0
187628
+ proxy: void 0,
187629
+ isProxyOwner: false
187030
187630
  }));
187031
187631
  setReloadTrigger((t) => t + 1);
187032
187632
  };
@@ -187050,7 +187650,7 @@ function DevUI() {
187050
187650
  }
187051
187651
  process.exit(1);
187052
187652
  }
187053
- shutdown();
187653
+ shutdown2();
187054
187654
  };
187055
187655
  process.on("SIGINT", handleSignal);
187056
187656
  process.on("SIGTERM", handleSignal);
@@ -187059,12 +187659,18 @@ function DevUI() {
187059
187659
  process.off("SIGTERM", handleSignal);
187060
187660
  };
187061
187661
  }, []);
187662
+ useEffect3(() => {
187663
+ if (state.status === "running" && !startTimeRef.current) {
187664
+ startTimeRef.current = Date.now();
187665
+ trackEvent("dev_started");
187666
+ }
187667
+ }, [state.status]);
187062
187668
  useEffect3(() => {
187063
187669
  if (state.status !== "running") return;
187064
- const configPath = path12.join(process.cwd(), "specific.hcl");
187670
+ const configPath = path14.join(process.cwd(), "specific.hcl");
187065
187671
  const watcher = watchConfigFile(configPath, 1e3, () => {
187066
187672
  try {
187067
- const hcl = fs11.readFileSync(configPath, "utf-8");
187673
+ const hcl = fs13.readFileSync(configPath, "utf-8");
187068
187674
  parseConfig(hcl).then(() => {
187069
187675
  triggerReload();
187070
187676
  }).catch((err) => {
@@ -187104,8 +187710,10 @@ function DevUI() {
187104
187710
  let startedDrizzleGateway = null;
187105
187711
  const startedServices = [];
187106
187712
  let startedProxy = null;
187107
- const stateManager = new InstanceStateManager(process.cwd());
187713
+ const stateManager = new InstanceStateManager(process.cwd(), instanceKey);
187108
187714
  stateManagerRef.current = stateManager;
187715
+ const proxyRegistry = new ProxyRegistryManager();
187716
+ proxyRegistryRef.current = proxyRegistry;
187109
187717
  async function start() {
187110
187718
  writeLog("system", "Starting dev server");
187111
187719
  await stateManager.cleanStaleState();
@@ -187128,8 +187736,8 @@ function DevUI() {
187128
187736
  }));
187129
187737
  return;
187130
187738
  }
187131
- const configPath = path12.join(process.cwd(), "specific.hcl");
187132
- if (!fs11.existsSync(configPath)) {
187739
+ const configPath = path14.join(process.cwd(), "specific.hcl");
187740
+ if (!fs13.existsSync(configPath)) {
187133
187741
  writeLog("system", "Waiting for specific.hcl to appear");
187134
187742
  setState((s) => ({
187135
187743
  ...s,
@@ -187148,7 +187756,7 @@ function DevUI() {
187148
187756
  }
187149
187757
  let config2;
187150
187758
  try {
187151
- const hcl = fs11.readFileSync(configPath, "utf-8");
187759
+ const hcl = fs13.readFileSync(configPath, "utf-8");
187152
187760
  config2 = await parseConfig(hcl);
187153
187761
  } catch (err) {
187154
187762
  setState((s) => ({
@@ -187181,7 +187789,7 @@ function DevUI() {
187181
187789
  colorIndex++;
187182
187790
  }
187183
187791
  setState((s) => ({ ...s, status: "starting", config: config2, colorMap }));
187184
- const portAllocator = new StablePortAllocator(process.cwd());
187792
+ const portAllocator = new StablePortAllocator(process.cwd(), instanceKey);
187185
187793
  const resourceStatus = /* @__PURE__ */ new Map();
187186
187794
  const syncDatabases = detectSyncDatabases(config2);
187187
187795
  let resources2;
@@ -187190,7 +187798,7 @@ function DevUI() {
187190
187798
  config: config2,
187191
187799
  selection: { mode: "all" },
187192
187800
  stateManager,
187193
- dataDir: ".specific/data",
187801
+ dataDir: `.specific/keys/${instanceKey}/data`,
187194
187802
  portAllocator,
187195
187803
  callbacks: {
187196
187804
  onResourceStarting: (name, type) => {
@@ -187254,7 +187862,7 @@ function DevUI() {
187254
187862
  const drizzleGateway = await startDrizzleGateway(
187255
187863
  postgresResources,
187256
187864
  drizzlePort,
187257
- path12.join(process.cwd(), ".specific")
187865
+ path14.join(process.cwd(), ".specific", "keys", instanceKey)
187258
187866
  );
187259
187867
  startedDrizzleGateway = drizzleGateway;
187260
187868
  drizzleGatewayRef.current = drizzleGateway;
@@ -187362,6 +187970,7 @@ function DevUI() {
187362
187970
  }
187363
187971
  }
187364
187972
  if (cancelled) return;
187973
+ const serviceInfos = [];
187365
187974
  const exposedServices = [];
187366
187975
  for (const service of config2.services) {
187367
187976
  const endpointInfos = serviceEndpoints.get(service.name) || [];
@@ -187372,6 +187981,10 @@ function DevUI() {
187372
187981
  if (endpointConfig?.public) {
187373
187982
  const proxyName = info.endpointName === "default" ? service.name : `${service.name}-${info.endpointName}`;
187374
187983
  exposedServices.push({ name: proxyName, port: info.port });
187984
+ serviceInfos.push({
187985
+ serviceName: proxyName,
187986
+ port: info.port
187987
+ });
187375
187988
  }
187376
187989
  }
187377
187990
  }
@@ -187395,26 +188008,114 @@ function DevUI() {
187395
188008
  syncEnabled: r.type === "postgres" && syncDatabases.has(name)
187396
188009
  }))
187397
188010
  });
187398
- try {
187399
- const certificate = generateCertificate("local.spcf.app");
187400
- const proxy2 = await startHttpProxy(
187401
- exposedServices,
187402
- certificate,
187403
- getState,
187404
- startedDrizzleGateway?.port
187405
- );
187406
- startedProxy = proxy2;
187407
- proxyRef.current = proxy2;
187408
- setState((s) => ({ ...s, proxy: proxy2 }));
187409
- } catch (err) {
187410
- const errorMsg = `Failed to start HTTP proxy: ${err instanceof Error ? err.message : String(err)}`;
187411
- writeLog("system:error", errorMsg);
187412
- setState((s) => ({
187413
- ...s,
187414
- status: "error",
187415
- error: errorMsg
187416
- }));
187417
- return;
188011
+ const adminServer = await startAdminServer(getState);
188012
+ adminServerRef.current = adminServer;
188013
+ writeLog("system", `Admin API server started on port ${adminServer.port}`);
188014
+ await proxyRegistry.registerServices(
188015
+ instanceKey,
188016
+ adminServer.port,
188017
+ serviceInfos,
188018
+ startedDrizzleGateway?.port
188019
+ );
188020
+ writeLog("system", `Registered ${serviceInfos.length} services with proxy registry`);
188021
+ const becameProxyOwner = await proxyRegistry.claimProxyOwnership(instanceKey);
188022
+ if (becameProxyOwner) {
188023
+ writeLog("system", "Claimed proxy ownership, starting HTTP proxy");
188024
+ try {
188025
+ const currentServices = await proxyRegistry.getAllServices();
188026
+ const registeredKeys = [...new Set(currentServices.map((s) => s.key))];
188027
+ const certificate = generateCertificate("local.spcf.app", registeredKeys);
188028
+ const proxy2 = await startHttpProxy(
188029
+ exposedServices,
188030
+ certificate,
188031
+ getState,
188032
+ instanceKey
188033
+ );
188034
+ startedProxy = proxy2;
188035
+ proxyRef.current = proxy2;
188036
+ setState((s) => ({ ...s, proxy: proxy2, isProxyOwner: true }));
188037
+ const knownKeys = new Set(registeredKeys);
188038
+ registryWatcherCleanupRef.current = proxyRegistry.watchRegistry(async (updatedServices, updatedKeys) => {
188039
+ writeLog("system", `Registry updated: ${updatedServices.length} services`);
188040
+ proxy2.updateServices(updatedServices, updatedKeys);
188041
+ const newKeyNames = Object.keys(updatedKeys).filter((k) => !knownKeys.has(k));
188042
+ if (newKeyNames.length > 0) {
188043
+ writeLog("system", `New keys detected: ${newKeyNames.join(", ")} - regenerating certificate`);
188044
+ for (const key of newKeyNames) {
188045
+ knownKeys.add(key);
188046
+ }
188047
+ const allKeys = [...knownKeys];
188048
+ const newCertificate = generateCertificate("local.spcf.app", allKeys);
188049
+ proxy2.updateCertificate(newCertificate);
188050
+ }
188051
+ });
188052
+ const currentKeys = await proxyRegistry.getAllKeyRegistrations();
188053
+ proxy2.updateServices(currentServices, currentKeys);
188054
+ writeLog("system", `Loaded ${currentServices.length} services from registry`);
188055
+ } catch (err) {
188056
+ const errorMsg = `Failed to start HTTP proxy: ${err instanceof Error ? err.message : String(err)}`;
188057
+ writeLog("system:error", errorMsg);
188058
+ setState((s) => ({
188059
+ ...s,
188060
+ status: "error",
188061
+ error: errorMsg
188062
+ }));
188063
+ return;
188064
+ }
188065
+ } else {
188066
+ writeLog("system", "Another instance owns the proxy, starting election watcher");
188067
+ setState((s) => ({ ...s, isProxyOwner: false }));
188068
+ const isProcessRunning = (pid) => {
188069
+ try {
188070
+ process.kill(pid, 0);
188071
+ return true;
188072
+ } catch {
188073
+ return false;
188074
+ }
188075
+ };
188076
+ electionIntervalRef.current = setInterval(async () => {
188077
+ if (cancelled || shuttingDown.current) {
188078
+ if (electionIntervalRef.current) {
188079
+ clearInterval(electionIntervalRef.current);
188080
+ electionIntervalRef.current = null;
188081
+ }
188082
+ return;
188083
+ }
188084
+ const owner = await proxyRegistry.getProxyOwner();
188085
+ if (!owner || !isProcessRunning(owner.pid)) {
188086
+ writeLog("system", "Proxy owner died, attempting election");
188087
+ const won = await proxyRegistry.attemptElection(instanceKey);
188088
+ if (won) {
188089
+ writeLog("system", "Won election, starting HTTP proxy");
188090
+ if (electionIntervalRef.current) {
188091
+ clearInterval(electionIntervalRef.current);
188092
+ electionIntervalRef.current = null;
188093
+ }
188094
+ try {
188095
+ const electionServices = await proxyRegistry.getAllServices();
188096
+ const electionKeyRegistrations = await proxyRegistry.getAllKeyRegistrations();
188097
+ const electionKeyNames = Object.keys(electionKeyRegistrations);
188098
+ const certificate = generateCertificate("local.spcf.app", electionKeyNames);
188099
+ const proxy2 = await startHttpProxy(
188100
+ exposedServices,
188101
+ certificate,
188102
+ getState,
188103
+ instanceKey
188104
+ );
188105
+ startedProxy = proxy2;
188106
+ proxyRef.current = proxy2;
188107
+ setState((s) => ({ ...s, proxy: proxy2, isProxyOwner: true }));
188108
+ registryWatcherCleanupRef.current = proxyRegistry.watchRegistry((updatedServices, updatedKeys) => {
188109
+ writeLog("system", `Registry updated: ${updatedServices.length} services`);
188110
+ proxy2.updateServices(updatedServices, updatedKeys);
188111
+ });
188112
+ proxy2.updateServices(electionServices, electionKeyRegistrations);
188113
+ } catch (err) {
188114
+ writeLog("system:error", `Failed to start proxy after election: ${err}`);
188115
+ }
188116
+ }
188117
+ }
188118
+ }, 1e3);
187418
188119
  }
187419
188120
  if (cancelled) return;
187420
188121
  writeLog("system", "Dev server running");
@@ -187423,6 +188124,14 @@ function DevUI() {
187423
188124
  start();
187424
188125
  return () => {
187425
188126
  cancelled = true;
188127
+ if (electionIntervalRef.current) {
188128
+ clearInterval(electionIntervalRef.current);
188129
+ electionIntervalRef.current = null;
188130
+ }
188131
+ if (registryWatcherCleanupRef.current) {
188132
+ registryWatcherCleanupRef.current();
188133
+ registryWatcherCleanupRef.current = null;
188134
+ }
187426
188135
  if (startedProxy) {
187427
188136
  startedProxy.stop().catch(() => {
187428
188137
  });
@@ -187443,10 +188152,14 @@ function DevUI() {
187443
188152
  resource.stop().catch(() => {
187444
188153
  });
187445
188154
  }
188155
+ proxyRegistry.unregisterServices(instanceKey).catch(() => {
188156
+ });
188157
+ proxyRegistry.releaseProxyOwnership().catch(() => {
188158
+ });
187446
188159
  stateManager.releaseOwnership().catch(() => {
187447
188160
  });
187448
188161
  };
187449
- }, [reloadTrigger, readyToStart]);
188162
+ }, [reloadTrigger, readyToStart, instanceKey]);
187450
188163
  if (state.status === "installing-ca") {
187451
188164
  if (state.caInstallPhase === "installing") {
187452
188165
  return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "cyan" }, "TLS Certificate Setup"), /* @__PURE__ */ React3.createElement(Text3, null, " "), /* @__PURE__ */ React3.createElement(Text3, null, "Installing a local certificate authority (CA) to enable HTTPS"), /* @__PURE__ */ React3.createElement(Text3, null, "for local development. The CA is limited to Specific projects."), /* @__PURE__ */ React3.createElement(Text3, null, " "), /* @__PURE__ */ React3.createElement(Text3, null, "Your password is required to add the CA to your system's trust store."), /* @__PURE__ */ React3.createElement(Text3, null, " "));
@@ -187524,17 +188237,15 @@ function DevUI() {
187524
188237
  const staticItems = [
187525
188238
  {
187526
188239
  key: "title",
187527
- content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "cyan" }, "Specific dev server"), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " (Ctrl+C to stop)"))
188240
+ content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: "cyan" }, "Specific dev server"), instanceKey !== "default" && /* @__PURE__ */ React3.createElement(Text3, { color: "yellow" }, " [", instanceKey, "]"), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " (Ctrl+C to stop)"))
187528
188241
  },
187529
188242
  { key: "space1", content: /* @__PURE__ */ React3.createElement(Text3, null, " ") },
187530
- // Show admin UI URL when proxy is running
187531
- ...proxy ? [
187532
- {
187533
- key: "admin",
187534
- content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Admin:"), /* @__PURE__ */ React3.createElement(Text3, null, " "), /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "https://local.spcf.app"))
187535
- },
187536
- { key: "admin-space", content: /* @__PURE__ */ React3.createElement(Text3, null, " ") }
187537
- ] : [],
188243
+ // Show admin UI URL
188244
+ {
188245
+ key: "admin",
188246
+ content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Admin:"), /* @__PURE__ */ React3.createElement(Text3, null, " "), /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "https://", instanceKey === "default" ? "" : `${instanceKey}.`, "local.spcf.app"))
188247
+ },
188248
+ { key: "admin-space", content: /* @__PURE__ */ React3.createElement(Text3, null, " ") },
187538
188249
  ...services.length > 0 ? [
187539
188250
  { key: "svc-header", content: /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Services:") },
187540
188251
  ...services.flatMap((svc) => {
@@ -187553,17 +188264,14 @@ function DevUI() {
187553
188264
  const proxyName = endpoint.name === "default" ? svc.name : `${svc.name}-${endpoint.name}`;
187554
188265
  return {
187555
188266
  key: `svc-${svc.name}-${endpoint.name}`,
187556
- content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, " \u25CF "), /* @__PURE__ */ React3.createElement(Text3, null, displayName), port ? endpoint.public && proxy ? /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(Text3, null, " \u2192 "), /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "https://", proxyName, ".local.spcf.app"), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " (localhost:", port, ")")) : /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " (localhost:", port, ")") : null)
188267
+ content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { color: "green" }, " \u25CF "), /* @__PURE__ */ React3.createElement(Text3, null, displayName), port ? endpoint.public ? /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(Text3, null, " \u2192 "), /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "https://", proxyName, instanceKey === "default" ? "" : `.${instanceKey}`, ".local.spcf.app"), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " (localhost:", port, ")")) : /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " (localhost:", port, ")") : null)
187557
188268
  };
187558
188269
  });
187559
188270
  }),
187560
188271
  { key: "space2", content: /* @__PURE__ */ React3.createElement(Text3, null, " ") }
187561
188272
  ] : [],
187562
188273
  ...config.postgres.length > 0 ? [
187563
- {
187564
- key: "pg-header",
187565
- content: /* @__PURE__ */ React3.createElement(Text3, null, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Postgres:"), drizzleGatewayRef.current && /* @__PURE__ */ React3.createElement(Text3, null, " ", "(admin \u2192 ", /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "http://localhost:", drizzleGatewayRef.current.port), ")"))
187566
- },
188274
+ { key: "pg-header", content: /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Postgres:") },
187567
188275
  ...config.postgres.map((pg) => {
187568
188276
  const instance = resources.get(pg.name);
187569
188277
  return {
@@ -187609,8 +188317,47 @@ function DevUI() {
187609
188317
  ];
187610
188318
  return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Static, { items: staticItems }, (item) => /* @__PURE__ */ React3.createElement(Box3, { key: item.key }, item.content)));
187611
188319
  }
187612
- function devCommand() {
187613
- render3(/* @__PURE__ */ React3.createElement(DevUI, null));
188320
+ function devCommand(instanceKey) {
188321
+ render3(/* @__PURE__ */ React3.createElement(DevUI, { instanceKey }));
188322
+ }
188323
+
188324
+ // src/lib/dev/git-worktree.ts
188325
+ import { execSync as execSync2 } from "child_process";
188326
+ import * as path15 from "path";
188327
+ function isInWorktree() {
188328
+ try {
188329
+ const commonDir = execSync2("git rev-parse --git-common-dir", {
188330
+ encoding: "utf-8",
188331
+ stdio: ["pipe", "pipe", "pipe"]
188332
+ }).trim();
188333
+ const gitDir = execSync2("git rev-parse --git-dir", {
188334
+ encoding: "utf-8",
188335
+ stdio: ["pipe", "pipe", "pipe"]
188336
+ }).trim();
188337
+ const resolvedCommonDir = path15.resolve(commonDir);
188338
+ const resolvedGitDir = path15.resolve(gitDir);
188339
+ return resolvedCommonDir !== resolvedGitDir;
188340
+ } catch {
188341
+ return false;
188342
+ }
188343
+ }
188344
+ function getWorktreeName() {
188345
+ if (!isInWorktree()) {
188346
+ return null;
188347
+ }
188348
+ try {
188349
+ const gitDir = execSync2("git rev-parse --git-dir", {
188350
+ encoding: "utf-8",
188351
+ stdio: ["pipe", "pipe", "pipe"]
188352
+ }).trim();
188353
+ return path15.basename(gitDir);
188354
+ } catch {
188355
+ return null;
188356
+ }
188357
+ }
188358
+ function getDefaultKey() {
188359
+ const worktreeName = getWorktreeName();
188360
+ return worktreeName ?? "default";
187614
188361
  }
187615
188362
 
187616
188363
  // src/commands/deploy.tsx
@@ -187620,30 +188367,30 @@ import Spinner5 from "ink-spinner";
187620
188367
 
187621
188368
  // ../../node_modules/open/index.js
187622
188369
  import process8 from "node:process";
187623
- import path13 from "node:path";
188370
+ import path16 from "node:path";
187624
188371
  import { fileURLToPath as fileURLToPath3 } from "node:url";
187625
188372
  import childProcess3 from "node:child_process";
187626
- import fs16, { constants as fsConstants2 } from "node:fs/promises";
188373
+ import fs18, { constants as fsConstants2 } from "node:fs/promises";
187627
188374
 
187628
188375
  // ../../node_modules/wsl-utils/index.js
187629
188376
  import { promisify as promisify2 } from "node:util";
187630
188377
  import childProcess2 from "node:child_process";
187631
- import fs15, { constants as fsConstants } from "node:fs/promises";
188378
+ import fs17, { constants as fsConstants } from "node:fs/promises";
187632
188379
 
187633
188380
  // ../../node_modules/is-wsl/index.js
187634
188381
  import process2 from "node:process";
187635
- import os4 from "node:os";
187636
- import fs14 from "node:fs";
188382
+ import os6 from "node:os";
188383
+ import fs16 from "node:fs";
187637
188384
 
187638
188385
  // ../../node_modules/is-inside-container/index.js
187639
- import fs13 from "node:fs";
188386
+ import fs15 from "node:fs";
187640
188387
 
187641
188388
  // ../../node_modules/is-docker/index.js
187642
- import fs12 from "node:fs";
188389
+ import fs14 from "node:fs";
187643
188390
  var isDockerCached;
187644
188391
  function hasDockerEnv() {
187645
188392
  try {
187646
- fs12.statSync("/.dockerenv");
188393
+ fs14.statSync("/.dockerenv");
187647
188394
  return true;
187648
188395
  } catch {
187649
188396
  return false;
@@ -187651,7 +188398,7 @@ function hasDockerEnv() {
187651
188398
  }
187652
188399
  function hasDockerCGroup() {
187653
188400
  try {
187654
- return fs12.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
188401
+ return fs14.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
187655
188402
  } catch {
187656
188403
  return false;
187657
188404
  }
@@ -187667,7 +188414,7 @@ function isDocker() {
187667
188414
  var cachedResult;
187668
188415
  var hasContainerEnv = () => {
187669
188416
  try {
187670
- fs13.statSync("/run/.containerenv");
188417
+ fs15.statSync("/run/.containerenv");
187671
188418
  return true;
187672
188419
  } catch {
187673
188420
  return false;
@@ -187685,14 +188432,14 @@ var isWsl = () => {
187685
188432
  if (process2.platform !== "linux") {
187686
188433
  return false;
187687
188434
  }
187688
- if (os4.release().toLowerCase().includes("microsoft")) {
188435
+ if (os6.release().toLowerCase().includes("microsoft")) {
187689
188436
  if (isInsideContainer()) {
187690
188437
  return false;
187691
188438
  }
187692
188439
  return true;
187693
188440
  }
187694
188441
  try {
187695
- return fs14.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
188442
+ return fs16.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
187696
188443
  } catch {
187697
188444
  return false;
187698
188445
  }
@@ -187760,14 +188507,14 @@ var wslDrivesMountPoint = /* @__PURE__ */ (() => {
187760
188507
  const configFilePath = "/etc/wsl.conf";
187761
188508
  let isConfigFileExists = false;
187762
188509
  try {
187763
- await fs15.access(configFilePath, fsConstants.F_OK);
188510
+ await fs17.access(configFilePath, fsConstants.F_OK);
187764
188511
  isConfigFileExists = true;
187765
188512
  } catch {
187766
188513
  }
187767
188514
  if (!isConfigFileExists) {
187768
188515
  return defaultMountPoint;
187769
188516
  }
187770
- const configContent = await fs15.readFile(configFilePath, { encoding: "utf8" });
188517
+ const configContent = await fs17.readFile(configFilePath, { encoding: "utf8" });
187771
188518
  const parsedMountPoint = parseMountPointFromConfig(configContent);
187772
188519
  if (parsedMountPoint === void 0) {
187773
188520
  return defaultMountPoint;
@@ -187787,7 +188534,7 @@ var canAccessPowerShell = async () => {
187787
188534
  canAccessPowerShellPromise ??= (async () => {
187788
188535
  try {
187789
188536
  const psPath = await powerShellPath2();
187790
- await fs15.access(psPath, fsConstants.X_OK);
188537
+ await fs17.access(psPath, fsConstants.X_OK);
187791
188538
  return true;
187792
188539
  } catch {
187793
188540
  return false;
@@ -187801,15 +188548,15 @@ var wslDefaultBrowser = async () => {
187801
188548
  const { stdout } = await executePowerShell(command, { powerShellPath: psPath });
187802
188549
  return stdout.trim();
187803
188550
  };
187804
- var convertWslPathToWindows = async (path20) => {
187805
- if (/^[a-z]+:\/\//i.test(path20)) {
187806
- return path20;
188551
+ var convertWslPathToWindows = async (path22) => {
188552
+ if (/^[a-z]+:\/\//i.test(path22)) {
188553
+ return path22;
187807
188554
  }
187808
188555
  try {
187809
- const { stdout } = await execFile2("wslpath", ["-aw", path20], { encoding: "utf8" });
188556
+ const { stdout } = await execFile2("wslpath", ["-aw", path22], { encoding: "utf8" });
187810
188557
  return stdout.trim();
187811
188558
  } catch {
187812
- return path20;
188559
+ return path22;
187813
188560
  }
187814
188561
  };
187815
188562
 
@@ -187951,8 +188698,8 @@ var is_in_ssh_default = isInSsh;
187951
188698
 
187952
188699
  // ../../node_modules/open/index.js
187953
188700
  var fallbackAttemptSymbol = Symbol("fallbackAttempt");
187954
- var __dirname3 = import.meta.url ? path13.dirname(fileURLToPath3(import.meta.url)) : "";
187955
- var localXdgOpenPath = path13.join(__dirname3, "xdg-open");
188701
+ var __dirname3 = import.meta.url ? path16.dirname(fileURLToPath3(import.meta.url)) : "";
188702
+ var localXdgOpenPath = path16.join(__dirname3, "xdg-open");
187956
188703
  var { platform: platform4, arch: arch2 } = process8;
187957
188704
  var tryEachApp = async (apps2, opener) => {
187958
188705
  if (apps2.length === 0) {
@@ -188100,7 +188847,7 @@ var baseOpen = async (options2) => {
188100
188847
  const isBundled = !__dirname3 || __dirname3 === "/";
188101
188848
  let exeLocalXdgOpen = false;
188102
188849
  try {
188103
- await fs16.access(localXdgOpenPath, fsConstants2.X_OK);
188850
+ await fs18.access(localXdgOpenPath, fsConstants2.X_OK);
188104
188851
  exeLocalXdgOpen = true;
188105
188852
  } catch {
188106
188853
  }
@@ -188123,19 +188870,19 @@ var baseOpen = async (options2) => {
188123
188870
  }
188124
188871
  const subprocess = childProcess3.spawn(command, cliArguments, childProcessOptions);
188125
188872
  if (options2.wait) {
188126
- return new Promise((resolve4, reject) => {
188873
+ return new Promise((resolve5, reject) => {
188127
188874
  subprocess.once("error", reject);
188128
188875
  subprocess.once("close", (exitCode) => {
188129
188876
  if (!options2.allowNonzeroExitCode && exitCode !== 0) {
188130
188877
  reject(new Error(`Exited with code ${exitCode}`));
188131
188878
  return;
188132
188879
  }
188133
- resolve4(subprocess);
188880
+ resolve5(subprocess);
188134
188881
  });
188135
188882
  });
188136
188883
  }
188137
188884
  if (isFallbackAttempt) {
188138
- return new Promise((resolve4, reject) => {
188885
+ return new Promise((resolve5, reject) => {
188139
188886
  subprocess.once("error", reject);
188140
188887
  subprocess.once("spawn", () => {
188141
188888
  subprocess.once("close", (exitCode) => {
@@ -188145,17 +188892,17 @@ var baseOpen = async (options2) => {
188145
188892
  return;
188146
188893
  }
188147
188894
  subprocess.unref();
188148
- resolve4(subprocess);
188895
+ resolve5(subprocess);
188149
188896
  });
188150
188897
  });
188151
188898
  });
188152
188899
  }
188153
188900
  subprocess.unref();
188154
- return new Promise((resolve4, reject) => {
188901
+ return new Promise((resolve5, reject) => {
188155
188902
  subprocess.once("error", reject);
188156
188903
  subprocess.once("spawn", () => {
188157
188904
  subprocess.off("error", reject);
188158
- resolve4(subprocess);
188905
+ resolve5(subprocess);
188159
188906
  });
188160
188907
  });
188161
188908
  };
@@ -188232,35 +188979,35 @@ defineLazyProperty(apps, "safari", () => detectPlatformBinary({
188232
188979
  var open_default = open2;
188233
188980
 
188234
188981
  // src/commands/deploy.tsx
188235
- import * as fs20 from "fs";
188236
- import * as path17 from "path";
188982
+ import * as fs21 from "fs";
188983
+ import * as path19 from "path";
188237
188984
 
188238
188985
  // src/lib/deploy/build-tester.ts
188239
188986
  import { spawn as spawn5 } from "child_process";
188240
- import { existsSync as existsSync14 } from "fs";
188241
- import { join as join15 } from "path";
188987
+ import { existsSync as existsSync16 } from "fs";
188988
+ import { join as join17 } from "path";
188242
188989
  function getDependencyInstallCommand(build, projectDir) {
188243
188990
  switch (build.base) {
188244
188991
  case "node":
188245
- if (existsSync14(join15(projectDir, "pnpm-lock.yaml"))) {
188992
+ if (existsSync16(join17(projectDir, "pnpm-lock.yaml"))) {
188246
188993
  return "pnpm install --frozen-lockfile";
188247
- } else if (existsSync14(join15(projectDir, "yarn.lock"))) {
188994
+ } else if (existsSync16(join17(projectDir, "yarn.lock"))) {
188248
188995
  return "yarn install --frozen-lockfile";
188249
- } else if (existsSync14(join15(projectDir, "package-lock.json"))) {
188996
+ } else if (existsSync16(join17(projectDir, "package-lock.json"))) {
188250
188997
  return "npm ci";
188251
188998
  } else {
188252
188999
  return "npm install";
188253
189000
  }
188254
189001
  case "python":
188255
- if (existsSync14(join15(projectDir, "poetry.lock"))) {
189002
+ if (existsSync16(join17(projectDir, "poetry.lock"))) {
188256
189003
  return "poetry install --no-interaction";
188257
- } else if (existsSync14(join15(projectDir, "Pipfile.lock"))) {
189004
+ } else if (existsSync16(join17(projectDir, "Pipfile.lock"))) {
188258
189005
  return "pipenv install --deploy";
188259
- } else if (existsSync14(join15(projectDir, "Pipfile"))) {
189006
+ } else if (existsSync16(join17(projectDir, "Pipfile"))) {
188260
189007
  return "pipenv install";
188261
- } else if (existsSync14(join15(projectDir, "pyproject.toml"))) {
189008
+ } else if (existsSync16(join17(projectDir, "pyproject.toml"))) {
188262
189009
  return "pip install .";
188263
- } else if (existsSync14(join15(projectDir, "requirements.txt"))) {
189010
+ } else if (existsSync16(join17(projectDir, "requirements.txt"))) {
188264
189011
  return "pip install -r requirements.txt";
188265
189012
  }
188266
189013
  return null;
@@ -188274,7 +189021,7 @@ function getDependencyInstallCommand(build, projectDir) {
188274
189021
  }
188275
189022
  }
188276
189023
  function runCommand2(command, projectDir, buildName) {
188277
- return new Promise((resolve4) => {
189024
+ return new Promise((resolve5) => {
188278
189025
  const stdout = [];
188279
189026
  const stderr = [];
188280
189027
  writeLog("build-test", `[${buildName}] Running: ${command}`);
@@ -188304,7 +189051,7 @@ function runCommand2(command, projectDir, buildName) {
188304
189051
  });
188305
189052
  child.on("error", (err) => {
188306
189053
  writeLog("build-test:error", `[${buildName}] Failed to start: ${err.message}`);
188307
- resolve4({
189054
+ resolve5({
188308
189055
  success: false,
188309
189056
  output: `Failed to start command: ${err.message}`
188310
189057
  });
@@ -188313,10 +189060,10 @@ function runCommand2(command, projectDir, buildName) {
188313
189060
  const output = [...stdout, ...stderr].join("");
188314
189061
  if (code === 0) {
188315
189062
  writeLog("build-test", `[${buildName}] Command succeeded (exit code 0)`);
188316
- resolve4({ success: true, output });
189063
+ resolve5({ success: true, output });
188317
189064
  } else {
188318
189065
  writeLog("build-test:error", `[${buildName}] Command failed with exit code ${code}`);
188319
- resolve4({
189066
+ resolve5({
188320
189067
  success: false,
188321
189068
  output: output || `Exit code: ${code}`
188322
189069
  });
@@ -188652,16 +189399,44 @@ var ApiClient = class {
188652
189399
  }
188653
189400
  return response.json();
188654
189401
  }
189402
+ async getMe(signal) {
189403
+ const url = `${this.baseUrl}/users/me`;
189404
+ writeLog("api", `GET ${url}`);
189405
+ const response = await fetch(url, {
189406
+ headers: this.authHeaders(),
189407
+ signal
189408
+ });
189409
+ writeLog("api", `Response: ${response.status} ${response.statusText}`);
189410
+ if (!response.ok) {
189411
+ let errorBody;
189412
+ try {
189413
+ const error = await response.json();
189414
+ errorBody = JSON.stringify(error);
189415
+ writeLog("api:error", `API error: ${error.error} (${error.code})`);
189416
+ writeLog("api:error", `Request was: GET ${url}`);
189417
+ writeLog("api:error", `Response body: ${errorBody}`);
189418
+ throw new Error(`Failed to get user: ${error.error} (${error.code})`);
189419
+ } catch (e) {
189420
+ if (e instanceof Error && e.message.startsWith("Failed to get user")) {
189421
+ throw e;
189422
+ }
189423
+ errorBody = await response.text();
189424
+ writeLog("api:error", `Failed to parse error response: ${errorBody}`);
189425
+ throw new Error(`Failed to get user: ${response.statusText}`);
189426
+ }
189427
+ }
189428
+ return response.json();
189429
+ }
188655
189430
  };
188656
189431
 
188657
189432
  // src/lib/tarball/create.ts
188658
- import { execSync as execSync2 } from "child_process";
188659
- import * as fs17 from "fs";
188660
- import * as path14 from "path";
189433
+ import { execSync as execSync3 } from "child_process";
189434
+ import * as fs19 from "fs";
189435
+ import * as path17 from "path";
188661
189436
  import { createTarPacker, createEntryItemGenerator } from "tar-vern";
188662
189437
  function isInsideGitRepository(dir) {
188663
189438
  try {
188664
- const result = execSync2("git rev-parse --is-inside-work-tree", {
189439
+ const result = execSync3("git rev-parse --is-inside-work-tree", {
188665
189440
  cwd: dir,
188666
189441
  encoding: "utf-8",
188667
189442
  stdio: ["pipe", "pipe", "pipe"]
@@ -188671,50 +189446,42 @@ function isInsideGitRepository(dir) {
188671
189446
  return false;
188672
189447
  }
188673
189448
  }
188674
- function getGitInfo(projectDir) {
188675
- const gitRoot = execSync2("git rev-parse --show-toplevel", {
188676
- cwd: projectDir,
188677
- encoding: "utf-8"
188678
- }).trim();
188679
- const relativePath = path14.relative(gitRoot, projectDir);
188680
- return { gitRoot, relativePath: relativePath || "." };
188681
- }
188682
- function createGitArchive(projectDir) {
188683
- writeLog("tarball", "Creating tarball using git archive");
188684
- const { gitRoot, relativePath } = getGitInfo(projectDir);
188685
- const isSubdirectory = relativePath !== ".";
188686
- writeLog(
188687
- "tarball",
188688
- isSubdirectory ? `Project is in subdirectory: ${relativePath}` : "Project is at git root"
188689
- );
188690
- const stashResult = execSync2("git stash create --include-untracked", {
188691
- cwd: gitRoot,
188692
- encoding: "utf-8"
188693
- }).trim();
188694
- const treeish = stashResult || "HEAD";
188695
- writeLog(
188696
- "tarball",
188697
- `Archiving from: ${treeish === "HEAD" ? "HEAD (no uncommitted changes)" : "stash (includes uncommitted changes)"}`
188698
- );
188699
- const archiveTarget = isSubdirectory ? `${treeish}:${relativePath}` : treeish;
188700
- const tarball = execSync2(
188701
- `git archive --format=tar.gz ${archiveTarget} -- . ":!.specific"`,
189449
+ async function createGitArchive(projectDir) {
189450
+ writeLog("tarball", "Creating tarball using git ls-files");
189451
+ const filesOutput = execSync3(
189452
+ "git ls-files --cached --others --exclude-standard",
188702
189453
  {
188703
- cwd: gitRoot,
189454
+ cwd: projectDir,
189455
+ encoding: "utf-8",
188704
189456
  maxBuffer: 100 * 1024 * 1024
188705
189457
  // 100MB max
188706
189458
  }
188707
189459
  );
188708
- writeLog("tarball", `Git archive created: ${tarball.length} bytes`);
189460
+ const allFiles = filesOutput.trim().split("\n").filter(Boolean);
189461
+ const filteredFiles = allFiles.filter(
189462
+ (file) => !file.startsWith(".specific/") && file !== ".specific"
189463
+ );
189464
+ writeLog(
189465
+ "tarball",
189466
+ `Found ${allFiles.length} files, ${filteredFiles.length} after filtering .specific`
189467
+ );
189468
+ const entryGenerator = createEntryItemGenerator(projectDir, filteredFiles);
189469
+ const tarStream = createTarPacker(entryGenerator, "gzip");
189470
+ const chunks = [];
189471
+ for await (const chunk of tarStream) {
189472
+ chunks.push(Buffer.from(chunk));
189473
+ }
189474
+ const tarball = Buffer.concat(chunks);
189475
+ writeLog("tarball", `Tarball created: ${tarball.length} bytes`);
188709
189476
  return tarball;
188710
189477
  }
188711
189478
  var EXCLUDED_DIRS = [".specific"];
188712
189479
  async function collectPaths(baseDir, currentDir, exclude) {
188713
189480
  const results = [];
188714
- const entries = await fs17.promises.readdir(currentDir, { withFileTypes: true });
189481
+ const entries = await fs19.promises.readdir(currentDir, { withFileTypes: true });
188715
189482
  for (const entry of entries) {
188716
- const fullPath = path14.join(currentDir, entry.name);
188717
- const relativePath = path14.relative(baseDir, fullPath);
189483
+ const fullPath = path17.join(currentDir, entry.name);
189484
+ const relativePath = path17.relative(baseDir, fullPath);
188718
189485
  if (entry.isDirectory()) {
188719
189486
  if (!exclude.includes(entry.name)) {
188720
189487
  results.push(relativePath);
@@ -188729,8 +189496,8 @@ async function collectPaths(baseDir, currentDir, exclude) {
188729
189496
  }
188730
189497
  async function createTarArchive(projectDir) {
188731
189498
  writeLog("tarball", "Creating tarball using tar-vern (non-git project)");
188732
- const configPath = path14.join(projectDir, "specific.hcl");
188733
- if (!fs17.existsSync(configPath)) {
189499
+ const configPath = path17.join(projectDir, "specific.hcl");
189500
+ if (!fs19.existsSync(configPath)) {
188734
189501
  throw new Error("specific.hcl not found in project directory");
188735
189502
  }
188736
189503
  const relativePaths = await collectPaths(projectDir, projectDir, EXCLUDED_DIRS);
@@ -188752,49 +189519,10 @@ async function createProjectTarball(projectDir) {
188752
189519
  return createTarArchive(projectDir);
188753
189520
  }
188754
189521
 
188755
- // src/lib/project/config.ts
188756
- import * as fs18 from "fs";
188757
- import * as path15 from "path";
188758
- var PROJECT_ID_FILE = ".specific/project_id";
188759
- var ProjectNotLinkedError = class extends Error {
188760
- constructor() {
188761
- super(
188762
- `Project not linked to Specific.
188763
-
188764
- The file ${PROJECT_ID_FILE} does not exist.
188765
-
188766
- Run: specific deploy`
188767
- );
188768
- this.name = "ProjectNotLinkedError";
188769
- }
188770
- };
188771
- function readProjectId(projectDir = process.cwd()) {
188772
- const projectIdPath = path15.join(projectDir, PROJECT_ID_FILE);
188773
- if (!fs18.existsSync(projectIdPath)) {
188774
- throw new ProjectNotLinkedError();
188775
- }
188776
- const projectId = fs18.readFileSync(projectIdPath, "utf-8").trim();
188777
- if (!projectId) {
188778
- throw new Error(`${PROJECT_ID_FILE} is empty`);
188779
- }
188780
- return projectId;
188781
- }
188782
- function hasProjectId(projectDir = process.cwd()) {
188783
- const projectIdPath = path15.join(projectDir, PROJECT_ID_FILE);
188784
- return fs18.existsSync(projectIdPath);
188785
- }
188786
- function writeProjectId(projectId, projectDir = process.cwd()) {
188787
- const specificDir = path15.join(projectDir, ".specific");
188788
- if (!fs18.existsSync(specificDir)) {
188789
- fs18.mkdirSync(specificDir, { recursive: true });
188790
- }
188791
- fs18.writeFileSync(path15.join(specificDir, "project_id"), projectId + "\n");
188792
- }
188793
-
188794
189522
  // src/lib/auth/credentials.ts
188795
- import * as fs19 from "fs";
188796
- import * as path16 from "path";
188797
- import * as os5 from "os";
189523
+ import * as fs20 from "fs";
189524
+ import * as path18 from "path";
189525
+ import * as os7 from "os";
188798
189526
 
188799
189527
  // src/lib/auth/errors.ts
188800
189528
  var RefreshTokenExpiredError = class extends Error {
@@ -188871,7 +189599,7 @@ import React4, { useState as useState4, useEffect as useEffect4 } from "react";
188871
189599
  import { render as render4, Box as Box4, Text as Text4, useApp as useApp3 } from "ink";
188872
189600
  import Spinner4 from "ink-spinner";
188873
189601
  function performLogin(options2 = {}) {
188874
- return new Promise((resolve4) => {
189602
+ return new Promise((resolve5) => {
188875
189603
  const instance = render4(
188876
189604
  /* @__PURE__ */ React4.createElement(
188877
189605
  LoginFlow,
@@ -188879,7 +189607,7 @@ function performLogin(options2 = {}) {
188879
189607
  isReauthentication: options2.isReauthentication,
188880
189608
  onComplete: (result) => {
188881
189609
  instance.unmount();
188882
- resolve4(result);
189610
+ resolve5(result);
188883
189611
  }
188884
189612
  }
188885
189613
  )
@@ -188933,10 +189661,14 @@ function LoginFlow({ isReauthentication, onComplete }) {
188933
189661
  }
188934
189662
  } else {
188935
189663
  const successResponse = response;
189664
+ const expiresAt2 = Date.now() + successResponse.expires_in * 1e3;
189665
+ const client2 = new ApiClient(successResponse.access_token);
189666
+ const user = await client2.getMe();
188936
189667
  writeUserCredentials({
188937
189668
  accessToken: successResponse.access_token,
188938
189669
  refreshToken: successResponse.refresh_token,
188939
- expiresAt: Date.now() + successResponse.expires_in * 1e3
189670
+ expiresAt: expiresAt2,
189671
+ userId: user.id
188940
189672
  });
188941
189673
  setUserEmail(successResponse.user.email);
188942
189674
  setPhase("success");
@@ -188985,18 +189717,18 @@ function LoginFlow({ isReauthentication, onComplete }) {
188985
189717
 
188986
189718
  // src/lib/auth/credentials.ts
188987
189719
  function getUserCredentialsDir() {
188988
- return path16.join(os5.homedir(), ".specific");
189720
+ return path18.join(os7.homedir(), ".specific");
188989
189721
  }
188990
189722
  function getCredentialsPath() {
188991
- return path16.join(getUserCredentialsDir(), "credentials.json");
189723
+ return path18.join(getUserCredentialsDir(), "credentials.json");
188992
189724
  }
188993
189725
  function readUserCredentials() {
188994
189726
  const credentialsPath = getCredentialsPath();
188995
- if (!fs19.existsSync(credentialsPath)) {
189727
+ if (!fs20.existsSync(credentialsPath)) {
188996
189728
  return null;
188997
189729
  }
188998
189730
  try {
188999
- const content = fs19.readFileSync(credentialsPath, "utf-8");
189731
+ const content = fs20.readFileSync(credentialsPath, "utf-8");
189000
189732
  return JSON.parse(content);
189001
189733
  } catch {
189002
189734
  return null;
@@ -189004,18 +189736,18 @@ function readUserCredentials() {
189004
189736
  }
189005
189737
  function writeUserCredentials(credentials) {
189006
189738
  const dir = getUserCredentialsDir();
189007
- if (!fs19.existsSync(dir)) {
189008
- fs19.mkdirSync(dir, { recursive: true, mode: 448 });
189739
+ if (!fs20.existsSync(dir)) {
189740
+ fs20.mkdirSync(dir, { recursive: true, mode: 448 });
189009
189741
  }
189010
189742
  const credentialsPath = getCredentialsPath();
189011
- fs19.writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2), {
189743
+ fs20.writeFileSync(credentialsPath, JSON.stringify(credentials, null, 2), {
189012
189744
  mode: 384
189013
189745
  });
189014
189746
  }
189015
189747
  function clearUserCredentials() {
189016
189748
  const credentialsPath = getCredentialsPath();
189017
- if (fs19.existsSync(credentialsPath)) {
189018
- fs19.unlinkSync(credentialsPath);
189749
+ if (fs20.existsSync(credentialsPath)) {
189750
+ fs20.unlinkSync(credentialsPath);
189019
189751
  }
189020
189752
  }
189021
189753
  function isLoggedIn() {
@@ -189272,8 +190004,8 @@ function DeployUI({ environment, config, skipBuildTest }) {
189272
190004
  async function loadProjects() {
189273
190005
  try {
189274
190006
  const token = await getValidAccessToken();
189275
- const client = new ApiClient(token);
189276
- const { projects: projects2 } = await client.listProjects();
190007
+ const client2 = new ApiClient(token);
190008
+ const { projects: projects2 } = await client2.listProjects();
189277
190009
  if (cancelled) return;
189278
190010
  setState({
189279
190011
  phase: "selecting-project",
@@ -189319,8 +190051,8 @@ function DeployUI({ environment, config, skipBuildTest }) {
189319
190051
  async function createProject() {
189320
190052
  try {
189321
190053
  const token = await getValidAccessToken();
189322
- const client = new ApiClient(token);
189323
- const project = await client.createProject(state.newProjectName);
190054
+ const client2 = new ApiClient(token);
190055
+ const project = await client2.createProject(state.newProjectName);
189324
190056
  if (cancelled) return;
189325
190057
  writeProjectId(project.id);
189326
190058
  setState({
@@ -189413,15 +190145,15 @@ function DeployUI({ environment, config, skipBuildTest }) {
189413
190145
  (secret) => secretValues[secret]
189414
190146
  );
189415
190147
  if (!allSecretsCollected) return;
189416
- const client = clientRef.current;
189417
- if (!client) return;
190148
+ const client2 = clientRef.current;
190149
+ if (!client2) return;
189418
190150
  (async () => {
189419
190151
  try {
189420
190152
  writeLog(
189421
190153
  "deploy",
189422
190154
  `Submitting secrets: ${Object.keys(secretValues).join(", ")}`
189423
190155
  );
189424
- await client.submitSecrets(deployment2.id, secretValues);
190156
+ await client2.submitSecrets(deployment2.id, secretValues);
189425
190157
  writeLog("deploy", "Secrets submitted successfully");
189426
190158
  setState((s) => ({
189427
190159
  ...s,
@@ -189455,15 +190187,15 @@ function DeployUI({ environment, config, skipBuildTest }) {
189455
190187
  (config2) => configValues[config2]
189456
190188
  );
189457
190189
  if (!allConfigsCollected) return;
189458
- const client = clientRef.current;
189459
- if (!client) return;
190190
+ const client2 = clientRef.current;
190191
+ if (!client2) return;
189460
190192
  (async () => {
189461
190193
  try {
189462
190194
  writeLog(
189463
190195
  "deploy",
189464
190196
  `Submitting configs: ${Object.keys(configValues).join(", ")}`
189465
190197
  );
189466
- await client.submitConfigs(deployment2.id, configValues);
190198
+ await client2.submitConfigs(deployment2.id, configValues);
189467
190199
  writeLog("deploy", "Configs submitted successfully");
189468
190200
  setState((s) => ({
189469
190201
  ...s,
@@ -189535,8 +190267,8 @@ ${errorMsg}`
189535
190267
  writeLog("deploy", `Starting deployment to "${environment}"`);
189536
190268
  writeLog("deploy", `Project directory: ${projectDir}`);
189537
190269
  const authToken = await getValidAccessToken();
189538
- const client = new ApiClient(authToken);
189539
- clientRef.current = client;
190270
+ const client2 = new ApiClient(authToken);
190271
+ clientRef.current = client2;
189540
190272
  if (cancelled) return;
189541
190273
  let tarball;
189542
190274
  try {
@@ -189561,7 +190293,7 @@ ${errorMsg}`
189561
190293
  let deployment2;
189562
190294
  try {
189563
190295
  writeLog("deploy", `Creating deployment for project ${state.projectId}`);
189564
- deployment2 = await client.createDeployment(state.projectId, environment);
190296
+ deployment2 = await client2.createDeployment(state.projectId, environment);
189565
190297
  writeLog("deploy", `Deployment created: ${deployment2.id}`);
189566
190298
  } catch (err) {
189567
190299
  const errorMsg = `Failed to create deployment: ${err instanceof Error ? err.message : String(err)}`;
@@ -189576,7 +190308,7 @@ ${errorMsg}`
189576
190308
  setState((s) => ({ ...s, phase: "uploading", deployment: deployment2 }));
189577
190309
  try {
189578
190310
  writeLog("deploy", `Uploading tarball (${tarball.length} bytes)`);
189579
- deployment2 = await client.uploadTarball(deployment2.id, tarball);
190311
+ deployment2 = await client2.uploadTarball(deployment2.id, tarball);
189580
190312
  writeLog("deploy", "Tarball uploaded successfully");
189581
190313
  } catch (err) {
189582
190314
  const errorMsg = `Failed to upload tarball: ${err instanceof Error ? err.message : String(err)}`;
@@ -189595,7 +190327,7 @@ ${errorMsg}`
189595
190327
  let lastState;
189596
190328
  const pollForCompletion = async () => {
189597
190329
  try {
189598
- const status = await client.getDeployment(deployment2.id);
190330
+ const status = await client2.getDeployment(deployment2.id);
189599
190331
  if (cancelled) return;
189600
190332
  if (status.state !== lastState) {
189601
190333
  writeLog(
@@ -189681,11 +190413,11 @@ ${errorMsg}`
189681
190413
  useEffect5(() => {
189682
190414
  let pollInterval;
189683
190415
  if ((state.phase === "building" || state.phase === "deploying") && state.deployment && state.missingSecrets === void 0 && state.secretValues === void 0 && state.missingConfigs === void 0 && state.configValues === void 0) {
189684
- const client = clientRef.current;
189685
- if (!client) return;
190416
+ const client2 = clientRef.current;
190417
+ if (!client2) return;
189686
190418
  const pollForCompletion = async () => {
189687
190419
  try {
189688
- const status = await client.getDeployment(state.deployment.id);
190420
+ const status = await client2.getDeployment(state.deployment.id);
189689
190421
  if (status.state === "failed") {
189690
190422
  setState((s) => ({
189691
190423
  ...s,
@@ -189717,12 +190449,24 @@ ${errorMsg}`
189717
190449
  }
189718
190450
  }, [state.phase, state.missingSecrets, state.secretValues, state.missingConfigs, state.configValues]);
189719
190451
  useEffect5(() => {
189720
- if (state.phase === "success" || state.phase === "error") {
190452
+ if (state.phase === "testing-builds") {
190453
+ trackEvent("deploy_started", { environment });
190454
+ }
190455
+ }, [state.phase, environment]);
190456
+ useEffect5(() => {
190457
+ if (state.phase === "success") {
190458
+ trackEvent("deploy_succeeded", { environment });
189721
190459
  closeDebugLog();
189722
190460
  const timer = setTimeout(() => exit(), 100);
189723
190461
  return () => clearTimeout(timer);
189724
190462
  }
189725
- }, [state.phase, exit]);
190463
+ if (state.phase === "error") {
190464
+ trackEvent("deploy_failed", { environment });
190465
+ closeDebugLog();
190466
+ const timer = setTimeout(() => exit(), 100);
190467
+ return () => clearTimeout(timer);
190468
+ }
190469
+ }, [state.phase, exit, environment]);
189726
190470
  const {
189727
190471
  phase,
189728
190472
  deployment,
@@ -189837,14 +190581,14 @@ ${errorMsg}`
189837
190581
  ), phase === "error" && /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, { color: "red" }, "Error: ", error)), phase === "success" && /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Text5, { color: "green" }, "Deployment successful!"), deployment?.publicUrls && Object.keys(deployment.publicUrls).length > 0 && /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Text5, { bold: true }, "Public URLs:"), Object.entries(deployment.publicUrls).map(([name, url]) => /* @__PURE__ */ React5.createElement(Text5, { key: name }, " ", name, ": ", /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, url))))));
189838
190582
  }
189839
190583
  async function deployCommand(environment, options2) {
189840
- const configPath = path17.join(process.cwd(), "specific.hcl");
189841
- if (!fs20.existsSync(configPath)) {
190584
+ const configPath = path19.join(process.cwd(), "specific.hcl");
190585
+ if (!fs21.existsSync(configPath)) {
189842
190586
  console.error("Error: No specific.hcl found in current directory");
189843
190587
  process.exit(1);
189844
190588
  }
189845
190589
  let config;
189846
190590
  try {
189847
- const hcl = fs20.readFileSync(configPath, "utf-8");
190591
+ const hcl = fs21.readFileSync(configPath, "utf-8");
189848
190592
  config = await parseConfig(hcl);
189849
190593
  } catch (err) {
189850
190594
  console.error(
@@ -189868,9 +190612,9 @@ async function deployCommand(environment, options2) {
189868
190612
 
189869
190613
  // src/commands/exec.tsx
189870
190614
  import { spawn as spawn6 } from "child_process";
189871
- import * as fs21 from "fs";
189872
- import * as path18 from "path";
189873
- async function execCommand(serviceName, command) {
190615
+ import * as fs22 from "fs";
190616
+ import * as path20 from "path";
190617
+ async function execCommand(serviceName, command, instanceKey = "default") {
189874
190618
  if (command.length === 0) {
189875
190619
  console.error(
189876
190620
  "Error: No command provided. Usage: specific exec <service> -- <command>"
@@ -189897,14 +190641,14 @@ async function execCommand(serviceName, command) {
189897
190641
  }
189898
190642
  }
189899
190643
  };
189900
- const configPath = path18.join(process.cwd(), "specific.hcl");
189901
- if (!fs21.existsSync(configPath)) {
190644
+ const configPath = path20.join(process.cwd(), "specific.hcl");
190645
+ if (!fs22.existsSync(configPath)) {
189902
190646
  console.error("Error: No specific.hcl found in current directory");
189903
190647
  process.exit(1);
189904
190648
  }
189905
190649
  let config;
189906
190650
  try {
189907
- const hcl = fs21.readFileSync(configPath, "utf-8");
190651
+ const hcl = fs22.readFileSync(configPath, "utf-8");
189908
190652
  config = await parseConfig(hcl);
189909
190653
  } catch (err) {
189910
190654
  console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
@@ -189921,7 +190665,7 @@ async function execCommand(serviceName, command) {
189921
190665
  const required = findRequiredResources(service);
189922
190666
  let resources = /* @__PURE__ */ new Map();
189923
190667
  const hasRequiredResources = required.postgres.length > 0 || required.redis.length > 0 || required.storage.length > 0;
189924
- stateManager = new InstanceStateManager(process.cwd());
190668
+ stateManager = new InstanceStateManager(process.cwd(), instanceKey);
189925
190669
  await stateManager.cleanStaleState();
189926
190670
  const existingInstances = await stateManager.getExistingInstances();
189927
190671
  if (hasRequiredResources) {
@@ -189976,7 +190720,7 @@ async function execCommand(serviceName, command) {
189976
190720
  storage: required.storage
189977
190721
  },
189978
190722
  stateManager,
189979
- dataDir: ".specific/data",
190723
+ dataDir: `.specific/keys/${instanceKey}/data`,
189980
190724
  portAllocator: new PortAllocator(),
189981
190725
  callbacks: {
189982
190726
  log: (msg) => console.error(msg)
@@ -190041,8 +190785,8 @@ async function execCommand(serviceName, command) {
190041
190785
 
190042
190786
  // src/commands/psql.tsx
190043
190787
  import { spawn as spawn7 } from "child_process";
190044
- async function psqlCommand(databaseName) {
190045
- const stateManager = new InstanceStateManager(process.cwd());
190788
+ async function psqlCommand(databaseName, instanceKey = "default") {
190789
+ const stateManager = new InstanceStateManager(process.cwd(), instanceKey);
190046
190790
  await stateManager.cleanStaleState();
190047
190791
  const existingInstances = await stateManager.getExistingInstances();
190048
190792
  if (!existingInstances) {
@@ -190098,41 +190842,85 @@ async function psqlCommand(databaseName) {
190098
190842
  import React6, { useState as useState6, useEffect as useEffect6 } from "react";
190099
190843
  import { render as render6, Text as Text6, Box as Box6 } from "ink";
190100
190844
  import Spinner6 from "ink-spinner";
190101
- import * as fs22 from "fs";
190102
- import * as path19 from "path";
190103
- function CleanUI() {
190845
+ import * as fs23 from "fs";
190846
+ import * as path21 from "path";
190847
+ function CleanUI({ instanceKey }) {
190104
190848
  const [state, setState] = useState6({ status: "checking" });
190105
190849
  useEffect6(() => {
190106
190850
  async function clean() {
190107
190851
  const projectRoot = process.cwd();
190108
- const specificDir = path19.join(projectRoot, ".specific");
190109
- if (!fs22.existsSync(specificDir)) {
190852
+ const specificDir = path21.join(projectRoot, ".specific");
190853
+ if (!fs23.existsSync(specificDir)) {
190110
190854
  setState({ status: "nothing" });
190111
190855
  return;
190112
190856
  }
190113
- const stateManager = new InstanceStateManager(projectRoot);
190114
- const existingInstances = await stateManager.getExistingInstances();
190115
- if (existingInstances) {
190116
- setState({
190117
- status: "error",
190118
- error: `Cannot clean while 'specific dev' is running (PID ${existingInstances.owner.pid}). Please stop it first.`
190119
- });
190120
- return;
190121
- }
190122
- await stateManager.cleanStaleState();
190123
- setState({ status: "cleaning" });
190124
- try {
190125
- fs22.rmSync(specificDir, { recursive: true, force: true });
190126
- setState({ status: "success" });
190127
- } catch (err) {
190128
- setState({
190129
- status: "error",
190130
- error: err instanceof Error ? err.message : String(err)
190131
- });
190857
+ if (instanceKey) {
190858
+ const keyDir = path21.join(specificDir, "keys", instanceKey);
190859
+ if (!fs23.existsSync(keyDir)) {
190860
+ setState({ status: "nothing" });
190861
+ return;
190862
+ }
190863
+ const stateManager = new InstanceStateManager(projectRoot, instanceKey);
190864
+ const existingInstances = await stateManager.getExistingInstances();
190865
+ if (existingInstances) {
190866
+ setState({
190867
+ status: "error",
190868
+ error: `Cannot clean while 'specific dev' is running for key "${instanceKey}" (PID ${existingInstances.owner.pid}). Please stop it first.`
190869
+ });
190870
+ return;
190871
+ }
190872
+ await stateManager.cleanStaleState();
190873
+ setState({ status: "cleaning" });
190874
+ try {
190875
+ fs23.rmSync(keyDir, { recursive: true, force: true });
190876
+ setState({ status: "success" });
190877
+ } catch (err) {
190878
+ setState({
190879
+ status: "error",
190880
+ error: err instanceof Error ? err.message : String(err)
190881
+ });
190882
+ }
190883
+ } else {
190884
+ const keysDir = path21.join(specificDir, "keys");
190885
+ if (fs23.existsSync(keysDir)) {
190886
+ const keys = fs23.readdirSync(keysDir).filter(
190887
+ (f) => fs23.statSync(path21.join(keysDir, f)).isDirectory()
190888
+ );
190889
+ for (const key of keys) {
190890
+ const stateManager2 = new InstanceStateManager(projectRoot, key);
190891
+ const existingInstances2 = await stateManager2.getExistingInstances();
190892
+ if (existingInstances2) {
190893
+ setState({
190894
+ status: "error",
190895
+ error: `Cannot clean while 'specific dev' is running for key "${key}" (PID ${existingInstances2.owner.pid}). Please stop it first.`
190896
+ });
190897
+ return;
190898
+ }
190899
+ }
190900
+ }
190901
+ const stateManager = new InstanceStateManager(projectRoot);
190902
+ const existingInstances = await stateManager.getExistingInstances();
190903
+ if (existingInstances) {
190904
+ setState({
190905
+ status: "error",
190906
+ error: `Cannot clean while 'specific dev' is running (PID ${existingInstances.owner.pid}). Please stop it first.`
190907
+ });
190908
+ return;
190909
+ }
190910
+ setState({ status: "cleaning" });
190911
+ try {
190912
+ fs23.rmSync(specificDir, { recursive: true, force: true });
190913
+ setState({ status: "success" });
190914
+ } catch (err) {
190915
+ setState({
190916
+ status: "error",
190917
+ error: err instanceof Error ? err.message : String(err)
190918
+ });
190919
+ }
190132
190920
  }
190133
190921
  }
190134
190922
  clean();
190135
- }, []);
190923
+ }, [instanceKey]);
190136
190924
  if (state.status === "checking") {
190137
190925
  return /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, /* @__PURE__ */ React6.createElement(Spinner6, { type: "dots" })), /* @__PURE__ */ React6.createElement(Text6, null, " Checking for running instances..."));
190138
190926
  }
@@ -190143,18 +190931,24 @@ function CleanUI() {
190143
190931
  return /* @__PURE__ */ React6.createElement(Text6, { color: "red" }, "Error: ", state.error);
190144
190932
  }
190145
190933
  if (state.status === "nothing") {
190146
- return /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, "Nothing to clean (.specific directory does not exist)");
190934
+ const target2 = instanceKey ? `key "${instanceKey}"` : ".specific directory";
190935
+ return /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, "Nothing to clean (", target2, " does not exist)");
190147
190936
  }
190148
- return /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, "Cleaned .specific directory");
190937
+ const target = instanceKey ? `key "${instanceKey}"` : ".specific directory";
190938
+ return /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, "Cleaned ", target);
190149
190939
  }
190150
- function cleanCommand() {
190151
- render6(/* @__PURE__ */ React6.createElement(CleanUI, null));
190940
+ function cleanCommand(instanceKey) {
190941
+ if (instanceKey) {
190942
+ render6(/* @__PURE__ */ React6.createElement(CleanUI, { instanceKey }));
190943
+ } else {
190944
+ render6(/* @__PURE__ */ React6.createElement(CleanUI, null));
190945
+ }
190152
190946
  }
190153
190947
 
190154
190948
  // src/commands/secrets.tsx
190155
190949
  import React7, { useState as useState7, useEffect as useEffect7 } from "react";
190156
190950
  import { render as render7, Text as Text7, Box as Box7, useInput as useInput4, useApp as useApp5 } from "ink";
190157
- import * as fs23 from "fs";
190951
+ import * as fs24 from "fs";
190158
190952
  var HEADER_COMMENT = "# Do not commit this file - it contains secrets\n";
190159
190953
  function SetSecretUI({ secretName }) {
190160
190954
  const { exit } = useApp5();
@@ -190173,8 +190967,8 @@ function SetSecretUI({ secretName }) {
190173
190967
  const hclLine = `${secretName} = "${escapedValue}"`;
190174
190968
  let content = "";
190175
190969
  let hasHeader = false;
190176
- if (fs23.existsSync(SECRETS_FILE)) {
190177
- content = fs23.readFileSync(SECRETS_FILE, "utf-8");
190970
+ if (fs24.existsSync(SECRETS_FILE)) {
190971
+ content = fs24.readFileSync(SECRETS_FILE, "utf-8");
190178
190972
  hasHeader = content.startsWith("#");
190179
190973
  const lines = content.split("\n");
190180
190974
  const newLines = [];
@@ -190189,7 +190983,7 @@ function SetSecretUI({ secretName }) {
190189
190983
  }
190190
190984
  }
190191
190985
  if (found) {
190192
- fs23.writeFileSync(SECRETS_FILE, newLines.join("\n"));
190986
+ fs24.writeFileSync(SECRETS_FILE, newLines.join("\n"));
190193
190987
  setDone(true);
190194
190988
  return;
190195
190989
  }
@@ -190202,7 +190996,7 @@ function SetSecretUI({ secretName }) {
190202
190996
  }
190203
190997
  newContent += `${hclLine}
190204
190998
  `;
190205
- fs23.writeFileSync(SECRETS_FILE, newContent);
190999
+ fs24.writeFileSync(SECRETS_FILE, newContent);
190206
191000
  setDone(true);
190207
191001
  } catch (err) {
190208
191002
  setError(err instanceof Error ? err.message : String(err));
@@ -190262,7 +191056,7 @@ async function secretsCommand(action, secretName) {
190262
191056
  // src/commands/config.tsx
190263
191057
  import React8, { useState as useState8, useEffect as useEffect8 } from "react";
190264
191058
  import { render as render8, Text as Text8, Box as Box8, useInput as useInput5, useApp as useApp6 } from "ink";
190265
- import * as fs24 from "fs";
191059
+ import * as fs25 from "fs";
190266
191060
  var HEADER_COMMENT2 = "# Configuration values for this project\n# These values override defaults defined in specific.hcl\n";
190267
191061
  function SetConfigUI({ configName, initialValue }) {
190268
191062
  const { exit } = useApp6();
@@ -190281,8 +191075,8 @@ function SetConfigUI({ configName, initialValue }) {
190281
191075
  const hclLine = `${configName} = "${escapedValue}"`;
190282
191076
  let content = "";
190283
191077
  let hasHeader = false;
190284
- if (fs24.existsSync(CONFIG_FILE)) {
190285
- content = fs24.readFileSync(CONFIG_FILE, "utf-8");
191078
+ if (fs25.existsSync(CONFIG_FILE)) {
191079
+ content = fs25.readFileSync(CONFIG_FILE, "utf-8");
190286
191080
  hasHeader = content.startsWith("#");
190287
191081
  const lines = content.split("\n");
190288
191082
  const newLines = [];
@@ -190297,7 +191091,7 @@ function SetConfigUI({ configName, initialValue }) {
190297
191091
  }
190298
191092
  }
190299
191093
  if (found) {
190300
- fs24.writeFileSync(CONFIG_FILE, newLines.join("\n"));
191094
+ fs25.writeFileSync(CONFIG_FILE, newLines.join("\n"));
190301
191095
  setDone(true);
190302
191096
  return;
190303
191097
  }
@@ -190310,7 +191104,7 @@ function SetConfigUI({ configName, initialValue }) {
190310
191104
  }
190311
191105
  newContent += `${hclLine}
190312
191106
  `;
190313
- fs24.writeFileSync(CONFIG_FILE, newContent);
191107
+ fs25.writeFileSync(CONFIG_FILE, newContent);
190314
191108
  setDone(true);
190315
191109
  } catch (err) {
190316
191110
  setError(err instanceof Error ? err.message : String(err));
@@ -190349,8 +191143,8 @@ async function configSetCommand(configName, configValue) {
190349
191143
  const hclLine = `${configName} = "${escapedValue}"`;
190350
191144
  let content = "";
190351
191145
  let hasHeader = false;
190352
- if (fs24.existsSync(CONFIG_FILE)) {
190353
- content = fs24.readFileSync(CONFIG_FILE, "utf-8");
191146
+ if (fs25.existsSync(CONFIG_FILE)) {
191147
+ content = fs25.readFileSync(CONFIG_FILE, "utf-8");
190354
191148
  hasHeader = content.startsWith("#");
190355
191149
  const lines = content.split("\n");
190356
191150
  const newLines = [];
@@ -190365,7 +191159,7 @@ async function configSetCommand(configName, configValue) {
190365
191159
  }
190366
191160
  }
190367
191161
  if (found) {
190368
- fs24.writeFileSync(CONFIG_FILE, newLines.join("\n"));
191162
+ fs25.writeFileSync(CONFIG_FILE, newLines.join("\n"));
190369
191163
  console.log(`Config '${configName}' saved to ${CONFIG_FILE}`);
190370
191164
  return;
190371
191165
  }
@@ -190378,7 +191172,7 @@ async function configSetCommand(configName, configValue) {
190378
191172
  }
190379
191173
  newContent += `${hclLine}
190380
191174
  `;
190381
- fs24.writeFileSync(CONFIG_FILE, newContent);
191175
+ fs25.writeFileSync(CONFIG_FILE, newContent);
190382
191176
  console.log(`Config '${configName}' saved to ${CONFIG_FILE}`);
190383
191177
  } catch (err) {
190384
191178
  console.error("Error:", err instanceof Error ? err.message : String(err));
@@ -190497,24 +191291,38 @@ function logoutCommand() {
190497
191291
  var program = new Command();
190498
191292
  var env = "production";
190499
191293
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
190500
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.37").enablePositionalOptions();
191294
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.39").enablePositionalOptions();
190501
191295
  program.command("init").description("Initialize project for use with a coding agent").action(initCommand);
190502
191296
  program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
190503
191297
  program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
190504
- program.command("dev").description("Start local development environment").action(devCommand);
191298
+ program.command("dev").description("Start local development environment").option("-k, --key <key>", "Namespace for isolated dev environment (auto-detected from git worktree if not specified)").action((options2) => {
191299
+ const key = options2.key ?? getDefaultKey();
191300
+ devCommand(key);
191301
+ });
190505
191302
  program.command("deploy [environment]").description("Deploy to Specific infrastructure").option("--skip-build-test", "Skip local build testing before deploy").action((environment, options2) => {
190506
191303
  deployCommand(environment, options2);
190507
191304
  });
190508
- program.command("exec <service> [args...]").description("Run a one-off command with service environment").passThroughOptions().action(async (service, args) => {
191305
+ program.command("exec <service> [args...]").description("Run a one-off command with service environment").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").passThroughOptions().action(async (service, args, options2) => {
190509
191306
  const filteredArgs = args[0] === "--" ? args.slice(1) : args;
190510
- await execCommand(service, filteredArgs);
191307
+ const key = options2.key ?? getDefaultKey();
191308
+ await execCommand(service, filteredArgs, key);
191309
+ });
191310
+ program.command("psql [database]").description("Connect to a running Postgres database").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").action((database, options2) => {
191311
+ const key = options2.key ?? getDefaultKey();
191312
+ psqlCommand(database, key);
191313
+ });
191314
+ program.command("clean").description("Remove .specific directory for a clean slate").option("-k, --key <key>", "Clean only the specified dev environment key").action((options2) => {
191315
+ cleanCommand(options2.key);
190511
191316
  });
190512
- program.command("psql [database]").description("Connect to a running Postgres database").action(psqlCommand);
190513
- program.command("clean").description("Remove .specific directory for a clean slate").action(cleanCommand);
190514
191317
  program.command("secrets [action] [name]").description("Manage secrets").action(secretsCommand);
190515
191318
  program.command("config [action] [name] [value]").description("Manage configuration values").action(configCommand);
190516
191319
  program.command("login").description("Log in to Specific").action(loginCommand);
190517
191320
  program.command("logout").description("Log out of Specific").action(logoutCommand);
191321
+ var commandName = process.argv[2] || "help";
191322
+ trackEvent("cli_command_invoked", { command: commandName });
191323
+ process.on("beforeExit", async () => {
191324
+ await shutdown();
191325
+ });
190518
191326
  program.parse();
190519
191327
  /*! Bundled license information:
190520
191328