@instafy/cli 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +4 -0
  2. package/dist/bind/LICENSE +661 -0
  3. package/dist/bind/bin/instafy-bind.mjs +542 -0
  4. package/dist/bind/client/bind-client.js +1219 -0
  5. package/dist/bind/src/apply.mjs +293 -0
  6. package/dist/bind/src/bound.mjs +202 -0
  7. package/dist/bind/src/budget.mjs +58 -0
  8. package/dist/bind/src/check.mjs +176 -0
  9. package/dist/bind/src/config.mjs +169 -0
  10. package/dist/bind/src/controller.mjs +244 -0
  11. package/dist/bind/src/detached-env.mjs +27 -0
  12. package/dist/bind/src/executors/agent.mjs +336 -0
  13. package/dist/bind/src/executors/script.mjs +163 -0
  14. package/dist/bind/src/handler-kit.mjs +150 -0
  15. package/dist/bind/src/init.mjs +238 -0
  16. package/dist/bind/src/jobs.mjs +316 -0
  17. package/dist/bind/src/net.mjs +281 -0
  18. package/dist/bind/src/page-fetch.mjs +43 -0
  19. package/dist/bind/src/preview-host.mjs +114 -0
  20. package/dist/bind/src/protocol.mjs +103 -0
  21. package/dist/bind/src/records-schema.mjs +580 -0
  22. package/dist/bind/src/records.mjs +161 -0
  23. package/dist/bind/src/save-owner.mjs +492 -0
  24. package/dist/bind/src/schema-lite.mjs +212 -0
  25. package/dist/bind/src/server.mjs +908 -0
  26. package/dist/bind/src/sse.mjs +119 -0
  27. package/dist/bind/src/starters/blank/app.js +17 -0
  28. package/dist/bind/src/starters/blank/index.html +22 -0
  29. package/dist/bind/src/starters/checklist/app.js +171 -0
  30. package/dist/bind/src/starters/checklist/bound.json +29 -0
  31. package/dist/bind/src/starters/checklist/data.json +26 -0
  32. package/dist/bind/src/starters/checklist/index.html +23 -0
  33. package/dist/bind/src/starters/compare/app.js +316 -0
  34. package/dist/bind/src/starters/compare/bound.json +26 -0
  35. package/dist/bind/src/starters/compare/data.json +170 -0
  36. package/dist/bind/src/starters/compare/index.html +23 -0
  37. package/dist/bind/src/starters/handlers/read_offer.mjs +55 -0
  38. package/dist/bind/src/starters/handlers/set_check.mjs +41 -0
  39. package/dist/bind/src/starters/ranked/app.js +166 -0
  40. package/dist/bind/src/starters/ranked/bound.json +26 -0
  41. package/dist/bind/src/starters/ranked/data.json +142 -0
  42. package/dist/bind/src/starters/ranked/index.html +23 -0
  43. package/dist/bind/src/starters/site.js +888 -0
  44. package/dist/bind/src/starters/theme.css +492 -0
  45. package/dist/bind/src/starters/timeline/app.js +143 -0
  46. package/dist/bind/src/starters/timeline/data.json +41 -0
  47. package/dist/bind/src/starters/timeline/index.html +22 -0
  48. package/dist/bind/src/static.mjs +141 -0
  49. package/dist/bind/src/tokens.mjs +102 -0
  50. package/dist/bind/src/try.mjs +139 -0
  51. package/dist/bind/src/web.mjs +191 -0
  52. package/dist/cli.js +2261 -387
  53. package/package.json +3 -2
package/dist/cli.js CHANGED
@@ -1263,7 +1263,7 @@ var org_exports = {};
1263
1263
  __export(org_exports, {
1264
1264
  listOrganizations: () => listOrganizations
1265
1265
  });
