@specific.dev/cli 0.1.58 → 0.1.60

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 (50) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.!KGRlZmF1bHQp.__PAGE__.txt +1 -1
  4. package/dist/admin/__next.!KGRlZmF1bHQp.txt +1 -1
  5. package/dist/admin/__next._full.txt +1 -1
  6. package/dist/admin/__next._head.txt +1 -1
  7. package/dist/admin/__next._index.txt +1 -1
  8. package/dist/admin/__next._tree.txt +1 -1
  9. package/dist/admin/_not-found/__next._full.txt +1 -1
  10. package/dist/admin/_not-found/__next._head.txt +1 -1
  11. package/dist/admin/_not-found/__next._index.txt +1 -1
  12. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  13. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  14. package/dist/admin/_not-found/__next._tree.txt +1 -1
  15. package/dist/admin/_not-found/index.html +1 -1
  16. package/dist/admin/_not-found/index.txt +1 -1
  17. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
  18. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
  19. package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +1 -1
  20. package/dist/admin/databases/__next._full.txt +1 -1
  21. package/dist/admin/databases/__next._head.txt +1 -1
  22. package/dist/admin/databases/__next._index.txt +1 -1
  23. package/dist/admin/databases/__next._tree.txt +1 -1
  24. package/dist/admin/databases/index.html +1 -1
  25. package/dist/admin/databases/index.txt +1 -1
  26. package/dist/admin/fullscreen/__next._full.txt +1 -1
  27. package/dist/admin/fullscreen/__next._head.txt +1 -1
  28. package/dist/admin/fullscreen/__next._index.txt +1 -1
  29. package/dist/admin/fullscreen/__next._tree.txt +1 -1
  30. package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +1 -1
  31. package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
  32. package/dist/admin/fullscreen/databases/__next._full.txt +1 -1
  33. package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
  34. package/dist/admin/fullscreen/databases/__next._index.txt +1 -1
  35. package/dist/admin/fullscreen/databases/__next._tree.txt +1 -1
  36. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
  37. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
  38. package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
  39. package/dist/admin/fullscreen/databases/index.html +1 -1
  40. package/dist/admin/fullscreen/databases/index.txt +1 -1
  41. package/dist/admin/fullscreen/index.html +1 -1
  42. package/dist/admin/fullscreen/index.txt +1 -1
  43. package/dist/admin/index.html +1 -1
  44. package/dist/admin/index.txt +1 -1
  45. package/dist/cli.js +141 -23
  46. package/dist/postinstall.js +1 -1
  47. package/package.json +2 -3
  48. /package/dist/admin/_next/static/{vIye3POXFcvd-nt32Xq4j → tZ6oW5Gt46x7GjGGbdB6L}/_buildManifest.js +0 -0
  49. /package/dist/admin/_next/static/{vIye3POXFcvd-nt32Xq4j → tZ6oW5Gt46x7GjGGbdB6L}/_clientMiddlewareManifest.json +0 -0
  50. /package/dist/admin/_next/static/{vIye3POXFcvd-nt32Xq4j → tZ6oW5Gt46x7GjGGbdB6L}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -183873,7 +183873,7 @@ function trackEvent(event, properties) {
183873
183873
  event,
183874
183874
  properties: {
183875
183875
  ...properties,
183876
- cli_version: "0.1.58",
183876
+ cli_version: "0.1.60",
183877
183877
  platform: process.platform,
183878
183878
  node_version: process.version,
183879
183879
  project_id: getProjectId(),
@@ -189380,29 +189380,133 @@ var StableSubdomainAllocator = class {
189380
189380
  }
189381
189381
  };
189382
189382
 
189383
+ // node_modules/.pnpm/@specific+tunnel-client@file+..+tunnel+client/node_modules/@specific/tunnel-client/dist/index.js
189384
+ import { EventEmitter as EventEmitter2 } from "node:events";
189385
+ import * as net4 from "node:net";
189386
+ var DEFAULT_HOST = "https://tunnel.spcf.app";
189387
+ async function register(baseUrl, subdomain) {
189388
+ const res = await fetch(`${baseUrl}/${subdomain}`);
189389
+ if (!res.ok) {
189390
+ const body = await res.text();
189391
+ throw new Error(`Registration failed (${res.status}): ${body}`);
189392
+ }
189393
+ return await res.json();
189394
+ }
189395
+ var TunnelClientImpl = class extends EventEmitter2 {
189396
+ info;
189397
+ localPort;
189398
+ tunnelHost;
189399
+ url;
189400
+ subdomain;
189401
+ pool = /* @__PURE__ */ new Set();
189402
+ closed = false;
189403
+ constructor(info, localPort, tunnelHost) {
189404
+ super();
189405
+ this.info = info;
189406
+ this.localPort = localPort;
189407
+ this.tunnelHost = tunnelHost;
189408
+ this.url = info.url;
189409
+ this.subdomain = info.id;
189410
+ this.fillPool();
189411
+ }
189412
+ close() {
189413
+ this.closed = true;
189414
+ for (const c of this.pool)
189415
+ c.destroy();
189416
+ this.pool.clear();
189417
+ }
189418
+ fillPool() {
189419
+ while (!this.closed && this.pool.size < this.info.max_conn_count) {
189420
+ this.addPoolConnection();
189421
+ }
189422
+ }
189423
+ addPoolConnection() {
189424
+ const remote = net4.connect({ host: this.tunnelHost, port: this.info.port });
189425
+ remote.setKeepAlive(true, 3e4);
189426
+ this.pool.add(remote);
189427
+ remote.once("data", (firstChunk) => {
189428
+ this.pool.delete(remote);
189429
+ this.pipeToLocal(remote, firstChunk);
189430
+ });
189431
+ let errored = false;
189432
+ remote.on("error", (err) => {
189433
+ errored = true;
189434
+ this.emit("error", err);
189435
+ });
189436
+ remote.on("close", () => this.onIdleClose(remote, errored));
189437
+ }
189438
+ pipeToLocal(remote, firstChunk) {
189439
+ const local = net4.connect({ host: "127.0.0.1", port: this.localPort }, () => {
189440
+ local.write(firstChunk);
189441
+ remote.pipe(local);
189442
+ local.pipe(remote);
189443
+ });
189444
+ local.on("error", () => remote.destroy());
189445
+ remote.on("close", () => {
189446
+ if (!this.closed)
189447
+ this.fillPool();
189448
+ });
189449
+ }
189450
+ onIdleClose(remote, errored) {
189451
+ if (!this.pool.delete(remote) || this.closed)
189452
+ return;
189453
+ if (errored) {
189454
+ if (this.pool.size === 0)
189455
+ this.emit("close");
189456
+ } else {
189457
+ this.fillPool();
189458
+ }
189459
+ }
189460
+ };
189461
+ async function connect2(options2) {
189462
+ const host = options2.host ?? DEFAULT_HOST;
189463
+ const tunnelHost = new URL(host).hostname;
189464
+ const info = await register(host, options2.subdomain);
189465
+ return new TunnelClientImpl(info, options2.port, tunnelHost);
189466
+ }
189467
+
189383
189468
  // src/lib/dev/tunnel-manager.ts
189384
- import localtunnel from "localtunnel";
189385
189469
  var TUNNEL_HOST = "https://tunnel.spcf.app";
189386
189470
  async function startTunnel(serviceName, endpointName, port, subdomain, callbacks) {
189387
- const tunnel = await localtunnel({
189388
- port,
189389
- subdomain,
189390
- host: TUNNEL_HOST
189391
- });
189392
- tunnel.on("error", (err) => {
189393
- callbacks?.onError?.(serviceName, endpointName, err);
189394
- });
189395
- tunnel.on("close", () => {
189396
- callbacks?.onClose?.(serviceName, endpointName);
189397
- });
189471
+ let currentTunnel = null;
189472
+ let stopped = false;
189473
+ function setupTunnel(tunnel) {
189474
+ tunnel.on("error", (err) => {
189475
+ callbacks?.onError?.(serviceName, endpointName, err);
189476
+ });
189477
+ tunnel.on("close", () => {
189478
+ if (!stopped) {
189479
+ callbacks?.onClose?.(serviceName, endpointName);
189480
+ reconnect();
189481
+ }
189482
+ });
189483
+ }
189484
+ async function reconnect() {
189485
+ const delays = [1e3, 2e3, 4e3, 8e3, 15e3];
189486
+ for (let attempt = 0; !stopped; attempt++) {
189487
+ const delay = delays[Math.min(attempt, delays.length - 1)];
189488
+ await new Promise((r) => setTimeout(r, delay));
189489
+ if (stopped) return;
189490
+ try {
189491
+ currentTunnel = await connect2({ port, subdomain, host: TUNNEL_HOST });
189492
+ setupTunnel(currentTunnel);
189493
+ callbacks?.onReconnect?.(serviceName, endpointName);
189494
+ return;
189495
+ } catch {
189496
+ }
189497
+ }
189498
+ }
189499
+ currentTunnel = await connect2({ port, subdomain, host: TUNNEL_HOST });
189500
+ setupTunnel(currentTunnel);
189398
189501
  return {
189399
189502
  serviceName,
189400
189503
  endpointName,
189401
189504
  localPort: port,
189402
- url: tunnel.url,
189505
+ url: currentTunnel.url,
189403
189506
  subdomain,
189404
189507
  stop: async () => {
189405
- tunnel.close();
189508
+ stopped = true;
189509
+ currentTunnel?.close();
189406
189510
  }
189407
189511
  };
189408
189512
  }
@@ -189411,7 +189515,7 @@ async function startTunnel(serviceName, endpointName, port, subdomain, callbacks
189411
189515
  import * as fs20 from "fs";
189412
189516
  import * as path17 from "path";
189413
189517
  import * as os7 from "os";
189414
- import * as net4 from "net";
189518
+ import * as net5 from "net";
189415
189519
  var ProxyRegistryManager = class {
189416
189520
  proxyDir;
189417
189521
  ownerPath;
@@ -189445,7 +189549,7 @@ var ProxyRegistryManager = class {
189445
189549
  */
189446
189550
  isProxyListening(port, timeoutMs = 1e3) {
189447
189551
  return new Promise((resolve10) => {
189448
- const socket = new net4.Socket();
189552
+ const socket = new net5.Socket();
189449
189553
  let resolved = false;
189450
189554
  const cleanup = () => {
189451
189555
  if (!resolved) {
@@ -189997,9 +190101,6 @@ function DevUI({ instanceKey, tunnelEnabled }) {
189997
190101
  // Stop all tunnels
189998
190102
  ...tunnelsRef.current.map((tunnel) => tunnel.stop())
189999
190103
  ]);
190000
- if (tunnelsRef.current.length > 0) {
190001
- await new Promise((resolve10) => setTimeout(resolve10, 1500));
190002
- }
190003
190104
  electricInstancesRef.current = [];
190004
190105
  reshapeWatchersRef.current = [];
190005
190106
  restartServicesRef.current = null;
@@ -190039,7 +190140,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
190039
190140
  ...servicesRef.current,
190040
190141
  ...electricInstancesRef.current,
190041
190142
  drizzleGatewayRef.current,
190042
- ...[...resourcesRef.current.values()]
190143
+ ...[...resourcesRef.current.values()],
190144
+ ...tunnelsRef.current
190043
190145
  ].filter(Boolean);
190044
190146
  for (const proc of allProcesses) {
190045
190147
  try {
@@ -190052,11 +190154,20 @@ function DevUI({ instanceKey, tunnelEnabled }) {
190052
190154
  }
190053
190155
  shutdown2();
190054
190156
  };
190157
+ const handleCrash = (err) => {
190158
+ const msg = err instanceof Error ? err.message : String(err);
190159
+ writeLog("system:error", `Uncaught error: ${msg}`);
190160
+ shutdown2();
190161
+ };
190055
190162
  process.on("SIGINT", handleSignal);
190056
190163
  process.on("SIGTERM", handleSignal);
190164
+ process.on("uncaughtException", handleCrash);
190165
+ process.on("unhandledRejection", handleCrash);
190057
190166
  return () => {
190058
190167
  process.off("SIGINT", handleSignal);
190059
190168
  process.off("SIGTERM", handleSignal);
190169
+ process.off("uncaughtException", handleCrash);
190170
+ process.off("unhandledRejection", handleCrash);
190060
190171
  };
190061
190172
  }, []);
190062
190173
  useEffect3(() => {
@@ -190631,7 +190742,14 @@ Add them to the config block in specific.local`);
190631
190742
  writeLog("system:error", `Tunnel error for ${serviceName}: ${error.message}`);
190632
190743
  },
190633
190744
  onClose: (serviceName) => {
190634
- writeLog("system", `Tunnel closed for ${serviceName}`);
190745
+ writeLog("system", `Tunnel closed for ${serviceName}, reconnecting...`);
190746
+ tunnelStatusMap.set(serviceName, "connecting");
190747
+ setState((s) => ({ ...s, tunnelStatus: new Map(tunnelStatusMap) }));
190748
+ },
190749
+ onReconnect: (serviceName) => {
190750
+ writeLog("system", `Tunnel reconnected for ${serviceName}`);
190751
+ tunnelStatusMap.set(serviceName, "connected");
190752
+ setState((s) => ({ ...s, tunnelStatus: new Map(tunnelStatusMap) }));
190635
190753
  }
190636
190754
  }
190637
190755
  );
@@ -193065,7 +193183,7 @@ function betaCommand() {
193065
193183
  var program = new Command();
193066
193184
  var env = "production";
193067
193185
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
193068
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.58").enablePositionalOptions();
193186
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.60").enablePositionalOptions();
193069
193187
  program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").action((options2) => initCommand(options2));
193070
193188
  program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
193071
193189
  program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
@@ -111,7 +111,7 @@ function trackEvent(event, properties) {
111
111
  event,
112
112
  properties: {
113
113
  ...properties,
114
- cli_version: "0.1.58",
114
+ cli_version: "0.1.60",
115
115
  platform: process.platform,
116
116
  node_version: process.version,
117
117
  project_id: getProjectId(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/cli",
3
- "version": "0.1.58",
3
+ "version": "0.1.60",
4
4
  "description": "CLI for Specific infrastructure-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -41,7 +41,6 @@
41
41
  "http-proxy": "^1.18.1",
42
42
  "ink": "^6.5.1",
43
43
  "ink-spinner": "^5.0.0",
44
- "localtunnel": "^2.0.2",
45
44
  "node-forge": "^1.3.1",
46
45
  "open": "^11.0.0",
47
46
  "posthog-node": "^5.24.1",
@@ -52,8 +51,8 @@
52
51
  },
53
52
  "devDependencies": {
54
53
  "@specific/config": "file:../config",
54
+ "@specific/tunnel-client": "file:../tunnel/client",
55
55
  "@types/http-proxy": "^1.17.17",
56
- "@types/localtunnel": "^2.0.4",
57
56
  "@types/node": "^25.0.1",
58
57
  "@types/node-forge": "^1.3.11",
59
58
  "@types/react": "^19.2.7",