1266
- import kleur17 from "kleur";
1266
+ import kleur18 from "kleur";
1267
1267
  function getTeamDisplayName2(name) {
1268
1268
  const trimmed = (name ?? "").trim();
1269
1269
  if (!trimmed || trimmed === "Personal organization" || trimmed === "Personal team" || trimmed === "Personal workspace") {
@@ -1301,11 +1301,11 @@ async function listOrganizations(params) {
1301
1301
  console.log(JSON.stringify(orgs, null, 2));
1302
1302
  } else {
1303
1303
  if (orgs.length === 0) {
1304
- console.log(kleur17.yellow("No teams found for this account."));
1304
+ console.log(kleur18.yellow("No teams found for this account."));
1305
1305
  } else {
1306
1306
  for (const org of orgs) {
1307
1307
  const role = org.role ? ` [${org.role}]` : "";
1308
- console.log(`${kleur17.green(getTeamDisplayName2(org.name))} (${org.id}${role})`);
1308
+ console.log(`${kleur18.green(getTeamDisplayName2(org.name))} (${org.id}${role})`);
1309
1309
  }
1310
1310
  }
1311
1311
  }
@@ -1335,9 +1335,9 @@ var init_hardwareProvider2 = __esm({
1335
1335
  });
1336
1336
 
1337
1337
  // ../instafy-sdk/src/hardwareNode.ts
1338
- import fs14 from "node:fs";
1339
- import os5 from "node:os";
1340
- import path15 from "node:path";
1338
+ import fs17 from "node:fs";
1339
+ import os7 from "node:os";
1340
+ import path18 from "node:path";
1341
1341
  function uniqueStrings(values) {
1342
1342
  const seen = /* @__PURE__ */ new Set();
1343
1343
  const result = [];
@@ -1376,14 +1376,14 @@ function classifySerialPort(fileName, fullPath) {
1376
1376
  return null;
1377
1377
  }
1378
1378
  function inspectPort(portPath) {
1379
- const resolvedPath = path15.resolve(portPath);
1379
+ const resolvedPath = path18.resolve(portPath);
1380
1380
  let exists = false;
1381
1381
  let readable = false;
1382
1382
  let writable = false;
1383
1383
  let isCharacterDevice = false;
1384
1384
  let error = null;
1385
1385
  try {
1386
- const stat = fs14.statSync(resolvedPath);
1386
+ const stat = fs17.statSync(resolvedPath);
1387
1387
  exists = true;
1388
1388
  isCharacterDevice = stat.isCharacterDevice();
1389
1389
  } catch (caught) {
@@ -1391,13 +1391,13 @@ function inspectPort(portPath) {
1391
1391
  }
1392
1392
  if (exists) {
1393
1393
  try {
1394
- fs14.accessSync(resolvedPath, fs14.constants.R_OK);
1394
+ fs17.accessSync(resolvedPath, fs17.constants.R_OK);
1395
1395
  readable = true;
1396
1396
  } catch {
1397
1397
  readable = false;
1398
1398
  }
1399
1399
  try {
1400
- fs14.accessSync(resolvedPath, fs14.constants.W_OK);
1400
+ fs17.accessSync(resolvedPath, fs17.constants.W_OK);
1401
1401
  writable = true;
1402
1402
  } catch {
1403
1403
  writable = false;
@@ -1419,12 +1419,12 @@ function listCandidates(scanDirs) {
1419
1419
  for (const scanDir of scanDirs) {
1420
1420
  let entries = [];
1421
1421
  try {
1422
- entries = fs14.readdirSync(scanDir);
1422
+ entries = fs17.readdirSync(scanDir);
1423
1423
  } catch {
1424
1424
  continue;
1425
1425
  }
1426
1426
  for (const entry of entries) {
1427
- const fullPath = path15.join(scanDir, entry);
1427
+ const fullPath = path18.join(scanDir, entry);
1428
1428
  const kind = classifySerialPort(entry, fullPath);
1429
1429
  if (!kind) {
1430
1430
  continue;
@@ -1432,7 +1432,7 @@ function listCandidates(scanDirs) {
1432
1432
  candidates.push({
1433
1433
  path: fullPath,
1434
1434
  kind,
1435
- displayName: path15.basename(fullPath)
1435
+ displayName: path18.basename(fullPath)
1436
1436
  });
1437
1437
  }
1438
1438
  }
@@ -1458,7 +1458,7 @@ function listHardwareSerialPorts(options = {}) {
1458
1458
  }
1459
1459
  return {
1460
1460
  providerId: SERIAL_HARDWARE_PROVIDER_ID2,
1461
- platform: os5.platform(),
1461
+ platform: os7.platform(),
1462
1462
  ports: [...byPath.values()].sort((left, right) => left.path.localeCompare(right.path))
1463
1463
  };
1464
1464
  }
@@ -1598,37 +1598,37 @@ __export(hardware_exports, {
1598
1598
  hardwareSerialList: () => hardwareSerialList,
1599
1599
  hardwareSerialProbe: () => hardwareSerialProbe
1600
1600
  });
1601
- import kleur18 from "kleur";
1601
+ import kleur19 from "kleur";
1602
1602
  function printSerialPorts(result) {
1603
1603
  if (result.ports.length === 0) {
1604
- console.log(kleur18.yellow("No local serial devices were discovered."));
1604
+ console.log(kleur19.yellow("No local serial devices were discovered."));
1605
1605
  return;
1606
1606
  }
1607
- console.log(kleur18.cyan(`Local serial devices (${result.platform}):`));
1607
+ console.log(kleur19.cyan(`Local serial devices (${result.platform}):`));
1608
1608
  for (const port of result.ports) {
1609
- const status = port.available ? kleur18.green("available") : kleur18.yellow("not ready");
1609
+ const status = port.available ? kleur19.green("available") : kleur19.yellow("not ready");
1610
1610
  console.log(`- ${port.path} (${port.kind}, ${status})`);
1611
1611
  }
1612
1612
  }
1613
1613
  function printProbe(probe) {
1614
- console.log(kleur18.cyan(`Serial device: ${probe.path}`));
1614
+ console.log(kleur19.cyan(`Serial device: ${probe.path}`));
1615
1615
  console.log(`Exists: ${probe.exists ? "yes" : "no"}`);
1616
1616
  console.log(`Readable: ${probe.readable ? "yes" : "no"}`);
1617
1617
  console.log(`Writable: ${probe.writable ? "yes" : "no"}`);
1618
1618
  console.log(`Character device: ${probe.isCharacterDevice ? "yes" : "no"}`);
1619
- console.log(`Available: ${probe.available ? kleur18.green("yes") : kleur18.yellow("no")}`);
1619
+ console.log(`Available: ${probe.available ? kleur19.green("yes") : kleur19.yellow("no")}`);
1620
1620
  if (probe.error) {
1621
1621
  console.log(`Detail: ${probe.error}`);
1622
1622
  }
1623
1623
  }
1624
1624
  function printIoOpportunities(result) {
1625
1625
  if (result.opportunities.length === 0) {
1626
- console.log(kleur18.yellow("No local IO opportunities were discovered."));
1626
+ console.log(kleur19.yellow("No local IO opportunities were discovered."));
1627
1627
  return;
1628
1628
  }
1629
- console.log(kleur18.cyan(`Local IO opportunities (${result.platform}):`));
1629
+ console.log(kleur19.cyan(`Local IO opportunities (${result.platform}):`));
1630
1630
  for (const opportunity of result.opportunities) {
1631
- const status = opportunity.available ? kleur18.green("available") : kleur18.yellow("not ready");
1631
+ const status = opportunity.available ? kleur19.green("available") : kleur19.yellow("not ready");
1632
1632
  console.log(`- ${opportunity.title} (${status})`);
1633
1633
  console.log(` Resource: ${opportunity.resource.path}`);
1634
1634
  const available = opportunity.actions.filter((action) => action.status === "available").map((action) => action.id);
@@ -1642,8 +1642,8 @@ function printIoOpportunities(result) {
1642
1642
  }
1643
1643
  }
1644
1644
  function printIoActionResult(result) {
1645
- const status = result.ok ? kleur18.green("ok") : kleur18.red("failed");
1646
- console.log(kleur18.cyan(`Local IO action: ${result.actionId} (${status})`));
1645
+ const status = result.ok ? kleur19.green("ok") : kleur19.red("failed");
1646
+ console.log(kleur19.cyan(`Local IO action: ${result.actionId} (${status})`));
1647
1647
  console.log(result.message);
1648
1648
  if (result.serialProbe) {
1649
1649
  printProbe(result.serialProbe);
@@ -1652,11 +1652,11 @@ function printIoActionResult(result) {
1652
1652
  const exit = result.process.exitCode ?? result.process.signal ?? "unknown";
1653
1653
  console.log(`Process exit: ${exit}`);
1654
1654
  if (result.process.stdout.trim()) {
1655
- console.log(kleur18.dim("stdout:"));
1655
+ console.log(kleur19.dim("stdout:"));
1656
1656
  console.log(result.process.stdout.trimEnd());
1657
1657
  }
1658
1658
  if (result.process.stderr.trim()) {
1659
- console.log(kleur18.dim("stderr:"));
1659
+ console.log(kleur19.dim("stderr:"));
1660
1660
  console.log(result.process.stderr.trimEnd());
1661
1661
  }
1662
1662
  }
@@ -1717,9 +1717,9 @@ var init_hardware = __esm({
1717
1717
 
1718
1718
  // src/index.ts
1719
1719
  import { Command, Option } from "commander";
1720
- import { pathToFileURL as pathToFileURL2 } from "node:url";
1721
- import { createRequire as createRequire3 } from "node:module";
1722
- import kleur19 from "kleur";
1720
+ import { pathToFileURL as pathToFileURL3 } from "node:url";
1721
+ import { createRequire as createRequire4 } from "node:module";
1722
+ import kleur20 from "kleur";
1723
1723
 
1724
1724
  // src/runtime.ts
1725
1725
  import { spawn as spawn2, spawnSync } from "node:child_process";
@@ -3672,21 +3672,45 @@ function resolveRequestHost(request) {
3672
3672
  function splitCsv(value) {
3673
3673
  return (value ?? "").split(",").map((entry) => entry.trim().toLowerCase()).filter(Boolean);
3674
3674
  }
3675
+ var DEFAULT_HOSTED_GIT_HOST = "git.instafy.dev";
3676
+ function hostOfUrl(raw) {
3677
+ if (!raw?.trim()) return null;
3678
+ try {
3679
+ return new URL(raw.trim()).host.toLowerCase() || null;
3680
+ } catch {
3681
+ return null;
3682
+ }
3683
+ }
3684
+ function configuredGitHosts() {
3685
+ let controllerHost = null;
3686
+ try {
3687
+ controllerHost = hostOfUrl(resolveControllerUrl({ controllerUrl: null }));
3688
+ } catch {
3689
+ controllerHost = null;
3690
+ }
3691
+ return [
3692
+ DEFAULT_HOSTED_GIT_HOST,
3693
+ controllerHost,
3694
+ hostOfUrl(process.env["ORIGIN_GIT_REMOTE_URL"]),
3695
+ ...splitCsv(process.env["INSTAFY_GIT_HOSTS"])
3696
+ ].filter((host) => Boolean(host));
3697
+ }
3698
+ var LOCAL_STACK_GIT_HOST = /^(?:git-edge|git-shard(?:-[a-z0-9]+)*)$/;
3675
3699
  function isAllowedGitHost(rawHost) {
3676
3700
  if (!rawHost) return false;
3677
3701
  const normalized = normalizeHost(rawHost);
3678
3702
  if (!normalized) return false;
3679
- const allowHosts = splitCsv(process.env["INSTAFY_GIT_HOSTS"]);
3703
+ const allowHosts = configuredGitHosts();
3680
3704
  if (allowHosts.includes(normalized.host) || allowHosts.includes(normalized.hostname)) {
3681
3705
  return true;
3682
3706
  }
3683
3707
  if (normalized.hostname === "localhost" || normalized.hostname === "127.0.0.1" || normalized.hostname === "::1" || normalized.hostname === "host.docker.internal") {
3684
3708
  return true;
3685
3709
  }
3686
- if (normalized.hostname === "git-edge" || normalized.hostname.startsWith("git-shard")) {
3710
+ if (LOCAL_STACK_GIT_HOST.test(normalized.hostname)) {
3687
3711
  return true;
3688
3712
  }
3689
- return normalized.hostname.endsWith(".instafy.dev");
3713
+ return false;
3690
3714
  }
3691
3715
  function normalizeRepoName(raw) {
3692
3716
  const trimmed = raw.trim().replace(/^\/+/, "");
@@ -5028,7 +5052,7 @@ async function credentialsRevoke(options) {
5028
5052
  import fs7 from "node:fs";
5029
5053
  import os4 from "node:os";
5030
5054
  import path8 from "node:path";
5031
- import { spawn as spawn3 } from "node:child_process";
5055
+ import { execFileSync, spawn as spawn3 } from "node:child_process";
5032
5056
  import { randomUUID } from "node:crypto";
5033
5057
  import kleur7 from "kleur";
5034
5058
  init_config();
@@ -5075,7 +5099,9 @@ function buildRatholeEnvironment(source = process.env) {
5075
5099
  return environment;
5076
5100
  }
5077
5101
  function cleanUrl(raw) {
5078
- return raw.replace(/\/+$/, "");
5102
+ let end = raw.length;
5103
+ while (end > 0 && raw[end - 1] === "/") end -= 1;
5104
+ return raw.slice(0, end);
5079
5105
  }
5080
5106
  function normalizeTunnelPurpose(raw) {
5081
5107
  const trimmed = raw.trim();
@@ -5090,7 +5116,11 @@ function normalizeTunnelPurpose(raw) {
5090
5116
  normalized += "_";
5091
5117
  }
5092
5118
  }
5093
- normalized = normalized.replace(/^_+/, "").replace(/_+$/, "");
5119
+ let start = 0;
5120
+ let end = normalized.length;
5121
+ while (start < end && normalized[start] === "_") start += 1;
5122
+ while (end > start && normalized[end - 1] === "_") end -= 1;
5123
+ normalized = normalized.slice(start, end);
5094
5124
  return normalized ? normalized : null;
5095
5125
  }
5096
5126
  function readEnv(key) {
@@ -5278,6 +5308,71 @@ async function waitForProcessExit(pid, timeoutMs) {
5278
5308
  }
5279
5309
  return !isProcessAlive2(pid);
5280
5310
  }
5311
+ function readProcessCommandLine(pid) {
5312
+ if (!isProcessAlive2(pid)) return null;
5313
+ try {
5314
+ if (process.platform === "linux") {
5315
+ const raw = fs7.readFileSync(`/proc/${pid}/cmdline`, "utf8");
5316
+ return raw.split("\0").filter(Boolean).join(" ") || null;
5317
+ }
5318
+ if (process.platform === "win32") {
5319
+ const out2 = execFileSync(
5320
+ "powershell.exe",
5321
+ [
5322
+ "-NoProfile",
5323
+ "-NonInteractive",
5324
+ "-Command",
5325
+ `(Get-CimInstance Win32_Process -Filter 'ProcessId=${Math.trunc(pid)}').CommandLine`
5326
+ ],
5327
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 1e4, windowsHide: true }
5328
+ );
5329
+ return out2.trim() || null;
5330
+ }
5331
+ const out = execFileSync("ps", ["-ww", "-o", "command=", "-p", String(Math.trunc(pid))], {
5332
+ encoding: "utf8",
5333
+ stdio: ["ignore", "pipe", "ignore"],
5334
+ timeout: 5e3
5335
+ });
5336
+ return out.trim() || null;
5337
+ } catch {
5338
+ return null;
5339
+ }
5340
+ }
5341
+ function isRatholeClientFor(pid, configPath2, binary) {
5342
+ if (!configPath2) return false;
5343
+ const commandLine = readProcessCommandLine(pid);
5344
+ return commandLine !== null && commandLineIsRatholeClient(commandLine, configPath2, binary);
5345
+ }
5346
+ function endsWithProgram(program2, binary) {
5347
+ if (program2 === binary) return true;
5348
+ if (!program2.endsWith(binary)) return false;
5349
+ const before = program2[program2.length - binary.length - 1];
5350
+ return before === " " || before === "/" || before === "\\";
5351
+ }
5352
+ function commandLineIsRatholeClient(commandLine, configPath2, binary) {
5353
+ if (!configPath2) return false;
5354
+ const line = commandLine.split('"').join("").trim();
5355
+ const tail = ` -c ${configPath2}`;
5356
+ if (!line.endsWith(tail)) return false;
5357
+ const program2 = line.slice(0, line.length - tail.length).trimEnd();
5358
+ if (!program2) return false;
5359
+ if (binary && endsWithProgram(program2, binary)) return true;
5360
+ const name = program2.split(/[\\/]/).pop() ?? "";
5361
+ return name.toLowerCase().includes("rathole");
5362
+ }
5363
+ async function stopRatholeClient(pid, configPath2, binary) {
5364
+ if (!isRatholeClientFor(pid, configPath2, binary)) return;
5365
+ try {
5366
+ process.kill(pid, "SIGTERM");
5367
+ } catch {
5368
+ }
5369
+ if (await waitForProcessExit(pid, 4e3)) return;
5370
+ if (!isRatholeClientFor(pid, configPath2, binary)) return;
5371
+ try {
5372
+ process.kill(pid, "SIGKILL");
5373
+ } catch {
5374
+ }
5375
+ }
5281
5376
  function upsertTunnelState(entry) {
5282
5377
  const state = readStateFile();
5283
5378
  const next = state.tunnels.filter((tunnel) => tunnel.tunnelId !== entry.tunnelId);
@@ -5367,8 +5462,19 @@ async function revokeTunnel(controllerUrl, auth, projectId2, tunnelId) {
5367
5462
  throw new Error(`Tunnel revoke failed (${response.status} ${response.statusText}): ${text2}`);
5368
5463
  }
5369
5464
  }
5465
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
5466
+ function resolveTunnelServerAddress(server, env = process.env, inContainer = fs7.existsSync("/.dockerenv")) {
5467
+ const match = /^(\[[^\]]+\]|[^:]+):(\d+)$/.exec(server.trim());
5468
+ if (!match) return server;
5469
+ const [, host, port] = match;
5470
+ const override = env["INSTAFY_TUNNEL_SERVER_HOST"]?.trim();
5471
+ if (override) return `${override}:${port}`;
5472
+ if (inContainer && LOOPBACK_HOSTS.has(host.toLowerCase())) return `host.docker.internal:${port}`;
5473
+ return server;
5474
+ }
5370
5475
  function buildRatholeConfig(creds, port) {
5371
- const server = typeof creds["server"] === "string" ? creds["server"] : null;
5476
+ const advertised = typeof creds["server"] === "string" ? creds["server"] : null;
5477
+ const server = advertised ? resolveTunnelServerAddress(advertised) : null;
5372
5478
  const token = typeof creds["token"] === "string" ? creds["token"] : null;
5373
5479
  const service = typeof creds["service"] === "string" && creds["service"] || typeof creds["serviceName"] === "string" && creds["serviceName"] || "runtime";
5374
5480
  const protocol = typeof creds["protocol"] === "string" && creds["protocol"] || "tcp";
@@ -5471,6 +5577,40 @@ async function runTunnelCommand(opts, options) {
5471
5577
  process.once("SIGTERM", handle);
5472
5578
  });
5473
5579
  }
5580
+ async function spawnRatholeDetached(params) {
5581
+ const logFd = fs7.openSync(params.logFile, "a");
5582
+ const child = spawn3(params.rathole, ["-c", params.configPath], {
5583
+ stdio: ["ignore", logFd, logFd],
5584
+ cwd: params.workdir,
5585
+ detached: true,
5586
+ env: buildRatholeEnvironment()
5587
+ });
5588
+ fs7.closeSync(logFd);
5589
+ let exitedEarly = false;
5590
+ let exitMessage = "";
5591
+ child.on("error", (error) => {
5592
+ exitedEarly = true;
5593
+ exitMessage = error.message;
5594
+ });
5595
+ child.on("exit", (code, signal) => {
5596
+ exitedEarly = true;
5597
+ exitMessage = code !== null ? `code ${code}` : `signal ${signal}`;
5598
+ });
5599
+ await new Promise((resolve) => setTimeout(resolve, 250));
5600
+ if (exitedEarly || !isProcessAlive2(child.pid ?? -1)) {
5601
+ return { pid: -1, exit: exitMessage || "unknown", logTail: readLogTail(params.logFile) };
5602
+ }
5603
+ child.unref();
5604
+ return { pid: child.pid ?? -1, exit: null, logTail: "" };
5605
+ }
5606
+ function readLogTail(logFile, lines = 20) {
5607
+ try {
5608
+ const text2 = fs7.readFileSync(logFile, "utf8");
5609
+ return text2.split(/\r?\n/).filter(Boolean).slice(-lines).join("\n");
5610
+ } catch {
5611
+ return "";
5612
+ }
5613
+ }
5474
5614
  async function startTunnelDetached(opts) {
5475
5615
  const projectId2 = resolveProject(opts);
5476
5616
  const controllerToken = resolveControllerToken(opts);
@@ -5505,39 +5645,16 @@ async function startTunnelDetached(opts) {
5505
5645
  const configPath2 = path8.join(workdir, "rathole.toml");
5506
5646
  fs7.writeFileSync(configPath2, configBody, { encoding: "utf8", mode: 384 });
5507
5647
  const logFile = opts.logFile?.trim() ? path8.resolve(opts.logFile.trim()) : path8.join(TUNNEL_LOG_DIR, `tunnel-${tunnelIdSafe}.log`);
5508
- const logFd = fs7.openSync(logFile, "a");
5509
- const child = spawn3(rathole, ["-c", configPath2], {
5510
- stdio: ["ignore", logFd, logFd],
5511
- cwd: workdir,
5512
- detached: true,
5513
- env: buildRatholeEnvironment()
5514
- });
5515
- let exitedEarly = false;
5516
- let exitMessage = "";
5517
- child.on("exit", (code, signal) => {
5518
- exitedEarly = true;
5519
- exitMessage = code !== null ? `code ${code}` : `signal ${signal}`;
5520
- });
5521
- await new Promise((resolve) => setTimeout(resolve, 250));
5522
- if (exitedEarly || !isProcessAlive2(child.pid ?? -1)) {
5523
- const logTail = (() => {
5524
- try {
5525
- const text2 = fs7.readFileSync(logFile, "utf8");
5526
- const lines = text2.split(/\r?\n/).filter(Boolean);
5527
- return lines.slice(-20).join("\n");
5528
- } catch {
5529
- return "";
5530
- }
5531
- })();
5648
+ const started = await spawnRatholeDetached({ rathole, configPath: configPath2, workdir, logFile });
5649
+ if (started.exit !== null) {
5532
5650
  await revokeTunnel(controllerUrl, controllerToken, projectId2, grant.tunnelId).catch(() => {
5533
5651
  });
5534
- const suffix = logTail ? `
5652
+ const suffix = started.logTail ? `
5535
5653
 
5536
5654
  Last logs:
5537
- ${logTail}` : "";
5538
- throw new Error(`Tunnel process exited (${exitMessage || "unknown"}).${suffix}`);
5655
+ ${started.logTail}` : "";
5656
+ throw new Error(`Tunnel process exited (${started.exit}).${suffix}`);
5539
5657
  }
5540
- child.unref();
5541
5658
  const entry = {
5542
5659
  tunnelId: grant.tunnelId,
5543
5660
  projectId: projectId2,
@@ -5545,10 +5662,11 @@ ${logTail}` : "";
5545
5662
  url: grant.url ?? `https://${grant.hostname}`,
5546
5663
  localPort: port,
5547
5664
  controllerUrl: cleanUrl(controllerUrl),
5548
- pid: child.pid ?? -1,
5665
+ pid: started.pid,
5549
5666
  logFile,
5550
5667
  workdir,
5551
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
5668
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
5669
+ binary: rathole
5552
5670
  };
5553
5671
  upsertTunnelState(entry);
5554
5672
  return entry;
@@ -5573,19 +5691,7 @@ async function stopTunnelSession(opts) {
5573
5691
  if (!entry) {
5574
5692
  throw new Error(`Tunnel not found in local state: ${tunnelId}`);
5575
5693
  }
5576
- if (isProcessAlive2(entry.pid)) {
5577
- try {
5578
- process.kill(entry.pid, "SIGTERM");
5579
- } catch {
5580
- }
5581
- const stopped = await waitForProcessExit(entry.pid, 4e3);
5582
- if (!stopped) {
5583
- try {
5584
- process.kill(entry.pid, "SIGKILL");
5585
- } catch {
5586
- }
5587
- }
5588
- }
5694
+ await stopRatholeClient(entry.pid, path8.join(entry.workdir, "rathole.toml"), entry.binary);
5589
5695
  const controllerToken = resolveControllerToken(opts, `instafy tunnel stop ${tunnelId}`);
5590
5696
  if (controllerToken.runtimeControllerUrl) {
5591
5697
  try {
@@ -5643,30 +5749,1111 @@ async function tailTunnelLogs(options) {
5643
5749
  console.log(tail.join("\n"));
5644
5750
  return;
5645
5751
  }
5646
- const child = process.platform === "win32" ? spawn3(
5647
- "powershell.exe",
5648
- [
5649
- "-NoProfile",
5650
- "-Command",
5651
- `Get-Content -LiteralPath '${logFile.replace(/'/g, "''")}' -Tail ${lineCount} -Wait`
5652
- ],
5653
- { stdio: "inherit" }
5654
- ) : spawn3("tail", ["-n", String(lineCount), "-f", logFile], { stdio: "inherit" });
5655
- const handleExit = () => {
5656
- try {
5657
- child.kill("SIGTERM");
5658
- } catch {
5659
- }
5660
- };
5661
- process.once("SIGINT", handleExit);
5662
- process.once("SIGTERM", handleExit);
5663
- await new Promise((resolve) => child.on("exit", () => resolve()));
5752
+ const child = process.platform === "win32" ? spawn3(
5753
+ "powershell.exe",
5754
+ [
5755
+ "-NoProfile",
5756
+ "-Command",
5757
+ `Get-Content -LiteralPath '${logFile.replace(/'/g, "''")}' -Tail ${lineCount} -Wait`
5758
+ ],
5759
+ { stdio: "inherit" }
5760
+ ) : spawn3("tail", ["-n", String(lineCount), "-f", logFile], { stdio: "inherit" });
5761
+ const handleExit = () => {
5762
+ try {
5763
+ child.kill("SIGTERM");
5764
+ } catch {
5765
+ }
5766
+ };
5767
+ process.once("SIGINT", handleExit);
5768
+ process.once("SIGTERM", handleExit);
5769
+ await new Promise((resolve) => child.on("exit", () => resolve()));
5770
+ }
5771
+
5772
+ // src/preview.ts
5773
+ import { spawn as spawn5 } from "node:child_process";
5774
+ import fs9 from "node:fs";
5775
+ import os5 from "node:os";
5776
+ import path10 from "node:path";
5777
+ import kleur8 from "kleur";
5778
+
5779
+ // src/bind.ts
5780
+ import { spawn as spawn4 } from "node:child_process";
5781
+ import fs8 from "node:fs";
5782
+ import path9 from "node:path";
5783
+ import { fileURLToPath as fileURLToPath2, pathToFileURL } from "node:url";
5784
+ var BIN_REL = path9.join("bin", "instafy-bind.mjs");
5785
+ function resolveBindBin(env = process.env) {
5786
+ const fromEnv = env.INSTAFY_BIND_BIN?.trim();
5787
+ if (fromEnv) return fs8.existsSync(fromEnv) ? path9.resolve(fromEnv) : null;
5788
+ const here = path9.dirname(fileURLToPath2(import.meta.url));
5789
+ for (const candidate of [path9.join(here, "bind", BIN_REL), path9.resolve(here, "..", "..", "instafy-bind", BIN_REL)]) {
5790
+ if (fs8.existsSync(candidate)) return candidate;
5791
+ }
5792
+ return null;
5793
+ }
5794
+ function bundledBindRoot() {
5795
+ const here = path9.dirname(fileURLToPath2(import.meta.url));
5796
+ for (const candidate of [path9.join(here, "bind"), path9.resolve(here, "..", "..", "instafy-bind")]) {
5797
+ if (fs8.existsSync(path9.join(candidate, BIN_REL))) return candidate;
5798
+ }
5799
+ return null;
5800
+ }
5801
+ async function loadCredentialFreeEnv() {
5802
+ const root = bundledBindRoot();
5803
+ const file = root ? path9.join(root, "src", "detached-env.mjs") : null;
5804
+ if (!file || !fs8.existsSync(file)) {
5805
+ throw new Error("the bundled binder (dist/bind) was not found, so a site cannot be started without credentials; rebuild the CLI");
5806
+ }
5807
+ const module = await import(pathToFileURL(file).href);
5808
+ if (typeof module.credentialFreeEnv !== "function") {
5809
+ throw new Error(`${file} does not export credentialFreeEnv; rebuild the CLI`);
5810
+ }
5811
+ return module.credentialFreeEnv;
5812
+ }
5813
+ function runBind(args) {
5814
+ const bin = resolveBindBin();
5815
+ if (!bin) {
5816
+ process.stderr.write(
5817
+ "instafy bind: the bundled binder (dist/bind) was not found; rebuild the CLI, or set INSTAFY_BIND_BIN to packages/instafy-bind/bin/instafy-bind.mjs, or run it directly: node packages/instafy-bind/bin/instafy-bind.mjs <command>\n"
5818
+ );
5819
+ return Promise.resolve(1);
5820
+ }
5821
+ return new Promise((resolve) => {
5822
+ const child = spawn4(process.execPath, [bin, ...args], { stdio: "inherit", env: process.env });
5823
+ const forward = (signal) => {
5824
+ if (!child.killed) child.kill(signal);
5825
+ };
5826
+ process.on("SIGINT", forward);
5827
+ process.on("SIGTERM", forward);
5828
+ child.on("error", (error) => {
5829
+ process.stderr.write(`instafy bind: ${error.message}
5830
+ `);
5831
+ resolve(1);
5832
+ });
5833
+ child.on("exit", (code, signal) => {
5834
+ process.off("SIGINT", forward);
5835
+ process.off("SIGTERM", forward);
5836
+ resolve(code ?? (signal ? 130 : 1));
5837
+ });
5838
+ });
5839
+ }
5840
+
5841
+ // src/preview.ts
5842
+ init_controller_fetch();
5843
+ init_errors();
5844
+
5845
+ // src/local-site.ts
5846
+ import http2 from "node:http";
5847
+ var PREVIEW_EXIT_NOT_LISTENING = 2;
5848
+ var PREVIEW_EXIT_PORT_NOT_ALLOWED = 3;
5849
+ var PreviewCommandError = class extends Error {
5850
+ constructor(message, exitCode) {
5851
+ super(message);
5852
+ this.exitCode = exitCode;
5853
+ }
5854
+ };
5855
+ var RESERVED_PORTS = [
5856
+ { env: "INSTAFY_VNC_PORT", kind: "port", port: 5900 },
5857
+ { env: "INSTAFY_PROVIDER_HOST_URL", kind: "url", port: 8789 },
5858
+ { env: "PROXY_BASE_URL", kind: "url", port: 8789 },
5859
+ { port: 9222 },
5860
+ { env: "INSTAFY_PLAYWRIGHT_CDP_PORT", kind: "port", port: 9223 },
5861
+ { env: "INSTAFY_PLAYWRIGHT_CDP_URL", kind: "url", port: 9223 },
5862
+ { port: 9225 },
5863
+ { env: "INSTAFY_BROWSER_EGRESS_PROXY_BIND", kind: "bind", port: 9226 },
5864
+ { env: "INSTAFY_PERSONAL_BROWSER_CONTROL_URL", kind: "url", port: 43127 },
5865
+ { env: "ORIGIN_BIND_PORT", kind: "port", port: 54332 }
5866
+ ];
5867
+ var PREVIEW_DENY_PORTS_ENV = "INSTAFY_PREVIEW_DENY_PORTS";
5868
+ var MIN_PREVIEW_PORT = 1024;
5869
+ var MAX_PREVIEW_PORT = 65535;
5870
+ var PROBE_TIMEOUT_MS = 5e3;
5871
+ function portNumber(raw) {
5872
+ if (!/^\d{1,5}$/.test(raw)) return null;
5873
+ const port = Number(raw);
5874
+ return port >= 1 && port <= MAX_PREVIEW_PORT ? port : null;
5875
+ }
5876
+ function portFromSetting(kind, raw) {
5877
+ const value = raw.trim();
5878
+ if (!value) return null;
5879
+ if (kind === "port") return portNumber(value);
5880
+ if (kind === "bind") return portNumber(value.slice(value.lastIndexOf(":") + 1));
5881
+ try {
5882
+ const url = new URL(value);
5883
+ return portNumber(url.port || (url.protocol === "https:" ? "443" : url.protocol === "http:" ? "80" : ""));
5884
+ } catch {
5885
+ return null;
5886
+ }
5887
+ }
5888
+ function reservedPreviewPorts(env = process.env) {
5889
+ const denied = /* @__PURE__ */ new Set();
5890
+ for (const entry of RESERVED_PORTS) {
5891
+ denied.add(entry.port);
5892
+ const raw = entry.env ? env[entry.env] : void 0;
5893
+ const port = entry.kind && raw ? portFromSetting(entry.kind, raw) : null;
5894
+ if (port !== null) denied.add(port);
5895
+ }
5896
+ for (const raw of (env[PREVIEW_DENY_PORTS_ENV] ?? "").split(",")) {
5897
+ if (!raw.trim()) continue;
5898
+ const port = portNumber(raw.trim());
5899
+ if (port === null) {
5900
+ throw new PreviewCommandError(
5901
+ `${PREVIEW_DENY_PORTS_ENV} has an entry that is not a port (${JSON.stringify(raw.trim().slice(0, 20))}), so no port is allowed until it is fixed.`,
5902
+ PREVIEW_EXIT_PORT_NOT_ALLOWED
5903
+ );
5904
+ }
5905
+ denied.add(port);
5906
+ }
5907
+ return denied;
5908
+ }
5909
+ function parsePreviewPort(raw, env = process.env) {
5910
+ const value = typeof raw === "number" ? raw : Number(String(raw ?? "").trim());
5911
+ if (!Number.isInteger(value) || value < MIN_PREVIEW_PORT || value > MAX_PREVIEW_PORT) {
5912
+ throw new PreviewCommandError(
5913
+ `Port must be a number from ${MIN_PREVIEW_PORT} to ${MAX_PREVIEW_PORT}.`,
5914
+ PREVIEW_EXIT_PORT_NOT_ALLOWED
5915
+ );
5916
+ }
5917
+ if (reservedPreviewPorts(env).has(value)) {
5918
+ throw new PreviewCommandError(
5919
+ `Port ${value} is reserved for Instafy and cannot be previewed. Serve the site on another port.`,
5920
+ PREVIEW_EXIT_PORT_NOT_ALLOWED
5921
+ );
5922
+ }
5923
+ return value;
5924
+ }
5925
+ function noAnswerMessage(port, probe, where, retry) {
5926
+ if (probe.timedOut) {
5927
+ return `A server accepted the connection on port ${port} but did not answer (${probe.reason ?? "no answer"}). It may still be starting or compiling: do not start a second server; wait for it, then ${retry}.`;
5928
+ }
5929
+ return `Nothing is answering on ${where} (${probe.reason ?? "no answer"}). Start the site first, then ${retry}.`;
5930
+ }
5931
+ var LOOPBACK_HOSTS2 = ["127.0.0.1", "::1"];
5932
+ function urlHost(host) {
5933
+ return host.includes(":") ? `[${host}]` : host;
5934
+ }
5935
+ function probeHost(host, port, sitePath, timeoutMs) {
5936
+ return new Promise((resolve) => {
5937
+ const request = http2.get({ host, port, path: sitePath, agent: false, timeout: timeoutMs }, (response) => {
5938
+ const status = response.statusCode ?? 0;
5939
+ response.resume();
5940
+ request.destroy();
5941
+ resolve({ listening: status > 0 && status < 500, status, reason: `HTTP ${status}`, host: urlHost(host) });
5942
+ });
5943
+ request.on("timeout", () => {
5944
+ request.destroy();
5945
+ resolve({ listening: false, status: null, reason: `no answer within ${timeoutMs / 1e3} s`, host: null, code: "TIMEOUT" });
5946
+ });
5947
+ request.on("error", (error) => {
5948
+ const code = error.code;
5949
+ resolve({ listening: false, status: null, reason: code ?? error.message, host: null, code });
5950
+ });
5951
+ });
5952
+ }
5953
+ async function probeLocalSite(port, sitePath, timeoutMs = PROBE_TIMEOUT_MS, hosts = LOOPBACK_HOSTS2) {
5954
+ const tried = [];
5955
+ let last = { listening: false, status: null, reason: "no answer", host: null };
5956
+ for (const host of hosts) {
5957
+ const { code, ...result } = await probeHost(host, port, sitePath, timeoutMs);
5958
+ if (result.status !== null) return result;
5959
+ tried.push(`${urlHost(host)}: ${result.reason}`);
5960
+ last = { ...result, reason: tried.join(", ") };
5961
+ if (code === "TIMEOUT") return { ...last, timedOut: true };
5962
+ }
5963
+ return last;
5964
+ }
5965
+
5966
+ // src/preview.ts
5967
+ var PREVIEW_EXIT_UNAVAILABLE = 5;
5968
+ var PREVIEWS_UNAVAILABLE_MESSAGE = "Previews are not available on this server";
5969
+ var RATHOLE_LOCAL_HOSTS = ["127.0.0.1"];
5970
+ var MAX_TITLE_LENGTH = 120;
5971
+ var MAX_PATH_LENGTH = 512;
5972
+ var STATE_LOCK_WAIT_MS = 12e4;
5973
+ var STATE_LOCK_STALE_MS = 10 * 6e4;
5974
+ function instafyDir() {
5975
+ return path10.join(os5.homedir(), ".instafy");
5976
+ }
5977
+ function previewStateFile() {
5978
+ return path10.join(instafyDir(), "cli-preview-state.json");
5979
+ }
5980
+ function previewStateLockFile() {
5981
+ return path10.join(instafyDir(), "cli-preview-state.lock");
5982
+ }
5983
+ function workdirsRoot() {
5984
+ return path10.join(instafyDir(), "cli-preview-workdirs");
5985
+ }
5986
+ function workdirFor(previewId) {
5987
+ return path10.join(workdirsRoot(), safePathSegment2(previewId));
5988
+ }
5989
+ var HOST_SUFFIX_RE = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*(?::\d{1,5})?$/;
5990
+ function parseViewerHostSuffix(raw) {
5991
+ if (typeof raw !== "string") return void 0;
5992
+ const suffix = raw.trim().toLowerCase();
5993
+ return suffix.length <= 253 && HOST_SUFFIX_RE.test(suffix) ? suffix : void 0;
5994
+ }
5995
+ function parseStudioOrigin(raw) {
5996
+ if (typeof raw !== "string" || raw.length > 300) return void 0;
5997
+ let url;
5998
+ try {
5999
+ url = new URL(raw.trim());
6000
+ } catch {
6001
+ return void 0;
6002
+ }
6003
+ if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
6004
+ if (url.username || url.password || url.search || url.hash || url.pathname !== "/" && url.pathname !== "") return void 0;
6005
+ return url.origin;
6006
+ }
6007
+ var CONTROL_CHARS_RE = /[\u0000-\u001f\u007f-\u009f]/;
6008
+ function parsePreviewTitle(raw) {
6009
+ const title = (raw ?? "").replace(/\s+/g, " ").trim();
6010
+ if (!title) {
6011
+ throw new Error('A title is required: --title "<name>".');
6012
+ }
6013
+ if ([...title].length > MAX_TITLE_LENGTH) {
6014
+ throw new Error(`Title must be at most ${MAX_TITLE_LENGTH} characters.`);
6015
+ }
6016
+ if (CONTROL_CHARS_RE.test(title)) {
6017
+ throw new Error("Title must not contain control characters.");
6018
+ }
6019
+ return title;
6020
+ }
6021
+ function parsePreviewPath(raw) {
6022
+ const value = raw?.trim() || "/";
6023
+ let printable = true;
6024
+ for (let index = 0; index < value.length; index += 1) {
6025
+ const code = value.charCodeAt(index);
6026
+ if (code < 33 || code > 126 || code === 92 || code === 35) {
6027
+ printable = false;
6028
+ break;
6029
+ }
6030
+ }
6031
+ if (!value.startsWith("/") || value.startsWith("//") || value.length > MAX_PATH_LENGTH || !printable) {
6032
+ throw new Error(
6033
+ `Path must start with / (not //) and be at most ${MAX_PATH_LENGTH} printable ASCII characters with no spaces, backslashes or #.`
6034
+ );
6035
+ }
6036
+ return value;
6037
+ }
6038
+ function readStateFile2() {
6039
+ try {
6040
+ const parsed = JSON.parse(fs9.readFileSync(previewStateFile(), "utf8"));
6041
+ if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.previews)) {
6042
+ return { version: 1, previews: [] };
6043
+ }
6044
+ return { version: 1, previews: parsed.previews.filter(Boolean) };
6045
+ } catch {
6046
+ return { version: 1, previews: [] };
6047
+ }
6048
+ }
6049
+ function writeStateFile2(state) {
6050
+ fs9.mkdirSync(instafyDir(), { recursive: true });
6051
+ fs9.writeFileSync(previewStateFile(), JSON.stringify(state, null, 2), { encoding: "utf8", mode: 384 });
6052
+ }
6053
+ function upsertPreviewState(entry) {
6054
+ const state = readStateFile2();
6055
+ const next = state.previews.filter((preview) => preview.previewId !== entry.previewId);
6056
+ next.unshift(entry);
6057
+ writeStateFile2({ version: 1, previews: next });
6058
+ }
6059
+ function removePreviewState(previewId) {
6060
+ const state = readStateFile2();
6061
+ const existing = state.previews.find((preview) => preview.previewId === previewId) ?? null;
6062
+ writeStateFile2({ version: 1, previews: state.previews.filter((preview) => preview.previewId !== previewId) });
6063
+ return existing;
6064
+ }
6065
+ function removeWorkdir(previewId) {
6066
+ fs9.rmSync(workdirFor(previewId), { recursive: true, force: true });
6067
+ }
6068
+ function forgetPreview(previewId) {
6069
+ const entry = removePreviewState(previewId);
6070
+ removeWorkdir(previewId);
6071
+ return entry;
6072
+ }
6073
+ function sleep(ms) {
6074
+ return new Promise((resolve) => setTimeout(resolve, ms));
6075
+ }
6076
+ function lockHolderIsGone(lockFile) {
6077
+ try {
6078
+ const stat = fs9.statSync(lockFile);
6079
+ if (Date.now() - stat.mtimeMs > STATE_LOCK_STALE_MS) return true;
6080
+ const pid = Number.parseInt(fs9.readFileSync(lockFile, "utf8").trim(), 10);
6081
+ if (!Number.isInteger(pid) || pid <= 0) return Date.now() - stat.mtimeMs > 5e3;
6082
+ try {
6083
+ process.kill(pid, 0);
6084
+ return false;
6085
+ } catch (error) {
6086
+ return error.code === "ESRCH";
6087
+ }
6088
+ } catch {
6089
+ return false;
6090
+ }
6091
+ }
6092
+ async function withStateLock(fn) {
6093
+ const lockFile = previewStateLockFile();
6094
+ fs9.mkdirSync(instafyDir(), { recursive: true });
6095
+ const deadline = Date.now() + STATE_LOCK_WAIT_MS;
6096
+ for (; ; ) {
6097
+ try {
6098
+ const fd = fs9.openSync(lockFile, "wx", 384);
6099
+ fs9.writeSync(fd, String(process.pid));
6100
+ fs9.closeSync(fd);
6101
+ break;
6102
+ } catch (error) {
6103
+ if (error.code !== "EEXIST") throw error;
6104
+ if (lockHolderIsGone(lockFile)) {
6105
+ fs9.rmSync(lockFile, { force: true });
6106
+ continue;
6107
+ }
6108
+ if (Date.now() > deadline) {
6109
+ throw new Error("Another instafy preview command on this machine is still running. Retry when it is done.");
6110
+ }
6111
+ await sleep(100);
6112
+ }
6113
+ }
6114
+ try {
6115
+ return await fn();
6116
+ } finally {
6117
+ fs9.rmSync(lockFile, { force: true });
6118
+ }
6119
+ }
6120
+ function configPathFor(workdir) {
6121
+ return path10.join(workdir, "rathole.toml");
6122
+ }
6123
+ async function stopClient(entry) {
6124
+ await stopRatholeClient(entry.pid, configPathFor(entry.workdir), entry.binary);
6125
+ }
6126
+ function shellQuote2(value) {
6127
+ return `'${value.split("'").join(`'\\''`)}'`;
6128
+ }
6129
+ function safePathSegment2(value) {
6130
+ return value.trim().replace(/[^a-zA-Z0-9._-]/g, "_") || "preview";
6131
+ }
6132
+ function stringField(record2, ...keys) {
6133
+ for (const key of keys) {
6134
+ const value = record2[key];
6135
+ if (typeof value === "string" && value.trim()) return value;
6136
+ }
6137
+ return null;
6138
+ }
6139
+ function numberField(record2, ...keys) {
6140
+ for (const key of keys) {
6141
+ const value = record2[key];
6142
+ if (typeof value === "number" && Number.isFinite(value)) return value;
6143
+ }
6144
+ return null;
6145
+ }
6146
+ function previewsUnavailable() {
6147
+ return new PreviewCommandError(`${PREVIEWS_UNAVAILABLE_MESSAGE}.`, PREVIEW_EXIT_UNAVAILABLE);
6148
+ }
6149
+ async function controllerRequest(params) {
6150
+ const { response } = await fetchWithControllerAuth({
6151
+ url: `${cleanUrl(params.controllerUrl)}${params.route}`,
6152
+ init: {
6153
+ method: params.method,
6154
+ headers: {
6155
+ accept: "application/json",
6156
+ ...params.body === void 0 ? {} : { "content-type": "application/json" }
6157
+ },
6158
+ body: params.body === void 0 ? void 0 : JSON.stringify(params.body)
6159
+ },
6160
+ accessToken: params.auth.token,
6161
+ tokenSource: params.auth.source,
6162
+ profile: params.auth.profile,
6163
+ cwd: process.cwd()
6164
+ });
6165
+ const text2 = await response.text().catch(() => "");
6166
+ if (!response.ok) {
6167
+ const code = errorCode(text2);
6168
+ if (response.status === 503 && code === "previews_unavailable" || response.status === 404 && params.notFoundMeansUnavailable && !isStructuredError(text2)) {
6169
+ throw previewsUnavailable();
6170
+ }
6171
+ if (response.status === 401 || response.status === 403) {
6172
+ throw formatAuthRejectedError({
6173
+ status: response.status,
6174
+ responseBody: text2,
6175
+ retryCommand: params.retryCommand
6176
+ });
6177
+ }
6178
+ if (response.status === 400 && code === "port_not_allowed") {
6179
+ throw new PreviewCommandError(
6180
+ `The server does not allow previews on this port: ${extractControllerErrorMessage(text2) ?? "port_not_allowed"}`,
6181
+ PREVIEW_EXIT_PORT_NOT_ALLOWED
6182
+ );
6183
+ }
6184
+ const message = extractControllerErrorMessage(text2) ?? response.statusText;
6185
+ throw new Error(`Preview request failed (${response.status}): ${message}`);
6186
+ }
6187
+ if (!text2.trim()) return { status: response.status, body: null };
6188
+ try {
6189
+ return { status: response.status, body: JSON.parse(text2) };
6190
+ } catch {
6191
+ throw new Error(`Preview request returned invalid JSON (${response.status}).`);
6192
+ }
6193
+ }
6194
+ function isStructuredError(body) {
6195
+ try {
6196
+ const parsed = JSON.parse(body);
6197
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
6198
+ const record2 = parsed;
6199
+ return ["message", "code", "error"].some((key) => typeof record2[key] === "string" && String(record2[key]).trim() !== "");
6200
+ } catch {
6201
+ return false;
6202
+ }
6203
+ }
6204
+ function errorCode(body) {
6205
+ try {
6206
+ const parsed = JSON.parse(body);
6207
+ for (const key of ["code", "error"]) {
6208
+ const value = parsed?.[key];
6209
+ if (typeof value === "string" && /^[a-z0-9_]{1,64}$/.test(value)) return value;
6210
+ }
6211
+ } catch {
6212
+ }
6213
+ return null;
6214
+ }
6215
+ function resolveAuth(opts, retryCommand) {
6216
+ const auth = resolveControllerToken({ controllerToken: opts.controllerToken }, retryCommand);
6217
+ const controllerUrl = resolveTunnelControllerUrl({ controllerUrl: opts.controllerUrl }, auth);
6218
+ return { auth, controllerUrl };
6219
+ }
6220
+ function previewsRoute(projectId2, previewId, action) {
6221
+ const base = `/projects/${encodeURIComponent(projectId2)}/previews`;
6222
+ if (!previewId) return base;
6223
+ return `${base}/${encodeURIComponent(previewId)}${action ? `/${action}` : ""}`;
6224
+ }
6225
+ async function closePreviewOnServer(controllerUrl, auth, projectId2, previewId) {
6226
+ await controllerRequest({
6227
+ controllerUrl,
6228
+ auth,
6229
+ method: "POST",
6230
+ route: previewsRoute(projectId2, previewId, "close"),
6231
+ body: {},
6232
+ retryCommand: `instafy preview close ${previewId}`
6233
+ });
6234
+ }
6235
+ async function resolveRathole(ratholeBin, log) {
6236
+ if (ratholeBin) {
6237
+ process.env.RATHOLE_BIN = ratholeBin;
6238
+ }
6239
+ const rathole = await resolveRatholeBinaryForCli({
6240
+ env: process.env,
6241
+ version: process.env.RATHOLE_VERSION ?? null,
6242
+ cacheDir: process.env.RATHOLE_CACHE_DIR ?? null,
6243
+ logger: (message) => log(`[rathole] ${message}`),
6244
+ warn: (message) => console.error(kleur8.yellow(message))
6245
+ });
6246
+ if (!rathole) {
6247
+ throw new Error("rathole is required to open a preview. Set RATHOLE_BIN or ensure it is on PATH.");
6248
+ }
6249
+ return rathole;
6250
+ }
6251
+ function readFileOrNull(file) {
6252
+ try {
6253
+ return fs9.readFileSync(file, "utf8");
6254
+ } catch {
6255
+ return null;
6256
+ }
6257
+ }
6258
+ function readyHealth(readyBody) {
6259
+ return readyBody["health"] && typeof readyBody["health"] === "object" && !Array.isArray(readyBody["health"]) ? readyBody["health"] : null;
6260
+ }
6261
+ function previewStatusFromHealth(readyBody) {
6262
+ const health = readyHealth(readyBody);
6263
+ if (health?.["listening"] === true) return "live";
6264
+ if (health?.["listening"] === false) return "not_responding";
6265
+ return "unknown";
6266
+ }
6267
+ function previewHttpStatusFromHealth(readyBody) {
6268
+ const status = readyHealth(readyBody)?.["status"];
6269
+ return typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599 ? status : null;
6270
+ }
6271
+ async function notListeningError(port, sitePath, probe) {
6272
+ const where = `http://127.0.0.1:${port}${sitePath}`;
6273
+ if (probe.status !== null) {
6274
+ return new PreviewCommandError(
6275
+ `The site at ${where} answered HTTP ${probe.status}. Fix the server error, then retry.`,
6276
+ PREVIEW_EXIT_NOT_LISTENING
6277
+ );
6278
+ }
6279
+ if (probe.timedOut) {
6280
+ return new PreviewCommandError(noAnswerMessage(port, probe, where, "retry"), PREVIEW_EXIT_NOT_LISTENING);
6281
+ }
6282
+ const ipv6 = await probeLocalSite(port, sitePath, void 0, ["::1"]);
6283
+ if (ipv6.status !== null) {
6284
+ return new PreviewCommandError(
6285
+ `The site answers only on [::1]:${port}, and the preview reaches it on 127.0.0.1. It is running: do not start it again. Bind it to 127.0.0.1 or 0.0.0.0 instead (for example --host 127.0.0.1), restart it, then retry.`,
6286
+ PREVIEW_EXIT_NOT_LISTENING
6287
+ );
6288
+ }
6289
+ return new PreviewCommandError(noAnswerMessage(port, probe, where, "retry"), PREVIEW_EXIT_NOT_LISTENING);
6290
+ }
6291
+ var SERVE_MAX_ARGS = 64;
6292
+ var SERVE_MAX_ARG_LENGTH = 2048;
6293
+ var EPHEMERAL_PREFIXES = ["/tmp/", "/var/tmp/", "/dev/shm/", "/run/"];
6294
+ function parseServeCommand(raw) {
6295
+ const argv = [];
6296
+ let current = "";
6297
+ let inWord = false;
6298
+ let quote = null;
6299
+ const text2 = String(raw ?? "");
6300
+ for (let index = 0; index < text2.length; index += 1) {
6301
+ const ch = text2[index];
6302
+ if (quote === "'") {
6303
+ if (ch === "'") quote = null;
6304
+ else current += ch;
6305
+ continue;
6306
+ }
6307
+ if (quote === '"') {
6308
+ if (ch === '"') {
6309
+ quote = null;
6310
+ } else if (ch === "\\" && index + 1 < text2.length && '"\\$`'.includes(text2[index + 1])) {
6311
+ current += text2[index + 1];
6312
+ index += 1;
6313
+ } else if (ch === "$" || ch === "`") {
6314
+ throw new Error(`--serve-cmd takes one command and its arguments, not shell syntax (found ${ch} in quotes).`);
6315
+ } else {
6316
+ current += ch;
6317
+ }
6318
+ continue;
6319
+ }
6320
+ if (ch === "'" || ch === '"') {
6321
+ quote = ch;
6322
+ inWord = true;
6323
+ continue;
6324
+ }
6325
+ if (ch === "\\") {
6326
+ if (index + 1 >= text2.length) throw new Error("--serve-cmd ends with a lone backslash.");
6327
+ current += text2[index + 1];
6328
+ index += 1;
6329
+ inWord = true;
6330
+ continue;
6331
+ }
6332
+ if (/\s/.test(ch)) {
6333
+ if (ch === "\n" || ch === "\r") {
6334
+ throw new Error("--serve-cmd takes one command on one line.");
6335
+ }
6336
+ if (inWord) {
6337
+ argv.push(current);
6338
+ current = "";
6339
+ inWord = false;
6340
+ }
6341
+ continue;
6342
+ }
6343
+ if ("|&;<>()$`".includes(ch)) {
6344
+ throw new Error(
6345
+ `--serve-cmd takes one command and its arguments, not shell syntax (found ${ch}). Put a pipeline or several commands in a script and pass the script.`
6346
+ );
6347
+ }
6348
+ current += ch;
6349
+ inWord = true;
6350
+ }
6351
+ if (quote) throw new Error("--serve-cmd has an unterminated quote.");
6352
+ if (inWord) argv.push(current);
6353
+ if (argv.length === 0) throw new Error("--serve-cmd is empty.");
6354
+ if (argv.length > SERVE_MAX_ARGS || argv.some((arg) => arg.length > SERVE_MAX_ARG_LENGTH || arg.includes("\0"))) {
6355
+ throw new Error(`--serve-cmd must have at most ${SERVE_MAX_ARGS} arguments of at most ${SERVE_MAX_ARG_LENGTH} characters.`);
6356
+ }
6357
+ return argv;
6358
+ }
6359
+ function realDir(dir) {
6360
+ try {
6361
+ const resolved = fs9.realpathSync(dir);
6362
+ return fs9.statSync(resolved).isDirectory() ? resolved : null;
6363
+ } catch {
6364
+ return null;
6365
+ }
6366
+ }
6367
+ function resolveWorkspaceRoot(env, projectId2) {
6368
+ const explicit = env["WORKSPACE_PROJECT_DIR"]?.trim();
6369
+ if (explicit) return realDir(explicit);
6370
+ const base = env["WORKSPACE_DIR"]?.trim();
6371
+ if (!base || !projectId2) return null;
6372
+ return realDir(path10.join(base, projectId2));
6373
+ }
6374
+ function workspaceRelativeDir(root, dir) {
6375
+ const resolved = realDir(dir);
6376
+ if (!resolved) return null;
6377
+ const relative = path10.relative(root, resolved);
6378
+ if (relative === "") return ".";
6379
+ if (relative.startsWith("..") || path10.isAbsolute(relative)) return null;
6380
+ return relative.split(path10.sep).join("/");
6381
+ }
6382
+ function listeningSocketInodes(procRoot, port) {
6383
+ const inodes = /* @__PURE__ */ new Set();
6384
+ for (const table of ["net/tcp", "net/tcp6"]) {
6385
+ let text2 = "";
6386
+ try {
6387
+ text2 = fs9.readFileSync(path10.join(procRoot, table), "utf8");
6388
+ } catch {
6389
+ continue;
6390
+ }
6391
+ for (const line of text2.split("\n").slice(1)) {
6392
+ const fields = line.trim().split(/\s+/);
6393
+ if (fields.length < 10) continue;
6394
+ const local = fields[1] ?? "";
6395
+ const portHex = local.slice(local.lastIndexOf(":") + 1);
6396
+ if (fields[3] === "0A" && Number.parseInt(portHex, 16) === port && fields[9] !== "0") {
6397
+ inodes.add(fields[9]);
6398
+ }
6399
+ }
6400
+ }
6401
+ return inodes;
6402
+ }
6403
+ function detectListeningProcess(port, procRoot = "/proc") {
6404
+ const inodes = listeningSocketInodes(procRoot, port);
6405
+ if (inodes.size === 0) return null;
6406
+ let pids = [];
6407
+ try {
6408
+ pids = fs9.readdirSync(procRoot).filter((name) => /^\d+$/.test(name)).map(Number).sort((a, b) => a - b);
6409
+ } catch {
6410
+ return null;
6411
+ }
6412
+ for (const pid of pids) {
6413
+ let fds = [];
6414
+ try {
6415
+ fds = fs9.readdirSync(path10.join(procRoot, String(pid), "fd"));
6416
+ } catch {
6417
+ continue;
6418
+ }
6419
+ const holds = fds.some((fd) => {
6420
+ try {
6421
+ const target = fs9.readlinkSync(path10.join(procRoot, String(pid), "fd", fd));
6422
+ const match = /^socket:\[(\d+)\]$/.exec(target);
6423
+ return Boolean(match && inodes.has(match[1]));
6424
+ } catch {
6425
+ return false;
6426
+ }
6427
+ });
6428
+ if (!holds) continue;
6429
+ try {
6430
+ const argv = fs9.readFileSync(path10.join(procRoot, String(pid), "cmdline"), "utf8").split("\0");
6431
+ while (argv.length > 0 && argv[argv.length - 1] === "") argv.pop();
6432
+ const cwd = fs9.readlinkSync(path10.join(procRoot, String(pid), "cwd"));
6433
+ if (argv.length === 0) continue;
6434
+ return { pid, argv, cwd };
6435
+ } catch {
6436
+ continue;
6437
+ }
6438
+ }
6439
+ return null;
6440
+ }
6441
+ var SECRET_FLAG = /^-{1,2}(?!-|no-)[\w.-]*(token|secret|passw(or)?d|passwd|api[-_]?key|access[-_]?key|private[-_]?key|credentials?|auth(?![a-z]))[\w.-]*$/i;
6442
+ var SECRET_ENV = /^(?=[A-Za-z_])\w*(TOKEN|SECRET|PASSWORD|PASSWD|API_?KEY|ACCESS_?KEY|PRIVATE_?KEY|CREDENTIALS?)[\w]*=./i;
6443
+ var SECRET_VALUE = /^(sk|pk|rk)[-_][A-Za-z0-9_-]{12,}|^gh[pousr]_[A-Za-z0-9]{20,}|^xox[abposr]-|^AKIA[0-9A-Z]{12,}|^eyJ[A-Za-z0-9_-]{20,}\./;
6444
+ var URL_USERINFO = /^[a-z][a-z0-9+.-]*:\/\/[^/\s@]*:[^/\s@]*@/i;
6445
+ function secretInServeCommand(argv) {
6446
+ for (let i = 0; i < argv.length; i += 1) {
6447
+ const arg = argv[i];
6448
+ const eq = arg.indexOf("=");
6449
+ const flag = arg.startsWith("-") ? eq > 0 ? arg.slice(0, eq) : arg : null;
6450
+ if (flag && SECRET_FLAG.test(flag)) {
6451
+ const value = eq > 0 ? arg.slice(eq + 1) : argv[i + 1];
6452
+ if (value && !value.startsWith("-")) return flag;
6453
+ }
6454
+ if (SECRET_ENV.test(arg)) return arg.slice(0, arg.indexOf("="));
6455
+ const bare = eq > 0 && arg.startsWith("-") ? arg.slice(eq + 1) : arg;
6456
+ if (URL_USERINFO.test(bare)) return "a URL with a password";
6457
+ if (SECRET_VALUE.test(bare)) return "a key";
6458
+ }
6459
+ return null;
6460
+ }
6461
+ function parseExplicitServeCommand(raw) {
6462
+ const argv = parseServeCommand(raw);
6463
+ const secret = secretInServeCommand(argv);
6464
+ if (secret) {
6465
+ throw new Error(
6466
+ `--serve-cmd carries what looks like a secret (${secret}), which is not recorded. Have the site read it from an env file and pass the command without it.`
6467
+ );
6468
+ }
6469
+ return argv;
6470
+ }
6471
+ function resolvePreviewServe(params) {
6472
+ const env = params.env ?? process.env;
6473
+ const root = resolveWorkspaceRoot(env, params.projectId);
6474
+ const explicit = params.serveCmd?.trim();
6475
+ if (explicit) {
6476
+ const argv = parseExplicitServeCommand(explicit);
6477
+ if (!root) {
6478
+ throw new Error("--serve-cmd needs the space's workspace (WORKSPACE_DIR); run it inside the space.");
6479
+ }
6480
+ const cwd2 = workspaceRelativeDir(root, params.cwd ?? process.cwd());
6481
+ if (!cwd2) {
6482
+ throw new Error(`--serve-cmd runs from the current directory, which must be inside the space's workspace (${root}).`);
6483
+ }
6484
+ return { serve: { argv, cwd: cwd2 } };
6485
+ }
6486
+ const notRestartable = (note) => ({ serve: null, note });
6487
+ const platform = params.platform ?? process.platform;
6488
+ if (platform !== "linux" && !params.procRoot) {
6489
+ return notRestartable("the start command can only be read on a Linux space; pass --serve-cmd");
6490
+ }
6491
+ if (!root) return notRestartable("this is not running inside a space's workspace");
6492
+ const found = detectListeningProcess(params.port, params.procRoot);
6493
+ if (!found) return notRestartable(`could not see which process listens on port ${params.port}; pass --serve-cmd`);
6494
+ const cwd = workspaceRelativeDir(root, found.cwd);
6495
+ if (!cwd) {
6496
+ return notRestartable(`the site runs from ${found.cwd}, outside the workspace, which does not survive a sleep`);
6497
+ }
6498
+ const ephemeral = found.argv.find((arg) => EPHEMERAL_PREFIXES.some((prefix) => arg.startsWith(prefix)));
6499
+ if (ephemeral) {
6500
+ return notRestartable(`its command uses ${ephemeral}, which does not survive a sleep; pass --serve-cmd`);
6501
+ }
6502
+ if (found.argv.length > SERVE_MAX_ARGS || found.argv.some((arg) => arg.length > SERVE_MAX_ARG_LENGTH)) {
6503
+ return notRestartable("its command line is too long to record; pass --serve-cmd");
6504
+ }
6505
+ const secret = secretInServeCommand(found.argv);
6506
+ if (secret) {
6507
+ return notRestartable(
6508
+ `its command line carries what looks like a secret (${secret}), which is not recorded; read it from an env file and pass --serve-cmd`
6509
+ );
6510
+ }
6511
+ return { serve: { argv: found.argv, cwd } };
6512
+ }
6513
+ var START_PORT_WAIT_MS = 6e4;
6514
+ var START_POLL_MS = 300;
6515
+ var START_PROBE_TIMEOUT_MS = 2e3;
6516
+ var START_LOGS_DIR = path10.join(".instafy", "agent", "logs");
6517
+ function displayPath(file, cwd = process.cwd()) {
6518
+ const relative = path10.relative(cwd, file);
6519
+ return relative && !relative.startsWith("..") && !path10.isAbsolute(relative) ? relative : file;
6520
+ }
6521
+ async function startPreviewSite(params) {
6522
+ const { port, sitePath, serve, root } = params;
6523
+ const credentialFreeEnv = await loadCredentialFreeEnv();
6524
+ const env = credentialFreeEnv(params.env ?? process.env);
6525
+ const cwd = path10.join(root, serve.cwd);
6526
+ const logFile = path10.join(root, START_LOGS_DIR, `preview-${port}.log`);
6527
+ fs9.mkdirSync(path10.dirname(logFile), { recursive: true });
6528
+ const fd = fs9.openSync(logFile, "a", 384);
6529
+ let exit = null;
6530
+ let spawnError = null;
6531
+ let pid;
6532
+ try {
6533
+ fs9.writeSync(fd, `
6534
+ [instafy preview open --start] ${(/* @__PURE__ */ new Date()).toISOString()} ${JSON.stringify(serve.argv)} in ${serve.cwd}
6535
+ `);
6536
+ const child = spawn5(serve.argv[0], serve.argv.slice(1), {
6537
+ cwd,
6538
+ env,
6539
+ detached: true,
6540
+ stdio: ["ignore", fd, fd]
6541
+ });
6542
+ child.on("error", (error) => {
6543
+ spawnError = error.code ?? error.message;
6544
+ });
6545
+ child.on("exit", (code, signal) => {
6546
+ exit = signal ? `signal ${signal}` : `exit status ${code}`;
6547
+ });
6548
+ child.unref();
6549
+ pid = child.pid;
6550
+ } finally {
6551
+ fs9.closeSync(fd);
6552
+ }
6553
+ await new Promise((resolve) => setImmediate(resolve));
6554
+ const shownLog = displayPath(logFile);
6555
+ const fail = (why) => new PreviewCommandError(`The site ${why} and did not answer on 127.0.0.1:${port}; log: ${shownLog}`, PREVIEW_EXIT_NOT_LISTENING);
6556
+ const deadline = Date.now() + (params.timeoutMs ?? START_PORT_WAIT_MS);
6557
+ for (; ; ) {
6558
+ if (spawnError || pid === void 0) throw fail(`could not be started (${spawnError ?? "no process"})`);
6559
+ if (exit) throw fail(`started with --serve-cmd exited (${exit})`);
6560
+ const probe = await probeLocalSite(port, sitePath, START_PROBE_TIMEOUT_MS, RATHOLE_LOCAL_HOSTS);
6561
+ if (probe.listening) return { pid, logFile };
6562
+ if (exit) throw fail(`started with --serve-cmd exited (${exit})`);
6563
+ if (Date.now() >= deadline) {
6564
+ try {
6565
+ process.kill(-pid, "SIGTERM");
6566
+ } catch {
6567
+ }
6568
+ throw fail(`started with --serve-cmd was stopped after ${Math.round((params.timeoutMs ?? START_PORT_WAIT_MS) / 1e3)} s`);
6569
+ }
6570
+ await new Promise((resolve) => setTimeout(resolve, START_POLL_MS));
6571
+ }
6572
+ }
6573
+ async function createAndStartClient(params) {
6574
+ const { opts, port, title, sitePath, projectId: projectId2, retryCommand, auth, controllerUrl, serverUrl, log, serve } = params;
6575
+ const conversationId = opts.conversationId?.trim() || void 0;
6576
+ const created = await controllerRequest({
6577
+ controllerUrl,
6578
+ auth,
6579
+ method: "POST",
6580
+ route: previewsRoute(projectId2),
6581
+ body: {
6582
+ runtimeId: readEnv("RUNTIME_ID"),
6583
+ runtimeLeaseId: readEnv("RUNTIME_LEASE_ID"),
6584
+ conversationId,
6585
+ port,
6586
+ path: sitePath,
6587
+ title,
6588
+ metadata: { source: "instafy-cli" },
6589
+ ...serve ? { serve } : {}
6590
+ },
6591
+ retryCommand,
6592
+ notFoundMeansUnavailable: true
6593
+ });
6594
+ const grant = created.body && !Array.isArray(created.body) ? created.body : {};
6595
+ const previewId = stringField(grant, "previewId");
6596
+ if (!previewId) {
6597
+ throw new Error("Preview response is missing previewId.");
6598
+ }
6599
+ const workdir = workdirFor(previewId);
6600
+ const logFile = path10.join(instafyDir(), "cli-preview-logs", `preview-${safePathSegment2(previewId)}.log`);
6601
+ const configPath2 = configPathFor(workdir);
6602
+ const hostSuffix = parseViewerHostSuffix(grant["viewerHostSuffix"]);
6603
+ const studioOrigin = parseStudioOrigin(grant["studioOrigin"]);
6604
+ const state = readStateFile2();
6605
+ const current = state.previews.find((preview) => preview.previewId === previewId) ?? null;
6606
+ let pid = null;
6607
+ let binary = current?.binary;
6608
+ try {
6609
+ const credentials = grant["credentials"] && typeof grant["credentials"] === "object" ? grant["credentials"] : {};
6610
+ const configBody = buildRatholeConfig(credentials, port);
6611
+ fs9.mkdirSync(workdir, { recursive: true });
6612
+ fs9.mkdirSync(path10.dirname(logFile), { recursive: true });
6613
+ if (current && readFileOrNull(configPath2) === configBody && isRatholeClientFor(current.pid, configPath2, current.binary)) {
6614
+ pid = current.pid;
6615
+ }
6616
+ for (const stale of state.previews) {
6617
+ if (stale.previewId === previewId) {
6618
+ if (pid === null) await stopClient(stale);
6619
+ continue;
6620
+ }
6621
+ if (stale.localPort !== port) continue;
6622
+ await stopClient(stale);
6623
+ forgetPreview(stale.previewId);
6624
+ if (cleanUrl(stale.controllerUrl) === serverUrl) {
6625
+ await closePreviewOnServer(controllerUrl, auth, stale.projectId, stale.previewId).catch(() => {
6626
+ });
6627
+ }
6628
+ }
6629
+ if (pid === null) {
6630
+ const rathole = await resolveRathole(opts.ratholeBin, log);
6631
+ fs9.writeFileSync(configPath2, configBody, { encoding: "utf8", mode: 384 });
6632
+ binary = rathole;
6633
+ const started = await spawnRatholeDetached({ rathole, configPath: configPath2, workdir, logFile });
6634
+ if (started.exit !== null) {
6635
+ const suffix = started.logTail ? `
6636
+
6637
+ Last logs:
6638
+ ${started.logTail}` : "";
6639
+ throw new Error(`Preview tunnel exited (${started.exit}).${suffix}`);
6640
+ }
6641
+ pid = started.pid;
6642
+ } else {
6643
+ log(`Reusing the running preview client (pid ${pid}).`);
6644
+ }
6645
+ } catch (error) {
6646
+ if (pid !== null) await stopRatholeClient(pid, configPath2, binary);
6647
+ forgetPreview(previewId);
6648
+ await closePreviewOnServer(controllerUrl, auth, projectId2, previewId).catch(() => {
6649
+ });
6650
+ const message = error instanceof Error ? error.message : String(error);
6651
+ const [first, ...rest] = message.split("\n");
6652
+ const closed = [`${first} The preview was closed.`, ...rest].join("\n");
6653
+ if (error instanceof PreviewCommandError) throw new PreviewCommandError(closed, error.exitCode);
6654
+ throw new Error(closed);
6655
+ }
6656
+ const entry = {
6657
+ previewId,
6658
+ projectId: projectId2,
6659
+ title,
6660
+ localPort: port,
6661
+ path: sitePath,
6662
+ controllerUrl: serverUrl,
6663
+ pid,
6664
+ logFile,
6665
+ workdir,
6666
+ startedAt: current?.pid === pid ? current.startedAt : (/* @__PURE__ */ new Date()).toISOString(),
6667
+ ...binary ? { binary } : {},
6668
+ ...hostSuffix ? { hostSuffix } : {},
6669
+ ...studioOrigin ? { studioOrigin } : {}
6670
+ };
6671
+ upsertPreviewState(entry);
6672
+ return { grant, previewId, entry, current };
6673
+ }
6674
+ async function previewOpen(opts) {
6675
+ const port = parsePreviewPort(opts.port);
6676
+ const title = parsePreviewTitle(opts.title);
6677
+ const sitePath = parsePreviewPath(opts.path);
6678
+ if (opts.serveCmd?.trim()) parseExplicitServeCommand(opts.serveCmd);
6679
+ const projectId2 = resolveProject({ project: opts.project });
6680
+ const retryCommand = `instafy preview open --port ${port} --title ${shellQuote2(title)}`;
6681
+ const { auth, controllerUrl } = resolveAuth(opts, retryCommand);
6682
+ const serverUrl = cleanUrl(controllerUrl);
6683
+ const log = (message) => console.error(kleur8.cyan(message));
6684
+ const explicitServe = opts.start && opts.serveCmd?.trim() ? resolvePreviewServe({ port, serveCmd: opts.serveCmd, projectId: projectId2, cwd: opts.cwd }).serve : null;
6685
+ let probe = await probeLocalSite(port, sitePath, void 0, RATHOLE_LOCAL_HOSTS);
6686
+ let started = null;
6687
+ if (!probe.listening && opts.start && probe.status === null && !probe.timedOut) {
6688
+ const ipv6 = await probeLocalSite(port, sitePath, void 0, ["::1"]);
6689
+ if (ipv6.status === null && !ipv6.timedOut) {
6690
+ if (!explicitServe) {
6691
+ throw new PreviewCommandError(
6692
+ `Nothing answers on 127.0.0.1:${port}, and --start needs --serve-cmd "<command>" to start the site.`,
6693
+ PREVIEW_EXIT_NOT_LISTENING
6694
+ );
6695
+ }
6696
+ const root = resolveWorkspaceRoot(process.env, projectId2);
6697
+ started = await startPreviewSite({ port, sitePath, serve: explicitServe, root, timeoutMs: opts.startTimeoutMs });
6698
+ log(`Started the site (pid ${started.pid}); log: ${displayPath(started.logFile)}`);
6699
+ probe = await probeLocalSite(port, sitePath, void 0, RATHOLE_LOCAL_HOSTS);
6700
+ }
6701
+ }
6702
+ if (!probe.listening) {
6703
+ throw await notListeningError(port, sitePath, probe);
6704
+ }
6705
+ const { serve, note: restartNote } = resolvePreviewServe({ port, serveCmd: opts.serveCmd, projectId: projectId2, cwd: opts.cwd });
6706
+ const { grant, previewId, entry } = await withStateLock(
6707
+ () => createAndStartClient({ opts, port, title, sitePath, projectId: projectId2, retryCommand, auth, controllerUrl, serverUrl, log, serve })
6708
+ );
6709
+ let ready;
6710
+ try {
6711
+ ready = await controllerRequest({
6712
+ controllerUrl,
6713
+ auth,
6714
+ method: "POST",
6715
+ route: previewsRoute(projectId2, previewId, "ready"),
6716
+ body: {},
6717
+ retryCommand
6718
+ });
6719
+ } catch (error) {
6720
+ if (error instanceof PreviewCommandError && error.exitCode === PREVIEW_EXIT_UNAVAILABLE) {
6721
+ await withStateLock(async () => {
6722
+ await stopClient(entry);
6723
+ forgetPreview(previewId);
6724
+ });
6725
+ }
6726
+ throw error;
6727
+ }
6728
+ const readyBody = ready.body && !Array.isArray(ready.body) ? ready.body : {};
6729
+ const restartable = serve !== null && grant["restartable"] === true;
6730
+ return {
6731
+ previewId,
6732
+ title,
6733
+ port,
6734
+ path: sitePath,
6735
+ status: previewStatusFromHealth(readyBody),
6736
+ httpStatus: previewHttpStatusFromHealth(readyBody),
6737
+ messageId: stringField(readyBody, "messageId"),
6738
+ viewerHostSuffix: parseViewerHostSuffix(grant["viewerHostSuffix"]) ?? null,
6739
+ restartable,
6740
+ ...restartable ? {} : { restartNote: restartNote ?? "the server did not record a start command" },
6741
+ ...opts.start ? { startedPid: started?.pid ?? null, logFile: started?.logFile ?? null } : {}
6742
+ };
6743
+ }
6744
+ function toPreviewSummary(raw) {
6745
+ if (!raw || typeof raw !== "object") return null;
6746
+ const record2 = raw;
6747
+ const previewId = stringField(record2, "previewId", "id");
6748
+ if (!previewId) return null;
6749
+ return {
6750
+ previewId,
6751
+ title: stringField(record2, "title") ?? "",
6752
+ port: numberField(record2, "port", "localPort") ?? 0,
6753
+ path: stringField(record2, "path") ?? "/",
6754
+ status: stringField(record2, "status") ?? "unknown",
6755
+ ...typeof record2["restartable"] === "boolean" ? { restartable: record2["restartable"] } : {}
6756
+ };
6757
+ }
6758
+ async function previewList(opts) {
6759
+ const projectId2 = resolveProject({ project: opts.project });
6760
+ const { auth, controllerUrl } = resolveAuth(opts, "instafy preview list");
6761
+ const listed = await controllerRequest({
6762
+ controllerUrl,
6763
+ auth,
6764
+ method: "GET",
6765
+ route: previewsRoute(projectId2),
6766
+ retryCommand: "instafy preview list",
6767
+ notFoundMeansUnavailable: true
6768
+ });
6769
+ const rows = Array.isArray(listed.body) ? listed.body : listed.body && Array.isArray(listed.body["previews"]) ? listed.body["previews"] : [];
6770
+ return rows.map(toPreviewSummary).filter((row) => row !== null);
6771
+ }
6772
+ async function previewClose(opts) {
6773
+ const previewId = opts.previewId?.trim() ?? "";
6774
+ if (!previewId) {
6775
+ throw new Error("Preview id is required. Use `instafy preview list` to find it.");
6776
+ }
6777
+ const entry = await withStateLock(async () => {
6778
+ const removed = removePreviewState(previewId);
6779
+ if (removed) await stopClient(removed);
6780
+ return removed;
6781
+ });
6782
+ const projectId2 = entry?.projectId ?? resolveProject({ project: opts.project });
6783
+ try {
6784
+ const { auth, controllerUrl } = resolveAuth(
6785
+ { ...opts, controllerUrl: opts.controllerUrl ?? entry?.controllerUrl },
6786
+ `instafy preview close ${previewId}`
6787
+ );
6788
+ await closePreviewOnServer(controllerUrl, auth, projectId2, previewId);
6789
+ } catch (error) {
6790
+ if (entry) await withStateLock(async () => upsertPreviewState({ ...entry, pid: -1 }));
6791
+ throw error;
6792
+ }
6793
+ removeWorkdir(previewId);
6794
+ return { ok: true, previewId };
6795
+ }
6796
+ function printPreviewOpen(result, json) {
6797
+ if (json) {
6798
+ console.log(JSON.stringify(result, null, 2));
6799
+ return;
6800
+ }
6801
+ const label = result.status === "live" ? kleur8.green("Ready to preview") : result.status === "not_responding" ? kleur8.yellow("Not responding yet") : kleur8.yellow("Preview opened (the server did not report whether the site answers)");
6802
+ console.log(`${label}: ${result.title} (port ${result.port}). Open it from the card in the conversation.`);
6803
+ if (result.startedPid) {
6804
+ console.log(kleur8.gray(`Started the site in the background (pid ${result.startedPid}); log: ${displayPath(result.logFile ?? "")}`));
6805
+ } else if (result.startedPid === null) {
6806
+ console.log(kleur8.gray("The site was already running; nothing was started."));
6807
+ }
6808
+ if (result.restartable) {
6809
+ console.log(kleur8.gray("Open starts the site again when the process that started it exits, or the space sleeps."));
6810
+ } else {
6811
+ console.log(
6812
+ kleur8.yellow(
6813
+ `When the process that started it exits, or the space sleeps, Open cannot start the site by itself: ${result.restartNote}.`
6814
+ )
6815
+ );
6816
+ }
6817
+ console.log(kleur8.gray(`previewId=${result.previewId}`));
6818
+ if (result.httpStatus !== null && result.httpStatus >= 400) {
6819
+ console.log(kleur8.yellow(`The site answered HTTP ${result.httpStatus} through the preview.`));
6820
+ }
6821
+ if (result.viewerHostSuffix) {
6822
+ console.log(kleur8.gray(`viewerHostSuffix=${result.viewerHostSuffix}`));
6823
+ }
6824
+ console.log("");
6825
+ console.log("Next:");
6826
+ console.log(`- ${kleur8.cyan("instafy preview list")}`);
6827
+ console.log(`- ${kleur8.cyan(`instafy preview close ${result.previewId}`)}`);
6828
+ }
6829
+ function printPreviewList(previews, json) {
6830
+ if (json) {
6831
+ console.log(JSON.stringify(previews, null, 2));
6832
+ return;
6833
+ }
6834
+ const stoppedNote = "A preview whose machine stopped or restarted is not listed; its card reads Stopped, and Open on a restartable card starts the site again.";
6835
+ if (previews.length === 0) {
6836
+ console.log(kleur8.yellow("No open previews."));
6837
+ console.log(kleur8.gray(stoppedNote));
6838
+ return;
6839
+ }
6840
+ for (const preview of previews) {
6841
+ const restart = preview.restartable === void 0 ? "" : preview.restartable ? " \xB7 restartable" : " \xB7 not restartable";
6842
+ console.log(
6843
+ `${preview.previewId} \xB7 ${preview.title || "(untitled)"} \xB7 port=${preview.port}${preview.path === "/" ? "" : ` \xB7 path=${preview.path}`} \xB7 preview ${preview.status}${restart}`
6844
+ );
6845
+ }
6846
+ console.log(
6847
+ kleur8.gray(
6848
+ `This lists open previews; it does not check whether a site answers (${kleur8.cyan("instafy preview check --port <port>")} does). ${stoppedNote}`
6849
+ )
6850
+ );
5664
6851
  }
5665
6852
 
5666
6853
  // src/config-command.ts
5667
6854
  init_config();
5668
6855
  init_config();
5669
- import kleur8 from "kleur";
6856
+ import kleur9 from "kleur";
5670
6857
  function normalizeKey(raw) {
5671
6858
  const key = raw.trim().toLowerCase().replace(/_/g, "-");
5672
6859
  if (key === "controller-url" || key === "controllerurl" || key === "server-url") {
@@ -5708,12 +6895,12 @@ function clearConfig(key) {
5708
6895
  }
5709
6896
  }
5710
6897
  function configPath(options) {
5711
- const path16 = getInstafyConfigPath();
6898
+ const path19 = getInstafyConfigPath();
5712
6899
  if (options?.json) {
5713
- console.log(JSON.stringify({ path: path16 }, null, 2));
6900
+ console.log(JSON.stringify({ path: path19 }, null, 2));
5714
6901
  return;
5715
6902
  }
5716
- console.log(path16);
6903
+ console.log(path19);
5717
6904
  }
5718
6905
  function configList(options) {
5719
6906
  const config = readInstafyCliConfig();
@@ -5730,13 +6917,13 @@ function configList(options) {
5730
6917
  console.log(JSON.stringify(payload, null, 2));
5731
6918
  return;
5732
6919
  }
5733
- console.log(kleur8.green("Instafy CLI config"));
6920
+ console.log(kleur9.green("Instafy CLI config"));
5734
6921
  console.log(`Path: ${payload.path}`);
5735
- console.log(`controller-url: ${payload.controllerUrl ?? kleur8.yellow("(not set)")}`);
6922
+ console.log(`controller-url: ${payload.controllerUrl ?? kleur9.yellow("(not set)")}`);
5736
6923
  console.log(`controller-url (effective): ${payload.effectiveControllerUrl}`);
5737
- console.log(`studio-url: ${payload.studioUrl ?? kleur8.yellow("(not set)")}`);
5738
- console.log(`access-token: ${payload.accessTokenSet ? kleur8.green("(set)") : kleur8.yellow("(not set)")}`);
5739
- console.log(`refresh-token: ${payload.refreshTokenSet ? kleur8.green("(set)") : kleur8.yellow("(not set)")}`);
6924
+ console.log(`studio-url: ${payload.studioUrl ?? kleur9.yellow("(not set)")}`);
6925
+ console.log(`access-token: ${payload.accessTokenSet ? kleur9.green("(set)") : kleur9.yellow("(not set)")}`);
6926
+ console.log(`refresh-token: ${payload.refreshTokenSet ? kleur9.green("(set)") : kleur9.yellow("(not set)")}`);
5740
6927
  if (payload.updatedAt) {
5741
6928
  console.log(`updated-at: ${payload.updatedAt}`);
5742
6929
  }
@@ -5765,7 +6952,7 @@ function configSet(params) {
5765
6952
  console.log(JSON.stringify({ key, value, path: getInstafyConfigPath() }, null, 2));
5766
6953
  return;
5767
6954
  }
5768
- console.log(kleur8.green(`Set ${key}`));
6955
+ console.log(kleur9.green(`Set ${key}`));
5769
6956
  console.log(value);
5770
6957
  }
5771
6958
  function configUnset(params) {
@@ -5775,7 +6962,7 @@ function configUnset(params) {
5775
6962
  console.log(JSON.stringify({ ok: true, key }, null, 2));
5776
6963
  return;
5777
6964
  }
5778
- console.log(kleur8.green(`Unset ${key}`));
6965
+ console.log(kleur9.green(`Unset ${key}`));
5779
6966
  }
5780
6967
 
5781
6968
  // src/index.ts
@@ -5784,27 +6971,27 @@ init_config();
5784
6971
  // src/git-wrapper.ts
5785
6972
  import { spawnSync as spawnSync4 } from "node:child_process";
5786
6973
  import { randomUUID as randomUUID2 } from "node:crypto";
5787
- import fs8 from "node:fs";
5788
- import path9 from "node:path";
5789
- import kleur9 from "kleur";
6974
+ import fs10 from "node:fs";
6975
+ import path11 from "node:path";
6976
+ import kleur10 from "kleur";
5790
6977
  function pathExists(candidate) {
5791
6978
  try {
5792
- fs8.statSync(candidate);
6979
+ fs10.statSync(candidate);
5793
6980
  return true;
5794
6981
  } catch {
5795
6982
  return false;
5796
6983
  }
5797
6984
  }
5798
6985
  function findInstafyGitContext(startDir) {
5799
- let current = path9.resolve(startDir);
5800
- const root = path9.parse(current).root;
6986
+ let current = path11.resolve(startDir);
6987
+ const root = path11.parse(current).root;
5801
6988
  while (true) {
5802
- const gitDir = path9.join(current, ".instafy", ".git");
6989
+ const gitDir = path11.join(current, ".instafy", ".git");
5803
6990
  if (pathExists(gitDir)) {
5804
6991
  return { workTree: current, gitDir };
5805
6992
  }
5806
6993
  if (current === root) break;
5807
- current = path9.dirname(current);
6994
+ current = path11.dirname(current);
5808
6995
  }
5809
6996
  return null;
5810
6997
  }
@@ -5851,9 +7038,9 @@ function validateGitArgs(userArgs) {
5851
7038
  }
5852
7039
  if (arg === "--git-dir" || arg.startsWith("--git-dir=") || arg === "--work-tree" || arg.startsWith("--work-tree=") || arg === "-C") {
5853
7040
  throw new Error(
5854
- `Unsupported argument "${arg}". Use ${kleur9.cyan(
7041
+ `Unsupported argument "${arg}". Use ${kleur10.cyan(
5855
7042
  "cd"
5856
- )} instead; ${kleur9.cyan("instafy git")} manages --git-dir/--work-tree automatically.`
7043
+ )} instead; ${kleur10.cyan("instafy git")} manages --git-dir/--work-tree automatically.`
5857
7044
  );
5858
7045
  }
5859
7046
  if (arg === "-c" || arg === "--config-env" || arg === "--exec-path" || arg === "--namespace") {
@@ -5960,7 +7147,7 @@ function collectDirtyPaths(context, options) {
5960
7147
  return unique;
5961
7148
  }
5962
7149
  function findEmbeddedGitDirs(context, pathSpecs) {
5963
- const instafyGitDir = path9.resolve(context.gitDir);
7150
+ const instafyGitDir = path11.resolve(context.gitDir);
5964
7151
  const seen = /* @__PURE__ */ new Set();
5965
7152
  for (const rawPath of pathSpecs) {
5966
7153
  const normalized = normalizePathSpec(rawPath).replace(/\/+$/, "");
@@ -5969,11 +7156,11 @@ function findEmbeddedGitDirs(context, pathSpecs) {
5969
7156
  }
5970
7157
  let cursor = normalized;
5971
7158
  while (cursor && cursor !== ".") {
5972
- const candidate = path9.join(context.workTree, ...cursor.split("/"), ".git");
5973
- if (pathExists(candidate) && path9.resolve(candidate) !== instafyGitDir) {
5974
- seen.add(path9.resolve(candidate));
7159
+ const candidate = path11.join(context.workTree, ...cursor.split("/"), ".git");
7160
+ if (pathExists(candidate) && path11.resolve(candidate) !== instafyGitDir) {
7161
+ seen.add(path11.resolve(candidate));
5975
7162
  }
5976
- const next = path9.posix.dirname(cursor);
7163
+ const next = path11.posix.dirname(cursor);
5977
7164
  if (next === "." || next === cursor) {
5978
7165
  break;
5979
7166
  }
@@ -5982,8 +7169,8 @@ function findEmbeddedGitDirs(context, pathSpecs) {
5982
7169
  }
5983
7170
  const candidates = Array.from(seen);
5984
7171
  candidates.sort((a, b) => {
5985
- const depthA = a.split(path9.sep).length;
5986
- const depthB = b.split(path9.sep).length;
7172
+ const depthA = a.split(path11.sep).length;
7173
+ const depthB = b.split(path11.sep).length;
5987
7174
  return depthB - depthA;
5988
7175
  });
5989
7176
  return candidates;
@@ -5993,16 +7180,16 @@ function hideEmbeddedGitDirs(context, pathSpecs) {
5993
7180
  if (candidates.length === 0) {
5994
7181
  return [];
5995
7182
  }
5996
- const stagingBase = path9.join(context.workTree, ".instafy", "origin-staging", "embedded-git");
5997
- fs8.mkdirSync(stagingBase, { recursive: true });
7183
+ const stagingBase = path11.join(context.workTree, ".instafy", "origin-staging", "embedded-git");
7184
+ fs10.mkdirSync(stagingBase, { recursive: true });
5998
7185
  const renames = [];
5999
7186
  for (const candidate of candidates) {
6000
- let hidden = path9.join(stagingBase, randomUUID2());
7187
+ let hidden = path11.join(stagingBase, randomUUID2());
6001
7188
  for (let attempt = 0; attempt < 8; attempt += 1) {
6002
7189
  if (!pathExists(hidden)) break;
6003
- hidden = path9.join(stagingBase, randomUUID2());
7190
+ hidden = path11.join(stagingBase, randomUUID2());
6004
7191
  }
6005
- fs8.renameSync(candidate, hidden);
7192
+ fs10.renameSync(candidate, hidden);
6006
7193
  renames.push({ original: candidate, hidden });
6007
7194
  }
6008
7195
  return renames;
@@ -6012,7 +7199,7 @@ function restoreEmbeddedGitDirs(renames) {
6012
7199
  const rename = renames[index];
6013
7200
  if (!rename) continue;
6014
7201
  try {
6015
- fs8.renameSync(rename.hidden, rename.original);
7202
+ fs10.renameSync(rename.hidden, rename.original);
6016
7203
  } catch {
6017
7204
  }
6018
7205
  }
@@ -6082,15 +7269,15 @@ function runInstafyGit(userArgs, options) {
6082
7269
  console.log("instafy git <git-args...>");
6083
7270
  console.log("");
6084
7271
  console.log(
6085
- `Runs ${kleur9.cyan("git")} against the Instafy canonical repo at ${kleur9.cyan(
7272
+ `Runs ${kleur10.cyan("git")} against the Instafy canonical repo at ${kleur10.cyan(
6086
7273
  ".instafy/.git"
6087
7274
  )} (auto-detected by walking up from cwd).`
6088
7275
  );
6089
7276
  console.log("");
6090
7277
  console.log("Example:");
6091
- console.log(` ${kleur9.cyan("instafy git status")}`);
6092
- console.log(` ${kleur9.cyan("instafy git add -A")}`);
6093
- console.log(` ${kleur9.cyan('instafy git commit -am "instafy: checkpoint"')}`);
7278
+ console.log(` ${kleur10.cyan("instafy git status")}`);
7279
+ console.log(` ${kleur10.cyan("instafy git add -A")}`);
7280
+ console.log(` ${kleur10.cyan('instafy git commit -am "instafy: checkpoint"')}`);
6094
7281
  return 0;
6095
7282
  }
6096
7283
  const context = findInstafyGitContext(cwd);
@@ -6100,8 +7287,8 @@ function runInstafyGit(userArgs, options) {
6100
7287
  "No Instafy canonical git checkout found (expected .instafy/.git).",
6101
7288
  "",
6102
7289
  "Tips:",
6103
- `- Run this inside a git-canonical workspace (where ${kleur9.cyan(".instafy/.git")} exists).`,
6104
- `- If you're trying to operate on a user repo, use normal ${kleur9.cyan("git")} (it uses .git).`
7290
+ `- Run this inside a git-canonical workspace (where ${kleur10.cyan(".instafy/.git")} exists).`,
7291
+ `- If you're trying to operate on a user repo, use normal ${kleur10.cyan("git")} (it uses .git).`
6105
7292
  ].join("\n")
6106
7293
  );
6107
7294
  }
@@ -6376,7 +7563,7 @@ async function runInstafyGitSync(args, options) {
6376
7563
  }
6377
7564
  } catch (error) {
6378
7565
  const message = error instanceof Error ? error.message : String(error);
6379
- console.warn(kleur9.yellow(`[instafy git sync] ${message}; falling back to environment token.`));
7566
+ console.warn(kleur10.yellow(`[instafy git sync] ${message}; falling back to environment token.`));
6380
7567
  }
6381
7568
  }
6382
7569
  if (!originToken) {
@@ -6424,7 +7611,7 @@ async function runInstafyGitSync(args, options) {
6424
7611
  } catch (error) {
6425
7612
  const message = error instanceof Error ? error.message : String(error);
6426
7613
  console.warn(
6427
- kleur9.yellow(
7614
+ kleur10.yellow(
6428
7615
  `[instafy git sync] failed to recover from invalid origin token: ${message}`
6429
7616
  )
6430
7617
  );
@@ -6438,18 +7625,18 @@ async function runInstafyGitSync(args, options) {
6438
7625
  console.log(JSON.stringify(saveReportJson(report, notSaved2), null, 2));
6439
7626
  } else {
6440
7627
  for (const sentence of notSaved2) {
6441
- console.error(kleur9.red(`${sentence}.`));
7628
+ console.error(kleur10.red(`${sentence}.`));
6442
7629
  }
6443
7630
  }
6444
7631
  return 1;
6445
7632
  }
6446
7633
  const suffix = text2.trim() ? `: ${text2.trim()}` : "";
6447
- console.error(kleur9.red(`Origin git sync failed (${response.status} ${response.statusText})${suffix}`));
7634
+ console.error(kleur10.red(`Origin git sync failed (${response.status} ${response.statusText})${suffix}`));
6448
7635
  return 1;
6449
7636
  }
6450
7637
  const rev = report.rev ?? "";
6451
7638
  if (!rev) {
6452
- console.error(kleur9.red("Origin git sync response missing rev field."));
7639
+ console.error(kleur10.red("Origin git sync response missing rev field."));
6453
7640
  return 1;
6454
7641
  }
6455
7642
  const notSaved = notSavedSentences(report, { failedWhole: false });
@@ -6458,7 +7645,7 @@ async function runInstafyGitSync(args, options) {
6458
7645
  } else {
6459
7646
  console.log(rev);
6460
7647
  for (const sentence of notSaved) {
6461
- console.error(kleur9.yellow(`${sentence}.`));
7648
+ console.error(kleur10.yellow(`${sentence}.`));
6462
7649
  }
6463
7650
  }
6464
7651
  return notSaved.length > 0 ? 1 : 0;
@@ -6477,21 +7664,21 @@ function parseSaveReport(text2) {
6477
7664
  payload = {};
6478
7665
  }
6479
7666
  const conflictedPaths = Array.isArray(payload["conflictedPaths"]) ? payload["conflictedPaths"].flatMap((entry) => {
6480
- const path16 = optionalString(entry);
6481
- return path16 ? [path16] : [];
7667
+ const path19 = optionalString(entry);
7668
+ return path19 ? [path19] : [];
6482
7669
  }) : [];
6483
7670
  const rejectedPaths = Array.isArray(payload["rejectedPaths"]) ? payload["rejectedPaths"].flatMap((entry) => {
6484
7671
  if (typeof entry === "string") {
6485
- const path17 = optionalString(entry);
6486
- return path17 ? [{ path: path17, reason: "", keptSavedVersion: false }] : [];
7672
+ const path20 = optionalString(entry);
7673
+ return path20 ? [{ path: path20, reason: "", keptSavedVersion: false }] : [];
6487
7674
  }
6488
7675
  if (!entry || typeof entry !== "object") return [];
6489
7676
  const record2 = entry;
6490
- const path16 = optionalString(record2["path"]);
6491
- if (!path16) return [];
7677
+ const path19 = optionalString(record2["path"]);
7678
+ if (!path19) return [];
6492
7679
  return [
6493
7680
  {
6494
- path: path16,
7681
+ path: path19,
6495
7682
  reason: optionalString(record2["reason"]) ?? "",
6496
7683
  keptSavedVersion: record2["keptSavedVersion"] === true
6497
7684
  }
@@ -6571,11 +7758,511 @@ function saveReportJson(report, notSaved) {
6571
7758
  };
6572
7759
  }
6573
7760
 
7761
+ // src/site-check.ts
7762
+ import { spawnSync as spawnSync5 } from "node:child_process";
7763
+ import { once } from "node:events";
7764
+ import fs11 from "node:fs";
7765
+ import http3 from "node:http";
7766
+ import { createRequire as createRequire3 } from "node:module";
7767
+ import os6 from "node:os";
7768
+ import path12 from "node:path";
7769
+ var CHECK_EXIT_OK = 0;
7770
+ var CHECK_EXIT_PROBLEMS = 1;
7771
+ var CHECK_EXIT_NOT_LISTENING = PREVIEW_EXIT_NOT_LISTENING;
7772
+ var CHECK_EXIT_NO_BROWSER = 4;
7773
+ var CHECK_EXIT_USAGE = 64;
7774
+ var CHECK_EXIT_INTERNAL = 70;
7775
+ var MAX_PATH_LENGTH2 = 512;
7776
+ var MAX_LIST = 20;
7777
+ var MAX_TEXT = 300;
7778
+ var CHROMIUM_ARGS = ["--use-angle=swiftshader", "--enable-unsafe-swiftshader", "--ignore-gpu-blocklist", "--disable-dev-shm-usage"];
7779
+ var SOFTWARE_GL_NOISE = /GL Driver Message|GPU stall due to ReadPixels|READ-usage buffer was written|WebGL: too many errors|Automatic fallback to software WebGL/;
7780
+ var PHONE_UA = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1";
7781
+ var VIEWPORTS = [
7782
+ { name: "desktop", width: 1280, height: 800, mobile: false },
7783
+ { name: "phone", width: 390, height: 844, mobile: true }
7784
+ ];
7785
+ var TRUSTED_PLAYWRIGHT_PATH_ENV = "INSTAFY_SHARED_BROWSER_PLAYWRIGHT_PATH";
7786
+ var TRUSTED_NODE_MODULES_ROOT_ENV = "INSTAFY_SHARED_BROWSER_TRUSTED_NODE_MODULES_ROOT";
7787
+ function isPlaywrightDir(dir) {
7788
+ try {
7789
+ const pkg2 = JSON.parse(fs11.readFileSync(path12.join(dir, "package.json"), "utf8"));
7790
+ return pkg2.name === "playwright" || pkg2.name === "playwright-core";
7791
+ } catch {
7792
+ return false;
7793
+ }
7794
+ }
7795
+ function playwrightIn(nodeModules) {
7796
+ const found = [];
7797
+ for (const name of ["playwright", "playwright-core"]) {
7798
+ const dir = path12.join(nodeModules, name);
7799
+ if (isPlaywrightDir(dir)) found.push(dir);
7800
+ }
7801
+ try {
7802
+ const store = path12.join(nodeModules, ".pnpm");
7803
+ for (const entry of fs11.readdirSync(store).filter((e) => /^playwright(-core)?@\d/.test(e)).sort().reverse()) {
7804
+ const name = entry.startsWith("playwright-core@") ? "playwright-core" : "playwright";
7805
+ const dir = path12.join(store, entry, "node_modules", name);
7806
+ if (isPlaywrightDir(dir)) found.push(dir);
7807
+ }
7808
+ } catch {
7809
+ }
7810
+ return found;
7811
+ }
7812
+ var npmRootCache;
7813
+ function npmGlobalRoot() {
7814
+ if (npmRootCache !== void 0) return npmRootCache;
7815
+ try {
7816
+ const out = spawnSync5("npm", ["root", "-g"], { cwd: path12.parse(process.cwd()).root, encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] });
7817
+ npmRootCache = out.status === 0 && out.stdout.trim() ? out.stdout.trim() : null;
7818
+ } catch {
7819
+ npmRootCache = null;
7820
+ }
7821
+ return npmRootCache;
7822
+ }
7823
+ function defaultGlobalRoots(env = process.env) {
7824
+ const roots = [];
7825
+ for (const entry of (env.NODE_PATH ?? "").split(path12.delimiter)) if (path12.isAbsolute(entry.trim())) roots.push(entry.trim());
7826
+ const npmRoot = npmGlobalRoot();
7827
+ if (npmRoot && path12.isAbsolute(npmRoot)) roots.push(npmRoot);
7828
+ roots.push("/usr/lib/node_modules", "/usr/local/lib/node_modules", "/opt/homebrew/lib/node_modules");
7829
+ return [...new Set(roots)];
7830
+ }
7831
+ function locateBrowserEngine(o = {}) {
7832
+ const env = o.env ?? process.env;
7833
+ const tried = [];
7834
+ const trustedPath = env[TRUSTED_PLAYWRIGHT_PATH_ENV]?.trim();
7835
+ if (trustedPath) {
7836
+ if (path12.isAbsolute(trustedPath) && isPlaywrightDir(trustedPath)) return { engine: { playwright: trustedPath, source: "trusted" }, tried };
7837
+ tried.push(`$${TRUSTED_PLAYWRIGHT_PATH_ENV} is not an absolute path to a playwright package`);
7838
+ }
7839
+ const trustedRoot = env[TRUSTED_NODE_MODULES_ROOT_ENV]?.trim();
7840
+ if (trustedRoot) {
7841
+ const found = path12.isAbsolute(trustedRoot) ? playwrightIn(trustedRoot) : [];
7842
+ if (found.length) return { engine: { playwright: found[0], source: "trusted" }, tried };
7843
+ tried.push(`no playwright in $${TRUSTED_NODE_MODULES_ROOT_ENV}`);
7844
+ }
7845
+ for (const root of o.globalRoots ?? defaultGlobalRoots(env)) {
7846
+ const found = playwrightIn(root);
7847
+ if (found.length) return { engine: { playwright: found[0], source: "global" }, tried };
7848
+ tried.push(`none in ${root}`);
7849
+ }
7850
+ if (!o.workspace) {
7851
+ tried.push("the workspace was not searched (--workspace-playwright)");
7852
+ return { engine: null, tried };
7853
+ }
7854
+ let dir = path12.resolve(o.cwd ?? process.cwd());
7855
+ for (let i = 0; i < 16; i += 1) {
7856
+ const found = playwrightIn(path12.join(dir, "node_modules"));
7857
+ if (found.length) return { engine: { playwright: found[0], source: "workspace" }, tried };
7858
+ const up = path12.dirname(dir);
7859
+ if (up === dir) break;
7860
+ dir = up;
7861
+ }
7862
+ tried.push(`no node_modules/playwright from ${o.cwd ?? process.cwd()} upwards`);
7863
+ return { engine: null, tried };
7864
+ }
7865
+ function chromiumCandidates(env = process.env, exists = fs11.existsSync) {
7866
+ const out = [];
7867
+ const browsersRoots = [env.PLAYWRIGHT_BROWSERS_PATH, "/ms-playwright", path12.join(os6.homedir(), ".cache", "ms-playwright")].filter((x) => Boolean(x && x !== "0"));
7868
+ for (const root of [...new Set(browsersRoots)]) {
7869
+ let entries = [];
7870
+ try {
7871
+ entries = fs11.readdirSync(root).sort().reverse();
7872
+ } catch {
7873
+ continue;
7874
+ }
7875
+ for (const e of entries.filter((x) => x.startsWith("chromium_headless_shell-"))) {
7876
+ out.push(path12.join(root, e, "chrome-headless-shell-linux64", "chrome-headless-shell"));
7877
+ out.push(path12.join(root, e, "chrome-linux", "headless_shell"));
7878
+ }
7879
+ for (const e of entries.filter((x) => /^chromium-\d/.test(x))) {
7880
+ out.push(path12.join(root, e, "chrome-linux64", "chrome"), path12.join(root, e, "chrome-linux", "chrome"));
7881
+ }
7882
+ }
7883
+ out.push(
7884
+ "/usr/bin/chromium",
7885
+ "/usr/bin/chromium-browser",
7886
+ "/usr/bin/google-chrome",
7887
+ "/usr/bin/google-chrome-stable",
7888
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
7889
+ "/Applications/Chromium.app/Contents/MacOS/Chromium"
7890
+ );
7891
+ return out.filter((p) => exists(p));
7892
+ }
7893
+ function loadPlaywright(engine) {
7894
+ const req = createRequire3(path12.join(engine.playwright, "package.json"));
7895
+ const mod = req(engine.playwright);
7896
+ const pw = mod?.chromium ? mod : mod?.default;
7897
+ if (!pw?.chromium) throw new Error(`${engine.playwright} has no chromium export`);
7898
+ return pw;
7899
+ }
7900
+ async function launchChromium(pw, env = process.env) {
7901
+ const errors = [];
7902
+ try {
7903
+ const browser = await pw.chromium.launch({ headless: true, args: CHROMIUM_ARGS });
7904
+ return { browser, executable: "playwright" };
7905
+ } catch (error) {
7906
+ errors.push(firstLine(error));
7907
+ }
7908
+ for (const executablePath of chromiumCandidates(env)) {
7909
+ try {
7910
+ const browser = await pw.chromium.launch({ headless: true, executablePath, args: CHROMIUM_ARGS });
7911
+ return { browser, executable: executablePath };
7912
+ } catch (error) {
7913
+ errors.push(`${executablePath}: ${firstLine(error)}`);
7914
+ }
7915
+ }
7916
+ throw new NoBrowserError(`No Chromium would start (${errors.join("; ") || "none found"})`);
7917
+ }
7918
+ var NoBrowserError = class extends Error {
7919
+ };
7920
+ function firstLine(error) {
7921
+ return String(error?.message ?? error).split("\n")[0].slice(0, MAX_TEXT);
7922
+ }
7923
+ function parseCheckPath(raw) {
7924
+ const value = raw?.trim() || "/";
7925
+ if (!value.startsWith("/") || value.startsWith("//") || value.length > MAX_PATH_LENGTH2 || /[\s\\]/.test(value)) {
7926
+ throw new PreviewCommandError("Path must start with / and contain no spaces or backslashes (a ?query and #hash are fine).", CHECK_EXIT_USAGE);
7927
+ }
7928
+ return value;
7929
+ }
7930
+ function siteUrlFor(siteOrigin, sitePath) {
7931
+ const url = new URL(sitePath, siteOrigin);
7932
+ if (url.origin !== siteOrigin) throw new PreviewCommandError("Path must stay on the site's own origin.", CHECK_EXIT_USAGE);
7933
+ return url.href;
7934
+ }
7935
+ function isSiteUrl(url, siteOrigin) {
7936
+ try {
7937
+ return new URL(url).origin === siteOrigin;
7938
+ } catch {
7939
+ return false;
7940
+ }
7941
+ }
7942
+ function makeDefaultOutDir(port) {
7943
+ return fs11.mkdtempSync(path12.join(os6.tmpdir(), `instafy-preview-check-${port}-`));
7944
+ }
7945
+ function escapeAttribute(value) {
7946
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
7947
+ }
7948
+ function framePage(siteUrl) {
7949
+ return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
7950
+ <title>instafy preview check</title>
7951
+ <style>html,body{margin:0;height:100%;background:#fff}iframe{display:block;border:0;width:100%;height:100%}</style>
7952
+ <iframe id="site" src="${escapeAttribute(siteUrl)}" allow="fullscreen; clipboard-write"></iframe>`;
7953
+ }
7954
+ async function startFrameHost(siteUrl) {
7955
+ const body = framePage(siteUrl);
7956
+ const server = http3.createServer((req, res) => {
7957
+ if (req.url !== "/") {
7958
+ res.writeHead(404, { "content-type": "text/plain" });
7959
+ res.end();
7960
+ return;
7961
+ }
7962
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
7963
+ res.end(body);
7964
+ });
7965
+ server.listen(0, "127.0.0.1");
7966
+ await once(server, "listening");
7967
+ const { port } = server.address();
7968
+ return { url: `http://localhost:${port}/`, close: () => server.close() };
7969
+ }
7970
+ function blankVerdict(input3) {
7971
+ if (input3.framed === "refused") {
7972
+ return { blank: true, reason: "the site refuses to be framed (X-Frame-Options or CSP frame-ancestors), so the frame stays empty" };
7973
+ }
7974
+ if (input3.framed === "missing") return { blank: true, reason: "the page did not load" };
7975
+ if (input3.dominant !== null && input3.dominant >= 0.995 && input3.text === 0) return { blank: true, reason: "the screen is a single colour" };
7976
+ if (input3.text === 0 && input3.media === 0 && input3.elements < 3) return { blank: true, reason: "the page has no text, images or canvas" };
7977
+ return { blank: false, reason: null };
7978
+ }
7979
+ async function measureDominantShare(measure) {
7980
+ try {
7981
+ const share = await measure();
7982
+ return Number.isFinite(share) && share >= 0 && share <= 1 ? share : null;
7983
+ } catch {
7984
+ return null;
7985
+ }
7986
+ }
7987
+ function incompleteBlankCheck(dominant, text2) {
7988
+ if (dominant !== null) return null;
7989
+ return text2 === 0 ? { error: true, message: "blank check incomplete: could not read the screenshot, and the page has no text to show it drew anything" } : { error: false, message: "blank check incomplete: could not read the screenshot, so a single-colour screen would not be caught" };
7990
+ }
7991
+ var FRAME_SNAPSHOT = () => {
7992
+ const visible = (el) => {
7993
+ const r = el.getBoundingClientRect();
7994
+ if (r.width < 2 || r.height < 2 || r.bottom < 0 || r.right < 0 || r.top > innerHeight || r.left > innerWidth) return false;
7995
+ const s = getComputedStyle(el);
7996
+ return s.visibility !== "hidden" && s.display !== "none" && Number(s.opacity) > 0.05;
7997
+ };
7998
+ const body = document.body;
7999
+ const text2 = (body?.innerText ?? "").replace(/\s+/g, " ").trim().length;
8000
+ let media = 0;
8001
+ let elements = 0;
8002
+ for (const el of Array.from(body?.querySelectorAll("*") ?? [])) {
8003
+ if (/^(SCRIPT|STYLE|NOSCRIPT|TEMPLATE|LINK|META)$/.test(el.tagName)) continue;
8004
+ if (!visible(el)) continue;
8005
+ elements += 1;
8006
+ if (/^(CANVAS|IMG|SVG|VIDEO|IFRAME|PICTURE|OBJECT|EMBED)$/i.test(el.tagName)) media += 1;
8007
+ }
8008
+ return { text: text2, media, elements, title: document.title };
8009
+ };
8010
+ var DOMINANT_COLOUR = async (b64) => {
8011
+ const img = new Image();
8012
+ img.src = `data:image/png;base64,${b64}`;
8013
+ await img.decode();
8014
+ const w = 160;
8015
+ const h = Math.max(1, Math.round(img.height / img.width * w));
8016
+ const c = document.createElement("canvas");
8017
+ c.width = w;
8018
+ c.height = h;
8019
+ const g = c.getContext("2d");
8020
+ g.imageSmoothingEnabled = false;
8021
+ g.drawImage(img, 0, 0, w, h);
8022
+ const data = g.getImageData(0, 0, w, h).data;
8023
+ const counts = /* @__PURE__ */ new Map();
8024
+ let max = 0;
8025
+ for (let i = 0; i < data.length; i += 4) {
8026
+ const key = data[i] >> 3 << 10 | data[i + 1] >> 3 << 5 | data[i + 2] >> 3;
8027
+ const n = (counts.get(key) ?? 0) + 1;
8028
+ counts.set(key, n);
8029
+ if (n > max) max = n;
8030
+ }
8031
+ return max / (w * h);
8032
+ };
8033
+ async function checkViewport(browser, target, vp, siteOrigin, outDir, timeoutMs, waitMs) {
8034
+ const errors = [];
8035
+ const warnings = [];
8036
+ const context = await browser.newContext({
8037
+ viewport: { width: vp.width, height: vp.height },
8038
+ deviceScaleFactor: vp.mobile ? 2 : 1,
8039
+ isMobile: vp.mobile,
8040
+ hasTouch: vp.mobile,
8041
+ ...vp.mobile ? { userAgent: PHONE_UA } : {},
8042
+ reducedMotion: "reduce",
8043
+ serviceWorkers: "block",
8044
+ acceptDownloads: false
8045
+ });
8046
+ const t0 = Date.now();
8047
+ let status = null;
8048
+ try {
8049
+ const page = await context.newPage();
8050
+ const ours = (u) => isSiteUrl(u, siteOrigin);
8051
+ const shortUrl = (u) => (ours(u) ? u.slice(siteOrigin.length) || "/" : u).slice(0, 200);
8052
+ page.on("console", (m) => {
8053
+ const text2 = String(m.text());
8054
+ if (/^Failed to load resource/.test(text2)) return;
8055
+ if (SOFTWARE_GL_NOISE.test(text2)) return;
8056
+ if (/^Refused to display .* in a frame/.test(text2)) return;
8057
+ if (m.type() === "error") errors.push(`console.error: ${text2.slice(0, MAX_TEXT)}`);
8058
+ });
8059
+ page.on("pageerror", (e) => errors.push(`uncaught: ${String(e?.message ?? e).split("\n")[0].slice(0, MAX_TEXT)}`));
8060
+ let refused = false;
8061
+ page.on("requestfailed", (r) => {
8062
+ const u = String(r.url());
8063
+ if (/\/favicon\.ico(\?|$)/.test(u)) return;
8064
+ const why = r.failure()?.errorText ?? "failed";
8065
+ if (target.framed && ours(u) && r.isNavigationRequest() && /ERR_BLOCKED_BY_RESPONSE/.test(why)) {
8066
+ refused = true;
8067
+ return;
8068
+ }
8069
+ if (/ERR_ABORTED/.test(why)) return;
8070
+ (ours(u) ? errors : warnings).push(`request failed: ${shortUrl(u)} (${why})`);
8071
+ });
8072
+ page.on("response", (r) => {
8073
+ const u = String(r.url());
8074
+ const s = Number(r.status());
8075
+ if (r.request().isNavigationRequest() && ours(u) && status === null) status = s;
8076
+ if (s < 400 || /\/favicon\.ico(\?|$)/.test(u)) return;
8077
+ (ours(u) ? errors : warnings).push(`HTTP ${s}: ${shortUrl(u)}`);
8078
+ });
8079
+ await page.goto(target.pageUrl, { waitUntil: "load", timeout: timeoutMs });
8080
+ await page.waitForLoadState("networkidle", { timeout: Math.min(8e3, timeoutMs) }).catch(() => {
8081
+ warnings.push("the network was still busy after 8 s (screenshot taken anyway)");
8082
+ });
8083
+ const frame = target.framed ? page.frames().find((f) => f !== page.mainFrame() && isSiteUrl(String(f.url()), siteOrigin)) : page.mainFrame();
8084
+ let framed = "ok";
8085
+ let snap = { text: 0, media: 0, elements: 0, title: "" };
8086
+ if (refused) framed = "refused";
8087
+ else if (!frame) framed = "missing";
8088
+ else {
8089
+ await frame.evaluate(() => document.fonts?.ready).catch(() => {
8090
+ });
8091
+ await page.waitForTimeout(waitMs);
8092
+ snap = await frame.evaluate(FRAME_SNAPSHOT).catch(() => snap);
8093
+ }
8094
+ if (status !== null && status >= 400) framed = "missing";
8095
+ const file = path12.join(outDir, `${vp.name}.png`);
8096
+ const png = await page.screenshot({ path: file });
8097
+ const dominant = await measureDominantShare(async () => {
8098
+ const helper = await context.newPage();
8099
+ try {
8100
+ return await helper.evaluate(DOMINANT_COLOUR, png.toString("base64"));
8101
+ } finally {
8102
+ await helper.close().catch(() => {
8103
+ });
8104
+ }
8105
+ });
8106
+ const blank = blankVerdict({ dominant, text: snap.text, media: snap.media, elements: snap.elements, framed });
8107
+ if (blank.blank) errors.push(`blank page: ${blank.reason}`);
8108
+ else {
8109
+ const incomplete = incompleteBlankCheck(dominant, snap.text);
8110
+ if (incomplete) (incomplete.error ? errors : warnings).push(incomplete.message);
8111
+ }
8112
+ return {
8113
+ name: vp.name,
8114
+ width: vp.width,
8115
+ height: vp.height,
8116
+ screenshot: file,
8117
+ title: snap.title.slice(0, 200),
8118
+ status,
8119
+ ms: Date.now() - t0,
8120
+ blank,
8121
+ errors: [...new Set(errors)].slice(0, MAX_LIST),
8122
+ warnings: [...new Set(warnings)].slice(0, MAX_LIST)
8123
+ };
8124
+ } catch (error) {
8125
+ errors.push(`the page did not load: ${firstLine(error)}`);
8126
+ return {
8127
+ name: vp.name,
8128
+ width: vp.width,
8129
+ height: vp.height,
8130
+ screenshot: null,
8131
+ title: "",
8132
+ status,
8133
+ ms: Date.now() - t0,
8134
+ blank: { blank: true, reason: "the page did not load" },
8135
+ errors: [...new Set(errors)].slice(0, MAX_LIST),
8136
+ warnings: [...new Set(warnings)].slice(0, MAX_LIST)
8137
+ };
8138
+ } finally {
8139
+ await context.close().catch(() => {
8140
+ });
8141
+ }
8142
+ }
8143
+ async function runSiteCheck(opts) {
8144
+ const env = opts.env ?? process.env;
8145
+ const port = parsePreviewPort(opts.port, env);
8146
+ const sitePath = parseCheckPath(opts.path);
8147
+ const timeoutMs = opts.timeoutMs ?? 3e4;
8148
+ const viewports = VIEWPORTS.filter((v) => !opts.phone || v.name === "phone");
8149
+ let outDir = null;
8150
+ if (opts.outDir !== void 0) {
8151
+ outDir = path12.resolve(opts.outDir);
8152
+ fs11.mkdirSync(outDir, { recursive: true });
8153
+ for (const v of VIEWPORTS) fs11.rmSync(path12.join(outDir, `${v.name}.png`), { force: true });
8154
+ }
8155
+ const probePath = sitePath.replace(/[?#].*$/, "") || "/";
8156
+ const probe = await probeLocalSite(port, probePath, timeoutMs);
8157
+ if (!probe.host) {
8158
+ return {
8159
+ code: CHECK_EXIT_NOT_LISTENING,
8160
+ report: null,
8161
+ message: noAnswerMessage(port, probe, `port ${port} at 127.0.0.1 or [::1]`, "check again")
8162
+ };
8163
+ }
8164
+ const siteOrigin = `http://${probe.host}:${port}`;
8165
+ const url = siteUrlFor(siteOrigin, sitePath);
8166
+ const { engine, tried } = locateBrowserEngine({ env, cwd: opts.cwd, globalRoots: opts.globalRoots, workspace: opts.workspacePlaywright });
8167
+ if (!engine) {
8168
+ return { code: CHECK_EXIT_NO_BROWSER, report: null, message: `SKIPPED, nothing was checked: Playwright not found (${tried.join("; ")}).` };
8169
+ }
8170
+ let launched;
8171
+ try {
8172
+ launched = await launchChromium(loadPlaywright(engine), env);
8173
+ } catch (error) {
8174
+ return { code: CHECK_EXIT_NO_BROWSER, report: null, message: `SKIPPED, nothing was checked: ${firstLine(error)}` };
8175
+ }
8176
+ const reports = [];
8177
+ let host = null;
8178
+ try {
8179
+ const shots = outDir ?? makeDefaultOutDir(port);
8180
+ host = opts.framed ? await startFrameHost(url) : null;
8181
+ const target = { pageUrl: host?.url ?? url, framed: Boolean(opts.framed) };
8182
+ for (const vp of viewports) {
8183
+ reports.push(await checkViewport(launched.browser, target, vp, siteOrigin, shots, timeoutMs, opts.waitMs ?? 600));
8184
+ }
8185
+ } finally {
8186
+ await launched.browser.close().catch(() => {
8187
+ });
8188
+ host?.close();
8189
+ }
8190
+ const problems = reports.reduce((n, r) => n + r.errors.length, 0);
8191
+ const report = {
8192
+ ok: problems === 0,
8193
+ url,
8194
+ title: reports.find((r) => r.title)?.title ?? "",
8195
+ framed: Boolean(opts.framed),
8196
+ engine: { playwright: engine.playwright, chromium: launched.executable },
8197
+ viewports: reports,
8198
+ problems
8199
+ };
8200
+ return { code: problems ? CHECK_EXIT_PROBLEMS : CHECK_EXIT_OK, report };
8201
+ }
8202
+ function formatSiteCheck(report) {
8203
+ const lines = [];
8204
+ lines.push(`${report.ok ? "\u2713" : "\u2717"} ${report.url}${report.framed ? " (framed)" : ""} \xB7 "${report.title || "(no title)"}"`);
8205
+ for (const r of report.viewports) {
8206
+ const timing = `${(r.ms / 1e3).toFixed(1)} s${r.status ? `, HTTP ${r.status}` : ""}`;
8207
+ lines.push(`${r.errors.length ? "\u2717" : "\u2713"} ${r.name} ${r.width}\xD7${r.height}: ${r.screenshot ?? "no screenshot"} (${timing})`);
8208
+ for (const e of r.errors) lines.push(` error: ${e}`);
8209
+ for (const w of r.warnings.slice(0, 8)) lines.push(` warn: ${w}`);
8210
+ }
8211
+ lines.push(
8212
+ report.ok ? "No problems found. Open the screenshots and check layout, legibility and overlap on both sizes." : "Fix every error above, reload the site, and run the check again. Open the screenshots either way."
8213
+ );
8214
+ return lines.join("\n");
8215
+ }
8216
+ async function runPreviewCheckCli(opts, io = { out: console.log, err: console.error }) {
8217
+ const env = io.env ?? process.env;
8218
+ const num = (raw, name, min, max) => {
8219
+ if (raw === void 0) return void 0;
8220
+ const n = Number(raw);
8221
+ if (!Number.isInteger(n) || n < min || n > max) throw new PreviewCommandError(`${name} must be a whole number from ${min} to ${max}.`, CHECK_EXIT_USAGE);
8222
+ return n;
8223
+ };
8224
+ try {
8225
+ if (opts.port === void 0) throw new PreviewCommandError("--port <port> is required.", CHECK_EXIT_USAGE);
8226
+ const result = await runSiteCheck({
8227
+ port: parsePreviewPort(opts.port, env),
8228
+ path: opts.path,
8229
+ phone: opts.phone,
8230
+ framed: opts.framed,
8231
+ workspacePlaywright: opts.workspacePlaywright,
8232
+ outDir: opts.out,
8233
+ timeoutMs: num(opts.timeout, "--timeout", 1e3, 12e4),
8234
+ waitMs: num(opts.wait, "--wait", 0, 3e4),
8235
+ env
8236
+ });
8237
+ if (!result.report) {
8238
+ if (opts.json) io.out(JSON.stringify({ ok: false, exitCode: result.code, error: result.message, skipped: result.code === CHECK_EXIT_NO_BROWSER }));
8239
+ else io.err(result.message ?? "check failed");
8240
+ return result.code;
8241
+ }
8242
+ io.out(opts.json ? JSON.stringify(result.report) : formatSiteCheck(result.report));
8243
+ return result.code;
8244
+ } catch (error) {
8245
+ const known = error instanceof PreviewCommandError;
8246
+ const code = known ? error.exitCode : CHECK_EXIT_INTERNAL;
8247
+ const message = known ? firstLine(error) : `The check itself failed, so nothing was checked: ${firstLine(error)}`;
8248
+ if (opts.json) io.out(JSON.stringify({ ok: false, exitCode: code, error: message, skipped: !known }));
8249
+ else io.err(message);
8250
+ return code;
8251
+ }
8252
+ }
8253
+ function registerPreviewCheckCommand(preview) {
8254
+ return preview.command("check").description(
8255
+ "Load a local site in a headless browser and report its title, errors, failed requests and a blank page; writes desktop and phone screenshots (exit 1: problems, 2: nothing answering, 3: port not allowed, 4: no browser, 64: bad options, 70: the check itself failed)"
8256
+ ).option("--port <port>", "Local port the site listens on (1024-65535)").option("--path <path>", "Path to load (default /); a ?query and #hash are fine").option("--phone", "Only the phone size (390\xD7844); default: desktop 1280\xD7800 and phone").option("--framed", "Load the site inside a frame on another origin, and report a site that refuses to be framed").option("--workspace-playwright", "Also look for Playwright in this project's node_modules (runs the project's own copy)").option("--out <dir>", "Folder for desktop.png and phone.png (default: a new private folder under the temp folder each run; the report names it)").option("--timeout <ms>", "Page load timeout in ms (default 30000)").option("--wait <ms>", "Extra settle time after load in ms (default 600)").option("--json", "Output JSON").action(async (opts) => {
8257
+ process.exitCode = await runPreviewCheckCli(opts);
8258
+ });
8259
+ }
8260
+
6574
8261
  // src/api.ts
6575
8262
  init_config();
6576
8263
  init_errors();
6577
8264
  init_controller_fetch();
6578
- import fs9 from "node:fs";
8265
+ import fs12 from "node:fs";
6579
8266
  function normalizeUrl5(raw) {
6580
8267
  const value = (raw ?? "").trim();
6581
8268
  if (!value) return "http://127.0.0.1:8788";
@@ -6652,7 +8339,7 @@ function parseHeader(raw) {
6652
8339
  }
6653
8340
  function parseJsonBody(options) {
6654
8341
  if (options.jsonFile) {
6655
- const raw = fs9.readFileSync(options.jsonFile, "utf8");
8342
+ const raw = fs12.readFileSync(options.jsonFile, "utf8");
6656
8343
  const parsed = JSON.parse(raw);
6657
8344
  return JSON.stringify(parsed);
6658
8345
  }
@@ -6796,7 +8483,7 @@ async function requestControllerApi(options) {
6796
8483
 
6797
8484
  // src/conversations.ts
6798
8485
  init_project_manifest();
6799
- import kleur10 from "kleur";
8486
+ import kleur11 from "kleur";
6800
8487
  var DEFAULT_CONVERSATION_LIMIT = 50;
6801
8488
  var DEFAULT_MESSAGE_LIMIT = 80;
6802
8489
  var MAX_LIMIT = 200;
@@ -7103,7 +8790,7 @@ async function buildSearchMatches(conversations, options) {
7103
8790
  }));
7104
8791
  }
7105
8792
  function formatConversationMatch(match) {
7106
- const lines = [`- ${kleur10.green(match.title)} (${match.id})`];
8793
+ const lines = [`- ${kleur11.green(match.title)} (${match.id})`];
7107
8794
  const details = [];
7108
8795
  if (match.matchedIn.length > 0) {
7109
8796
  details.push(`match: ${match.matchedIn.join(", ")}`);
@@ -7115,7 +8802,7 @@ function formatConversationMatch(match) {
7115
8802
  lines.push(` ${details.join(" \xB7 ")}`);
7116
8803
  }
7117
8804
  if (match.preview) {
7118
- lines.push(` ${kleur10.gray(summarizeContent(match.preview))}`);
8805
+ lines.push(` ${kleur11.gray(summarizeContent(match.preview))}`);
7119
8806
  }
7120
8807
  return lines.join("\n");
7121
8808
  }
@@ -7174,16 +8861,16 @@ async function createConversation(options) {
7174
8861
  console.log(JSON.stringify(payload, null, 2));
7175
8862
  return;
7176
8863
  }
7177
- console.log(kleur10.green("Created conversation"));
7178
- console.log(`${kleur10.gray("ID:")} ${conversationId}`);
8864
+ console.log(kleur11.green("Created conversation"));
8865
+ console.log(`${kleur11.gray("ID:")} ${conversationId}`);
7179
8866
  if (title) {
7180
- console.log(`${kleur10.gray("Title:")} ${title}`);
8867
+ console.log(`${kleur11.gray("Title:")} ${title}`);
7181
8868
  }
7182
8869
  if (parentConversationId) {
7183
- console.log(`${kleur10.gray("Parent:")} ${parentConversationId}`);
8870
+ console.log(`${kleur11.gray("Parent:")} ${parentConversationId}`);
7184
8871
  }
7185
8872
  if (threadKind) {
7186
- console.log(`${kleur10.gray("Thread kind:")} ${threadKind}`);
8873
+ console.log(`${kleur11.gray("Thread kind:")} ${threadKind}`);
7187
8874
  }
7188
8875
  }
7189
8876
  async function listConversations(options) {
@@ -7210,10 +8897,10 @@ async function listConversations(options) {
7210
8897
  return;
7211
8898
  }
7212
8899
  if (items.length === 0) {
7213
- console.log(kleur10.yellow("No conversations found for this space."));
8900
+ console.log(kleur11.yellow("No conversations found for this space."));
7214
8901
  return;
7215
8902
  }
7216
- console.log(kleur10.green(`Conversations (${items.length})`));
8903
+ console.log(kleur11.green(`Conversations (${items.length})`));
7217
8904
  for (const item of items) {
7218
8905
  console.log(formatConversationMatch({ ...item, score: 0, matchedIn: [] }));
7219
8906
  }
@@ -7235,10 +8922,10 @@ async function searchConversations(options) {
7235
8922
  return matches;
7236
8923
  }
7237
8924
  if (matches.length === 0) {
7238
- console.log(kleur10.yellow(`No conversations matched "${options.query}".`));
8925
+ console.log(kleur11.yellow(`No conversations matched "${options.query}".`));
7239
8926
  return matches;
7240
8927
  }
7241
- console.log(kleur10.green(`Matches for "${options.query}"`));
8928
+ console.log(kleur11.green(`Matches for "${options.query}"`));
7242
8929
  for (const match of matches.slice(0, Math.min(limit, matches.length))) {
7243
8930
  console.log(formatConversationMatch(match));
7244
8931
  }
@@ -7356,27 +9043,27 @@ async function showConversation(options) {
7356
9043
  );
7357
9044
  return;
7358
9045
  }
7359
- console.log(kleur10.green(selected.title));
7360
- console.log(`${kleur10.gray("ID:")} ${selected.id}`);
9046
+ console.log(kleur11.green(selected.title));
9047
+ console.log(`${kleur11.gray("ID:")} ${selected.id}`);
7361
9048
  if (selected.updatedAt) {
7362
- console.log(`${kleur10.gray("Updated:")} ${selected.updatedAt}`);
9049
+ console.log(`${kleur11.gray("Updated:")} ${selected.updatedAt}`);
7363
9050
  }
7364
9051
  if (selected.preview) {
7365
- console.log(`${kleur10.gray("Preview:")} ${summarizeContent(selected.preview, 240)}`);
9052
+ console.log(`${kleur11.gray("Preview:")} ${summarizeContent(selected.preview, 240)}`);
7366
9053
  }
7367
9054
  console.log("");
7368
9055
  if (transcript?.hasMore) {
7369
- console.log(kleur10.yellow(`Older rows remain; continue with --transcript --cursor ${transcript.nextCursor}.`));
9056
+ console.log(kleur11.yellow(`Older rows remain; continue with --transcript --cursor ${transcript.nextCursor}.`));
7370
9057
  }
7371
9058
  if (messages.length === 0) {
7372
- console.log(kleur10.yellow(transcript ? "No transcript messages in the scanned rows." : "No messages found in this conversation."));
9059
+ console.log(kleur11.yellow(transcript ? "No transcript messages in the scanned rows." : "No messages found in this conversation."));
7373
9060
  return;
7374
9061
  }
7375
9062
  for (const message of messages.slice().reverse()) {
7376
9063
  const role = (message.role ?? "unknown").trim().toLowerCase();
7377
9064
  const createdAt = trimOrNull(message.createdAt) ?? trimOrNull(message.created_at);
7378
9065
  const header = createdAt ? `${role} \xB7 ${createdAt}` : role;
7379
- console.log(kleur10.cyan(`[${header}${transcript ? ` \xB7 ${message.id}` : ""}]`));
9066
+ console.log(kleur11.cyan(`[${header}${transcript ? ` \xB7 ${message.id}` : ""}]`));
7380
9067
  console.log(transcript ? message.content : summarizeContent(message.content ?? "", 4e3));
7381
9068
  console.log("");
7382
9069
  }
@@ -7473,9 +9160,9 @@ async function historyConversations(options) {
7473
9160
 
7474
9161
  // src/hardware-bindings.ts
7475
9162
  init_project_manifest();
7476
- import fs10 from "node:fs";
7477
- import path10 from "node:path";
7478
- import kleur11 from "kleur";
9163
+ import fs13 from "node:fs";
9164
+ import path13 from "node:path";
9165
+ import kleur12 from "kleur";
7479
9166
  var SERIAL_HARDWARE_PROVIDER_ID = "hardware.serial";
7480
9167
  var DEFAULT_SERIAL_CAPABILITIES = [
7481
9168
  "hardware_serial_list",
@@ -7485,7 +9172,7 @@ var SUPPORTED_HARDWARE_CAPABILITIES = new Set(
7485
9172
  DEFAULT_SERIAL_CAPABILITIES
7486
9173
  );
7487
9174
  function resolveHardwareBindingContext(startDir) {
7488
- const lookup = findProjectManifest(path10.resolve(startDir));
9175
+ const lookup = findProjectManifest(path13.resolve(startDir));
7489
9176
  if (!lookup.manifest || !lookup.path) {
7490
9177
  throw new Error(
7491
9178
  "Linked space manifest required. Run `instafy space init` in this folder or pass --path <dir>."
@@ -7495,17 +9182,17 @@ function resolveHardwareBindingContext(startDir) {
7495
9182
  if (!projectId2) {
7496
9183
  throw new Error("Linked space manifest is missing a spaceId.");
7497
9184
  }
7498
- const instafyDir = path10.dirname(lookup.path);
9185
+ const instafyDir2 = path13.dirname(lookup.path);
7499
9186
  return {
7500
- bindingsPath: path10.join(instafyDir, "hardware-bindings.json"),
9187
+ bindingsPath: path13.join(instafyDir2, "hardware-bindings.json"),
7501
9188
  projectId: projectId2
7502
9189
  };
7503
9190
  }
7504
9191
  function readHardwareBindingStore(bindingsPath) {
7505
- if (!fs10.existsSync(bindingsPath)) {
9192
+ if (!fs13.existsSync(bindingsPath)) {
7506
9193
  return { version: 1, bindings: {} };
7507
9194
  }
7508
- const raw = JSON.parse(fs10.readFileSync(bindingsPath, "utf8"));
9195
+ const raw = JSON.parse(fs13.readFileSync(bindingsPath, "utf8"));
7509
9196
  const bindings = raw && typeof raw.bindings === "object" && raw.bindings !== null ? raw.bindings : {};
7510
9197
  return {
7511
9198
  version: 1,
@@ -7513,8 +9200,8 @@ function readHardwareBindingStore(bindingsPath) {
7513
9200
  };
7514
9201
  }
7515
9202
  function writeHardwareBindingStore(bindingsPath, store) {
7516
- fs10.mkdirSync(path10.dirname(bindingsPath), { recursive: true });
7517
- fs10.writeFileSync(bindingsPath, JSON.stringify(store, null, 2), "utf8");
9203
+ fs13.mkdirSync(path13.dirname(bindingsPath), { recursive: true });
9204
+ fs13.writeFileSync(bindingsPath, JSON.stringify(store, null, 2), "utf8");
7518
9205
  }
7519
9206
  function normalizeCapabilities(providerId, values) {
7520
9207
  const requested = Array.isArray(values) && values.length > 0 ? values : providerId === SERIAL_HARDWARE_PROVIDER_ID ? DEFAULT_SERIAL_CAPABILITIES : [];
@@ -7542,18 +9229,18 @@ function normalizeDevices(values) {
7542
9229
  if (!trimmed) {
7543
9230
  continue;
7544
9231
  }
7545
- const resolved = path10.resolve(trimmed);
9232
+ const resolved = path13.resolve(trimmed);
7546
9233
  unique.set(resolved, {
7547
9234
  kind: "serial_device",
7548
9235
  id: resolved,
7549
9236
  path: resolved,
7550
- displayName: path10.basename(resolved)
9237
+ displayName: path13.basename(resolved)
7551
9238
  });
7552
9239
  }
7553
9240
  return [...unique.values()];
7554
9241
  }
7555
9242
  function printHardwareBinding(record2) {
7556
- console.log(kleur11.cyan(`Hardware provider: ${record2.providerId}`));
9243
+ console.log(kleur12.cyan(`Hardware provider: ${record2.providerId}`));
7557
9244
  console.log(`Status: ${record2.status}`);
7558
9245
  console.log(`Project: ${record2.projectId}`);
7559
9246
  console.log(`Capabilities: ${record2.grantedCapabilities.join(", ") || "(none)"}`);
@@ -7599,10 +9286,10 @@ function showHardwareBinding(options) {
7599
9286
  return;
7600
9287
  }
7601
9288
  if (bindings.length === 0) {
7602
- console.log(kleur11.yellow("No hardware bindings recorded for this space."));
9289
+ console.log(kleur12.yellow("No hardware bindings recorded for this space."));
7603
9290
  return;
7604
9291
  }
7605
- console.log(kleur11.cyan(`Hardware bindings for ${context.projectId}:`));
9292
+ console.log(kleur12.cyan(`Hardware bindings for ${context.projectId}:`));
7606
9293
  for (const record2 of bindings) {
7607
9294
  console.log(`- ${record2.providerId} (${record2.status})`);
7608
9295
  }
@@ -7635,9 +9322,9 @@ function grantHardwareBinding(options) {
7635
9322
  console.log(JSON.stringify(record2));
7636
9323
  return;
7637
9324
  }
7638
- console.log(kleur11.green(`Hardware binding granted: ${providerId}`));
9325
+ console.log(kleur12.green(`Hardware binding granted: ${providerId}`));
7639
9326
  printHardwareBinding(record2);
7640
- console.log(kleur11.cyan(`Stored at ${context.bindingsPath}`));
9327
+ console.log(kleur12.cyan(`Stored at ${context.bindingsPath}`));
7641
9328
  }
7642
9329
  function revokeHardwareBinding(options) {
7643
9330
  const providerId = options.providerId.trim();
@@ -7656,12 +9343,12 @@ function revokeHardwareBinding(options) {
7656
9343
  console.log(JSON.stringify({ revoked: true, providerId }));
7657
9344
  return;
7658
9345
  }
7659
- console.log(kleur11.green(`Hardware binding revoked: ${providerId}`));
7660
- console.log(kleur11.cyan(`Updated ${context.bindingsPath}`));
9346
+ console.log(kleur12.green(`Hardware binding revoked: ${providerId}`));
9347
+ console.log(kleur12.cyan(`Updated ${context.bindingsPath}`));
7661
9348
  }
7662
9349
 
7663
9350
  // src/agents.ts
7664
- import kleur12 from "kleur";
9351
+ import kleur13 from "kleur";
7665
9352
  init_project_manifest();
7666
9353
  var DEFAULT_CONTEXT_LIMIT = 50;
7667
9354
  var MAX_CONTEXT_LIMIT = 200;
@@ -7770,20 +9457,107 @@ async function agentsList(options) {
7770
9457
  return;
7771
9458
  }
7772
9459
  if (!agents.length) {
7773
- console.log(kleur12.yellow("No agents found."));
9460
+ console.log(kleur13.yellow("No agents found."));
7774
9461
  return;
7775
9462
  }
7776
- console.log(kleur12.bold("Agents"));
9463
+ console.log(kleur13.bold("Agents"));
7777
9464
  for (const agent of agents) {
7778
9465
  const provider = trimOrNull2(agent.provider);
7779
9466
  const model = trimOrNull2(agent.model);
7780
9467
  const details = [provider, model].filter(Boolean).join(" \xB7 ");
7781
9468
  console.log(
7782
- `- ${kleur12.cyan(`@${agent.handle}`)} ${displayName(agent)}${details ? ` (${details})` : ""}`
9469
+ `- ${kleur13.cyan(`@${agent.handle}`)} ${displayName(agent)}${details ? ` (${details})` : ""}`
7783
9470
  );
7784
9471
  console.log(` id: ${agent.id}`);
7785
9472
  }
7786
9473
  }
9474
+ var agentSettingFields = [
9475
+ { key: "maxAgentsPerPlan", flag: "--max-agents-per-plan", label: "Maximum agents per plan" },
9476
+ { key: "maxConcurrentAgents", flag: "--max-concurrent-agents", label: "Maximum concurrent team workers across this space" }
9477
+ ];
9478
+ function parseAgentSetting(value, label) {
9479
+ if (!/^[1-9]\d*$/.test(value.trim()) || !Number.isSafeInteger(Number(value))) {
9480
+ throw new Error(`${label} must be a positive whole number.`);
9481
+ }
9482
+ return Number(value);
9483
+ }
9484
+ async function agentSettings(options) {
9485
+ const patch = {};
9486
+ for (const field of agentSettingFields) {
9487
+ if (options[field.key] !== void 0) {
9488
+ patch[field.key] = parseAgentSetting(options[field.key], field.flag);
9489
+ }
9490
+ }
9491
+ if (options.interactive) {
9492
+ if (options.json || Object.keys(patch).length) {
9493
+ throw new Error("Use --interactive without --json or setting flags.");
9494
+ }
9495
+ if (!process.stdin.isTTY || !process.stdout.isTTY || process.env.CI === "true") {
9496
+ throw new Error("--interactive requires a terminal. Use --max-agents-per-plan or --max-concurrent-agents instead.");
9497
+ }
9498
+ }
9499
+ const projectId2 = resolveProjectId6(options.project);
9500
+ const request = {
9501
+ path: `/projects/${encodeURIComponent(projectId2)}/multi-agent-settings`,
9502
+ controllerUrl: options.controllerUrl,
9503
+ accessToken: options.accessToken,
9504
+ serviceToken: options.serviceToken
9505
+ };
9506
+ let settings = await requestControllerApiJson({ ...request, method: "GET" });
9507
+ if ((options.interactive || Object.keys(patch).length) && !settings.canManage) {
9508
+ throw new Error("You do not have permission to manage this space's multi-agent settings.");
9509
+ }
9510
+ if (options.interactive) {
9511
+ const { isCancel, text: text2 } = await import("@clack/prompts");
9512
+ console.log("These settings are shared by everyone in this space. Runtime capacity can limit concurrency further; lead and root jobs are excluded.");
9513
+ for (const field of agentSettingFields) {
9514
+ const bounds = settings.limits[field.key];
9515
+ const entered = await text2({
9516
+ message: `${field.label} (${bounds.min}\u2013${bounds.max}; default ${bounds.default})`,
9517
+ placeholder: String(settings[field.key]),
9518
+ defaultValue: String(settings[field.key]),
9519
+ validate(value2) {
9520
+ try {
9521
+ const parsed = parseAgentSetting(value2 || String(settings[field.key]), field.label);
9522
+ if (parsed < bounds.min || parsed > bounds.max) {
9523
+ return `Enter a whole number from ${bounds.min} to ${bounds.max}.`;
9524
+ }
9525
+ } catch (error) {
9526
+ return error.message;
9527
+ }
9528
+ }
9529
+ });
9530
+ if (isCancel(entered)) throw new Error("Cancelled. No settings were changed.");
9531
+ const value = parseAgentSetting(String(entered), field.label);
9532
+ if (value !== settings[field.key]) patch[field.key] = value;
9533
+ }
9534
+ }
9535
+ for (const field of agentSettingFields) {
9536
+ const value = patch[field.key];
9537
+ const bounds = settings.limits[field.key];
9538
+ if (value !== void 0 && (value < bounds.min || value > bounds.max)) {
9539
+ throw new Error(`${field.flag} must be between ${bounds.min} and ${bounds.max}.`);
9540
+ }
9541
+ }
9542
+ if (Object.keys(patch).length) {
9543
+ settings = await requestControllerApiJson({
9544
+ ...request,
9545
+ method: "PATCH",
9546
+ jsonBody: patch
9547
+ });
9548
+ }
9549
+ if (options.json) {
9550
+ console.log(JSON.stringify(settings, null, 2));
9551
+ return;
9552
+ }
9553
+ console.log(kleur13.bold(`Multi-agent settings for space ${projectId2}`));
9554
+ for (const field of agentSettingFields) {
9555
+ const bounds = settings.limits[field.key];
9556
+ console.log(`${field.label}: ${settings[field.key]} (range ${bounds.min}\u2013${bounds.max}; default ${bounds.default})`);
9557
+ }
9558
+ console.log("Shared across this space. Lead/root jobs are excluded from the concurrent worker limit; runtime capacity can limit concurrency further.");
9559
+ if (!settings.canManage) console.log("Read-only: your role cannot change these settings.");
9560
+ }
7787
9561
  async function agentContextsList(options) {
7788
9562
  const projectId2 = resolveProjectId6(options.project);
7789
9563
  const contexts = await requestControllerApiJson({
@@ -7799,15 +9573,15 @@ async function agentContextsList(options) {
7799
9573
  return;
7800
9574
  }
7801
9575
  if (!contexts.length) {
7802
- console.log(kleur12.yellow("No agent context cards found."));
9576
+ console.log(kleur13.yellow("No agent context cards found."));
7803
9577
  return;
7804
9578
  }
7805
- console.log(kleur12.bold("Agent context"));
9579
+ console.log(kleur13.bold("Agent context"));
7806
9580
  for (const card of contexts) {
7807
9581
  const title = trimOrNull2(card.title);
7808
9582
  const updatedAt = trimOrNull2(card.updatedAt) ?? trimOrNull2(card.updated_at);
7809
9583
  console.log(
7810
- `- ${kleur12.cyan(contextAgentLabel(card))} ${kleur12.gray(`[${contextScope(card)}]`)}${title ? ` ${title}` : ""}`
9584
+ `- ${kleur13.cyan(contextAgentLabel(card))} ${kleur13.gray(`[${contextScope(card)}]`)}${title ? ` ${title}` : ""}`
7811
9585
  );
7812
9586
  console.log(` ${summarize(card.context)}`);
7813
9587
  if (updatedAt) {
@@ -7844,7 +9618,7 @@ async function agentContextPut(options) {
7844
9618
  return;
7845
9619
  }
7846
9620
  console.log(
7847
- `${kleur12.green("Saved")} ${kleur12.cyan(contextAgentLabel(saved))} context for ${kleur12.gray(contextScope(saved))}.`
9621
+ `${kleur13.green("Saved")} ${kleur13.cyan(contextAgentLabel(saved))} context for ${kleur13.gray(contextScope(saved))}.`
7848
9622
  );
7849
9623
  }
7850
9624
  function workerLabel(worker) {
@@ -7870,7 +9644,7 @@ async function agentPlanGroupStatus(options) {
7870
9644
  console.log(JSON.stringify(status, null, 2));
7871
9645
  return;
7872
9646
  }
7873
- console.log(kleur12.bold(`Plan group ${trimOrNull2(status.groupId) ?? groupId}`));
9647
+ console.log(kleur13.bold(`Plan group ${trimOrNull2(status.groupId) ?? groupId}`));
7874
9648
  const conversationId = trimOrNull2(status.conversationId);
7875
9649
  if (conversationId) {
7876
9650
  console.log(` conversation: ${conversationId}`);
@@ -7880,13 +9654,13 @@ async function agentPlanGroupStatus(options) {
7880
9654
  );
7881
9655
  const workers = status.workers ?? [];
7882
9656
  if (!workers.length) {
7883
- console.log(kleur12.yellow("No worker jobs found."));
9657
+ console.log(kleur13.yellow("No worker jobs found."));
7884
9658
  return;
7885
9659
  }
7886
9660
  for (const worker of workers) {
7887
9661
  const marker = worker.terminal ? "terminal" : "running";
7888
9662
  const details = [trimOrNull2(worker.status) ?? "unknown", marker, trimOrNull2(worker.outcome)].filter(Boolean).join(" \xB7 ");
7889
- console.log(`- ${kleur12.cyan(workerLabel(worker))} ${kleur12.gray(`[${details}]`)}`);
9663
+ console.log(`- ${kleur13.cyan(workerLabel(worker))} ${kleur13.gray(`[${details}]`)}`);
7890
9664
  const scope = trimOrNull2(worker.scopeSummary);
7891
9665
  if (scope) {
7892
9666
  console.log(` scope: ${scope}`);
@@ -7897,7 +9671,7 @@ async function agentPlanGroupStatus(options) {
7897
9671
  }
7898
9672
  const errorMessage = trimOrNull2(worker.errorMessage);
7899
9673
  if (errorMessage) {
7900
- console.log(` ${kleur12.red(`error: ${summarize(errorMessage)}`)}`);
9674
+ console.log(` ${kleur13.red(`error: ${summarize(errorMessage)}`)}`);
7901
9675
  }
7902
9676
  }
7903
9677
  }
@@ -7924,11 +9698,11 @@ async function agentJobsCancel(options) {
7924
9698
  const canceledJobs = result.canceledJobIds?.length ?? 0;
7925
9699
  const canceledRuns = result.canceledRunIds?.length ?? 0;
7926
9700
  if (!canceledJobs && !canceledRuns) {
7927
- console.log(kleur12.yellow(`Nothing to cancel for ${target} (no queued or leased jobs).`));
9701
+ console.log(kleur13.yellow(`Nothing to cancel for ${target} (no queued or leased jobs).`));
7928
9702
  return;
7929
9703
  }
7930
9704
  console.log(
7931
- `${kleur12.green("Canceled")} ${canceledJobs} job(s) and ${canceledRuns} run(s) for ${target}.`
9705
+ `${kleur13.green("Canceled")} ${canceledJobs} job(s) and ${canceledRuns} run(s) for ${target}.`
7932
9706
  );
7933
9707
  }
7934
9708
 
@@ -8080,7 +9854,7 @@ function isAssistantReplyCandidate(message, runId, options) {
8080
9854
  }
8081
9855
  return typeof message.content === "string" && message.content.trim().length > 0;
8082
9856
  }
8083
- async function sleep(ms) {
9857
+ async function sleep2(ms) {
8084
9858
  await new Promise((resolve) => setTimeout(resolve, ms));
8085
9859
  }
8086
9860
  async function dispatchPrompt(options) {
@@ -8188,7 +9962,7 @@ async function waitForAssistantReply(conversationId, runId, options) {
8188
9962
  break;
8189
9963
  }
8190
9964
  }
8191
- await sleep(pollMs);
9965
+ await sleep2(pollMs);
8192
9966
  }
8193
9967
  const finalMessages = await fetchConversationMessages2(conversationId, options);
8194
9968
  const assistant = findLatestAssistantReply(finalMessages, runId, options);
@@ -8234,8 +10008,8 @@ async function chatPrompt(options) {
8234
10008
  }
8235
10009
 
8236
10010
  // src/recommendations.ts
8237
- import fs11 from "node:fs";
8238
- import path11 from "node:path";
10011
+ import fs14 from "node:fs";
10012
+ import path14 from "node:path";
8239
10013
  init_project_manifest();
8240
10014
  var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8241
10015
  var MAX_FILE_BYTES = 65536;
@@ -8264,26 +10038,26 @@ function text(value, label, max) {
8264
10038
  }
8265
10039
  function readProposalFile(file) {
8266
10040
  const manifestPath = findProjectManifest(process.cwd()).path;
8267
- const root = fs11.realpathSync(manifestPath ? path11.dirname(path11.dirname(manifestPath)) : process.cwd());
8268
- const selected = path11.resolve(file);
8269
- const canonical = path11.join(fs11.realpathSync(path11.dirname(selected)), path11.basename(selected));
8270
- const relative = path11.relative(root, canonical);
8271
- if (relative === ".." || relative.startsWith(`..${path11.sep}`) || path11.isAbsolute(relative)) {
10041
+ const root = fs14.realpathSync(manifestPath ? path14.dirname(path14.dirname(manifestPath)) : process.cwd());
10042
+ const selected = path14.resolve(file);
10043
+ const canonical = path14.join(fs14.realpathSync(path14.dirname(selected)), path14.basename(selected));
10044
+ const relative = path14.relative(root, canonical);
10045
+ if (relative === ".." || relative.startsWith(`..${path14.sep}`) || path14.isAbsolute(relative)) {
8272
10046
  throw new Error("Recommendation file must stay inside the active Instafy workspace.");
8273
10047
  }
8274
- const initial = fs11.lstatSync(canonical);
10048
+ const initial = fs14.lstatSync(canonical);
8275
10049
  if (!initial.isFile() || initial.isSymbolicLink()) throw new Error("Recommendation file must be a regular file, not a symlink.");
8276
- const descriptor = fs11.openSync(canonical, fs11.constants.O_RDONLY | (fs11.constants.O_NOFOLLOW ?? 0));
10050
+ const descriptor = fs14.openSync(canonical, fs14.constants.O_RDONLY | (fs14.constants.O_NOFOLLOW ?? 0));
8277
10051
  let bytes;
8278
10052
  try {
8279
- const opened = fs11.fstatSync(descriptor);
10053
+ const opened = fs14.fstatSync(descriptor);
8280
10054
  if (!opened.isFile() || opened.dev !== initial.dev || opened.ino !== initial.ino || opened.size > MAX_FILE_BYTES) {
8281
10055
  throw new Error(`Recommendation file must be a regular file no larger than ${MAX_FILE_BYTES} bytes.`);
8282
10056
  }
8283
- bytes = fs11.readFileSync(descriptor);
10057
+ bytes = fs14.readFileSync(descriptor);
8284
10058
  if (bytes.length > MAX_FILE_BYTES) throw new Error(`Recommendation file must be ${MAX_FILE_BYTES} bytes or smaller.`);
8285
10059
  } finally {
8286
- fs11.closeSync(descriptor);
10060
+ fs14.closeSync(descriptor);
8287
10061
  }
8288
10062
  return bytes;
8289
10063
  }
@@ -8615,11 +10389,11 @@ async function showConversationContext(options) {
8615
10389
  }
8616
10390
 
8617
10391
  // src/invitations.ts
8618
- import kleur13 from "kleur";
8619
- import path12 from "node:path";
10392
+ import kleur14 from "kleur";
10393
+ import path15 from "node:path";
8620
10394
  init_project_manifest();
8621
10395
  function resolveStartDir(rawPath) {
8622
- return path12.resolve(rawPath ?? process.cwd());
10396
+ return path15.resolve(rawPath ?? process.cwd());
8623
10397
  }
8624
10398
  function trimOrNull3(value) {
8625
10399
  const trimmed = value?.trim() ?? "";
@@ -8761,7 +10535,7 @@ async function inviteSpaceMember(options) {
8761
10535
  return normalized;
8762
10536
  }
8763
10537
  const scopeLabel = orgName ? `${orgName}` : "this space";
8764
- console.log(kleur13.green(`Invitation sent to ${normalized.email}`));
10538
+ console.log(kleur14.green(`Invitation sent to ${normalized.email}`));
8765
10539
  console.log(`Scope: ${scopeLabel}`);
8766
10540
  console.log(`Role: ${normalized.role}`);
8767
10541
  if (normalized.expiresAt) {
@@ -8821,7 +10595,7 @@ async function setSpaceMemberRole(options) {
8821
10595
  return normalized;
8822
10596
  }
8823
10597
  console.log(
8824
- kleur13.green(
10598
+ kleur14.green(
8825
10599
  normalized.status === "updated" ? `Updated ${normalized.email} to ${normalized.role}` : `${normalized.email} already has role ${normalized.role}`
8826
10600
  )
8827
10601
  );
@@ -8860,7 +10634,7 @@ async function setSpaceMemberRole(options) {
8860
10634
  return normalized;
8861
10635
  }
8862
10636
  console.log(
8863
- kleur13.green(
10637
+ kleur14.green(
8864
10638
  normalized.status === "updated" ? `Updated pending invite for ${normalized.email} to ${normalized.role}` : `Pending invite for ${normalized.email} already has role ${normalized.role}`
8865
10639
  )
8866
10640
  );
@@ -8910,7 +10684,7 @@ async function sendSpaceInvitation(options) {
8910
10684
  init_config();
8911
10685
  init_controller_fetch();
8912
10686
  init_errors();
8913
- import kleur14 from "kleur";
10687
+ import kleur15 from "kleur";
8914
10688
  var DEFAULT_STUDIO_URL = "https://instafy.dev";
8915
10689
  var ORG_ROLES = ["owner", "admin", "builder", "viewer"];
8916
10690
  var INVITE_LINK_ROLES = ["builder", "viewer"];
@@ -9065,7 +10839,7 @@ function printTable(headers, rows) {
9065
10839
  (header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length))
9066
10840
  );
9067
10841
  const format = (cells) => cells.map((cell, index) => (cell ?? "").padEnd(widths[index])).join(" ").trimEnd();
9068
- console.log(kleur14.bold(format(headers)));
10842
+ console.log(kleur15.bold(format(headers)));
9069
10843
  for (const row of rows) {
9070
10844
  console.log(format(row));
9071
10845
  }
@@ -9094,7 +10868,7 @@ async function teamMembersList(options) {
9094
10868
  return;
9095
10869
  }
9096
10870
  if (members.length === 0) {
9097
- console.log(kleur14.gray("No members."));
10871
+ console.log(kleur15.gray("No members."));
9098
10872
  return;
9099
10873
  }
9100
10874
  const rows = members.map((member) => [
@@ -9150,7 +10924,7 @@ If they already have an Instafy account, add them directly with:
9150
10924
  );
9151
10925
  return;
9152
10926
  }
9153
- console.log(kleur14.green(`Invitation sent to ${invitation.email}`));
10927
+ console.log(kleur15.green(`Invitation sent to ${invitation.email}`));
9154
10928
  console.log(`Role: ${invitation.role}`);
9155
10929
  if (invitation.expiresAt) {
9156
10930
  console.log(`Expires: ${invitation.expiresAt}`);
@@ -9181,7 +10955,7 @@ async function teamInviteLink(options) {
9181
10955
  console.log(JSON.stringify({ inviteLink, acceptUrl }, null, 2));
9182
10956
  return;
9183
10957
  }
9184
- console.log(kleur14.green("Shareable invite link created."));
10958
+ console.log(kleur15.green("Shareable invite link created."));
9185
10959
  console.log(`Role: ${inviteLink.role}`);
9186
10960
  console.log(`Link: ${acceptUrl}`);
9187
10961
  console.log(`Token: ${inviteLink.token}`);
@@ -9213,11 +10987,11 @@ async function teamInvites(options) {
9213
10987
  return;
9214
10988
  }
9215
10989
  if (invitations.length === 0 && inviteLinks.length === 0) {
9216
- console.log(kleur14.gray("No pending invitations or invite links."));
10990
+ console.log(kleur15.gray("No pending invitations or invite links."));
9217
10991
  return;
9218
10992
  }
9219
10993
  if (invitations.length > 0) {
9220
- console.log(kleur14.bold("Email invitations"));
10994
+ console.log(kleur15.bold("Email invitations"));
9221
10995
  const rows = invitations.map((invitation) => [
9222
10996
  shortId(invitation.id),
9223
10997
  invitation.email,
@@ -9228,7 +11002,7 @@ async function teamInvites(options) {
9228
11002
  }
9229
11003
  if (inviteLinks.length > 0) {
9230
11004
  if (invitations.length > 0) console.log("");
9231
- console.log(kleur14.bold("Invite links (shareable, grant join on sign-in)"));
11005
+ console.log(kleur15.bold("Invite links (shareable, grant join on sign-in)"));
9232
11006
  const rows = inviteLinks.map((link) => [
9233
11007
  shortId(link.id),
9234
11008
  link.role,
@@ -9259,7 +11033,7 @@ async function teamAddMember(options) {
9259
11033
  return;
9260
11034
  }
9261
11035
  const label = (member.email ?? "").trim() || member.userId;
9262
- console.log(kleur14.green(`Added ${label} as ${member.role}.`));
11036
+ console.log(kleur15.green(`Added ${label} as ${member.role}.`));
9263
11037
  }
9264
11038
  async function teamAccept(options) {
9265
11039
  const auth = resolveControllerAuth4(options, "instafy team accept");
@@ -9274,7 +11048,7 @@ async function teamAccept(options) {
9274
11048
  return;
9275
11049
  }
9276
11050
  const name = (payload?.orgName ?? "").trim() || payload?.orgSlug || payload?.orgId || "team";
9277
- console.log(kleur14.green(`Joined ${name} as ${payload?.role ?? "member"}.`));
11051
+ console.log(kleur15.green(`Joined ${name} as ${payload?.role ?? "member"}.`));
9278
11052
  }
9279
11053
  async function confirmDestructive(message, yes, json) {
9280
11054
  if (yes) return;
@@ -9308,7 +11082,7 @@ async function teamRevokeInvite(options) {
9308
11082
  console.log(JSON.stringify({ ok: true, invitationId }, null, 2));
9309
11083
  return;
9310
11084
  }
9311
- console.log(kleur14.green(`Revoked invitation ${invitationId}.`));
11085
+ console.log(kleur15.green(`Revoked invitation ${invitationId}.`));
9312
11086
  }
9313
11087
  async function teamRevokeLink(options) {
9314
11088
  const auth = resolveControllerAuth4(options, "instafy team revoke-link");
@@ -9327,17 +11101,17 @@ async function teamRevokeLink(options) {
9327
11101
  console.log(JSON.stringify({ ok: true, inviteLinkId }, null, 2));
9328
11102
  return;
9329
11103
  }
9330
- console.log(kleur14.green(`Revoked invite link ${inviteLinkId}.`));
11104
+ console.log(kleur15.green(`Revoked invite link ${inviteLinkId}.`));
9331
11105
  }
9332
11106
 
9333
11107
  // src/provider-bindings.ts
9334
11108
  init_project_manifest();
9335
- import fs12 from "node:fs";
9336
- import path13 from "node:path";
9337
- import { pathToFileURL } from "node:url";
9338
- import kleur15 from "kleur";
11109
+ import fs15 from "node:fs";
11110
+ import path16 from "node:path";
11111
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
11112
+ import kleur16 from "kleur";
9339
11113
  function resolveBindingContext(startDir) {
9340
- const lookup = findProjectManifest(path13.resolve(startDir));
11114
+ const lookup = findProjectManifest(path16.resolve(startDir));
9341
11115
  if (!lookup.manifest || !lookup.path) {
9342
11116
  throw new Error(
9343
11117
  "Linked space manifest required. Run `instafy space init` in this folder or pass --path <dir>."
@@ -9347,19 +11121,19 @@ function resolveBindingContext(startDir) {
9347
11121
  if (!projectId2) {
9348
11122
  throw new Error("Linked space manifest is missing a spaceId.");
9349
11123
  }
9350
- const instafyDir = path13.dirname(lookup.path);
9351
- const projectRoot = path13.dirname(instafyDir);
11124
+ const instafyDir2 = path16.dirname(lookup.path);
11125
+ const projectRoot = path16.dirname(instafyDir2);
9352
11126
  return {
9353
- bindingsPath: path13.join(instafyDir, "provider-bindings.json"),
11127
+ bindingsPath: path16.join(instafyDir2, "provider-bindings.json"),
9354
11128
  projectId: projectId2,
9355
11129
  projectRoot
9356
11130
  };
9357
11131
  }
9358
11132
  function readBindingStore(bindingsPath) {
9359
- if (!fs12.existsSync(bindingsPath)) {
11133
+ if (!fs15.existsSync(bindingsPath)) {
9360
11134
  return { version: 1, bindings: {} };
9361
11135
  }
9362
- const raw = JSON.parse(fs12.readFileSync(bindingsPath, "utf8"));
11136
+ const raw = JSON.parse(fs15.readFileSync(bindingsPath, "utf8"));
9363
11137
  const bindings = raw && typeof raw.bindings === "object" && raw.bindings !== null ? raw.bindings : {};
9364
11138
  return {
9365
11139
  version: 1,
@@ -9367,8 +11141,8 @@ function readBindingStore(bindingsPath) {
9367
11141
  };
9368
11142
  }
9369
11143
  function writeBindingStore(bindingsPath, store) {
9370
- fs12.mkdirSync(path13.dirname(bindingsPath), { recursive: true });
9371
- fs12.writeFileSync(bindingsPath, JSON.stringify(store, null, 2), "utf8");
11144
+ fs15.mkdirSync(path16.dirname(bindingsPath), { recursive: true });
11145
+ fs15.writeFileSync(bindingsPath, JSON.stringify(store, null, 2), "utf8");
9372
11146
  }
9373
11147
  function normalizeCapabilities2(values) {
9374
11148
  const requested = Array.isArray(values) && values.length > 0 ? values : ["project_content_read", "project_content_write"];
@@ -9391,7 +11165,7 @@ function deriveStatus(capabilities) {
9391
11165
  return "bound_read_only";
9392
11166
  }
9393
11167
  function printBinding(record2) {
9394
- console.log(kleur15.cyan(`Provider: ${record2.providerId}`));
11168
+ console.log(kleur16.cyan(`Provider: ${record2.providerId}`));
9395
11169
  console.log(`Status: ${record2.status}`);
9396
11170
  console.log(`Project: ${record2.projectId}`);
9397
11171
  console.log(`Root URI: ${record2.rootUri}`);
@@ -9433,10 +11207,10 @@ function showProviderBinding(options) {
9433
11207
  return;
9434
11208
  }
9435
11209
  if (bindings.length === 0) {
9436
- console.log(kleur15.yellow("No provider bindings recorded for this space."));
11210
+ console.log(kleur16.yellow("No provider bindings recorded for this space."));
9437
11211
  return;
9438
11212
  }
9439
- console.log(kleur15.cyan(`Bindings for ${context.projectId}:`));
11213
+ console.log(kleur16.cyan(`Bindings for ${context.projectId}:`));
9440
11214
  for (const record2 of bindings) {
9441
11215
  console.log(`- ${record2.providerId} (${record2.status})`);
9442
11216
  }
@@ -9454,7 +11228,7 @@ function grantProviderBinding(options) {
9454
11228
  const record2 = {
9455
11229
  providerId,
9456
11230
  projectId: context.projectId,
9457
- rootUri: pathToFileURL(context.projectRoot).toString(),
11231
+ rootUri: pathToFileURL2(context.projectRoot).toString(),
9458
11232
  grantedCapabilities: capabilities,
9459
11233
  grantedPrefix: options.prefix?.trim() || `.instafy/providers/${providerId}/`,
9460
11234
  purpose: options.purpose?.trim() || null,
@@ -9468,9 +11242,9 @@ function grantProviderBinding(options) {
9468
11242
  console.log(JSON.stringify(record2));
9469
11243
  return;
9470
11244
  }
9471
- console.log(kleur15.green(`Provider binding granted: ${providerId}`));
11245
+ console.log(kleur16.green(`Provider binding granted: ${providerId}`));
9472
11246
  printBinding(record2);
9473
- console.log(kleur15.cyan(`Stored at ${context.bindingsPath}`));
11247
+ console.log(kleur16.cyan(`Stored at ${context.bindingsPath}`));
9474
11248
  }
9475
11249
  function revokeProviderBinding(options) {
9476
11250
  const providerId = options.providerId.trim();
@@ -9489,12 +11263,12 @@ function revokeProviderBinding(options) {
9489
11263
  console.log(JSON.stringify({ revoked: true, providerId }));
9490
11264
  return;
9491
11265
  }
9492
- console.log(kleur15.green(`Provider binding revoked: ${providerId}`));
9493
- console.log(kleur15.cyan(`Updated ${context.bindingsPath}`));
11266
+ console.log(kleur16.green(`Provider binding revoked: ${providerId}`));
11267
+ console.log(kleur16.cyan(`Updated ${context.bindingsPath}`));
9494
11268
  }
9495
11269
 
9496
11270
  // src/providers.ts
9497
- import kleur16 from "kleur";
11271
+ import kleur17 from "kleur";
9498
11272
 
9499
11273
  // ../provider-client/index.js
9500
11274
  var DEFAULT_PROVIDER_HOST_BASE_URL = "http://127.0.0.1:8797";
@@ -9566,12 +11340,12 @@ function resolveFetchImplementation(fetchImpl) {
9566
11340
  function createProviderHostClient(options = {}) {
9567
11341
  const fetchImpl = resolveFetchImplementation(options.fetch);
9568
11342
  const baseUrl = normalizeProviderHostBaseUrl(options.baseUrl);
9569
- async function jsonRequest(path16, init) {
9570
- const response = await fetchImpl(`${baseUrl}${path16}`, init);
11343
+ async function jsonRequest(path19, init) {
11344
+ const response = await fetchImpl(`${baseUrl}${path19}`, init);
9571
11345
  const json = await response.json();
9572
11346
  if (!response.ok || json?.ok === false) {
9573
11347
  throw new Error(
9574
- json?.error || json?.stderr || `provider host request failed for ${path16}`
11348
+ json?.error || json?.stderr || `provider host request failed for ${path19}`
9575
11349
  );
9576
11350
  }
9577
11351
  return json;
@@ -9664,7 +11438,7 @@ function formatProviderLabel(provider) {
9664
11438
  return provider.title?.trim().length ? provider.title.trim() : provider.id;
9665
11439
  }
9666
11440
  function printProviderSummary(provider) {
9667
- console.log(kleur16.green(formatProviderLabel(provider)));
11441
+ console.log(kleur17.green(formatProviderLabel(provider)));
9668
11442
  console.log(` id: ${provider.id}`);
9669
11443
  if (provider.description) {
9670
11444
  console.log(` description: ${provider.description}`);
@@ -9676,7 +11450,7 @@ function printProviderSummary(provider) {
9676
11450
  console.log(` type: ${provider.providerType}`);
9677
11451
  }
9678
11452
  console.log(
9679
- ` discoverable: ${provider.discoverable !== false ? kleur16.green("yes") : kleur16.yellow("no")}`
11453
+ ` discoverable: ${provider.discoverable !== false ? kleur17.green("yes") : kleur17.yellow("no")}`
9680
11454
  );
9681
11455
  if (provider.capabilityIds?.length) {
9682
11456
  console.log(` capabilities: ${provider.capabilityIds.join(", ")}`);
@@ -9688,7 +11462,7 @@ function printProviderSummary(provider) {
9688
11462
  console.log(` resources: ${provider.resourceUris.join(", ")}`);
9689
11463
  }
9690
11464
  if (provider.error) {
9691
- console.log(` error: ${kleur16.yellow(provider.error)}`);
11465
+ console.log(` error: ${kleur17.yellow(provider.error)}`);
9692
11466
  }
9693
11467
  }
9694
11468
  function parseCommandJson(commandJson) {
@@ -9708,10 +11482,10 @@ async function providersList(options = {}) {
9708
11482
  });
9709
11483
  return;
9710
11484
  }
9711
- console.log(kleur16.green("Local providers"));
11485
+ console.log(kleur17.green("Local providers"));
9712
11486
  console.log(`Host: ${providerHostUrl}`);
9713
11487
  if (!result.providers.length) {
9714
- console.log(kleur16.yellow("No providers discovered."));
11488
+ console.log(kleur17.yellow("No providers discovered."));
9715
11489
  return;
9716
11490
  }
9717
11491
  for (const provider of result.providers) {
@@ -9728,7 +11502,7 @@ async function providersDiscover(params) {
9728
11502
  });
9729
11503
  return;
9730
11504
  }
9731
- console.log(kleur16.green(`Provider discovery: ${params.providerId}`));
11505
+ console.log(kleur17.green(`Provider discovery: ${params.providerId}`));
9732
11506
  console.log(`Host: ${providerHostUrl}`);
9733
11507
  console.log(JSON.stringify(result.provider ?? result, null, 2));
9734
11508
  }
@@ -9742,10 +11516,10 @@ async function providersRead(params) {
9742
11516
  });
9743
11517
  return;
9744
11518
  }
9745
- console.log(kleur16.green(`Provider resource: ${params.uri}`));
11519
+ console.log(kleur17.green(`Provider resource: ${params.uri}`));
9746
11520
  console.log(`Host: ${providerHostUrl}`);
9747
11521
  if (result.exists === false) {
9748
- console.log(kleur16.yellow("Resource does not exist."));
11522
+ console.log(kleur17.yellow("Resource does not exist."));
9749
11523
  return;
9750
11524
  }
9751
11525
  console.log(JSON.stringify(result.value ?? result, null, 2));
@@ -9771,7 +11545,7 @@ async function providersProbe(params) {
9771
11545
  });
9772
11546
  return;
9773
11547
  }
9774
- console.log(kleur16.green(`Provider transport probe: ${params.providerId}`));
11548
+ console.log(kleur17.green(`Provider transport probe: ${params.providerId}`));
9775
11549
  console.log(`Host: ${providerHostUrl}`);
9776
11550
  console.log(JSON.stringify(result, null, 2));
9777
11551
  }
@@ -9873,9 +11647,9 @@ async function diagnosticsRunResult(options) {
9873
11647
  }
9874
11648
 
9875
11649
  // src/support.ts
9876
- import fs13 from "node:fs";
11650
+ import fs16 from "node:fs";
9877
11651
  import { randomUUID as randomUUID3 } from "node:crypto";
9878
- import path14 from "node:path";
11652
+ import path17 from "node:path";
9879
11653
  init_project_manifest();
9880
11654
  var MAX_SUMMARY_CHARS = 500;
9881
11655
  var MAX_DETAILS_BYTES = 2e4;
@@ -9924,34 +11698,34 @@ function requiredCursorUuid(value, flag) {
9924
11698
  return id;
9925
11699
  }
9926
11700
  function readRegularFile(filePath, maxBytes, label) {
9927
- const resolved = path14.resolve(filePath);
11701
+ const resolved = path17.resolve(filePath);
9928
11702
  const manifestPath = findProjectManifest(process.cwd()).path;
9929
- const workspaceRoot = manifestPath ? path14.dirname(path14.dirname(manifestPath)) : process.cwd();
9930
- const canonicalRoot = fs13.realpathSync(workspaceRoot);
9931
- const canonicalParent = fs13.realpathSync(path14.dirname(resolved));
9932
- const canonicalTarget = path14.join(canonicalParent, path14.basename(resolved));
9933
- const relativeTarget = path14.relative(canonicalRoot, canonicalTarget);
9934
- if (relativeTarget === ".." || relativeTarget.startsWith(`..${path14.sep}`) || path14.isAbsolute(relativeTarget)) {
11703
+ const workspaceRoot = manifestPath ? path17.dirname(path17.dirname(manifestPath)) : process.cwd();
11704
+ const canonicalRoot = fs16.realpathSync(workspaceRoot);
11705
+ const canonicalParent = fs16.realpathSync(path17.dirname(resolved));
11706
+ const canonicalTarget = path17.join(canonicalParent, path17.basename(resolved));
11707
+ const relativeTarget = path17.relative(canonicalRoot, canonicalTarget);
11708
+ if (relativeTarget === ".." || relativeTarget.startsWith(`..${path17.sep}`) || path17.isAbsolute(relativeTarget)) {
9935
11709
  throw new Error(`${label} must stay inside the active Instafy workspace.`);
9936
11710
  }
9937
- const initialStat = fs13.lstatSync(canonicalTarget);
11711
+ const initialStat = fs16.lstatSync(canonicalTarget);
9938
11712
  if (initialStat.isSymbolicLink() || !initialStat.isFile()) {
9939
11713
  throw new Error(`${label} must be a regular file, not a symlink.`);
9940
11714
  }
9941
- const noFollow = typeof fs13.constants.O_NOFOLLOW === "number" ? fs13.constants.O_NOFOLLOW : 0;
9942
- const descriptor = fs13.openSync(canonicalTarget, fs13.constants.O_RDONLY | noFollow);
11715
+ const noFollow = typeof fs16.constants.O_NOFOLLOW === "number" ? fs16.constants.O_NOFOLLOW : 0;
11716
+ const descriptor = fs16.openSync(canonicalTarget, fs16.constants.O_RDONLY | noFollow);
9943
11717
  try {
9944
- const openedStat = fs13.fstatSync(descriptor);
11718
+ const openedStat = fs16.fstatSync(descriptor);
9945
11719
  if (!openedStat.isFile() || openedStat.dev !== initialStat.dev || openedStat.ino !== initialStat.ino || openedStat.size > maxBytes) {
9946
11720
  throw new Error(`${label} must be a regular file no larger than ${maxBytes} bytes.`);
9947
11721
  }
9948
- const bytes = fs13.readFileSync(descriptor);
11722
+ const bytes = fs16.readFileSync(descriptor);
9949
11723
  if (bytes.length > maxBytes) {
9950
11724
  throw new Error(`${label} must be ${maxBytes} bytes or smaller.`);
9951
11725
  }
9952
11726
  return bytes;
9953
11727
  } finally {
9954
- fs13.closeSync(descriptor);
11728
+ fs16.closeSync(descriptor);
9955
11729
  }
9956
11730
  }
9957
11731
  function readJsonFile(filePath, maxBytes, label) {
@@ -10025,7 +11799,7 @@ function resolveScreenshots(filePaths = []) {
10025
11799
  );
10026
11800
  }
10027
11801
  return {
10028
- fileName: path14.basename(filePath),
11802
+ fileName: path17.basename(filePath),
10029
11803
  mediaType,
10030
11804
  dataBase64: bytes.toString("base64"),
10031
11805
  byteLength: bytes.length
@@ -10423,7 +12197,7 @@ function failWithGroupHelp(command) {
10423
12197
  process.exitCode = 1;
10424
12198
  });
10425
12199
  }
10426
- var require4 = createRequire3(import.meta.url);
12200
+ var require4 = createRequire4(import.meta.url);
10427
12201
  var pkg = require4("../package.json");
10428
12202
  function addServerUrlOptions(command) {
10429
12203
  return command.option(
@@ -10508,7 +12282,7 @@ program.command("login").description("Log in and save an access token for future
10508
12282
  json: opts.json
10509
12283
  });
10510
12284
  } catch (error) {
10511
- console.error(kleur19.red(String(error)));
12285
+ console.error(kleur20.red(String(error)));
10512
12286
  process.exit(1);
10513
12287
  }
10514
12288
  });
@@ -10516,7 +12290,7 @@ program.command("logout").description("Clear the saved CLI access token").option
10516
12290
  try {
10517
12291
  await logout({ json: opts.json, profile: opts.profile });
10518
12292
  } catch (error) {
10519
- console.error(kleur19.red(String(error)));
12293
+ console.error(kleur20.red(String(error)));
10520
12294
  process.exit(1);
10521
12295
  }
10522
12296
  });
@@ -10540,7 +12314,7 @@ chatCommand.option("--json", "Output JSON").action(async (promptParts, opts) =>
10540
12314
  json: opts.json
10541
12315
  });
10542
12316
  } catch (error) {
10543
- console.error(kleur19.red(String(error)));
12317
+ console.error(kleur20.red(String(error)));
10544
12318
  process.exit(1);
10545
12319
  }
10546
12320
  });
@@ -10564,7 +12338,7 @@ function registerSpaceCommand(command) {
10564
12338
  json: opts.json
10565
12339
  });
10566
12340
  } catch (error) {
10567
- console.error(kleur19.red(String(error)));
12341
+ console.error(kleur20.red(String(error)));
10568
12342
  process.exit(1);
10569
12343
  }
10570
12344
  });
@@ -10580,7 +12354,7 @@ function registerSpaceCommand(command) {
10580
12354
  json: opts.json
10581
12355
  });
10582
12356
  } catch (error) {
10583
- console.error(kleur19.red(String(error)));
12357
+ console.error(kleur20.red(String(error)));
10584
12358
  process.exit(1);
10585
12359
  }
10586
12360
  });
@@ -10594,7 +12368,7 @@ function registerSpaceCommand(command) {
10594
12368
  json: opts.json
10595
12369
  });
10596
12370
  } catch (error) {
10597
- console.error(kleur19.red(String(error)));
12371
+ console.error(kleur20.red(String(error)));
10598
12372
  process.exit(1);
10599
12373
  }
10600
12374
  });
@@ -10617,7 +12391,7 @@ function registerSpaceCommand(command) {
10617
12391
  json: opts.json
10618
12392
  });
10619
12393
  } catch (error) {
10620
- console.error(kleur19.red(String(error)));
12394
+ console.error(kleur20.red(String(error)));
10621
12395
  process.exit(1);
10622
12396
  }
10623
12397
  });
@@ -10629,7 +12403,7 @@ function registerSpaceCommand(command) {
10629
12403
  json: opts.json
10630
12404
  });
10631
12405
  } catch (error) {
10632
- console.error(kleur19.red(String(error)));
12406
+ console.error(kleur20.red(String(error)));
10633
12407
  process.exit(1);
10634
12408
  }
10635
12409
  });
@@ -10651,7 +12425,7 @@ function registerSpaceCommand(command) {
10651
12425
  json: opts.json
10652
12426
  });
10653
12427
  } catch (error) {
10654
- console.error(kleur19.red(String(error)));
12428
+ console.error(kleur20.red(String(error)));
10655
12429
  process.exit(1);
10656
12430
  }
10657
12431
  });
@@ -10667,7 +12441,7 @@ function registerSpaceCommand(command) {
10667
12441
  json: opts.json
10668
12442
  });
10669
12443
  } catch (error) {
10670
- console.error(kleur19.red(String(error)));
12444
+ console.error(kleur20.red(String(error)));
10671
12445
  process.exit(1);
10672
12446
  }
10673
12447
  });
@@ -10693,7 +12467,7 @@ function registerSpaceCommand(command) {
10693
12467
  json: opts.json
10694
12468
  });
10695
12469
  } catch (error) {
10696
- console.error(kleur19.red(String(error)));
12470
+ console.error(kleur20.red(String(error)));
10697
12471
  process.exit(1);
10698
12472
  }
10699
12473
  });
@@ -10718,7 +12492,7 @@ function registerSpaceCommand(command) {
10718
12492
  json: opts.json
10719
12493
  });
10720
12494
  } catch (error) {
10721
- console.error(kleur19.red(String(error)));
12495
+ console.error(kleur20.red(String(error)));
10722
12496
  process.exit(1);
10723
12497
  }
10724
12498
  });
@@ -10738,7 +12512,7 @@ hardwareBindingsCommand.command("show").description("Show hardware bindings for
10738
12512
  json: opts.json
10739
12513
  });
10740
12514
  } catch (error) {
10741
- console.error(kleur19.red(String(error)));
12515
+ console.error(kleur20.red(String(error)));
10742
12516
  process.exit(1);
10743
12517
  }
10744
12518
  });
@@ -10758,7 +12532,7 @@ hardwareBindingsCommand.command("grant").description("Grant a local hardware pro
10758
12532
  json: opts.json
10759
12533
  });
10760
12534
  } catch (error) {
10761
- console.error(kleur19.red(String(error)));
12535
+ console.error(kleur20.red(String(error)));
10762
12536
  process.exit(1);
10763
12537
  }
10764
12538
  });
@@ -10770,7 +12544,7 @@ hardwareBindingsCommand.command("revoke").description("Revoke a local hardware b
10770
12544
  json: opts.json
10771
12545
  });
10772
12546
  } catch (error) {
10773
- console.error(kleur19.red(String(error)));
12547
+ console.error(kleur20.red(String(error)));
10774
12548
  process.exit(1);
10775
12549
  }
10776
12550
  });
@@ -10784,7 +12558,7 @@ hardwareSerialCommand.command("list").description("List local serial devices vis
10784
12558
  scanDirs: opts.scanDir
10785
12559
  });
10786
12560
  } catch (error) {
10787
- console.error(kleur19.red(String(error)));
12561
+ console.error(kleur20.red(String(error)));
10788
12562
  process.exit(1);
10789
12563
  }
10790
12564
  });
@@ -10796,7 +12570,7 @@ hardwareSerialCommand.command("probe").description("Probe host permissions for o
10796
12570
  json: opts.json
10797
12571
  });
10798
12572
  } catch (error) {
10799
- console.error(kleur19.red(String(error)));
12573
+ console.error(kleur20.red(String(error)));
10800
12574
  process.exit(1);
10801
12575
  }
10802
12576
  });
@@ -10808,7 +12582,7 @@ hardwareCommand.command("opportunities").description("List host-local IO opportu
10808
12582
  scanDirs: opts.scanDir
10809
12583
  });
10810
12584
  } catch (error) {
10811
- console.error(kleur19.red(String(error)));
12585
+ console.error(kleur20.red(String(error)));
10812
12586
  process.exit(1);
10813
12587
  }
10814
12588
  });
@@ -10821,7 +12595,7 @@ hardwareCommand.command("run").description("Run one available host-local IO acti
10821
12595
  json: opts.json
10822
12596
  });
10823
12597
  } catch (error) {
10824
- console.error(kleur19.red(String(error)));
12598
+ console.error(kleur20.red(String(error)));
10825
12599
  process.exit(1);
10826
12600
  }
10827
12601
  });
@@ -10836,7 +12610,7 @@ providersListCommand.option("--json", "Output JSON").action(async (opts) => {
10836
12610
  json: opts.json
10837
12611
  });
10838
12612
  } catch (error) {
10839
- console.error(kleur19.red(String(error)));
12613
+ console.error(kleur20.red(String(error)));
10840
12614
  process.exit(1);
10841
12615
  }
10842
12616
  });
@@ -10850,7 +12624,7 @@ providersDiscoverCommand.option("--json", "Output JSON").action(async (providerI
10850
12624
  json: opts.json
10851
12625
  });
10852
12626
  } catch (error) {
10853
- console.error(kleur19.red(String(error)));
12627
+ console.error(kleur20.red(String(error)));
10854
12628
  process.exit(1);
10855
12629
  }
10856
12630
  });
@@ -10865,7 +12639,7 @@ providersReadCommand.option("--json", "Output JSON").action(async (providerId, u
10865
12639
  json: opts.json
10866
12640
  });
10867
12641
  } catch (error) {
10868
- console.error(kleur19.red(String(error)));
12642
+ console.error(kleur20.red(String(error)));
10869
12643
  process.exit(1);
10870
12644
  }
10871
12645
  });
@@ -10888,7 +12662,7 @@ providersProbeCommand.option("--json", "Output JSON").action(async (providerId,
10888
12662
  json: opts.json
10889
12663
  });
10890
12664
  } catch (error) {
10891
- console.error(kleur19.red(String(error)));
12665
+ console.error(kleur20.red(String(error)));
10892
12666
  process.exit(1);
10893
12667
  }
10894
12668
  });
@@ -10908,7 +12682,7 @@ for (const operation of ["list", "submit"]) {
10908
12682
  if (operation === "list") await recommendationsList({ ...common, limit: opts.limit });
10909
12683
  else await recommendationsSubmit({ ...common, file: opts.file });
10910
12684
  } catch (error) {
10911
- console.error(kleur19.red(String(error)));
12685
+ console.error(kleur20.red(String(error)));
10912
12686
  process.exit(1);
10913
12687
  }
10914
12688
  });
@@ -10932,13 +12706,33 @@ for (const operation of ["current", "dismiss", "remind"]) {
10932
12706
  else if (operation === "dismiss") await recommendationsDismiss(common);
10933
12707
  else await recommendationsRemind({ ...common, at: opts.at, timezone: opts.timezone });
10934
12708
  } catch (error) {
10935
- console.error(kleur19.red(String(error)));
12709
+ console.error(kleur20.red(String(error)));
10936
12710
  process.exit(1);
10937
12711
  }
10938
12712
  });
10939
12713
  }
10940
- var agentsCommand = program.command("agents").description("Inspect agents and scoped agent context");
12714
+ var agentsCommand = program.command("agents").description("Inspect agents, shared multi-agent settings, and scoped agent context");
10941
12715
  failWithGroupHelp(agentsCommand);
12716
+ var agentsSettingsCommand = agentsCommand.command("settings").description("Inspect or change the space's shared multi-agent planning and concurrency limits");
12717
+ addSpaceOption(agentsSettingsCommand, "Space UUID (defaults to .instafy/space.json or SPACE_ID)");
12718
+ addServerUrlOptions(agentsSettingsCommand);
12719
+ addAccessTokenOptions(agentsSettingsCommand, "Instafy access token");
12720
+ agentsSettingsCommand.option("--max-agents-per-plan <n>", "Set the maximum team workers in one plan (default: 64; range: 1\u2013256)").option("--max-concurrent-agents <n>", "Set the maximum running team workers across this space (default: 8; range: 1\u201364)").option("--interactive", "Interactively edit both shared settings").option("--json", "Output JSON (noninteractive)").action(async (opts) => {
12721
+ try {
12722
+ await agentSettings({
12723
+ project: resolveSpaceIdOption(opts),
12724
+ controllerUrl: opts.serverUrl,
12725
+ accessToken: opts.accessToken,
12726
+ maxAgentsPerPlan: opts.maxAgentsPerPlan,
12727
+ maxConcurrentAgents: opts.maxConcurrentAgents,
12728
+ interactive: opts.interactive,
12729
+ json: opts.json
12730
+ });
12731
+ } catch (error) {
12732
+ console.error(kleur20.red(String(error)));
12733
+ process.exit(1);
12734
+ }
12735
+ });
10942
12736
  var agentsListCommand = agentsCommand.command("list").description("List agents available to this account");
10943
12737
  addSpaceOption(
10944
12738
  agentsListCommand,
@@ -10955,7 +12749,7 @@ agentsListCommand.option("--json", "Output JSON").action(async (opts) => {
10955
12749
  json: opts.json
10956
12750
  });
10957
12751
  } catch (error) {
10958
- console.error(kleur19.red(String(error)));
12752
+ console.error(kleur20.red(String(error)));
10959
12753
  process.exit(1);
10960
12754
  }
10961
12755
  });
@@ -10983,7 +12777,7 @@ agentsContextListCommand.option("--agent <handle>", "Filter by agent handle (for
10983
12777
  json: opts.json
10984
12778
  });
10985
12779
  } catch (error) {
10986
- console.error(kleur19.red(String(error)));
12780
+ console.error(kleur20.red(String(error)));
10987
12781
  process.exit(1);
10988
12782
  }
10989
12783
  });
@@ -11009,7 +12803,7 @@ agentsContextPutCommand.action(async (contextParts, opts) => {
11009
12803
  json: opts.json
11010
12804
  });
11011
12805
  } catch (error) {
11012
- console.error(kleur19.red(String(error)));
12806
+ console.error(kleur20.red(String(error)));
11013
12807
  process.exit(1);
11014
12808
  }
11015
12809
  });
@@ -11025,7 +12819,7 @@ agentsStatusCommand.option("--json", "Output JSON").action(async (groupId, opts)
11025
12819
  json: opts.json
11026
12820
  });
11027
12821
  } catch (error) {
11028
- console.error(kleur19.red(String(error)));
12822
+ console.error(kleur20.red(String(error)));
11029
12823
  process.exit(1);
11030
12824
  }
11031
12825
  });
@@ -11043,7 +12837,7 @@ agentsCancelCommand.option("--json", "Output JSON").action(async (opts) => {
11043
12837
  json: opts.json
11044
12838
  });
11045
12839
  } catch (error) {
11046
- console.error(kleur19.red(String(error)));
12840
+ console.error(kleur20.red(String(error)));
11047
12841
  process.exit(1);
11048
12842
  }
11049
12843
  });
@@ -11065,7 +12859,7 @@ secretsListCommand.option("--json", "Output JSON").action(async (opts) => {
11065
12859
  json: opts.json
11066
12860
  });
11067
12861
  } catch (error) {
11068
- console.error(kleur19.red(String(error)));
12862
+ console.error(kleur20.red(String(error)));
11069
12863
  process.exit(1);
11070
12864
  }
11071
12865
  });
@@ -11086,7 +12880,7 @@ secretsGetCommand.option("--json", "Output JSON").action(async (nameOrId, opts)
11086
12880
  json: opts.json
11087
12881
  });
11088
12882
  } catch (error) {
11089
- console.error(kleur19.red(String(error)));
12883
+ console.error(kleur20.red(String(error)));
11090
12884
  process.exit(1);
11091
12885
  }
11092
12886
  });
@@ -11111,7 +12905,7 @@ secretsPutCommand.option("--json", "Output JSON").action(async (name, opts) => {
11111
12905
  json: opts.json
11112
12906
  });
11113
12907
  } catch (error) {
11114
- console.error(kleur19.red(String(error)));
12908
+ console.error(kleur20.red(String(error)));
11115
12909
  process.exit(1);
11116
12910
  }
11117
12911
  });
@@ -11132,7 +12926,7 @@ secretsRevokeCommand.option("--json", "Output JSON").action(async (nameOrId, opt
11132
12926
  json: opts.json
11133
12927
  });
11134
12928
  } catch (error) {
11135
- console.error(kleur19.red(String(error)));
12929
+ console.error(kleur20.red(String(error)));
11136
12930
  process.exit(1);
11137
12931
  }
11138
12932
  });
@@ -11166,7 +12960,7 @@ automationsListCommand.option("--json", "Output JSON").action(async (opts) => {
11166
12960
  json: opts.json
11167
12961
  });
11168
12962
  } catch (error) {
11169
- console.error(kleur19.red(String(error)));
12963
+ console.error(kleur20.red(String(error)));
11170
12964
  process.exit(1);
11171
12965
  }
11172
12966
  });
@@ -11212,7 +13006,7 @@ automationsCreateCommand.option("--json", "Output JSON").action(async (opts) =>
11212
13006
  json: opts.json
11213
13007
  });
11214
13008
  } catch (error) {
11215
- console.error(kleur19.red(String(error)));
13009
+ console.error(kleur20.red(String(error)));
11216
13010
  process.exit(1);
11217
13011
  }
11218
13012
  });
@@ -11258,7 +13052,7 @@ automationsUpdateCommand.option("--json", "Output JSON").action(async (automatio
11258
13052
  json: opts.json
11259
13053
  });
11260
13054
  } catch (error) {
11261
- console.error(kleur19.red(String(error)));
13055
+ console.error(kleur20.red(String(error)));
11262
13056
  process.exit(1);
11263
13057
  }
11264
13058
  });
@@ -11275,7 +13069,7 @@ automationsPauseCommand.option("--json", "Output JSON").action(async (automation
11275
13069
  json: opts.json
11276
13070
  });
11277
13071
  } catch (error) {
11278
- console.error(kleur19.red(String(error)));
13072
+ console.error(kleur20.red(String(error)));
11279
13073
  process.exit(1);
11280
13074
  }
11281
13075
  });
@@ -11292,7 +13086,7 @@ automationsResumeCommand.option("--json", "Output JSON").action(async (automatio
11292
13086
  json: opts.json
11293
13087
  });
11294
13088
  } catch (error) {
11295
- console.error(kleur19.red(String(error)));
13089
+ console.error(kleur20.red(String(error)));
11296
13090
  process.exit(1);
11297
13091
  }
11298
13092
  });
@@ -11308,7 +13102,7 @@ automationsRunCommand.option("--json", "Output JSON").action(async (automationId
11308
13102
  json: opts.json
11309
13103
  });
11310
13104
  } catch (error) {
11311
- console.error(kleur19.red(String(error)));
13105
+ console.error(kleur20.red(String(error)));
11312
13106
  process.exit(1);
11313
13107
  }
11314
13108
  });
@@ -11324,7 +13118,7 @@ automationsDeleteCommand.option("--json", "Output JSON").action(async (automatio
11324
13118
  json: opts.json
11325
13119
  });
11326
13120
  } catch (error) {
11327
- console.error(kleur19.red(String(error)));
13121
+ console.error(kleur20.red(String(error)));
11328
13122
  process.exit(1);
11329
13123
  }
11330
13124
  });
@@ -11342,7 +13136,7 @@ credentialsListCommand.option("--json", "Output JSON").action(async (opts) => {
11342
13136
  json: opts.json
11343
13137
  });
11344
13138
  } catch (error) {
11345
- console.error(kleur19.red(String(error)));
13139
+ console.error(kleur20.red(String(error)));
11346
13140
  process.exit(1);
11347
13141
  }
11348
13142
  });
@@ -11361,7 +13155,7 @@ credentialsTestCommand.option("--json", "Output JSON").action(async (idOrPrefix,
11361
13155
  process.exitCode = 1;
11362
13156
  }
11363
13157
  } catch (error) {
11364
- console.error(kleur19.red(String(error)));
13158
+ console.error(kleur20.red(String(error)));
11365
13159
  process.exit(1);
11366
13160
  }
11367
13161
  });
@@ -11392,7 +13186,7 @@ credentialsDefaultCommand.option("--json", "Output JSON").action(async (idOrPref
11392
13186
  json: opts.json
11393
13187
  });
11394
13188
  } catch (error) {
11395
- console.error(kleur19.red(String(error)));
13189
+ console.error(kleur20.red(String(error)));
11396
13190
  process.exit(1);
11397
13191
  }
11398
13192
  });
@@ -11409,7 +13203,7 @@ credentialsRevokeCommand.option("--json", "Output JSON").action(async (idOrPrefi
11409
13203
  json: opts.json
11410
13204
  });
11411
13205
  } catch (error) {
11412
- console.error(kleur19.red(String(error)));
13206
+ console.error(kleur20.red(String(error)));
11413
13207
  process.exit(1);
11414
13208
  }
11415
13209
  });
@@ -11419,7 +13213,7 @@ configCommand.command("path").description("Print the config file path").option("
11419
13213
  try {
11420
13214
  configPath({ json: opts.json });
11421
13215
  } catch (error) {
11422
- console.error(kleur19.red(String(error)));
13216
+ console.error(kleur20.red(String(error)));
11423
13217
  process.exit(1);
11424
13218
  }
11425
13219
  });
@@ -11427,7 +13221,7 @@ configCommand.command("list").description("List saved configuration").option("--
11427
13221
  try {
11428
13222
  configList({ json: opts.json });
11429
13223
  } catch (error) {
11430
- console.error(kleur19.red(String(error)));
13224
+ console.error(kleur20.red(String(error)));
11431
13225
  process.exit(1);
11432
13226
  }
11433
13227
  });
@@ -11435,7 +13229,7 @@ configCommand.command("get").description("Get a config value (controller-url, st
11435
13229
  try {
11436
13230
  configGet({ key, json: opts.json });
11437
13231
  } catch (error) {
11438
- console.error(kleur19.red(String(error)));
13232
+ console.error(kleur20.red(String(error)));
11439
13233
  process.exit(1);
11440
13234
  }
11441
13235
  });
@@ -11443,7 +13237,7 @@ configCommand.command("set").description("Set a config value (controller-url, st
11443
13237
  try {
11444
13238
  configSet({ key, value, json: opts.json });
11445
13239
  } catch (error) {
11446
- console.error(kleur19.red(String(error)));
13240
+ console.error(kleur20.red(String(error)));
11447
13241
  process.exit(1);
11448
13242
  }
11449
13243
  });
@@ -11451,7 +13245,7 @@ configCommand.command("unset").description("Unset a config value (controller-url
11451
13245
  try {
11452
13246
  configUnset({ key, json: opts.json });
11453
13247
  } catch (error) {
11454
- console.error(kleur19.red(String(error)));
13248
+ console.error(kleur20.red(String(error)));
11455
13249
  process.exit(1);
11456
13250
  }
11457
13251
  });
@@ -11471,7 +13265,7 @@ runtimeStartCommand.option("--supabase-access-token <token>", "Supabase session
11471
13265
  controllerAccessToken: opts.accessToken
11472
13266
  });
11473
13267
  } catch (error) {
11474
- console.error(kleur19.red(String(error)));
13268
+ console.error(kleur20.red(String(error)));
11475
13269
  process.exit(1);
11476
13270
  }
11477
13271
  });
@@ -11479,7 +13273,7 @@ runtimeCommand.command("status").description("Show runtime health").option("--js
11479
13273
  try {
11480
13274
  await runtimeStatus({ json: opts.json });
11481
13275
  } catch (error) {
11482
- console.error(kleur19.red(String(error)));
13276
+ console.error(kleur20.red(String(error)));
11483
13277
  process.exit(1);
11484
13278
  }
11485
13279
  });
@@ -11487,7 +13281,7 @@ runtimeCommand.command("stop").description("Stop the local Instafy runtime").opt
11487
13281
  try {
11488
13282
  await runtimeStop({ json: opts.json });
11489
13283
  } catch (error) {
11490
- console.error(kleur19.red(String(error)));
13284
+ console.error(kleur20.red(String(error)));
11491
13285
  process.exit(1);
11492
13286
  }
11493
13287
  });
@@ -11513,7 +13307,7 @@ runtimeTokenCommand.option("--runtime-id <uuid>", "Runtime ID to bind token to")
11513
13307
  json: opts.json
11514
13308
  });
11515
13309
  } catch (error) {
11516
- console.error(kleur19.red(String(error)));
13310
+ console.error(kleur20.red(String(error)));
11517
13311
  process.exit(1);
11518
13312
  }
11519
13313
  });
@@ -11535,7 +13329,7 @@ diagnosticsRuntimeEventsCommand.action(async (opts) => {
11535
13329
  accessToken: opts.accessToken
11536
13330
  });
11537
13331
  } catch (error) {
11538
- console.error(kleur19.red(String(error)));
13332
+ console.error(kleur20.red(String(error)));
11539
13333
  process.exit(1);
11540
13334
  }
11541
13335
  });
@@ -11550,7 +13344,7 @@ diagnosticsRunResultCommand.action(async (runId, opts) => {
11550
13344
  accessToken: opts.accessToken
11551
13345
  });
11552
13346
  } catch (error) {
11553
- console.error(kleur19.red(String(error)));
13347
+ console.error(kleur20.red(String(error)));
11554
13348
  process.exit(1);
11555
13349
  }
11556
13350
  });
@@ -11588,7 +13382,7 @@ supportReportCommand.action(async (summaryParts, opts) => {
11588
13382
  json: opts.json
11589
13383
  });
11590
13384
  } catch (error) {
11591
- console.error(kleur19.red(String(error)));
13385
+ console.error(kleur20.red(String(error)));
11592
13386
  process.exit(1);
11593
13387
  }
11594
13388
  });
@@ -11623,7 +13417,7 @@ supportListCommand.action(async (opts) => {
11623
13417
  json: opts.json
11624
13418
  });
11625
13419
  } catch (error) {
11626
- console.error(kleur19.red(String(error)));
13420
+ console.error(kleur20.red(String(error)));
11627
13421
  process.exit(1);
11628
13422
  }
11629
13423
  });
@@ -11639,7 +13433,7 @@ supportShowCommand.action(async (reportId, opts) => {
11639
13433
  json: opts.json
11640
13434
  });
11641
13435
  } catch (error) {
11642
- console.error(kleur19.red(String(error)));
13436
+ console.error(kleur20.red(String(error)));
11643
13437
  process.exit(1);
11644
13438
  }
11645
13439
  });
@@ -11664,7 +13458,7 @@ supportMessagesCommand.action(async (reportId, opts) => {
11664
13458
  json: opts.json
11665
13459
  });
11666
13460
  } catch (error) {
11667
- console.error(kleur19.red(String(error)));
13461
+ console.error(kleur20.red(String(error)));
11668
13462
  process.exit(1);
11669
13463
  }
11670
13464
  });
@@ -11685,11 +13479,12 @@ supportReplyCommand.action(async (reportId, messageParts, opts) => {
11685
13479
  json: opts.json
11686
13480
  });
11687
13481
  } catch (error) {
11688
- console.error(kleur19.red(String(error)));
13482
+ console.error(kleur20.red(String(error)));
11689
13483
  process.exit(1);
11690
13484
  }
11691
13485
  });
11692
13486
  program.command("git").description("Run git against the Instafy canonical checkout (.instafy/.git)").allowUnknownOption(true);
13487
+ program.command("bind").description("Bound sites: serve a website whose clicks run scripts and stream records back to every viewer (instafy-bind)").allowUnknownOption(true);
11693
13488
  var tunnelCommand = program.command("tunnel").description("Create and manage shareable tunnels");
11694
13489
  failWithGroupHelp(tunnelCommand);
11695
13490
  var tunnelStartCommand = tunnelCommand.command("start").description("Create (or reuse) a shareable tunnel URL for a local port (defaults to detached)").option("--port <port>", "Local port to expose (default 3000)").option("--name <name>", "Stable tunnel name (default web)").option("--rotate", "Rotate the stable tunnel hostname before starting");
@@ -11730,19 +13525,19 @@ tunnelStartCommand.option("--no-detach", "Run in foreground until interrupted").
11730
13525
  console.log(JSON.stringify(started, null, 2));
11731
13526
  return;
11732
13527
  }
11733
- console.log(kleur19.green(`Tunnel started: ${started.url} (tunnelId=${started.tunnelId})`));
11734
- console.log(kleur19.gray(`pid=${started.pid} \xB7 port=${started.localPort}`));
11735
- console.log(kleur19.gray(`log: ${started.logFile}`));
13528
+ console.log(kleur20.green(`Tunnel started: ${started.url} (tunnelId=${started.tunnelId})`));
13529
+ console.log(kleur20.gray(`pid=${started.pid} \xB7 port=${started.localPort}`));
13530
+ console.log(kleur20.gray(`log: ${started.logFile}`));
11736
13531
  console.log("");
11737
13532
  console.log("Next:");
11738
- console.log(`- ${kleur19.cyan(`instafy tunnel list`)}`);
11739
- console.log(`- ${kleur19.cyan(`instafy tunnel logs ${started.tunnelId} --follow`)}`);
11740
- console.log(`- ${kleur19.cyan(`instafy tunnel stop ${started.tunnelId}`)}`);
13533
+ console.log(`- ${kleur20.cyan(`instafy tunnel list`)}`);
13534
+ console.log(`- ${kleur20.cyan(`instafy tunnel logs ${started.tunnelId} --follow`)}`);
13535
+ console.log(`- ${kleur20.cyan(`instafy tunnel stop ${started.tunnelId}`)}`);
11741
13536
  if (process.platform !== "win32") {
11742
- console.log(kleur19.gray(`(or) tail -n 200 -f ${started.logFile}`));
13537
+ console.log(kleur20.gray(`(or) tail -n 200 -f ${started.logFile}`));
11743
13538
  }
11744
13539
  } catch (error) {
11745
- console.error(kleur19.red(String(error)));
13540
+ console.error(kleur20.red(String(error)));
11746
13541
  process.exit(1);
11747
13542
  }
11748
13543
  });
@@ -11754,14 +13549,14 @@ tunnelCommand.command("list").description("List local tunnel sessions started by
11754
13549
  return;
11755
13550
  }
11756
13551
  if (tunnels.length === 0) {
11757
- console.log(kleur19.yellow("No tunnels found."));
13552
+ console.log(kleur20.yellow("No tunnels found."));
11758
13553
  return;
11759
13554
  }
11760
13555
  for (const tunnel of tunnels) {
11761
13556
  console.log(`${tunnel.tunnelId} \xB7 ${tunnel.url} \xB7 port=${tunnel.localPort} \xB7 pid=${tunnel.pid}`);
11762
13557
  }
11763
13558
  } catch (error) {
11764
- console.error(kleur19.red(String(error)));
13559
+ console.error(kleur20.red(String(error)));
11765
13560
  process.exit(1);
11766
13561
  }
11767
13562
  });
@@ -11777,9 +13572,9 @@ tunnelCommand.command("stop").description("Stop a local tunnel session and revok
11777
13572
  console.log(JSON.stringify(result, null, 2));
11778
13573
  return;
11779
13574
  }
11780
- console.log(kleur19.green(`Tunnel stopped: ${result.tunnelId}`));
13575
+ console.log(kleur20.green(`Tunnel stopped: ${result.tunnelId}`));
11781
13576
  } catch (error) {
11782
- console.error(kleur19.red(String(error)));
13577
+ console.error(kleur20.red(String(error)));
11783
13578
  process.exit(1);
11784
13579
  }
11785
13580
  });
@@ -11793,10 +13588,85 @@ tunnelCommand.command("logs").description("Show logs for a local tunnel session"
11793
13588
  json: Boolean(opts.json)
11794
13589
  });
11795
13590
  } catch (error) {
11796
- console.error(kleur19.red(String(error)));
13591
+ console.error(kleur20.red(String(error)));
11797
13592
  process.exit(1);
11798
13593
  }
11799
13594
  });
13595
+ function exitWithPreviewError(error) {
13596
+ console.error(kleur20.red(error instanceof Error ? error.message : String(error)));
13597
+ process.exit(error instanceof PreviewCommandError ? error.exitCode : 1);
13598
+ }
13599
+ var previewCommand = program.command("preview").description("Look at sites running in this space, and show them in Studio as a private Ready to preview card");
13600
+ failWithGroupHelp(previewCommand);
13601
+ registerPreviewCheckCommand(previewCommand);
13602
+ var previewOpenCommand = previewCommand.command("open").description(
13603
+ "Open a private preview of a local port (exit 2: nothing listening, 3: port not allowed, 5: previews not available)"
13604
+ ).requiredOption("--port <port>", "Local port the site listens on (1024-65535)").requiredOption("--title <title>", "Card title (at most 120 characters)").option("--path <path>", "Path the preview opens at (default /)");
13605
+ addSpaceOption(previewOpenCommand, "Space UUID (defaults to .instafy/space.json or SPACE_ID)");
13606
+ addServerUrlOptions(previewOpenCommand);
13607
+ addAccessTokenOptions(previewOpenCommand, "Instafy access token (defaults to the job token, then the saved `instafy login` token)");
13608
+ previewOpenCommand.option("--conversation <id>", "Conversation for the card (only needed without a job token)").option("--rathole-bin <path>", "Path to rathole binary (or set RATHOLE_BIN)").option(
13609
+ "--serve-cmd <command>",
13610
+ "How to start the site again when the process that started it exits, or the space sleeps, run from the current directory (one command, no shell); default: read from the process listening on the port"
13611
+ ).option(
13612
+ "--start",
13613
+ "Start --serve-cmd in the background, in its own session, without Instafy credentials, logging to .instafy/agent/logs, and wait for the port (nothing is started when the port already answers)"
13614
+ ).option("--json", "Output JSON").action(async (opts) => {
13615
+ try {
13616
+ const result = await previewOpen({
13617
+ project: resolveSpaceIdOption(opts),
13618
+ controllerUrl: opts.serverUrl,
13619
+ controllerToken: opts.accessToken,
13620
+ conversationId: opts.conversation,
13621
+ port: opts.port,
13622
+ title: opts.title,
13623
+ path: opts.path,
13624
+ ratholeBin: opts.ratholeBin,
13625
+ serveCmd: opts.serveCmd,
13626
+ start: Boolean(opts.start)
13627
+ });
13628
+ printPreviewOpen(result, Boolean(opts.json));
13629
+ } catch (error) {
13630
+ exitWithPreviewError(error);
13631
+ }
13632
+ });
13633
+ var previewListCommand = previewCommand.command("list").description("List this space's open previews (it does not check the sites: `instafy preview check` does)");
13634
+ addSpaceOption(previewListCommand, "Space UUID (defaults to .instafy/space.json or SPACE_ID)");
13635
+ addServerUrlOptions(previewListCommand);
13636
+ addAccessTokenOptions(previewListCommand, "Instafy access token (defaults to the job token, then the saved `instafy login` token)");
13637
+ previewListCommand.option("--json", "Output JSON").action(async (opts) => {
13638
+ try {
13639
+ const previews = await previewList({
13640
+ project: resolveSpaceIdOption(opts),
13641
+ controllerUrl: opts.serverUrl,
13642
+ controllerToken: opts.accessToken
13643
+ });
13644
+ printPreviewList(previews, Boolean(opts.json));
13645
+ } catch (error) {
13646
+ exitWithPreviewError(error);
13647
+ }
13648
+ });
13649
+ var previewCloseCommand = previewCommand.command("close").description("Close a preview: stop its local client and revoke it").argument("<previewId>", "Preview ID (from `instafy preview list`)");
13650
+ addSpaceOption(previewCloseCommand, "Space UUID (defaults to .instafy/space.json or SPACE_ID)");
13651
+ addServerUrlOptions(previewCloseCommand);
13652
+ addAccessTokenOptions(previewCloseCommand, "Instafy access token (defaults to the job token, then the saved `instafy login` token)");
13653
+ previewCloseCommand.option("--json", "Output JSON").action(async (previewId, opts) => {
13654
+ try {
13655
+ const result = await previewClose({
13656
+ previewId,
13657
+ project: resolveSpaceIdOption(opts),
13658
+ controllerUrl: opts.serverUrl,
13659
+ controllerToken: opts.accessToken
13660
+ });
13661
+ if (opts.json) {
13662
+ console.log(JSON.stringify(result, null, 2));
13663
+ return;
13664
+ }
13665
+ console.log(kleur20.green(`Preview closed: ${result.previewId}`));
13666
+ } catch (error) {
13667
+ exitWithPreviewError(error);
13668
+ }
13669
+ });
11800
13670
  var orgCommand = program.command("team").alias("org").description("Team utilities");
11801
13671
  failWithGroupHelp(orgCommand);
11802
13672
  var orgListCommand = orgCommand.command("list").description("List teams for your account");
@@ -11809,7 +13679,7 @@ orgListCommand.option("--access-token <token>", "Instafy or Supabase access toke
11809
13679
  json: opts.json
11810
13680
  });
11811
13681
  } catch (error) {
11812
- console.error(kleur19.red(String(error)));
13682
+ console.error(kleur20.red(String(error)));
11813
13683
  process.exit(1);
11814
13684
  }
11815
13685
  });
@@ -11830,7 +13700,7 @@ teamMembersCommand.action(async (opts) => {
11830
13700
  json: opts.json
11831
13701
  });
11832
13702
  } catch (error) {
11833
- console.error(kleur19.red(String(error)));
13703
+ console.error(kleur20.red(String(error)));
11834
13704
  process.exit(1);
11835
13705
  }
11836
13706
  });
@@ -11849,7 +13719,7 @@ teamInviteCommand.option("--role <role>", "owner, admin, builder, or viewer (def
11849
13719
  json: opts.json
11850
13720
  });
11851
13721
  } catch (error) {
11852
- console.error(kleur19.red(String(error)));
13722
+ console.error(kleur20.red(String(error)));
11853
13723
  process.exit(1);
11854
13724
  }
11855
13725
  });
@@ -11868,7 +13738,7 @@ teamInviteLinkCommand.option("--role <role>", "builder or viewer (default: build
11868
13738
  json: opts.json
11869
13739
  });
11870
13740
  } catch (error) {
11871
- console.error(kleur19.red(String(error)));
13741
+ console.error(kleur20.red(String(error)));
11872
13742
  process.exit(1);
11873
13743
  }
11874
13744
  });
@@ -11885,7 +13755,7 @@ teamInvitesCommand.action(async (opts) => {
11885
13755
  json: opts.json
11886
13756
  });
11887
13757
  } catch (error) {
11888
- console.error(kleur19.red(String(error)));
13758
+ console.error(kleur20.red(String(error)));
11889
13759
  process.exit(1);
11890
13760
  }
11891
13761
  });
@@ -11904,7 +13774,7 @@ teamAddMemberCommand.requiredOption("--user-id <uuid>", "User id of an existing
11904
13774
  json: opts.json
11905
13775
  });
11906
13776
  } catch (error) {
11907
- console.error(kleur19.red(String(error)));
13777
+ console.error(kleur20.red(String(error)));
11908
13778
  process.exit(1);
11909
13779
  }
11910
13780
  });
@@ -11922,7 +13792,7 @@ teamAcceptCommand.action(async (token, opts) => {
11922
13792
  json: opts.json
11923
13793
  });
11924
13794
  } catch (error) {
11925
- console.error(kleur19.red(String(error)));
13795
+ console.error(kleur20.red(String(error)));
11926
13796
  process.exit(1);
11927
13797
  }
11928
13798
  });
@@ -11941,7 +13811,7 @@ teamRevokeInviteCommand.option("--yes", "Skip the confirmation prompt").action(a
11941
13811
  json: opts.json
11942
13812
  });
11943
13813
  } catch (error) {
11944
- console.error(kleur19.red(String(error)));
13814
+ console.error(kleur20.red(String(error)));
11945
13815
  process.exit(1);
11946
13816
  }
11947
13817
  });
@@ -11960,7 +13830,7 @@ teamRevokeLinkCommand.option("--yes", "Skip the confirmation prompt").action(asy
11960
13830
  json: opts.json
11961
13831
  });
11962
13832
  } catch (error) {
11963
- console.error(kleur19.red(String(error)));
13833
+ console.error(kleur20.red(String(error)));
11964
13834
  process.exit(1);
11965
13835
  }
11966
13836
  });
@@ -11985,17 +13855,17 @@ profileCommand.command("list").description("List saved CLI profiles").option("--
11985
13855
  return;
11986
13856
  }
11987
13857
  if (profiles.length === 0) {
11988
- console.log(kleur19.yellow("No profiles found."));
11989
- console.log(`Create one with: ${kleur19.cyan("instafy login --profile <name>")}`);
13858
+ console.log(kleur20.yellow("No profiles found."));
13859
+ console.log(`Create one with: ${kleur20.cyan("instafy login --profile <name>")}`);
11990
13860
  return;
11991
13861
  }
11992
- console.log(kleur19.green("Instafy CLI profiles"));
13862
+ console.log(kleur20.green("Instafy CLI profiles"));
11993
13863
  for (const profile of profiles) {
11994
- const token = profile.accessTokenSet ? kleur19.green("token") : kleur19.yellow("no-token");
11995
- console.log(`- ${kleur19.cyan(profile.name)} (${token})`);
13864
+ const token = profile.accessTokenSet ? kleur20.green("token") : kleur20.yellow("no-token");
13865
+ console.log(`- ${kleur20.cyan(profile.name)} (${token})`);
11996
13866
  }
11997
13867
  } catch (error) {
11998
- console.error(kleur19.red(String(error)));
13868
+ console.error(kleur20.red(String(error)));
11999
13869
  process.exit(1);
12000
13870
  }
12001
13871
  });
@@ -12015,7 +13885,7 @@ historyMessagesCommand.option("--limit <n>", "Max messages to return (1-200, def
12015
13885
  pretty: opts.pretty
12016
13886
  });
12017
13887
  } catch (error) {
12018
- console.error(kleur19.red(String(error)));
13888
+ console.error(kleur20.red(String(error)));
12019
13889
  process.exit(1);
12020
13890
  }
12021
13891
  });
@@ -12032,7 +13902,7 @@ historyRunsCommand.option("--limit <n>", "Max runs to return (1-200, default: 50
12032
13902
  pretty: opts.pretty
12033
13903
  });
12034
13904
  } catch (error) {
12035
- console.error(kleur19.red(String(error)));
13905
+ console.error(kleur20.red(String(error)));
12036
13906
  process.exit(1);
12037
13907
  }
12038
13908
  });
@@ -12049,7 +13919,7 @@ historyConversationsCommand.option("--limit <n>", "Max conversations to return (
12049
13919
  pretty: opts.pretty
12050
13920
  });
12051
13921
  } catch (error) {
12052
- console.error(kleur19.red(String(error)));
13922
+ console.error(kleur20.red(String(error)));
12053
13923
  process.exit(1);
12054
13924
  }
12055
13925
  });
@@ -12070,7 +13940,7 @@ conversationCreateCommand.action(async (opts) => {
12070
13940
  json: opts.json
12071
13941
  });
12072
13942
  } catch (error) {
12073
- console.error(kleur19.red(String(error)));
13943
+ console.error(kleur20.red(String(error)));
12074
13944
  process.exit(1);
12075
13945
  }
12076
13946
  });
@@ -12088,7 +13958,7 @@ conversationListCommand.action(async (opts) => {
12088
13958
  json: opts.json
12089
13959
  });
12090
13960
  } catch (error) {
12091
- console.error(kleur19.red(String(error)));
13961
+ console.error(kleur20.red(String(error)));
12092
13962
  process.exit(1);
12093
13963
  }
12094
13964
  });
@@ -12107,7 +13977,7 @@ conversationSearchCommand.action(async (queryParts, opts) => {
12107
13977
  json: opts.json
12108
13978
  });
12109
13979
  } catch (error) {
12110
- console.error(kleur19.red(String(error)));
13980
+ console.error(kleur20.red(String(error)));
12111
13981
  process.exit(1);
12112
13982
  }
12113
13983
  });
@@ -12131,7 +14001,7 @@ conversationGrepCommand.action(async (parts, opts) => {
12131
14001
  });
12132
14002
  process.exitCode = found ? 0 : 1;
12133
14003
  } catch (error) {
12134
- console.error(kleur19.red(String(error)));
14004
+ console.error(kleur20.red(String(error)));
12135
14005
  process.exitCode = 2;
12136
14006
  }
12137
14007
  });
@@ -12151,7 +14021,7 @@ conversationContextCommand.action(async (conversationId, messageId, opts) => {
12151
14021
  accessToken: opts.accessToken
12152
14022
  });
12153
14023
  } catch (error) {
12154
- console.error(kleur19.red(String(error)));
14024
+ console.error(kleur20.red(String(error)));
12155
14025
  process.exitCode = 2;
12156
14026
  }
12157
14027
  });
@@ -12173,7 +14043,7 @@ conversationShowCommand.action(async (targetParts, opts) => {
12173
14043
  json: opts.json
12174
14044
  });
12175
14045
  } catch (error) {
12176
- console.error(kleur19.red(String(error)));
14046
+ console.error(kleur20.red(String(error)));
12177
14047
  process.exit(1);
12178
14048
  }
12179
14049
  });
@@ -12183,6 +14053,10 @@ async function runCli(argv = process.argv) {
12183
14053
  return;
12184
14054
  }
12185
14055
  const args = argv.slice(2);
14056
+ if (args[0] === "bind") {
14057
+ process.exitCode = await runBind(args.slice(1));
14058
+ return;
14059
+ }
12186
14060
  if (args[0] === "git") {
12187
14061
  if (args[1] === "credential") {
12188
14062
  try {
@@ -12204,7 +14078,7 @@ async function runCli(argv = process.argv) {
12204
14078
  }
12205
14079
  await program.parseAsync(argv);
12206
14080
  }
12207
- if (import.meta.url === pathToFileURL2(process.argv[1] ?? "").href) {
14081
+ if (import.meta.url === pathToFileURL3(process.argv[1] ?? "").href) {
12208
14082
  void runCli(process.argv);
12209
14083
  }
12210
14084
  export {