@instafy/cli 0.2.3 → 0.2.4
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.
- package/README.md +4 -0
- package/dist/bind/LICENSE +661 -0
- package/dist/bind/bin/instafy-bind.mjs +542 -0
- package/dist/bind/client/bind-client.js +1219 -0
- package/dist/bind/src/apply.mjs +293 -0
- package/dist/bind/src/bound.mjs +202 -0
- package/dist/bind/src/budget.mjs +58 -0
- package/dist/bind/src/check.mjs +176 -0
- package/dist/bind/src/config.mjs +180 -0
- package/dist/bind/src/controller.mjs +244 -0
- package/dist/bind/src/executors/agent.mjs +336 -0
- package/dist/bind/src/executors/script.mjs +163 -0
- package/dist/bind/src/handler-kit.mjs +150 -0
- package/dist/bind/src/init.mjs +238 -0
- package/dist/bind/src/jobs.mjs +316 -0
- package/dist/bind/src/net.mjs +281 -0
- package/dist/bind/src/page-fetch.mjs +43 -0
- package/dist/bind/src/preview-host.mjs +114 -0
- package/dist/bind/src/protocol.mjs +103 -0
- package/dist/bind/src/records-schema.mjs +580 -0
- package/dist/bind/src/records.mjs +161 -0
- package/dist/bind/src/save-owner.mjs +492 -0
- package/dist/bind/src/schema-lite.mjs +212 -0
- package/dist/bind/src/server.mjs +908 -0
- package/dist/bind/src/sse.mjs +119 -0
- package/dist/bind/src/starters/blank/app.js +17 -0
- package/dist/bind/src/starters/blank/index.html +22 -0
- package/dist/bind/src/starters/checklist/app.js +171 -0
- package/dist/bind/src/starters/checklist/bound.json +29 -0
- package/dist/bind/src/starters/checklist/data.json +26 -0
- package/dist/bind/src/starters/checklist/index.html +23 -0
- package/dist/bind/src/starters/compare/app.js +316 -0
- package/dist/bind/src/starters/compare/bound.json +26 -0
- package/dist/bind/src/starters/compare/data.json +170 -0
- package/dist/bind/src/starters/compare/index.html +23 -0
- package/dist/bind/src/starters/handlers/read_offer.mjs +55 -0
- package/dist/bind/src/starters/handlers/set_check.mjs +41 -0
- package/dist/bind/src/starters/ranked/app.js +166 -0
- package/dist/bind/src/starters/ranked/bound.json +26 -0
- package/dist/bind/src/starters/ranked/data.json +142 -0
- package/dist/bind/src/starters/ranked/index.html +23 -0
- package/dist/bind/src/starters/site.js +888 -0
- package/dist/bind/src/starters/theme.css +492 -0
- package/dist/bind/src/starters/timeline/app.js +143 -0
- package/dist/bind/src/starters/timeline/data.json +41 -0
- package/dist/bind/src/starters/timeline/index.html +22 -0
- package/dist/bind/src/static.mjs +141 -0
- package/dist/bind/src/tokens.mjs +102 -0
- package/dist/bind/src/try.mjs +139 -0
- package/dist/bind/src/web.mjs +191 -0
- package/dist/cli.js +2117 -365
- 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
|
|
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(
|
|
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(`${
|
|
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
|
|
1339
|
-
import
|
|
1340
|
-
import
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1422
|
+
entries = fs17.readdirSync(scanDir);
|
|
1423
1423
|
} catch {
|
|
1424
1424
|
continue;
|
|
1425
1425
|
}
|
|
1426
1426
|
for (const entry of entries) {
|
|
1427
|
-
const fullPath =
|
|
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:
|
|
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:
|
|
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
|
|
1601
|
+
import kleur19 from "kleur";
|
|
1602
1602
|
function printSerialPorts(result) {
|
|
1603
1603
|
if (result.ports.length === 0) {
|
|
1604
|
-
console.log(
|
|
1604
|
+
console.log(kleur19.yellow("No local serial devices were discovered."));
|
|
1605
1605
|
return;
|
|
1606
1606
|
}
|
|
1607
|
-
console.log(
|
|
1607
|
+
console.log(kleur19.cyan(`Local serial devices (${result.platform}):`));
|
|
1608
1608
|
for (const port of result.ports) {
|
|
1609
|
-
const status = port.available ?
|
|
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(
|
|
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 ?
|
|
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(
|
|
1626
|
+
console.log(kleur19.yellow("No local IO opportunities were discovered."));
|
|
1627
1627
|
return;
|
|
1628
1628
|
}
|
|
1629
|
-
console.log(
|
|
1629
|
+
console.log(kleur19.cyan(`Local IO opportunities (${result.platform}):`));
|
|
1630
1630
|
for (const opportunity of result.opportunities) {
|
|
1631
|
-
const status = opportunity.available ?
|
|
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 ?
|
|
1646
|
-
console.log(
|
|
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(
|
|
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(
|
|
1659
|
+
console.log(kleur19.dim("stderr:"));
|
|
1660
1660
|
console.log(result.process.stderr.trimEnd());
|
|
1661
1661
|
}
|
|
1662
1662
|
}
|
|
@@ -1718,8 +1718,8 @@ var init_hardware = __esm({
|
|
|
1718
1718
|
// src/index.ts
|
|
1719
1719
|
import { Command, Option } from "commander";
|
|
1720
1720
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
1721
|
-
import { createRequire as
|
|
1722
|
-
import
|
|
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 =
|
|
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 (
|
|
3710
|
+
if (LOCAL_STACK_GIT_HOST.test(normalized.hostname)) {
|
|
3687
3711
|
return true;
|
|
3688
3712
|
}
|
|
3689
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
5509
|
-
|
|
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 (${
|
|
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:
|
|
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
|
-
|
|
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 {
|
|
@@ -5663,10 +5769,930 @@ async function tailTunnelLogs(options) {
|
|
|
5663
5769
|
await new Promise((resolve) => child.on("exit", () => resolve()));
|
|
5664
5770
|
}
|
|
5665
5771
|
|
|
5772
|
+
// src/preview.ts
|
|
5773
|
+
import fs8 from "node:fs";
|
|
5774
|
+
import os5 from "node:os";
|
|
5775
|
+
import path9 from "node:path";
|
|
5776
|
+
import kleur8 from "kleur";
|
|
5777
|
+
init_controller_fetch();
|
|
5778
|
+
init_errors();
|
|
5779
|
+
|
|
5780
|
+
// src/local-site.ts
|
|
5781
|
+
import http2 from "node:http";
|
|
5782
|
+
var PREVIEW_EXIT_NOT_LISTENING = 2;
|
|
5783
|
+
var PREVIEW_EXIT_PORT_NOT_ALLOWED = 3;
|
|
5784
|
+
var PreviewCommandError = class extends Error {
|
|
5785
|
+
constructor(message, exitCode) {
|
|
5786
|
+
super(message);
|
|
5787
|
+
this.exitCode = exitCode;
|
|
5788
|
+
}
|
|
5789
|
+
};
|
|
5790
|
+
var RESERVED_PORTS = [
|
|
5791
|
+
{ env: "INSTAFY_VNC_PORT", kind: "port", port: 5900 },
|
|
5792
|
+
{ env: "INSTAFY_PROVIDER_HOST_URL", kind: "url", port: 8789 },
|
|
5793
|
+
{ env: "PROXY_BASE_URL", kind: "url", port: 8789 },
|
|
5794
|
+
{ port: 9222 },
|
|
5795
|
+
{ env: "INSTAFY_PLAYWRIGHT_CDP_PORT", kind: "port", port: 9223 },
|
|
5796
|
+
{ env: "INSTAFY_PLAYWRIGHT_CDP_URL", kind: "url", port: 9223 },
|
|
5797
|
+
{ port: 9225 },
|
|
5798
|
+
{ env: "INSTAFY_BROWSER_EGRESS_PROXY_BIND", kind: "bind", port: 9226 },
|
|
5799
|
+
{ env: "INSTAFY_PERSONAL_BROWSER_CONTROL_URL", kind: "url", port: 43127 },
|
|
5800
|
+
{ env: "ORIGIN_BIND_PORT", kind: "port", port: 54332 }
|
|
5801
|
+
];
|
|
5802
|
+
var PREVIEW_DENY_PORTS_ENV = "INSTAFY_PREVIEW_DENY_PORTS";
|
|
5803
|
+
var MIN_PREVIEW_PORT = 1024;
|
|
5804
|
+
var MAX_PREVIEW_PORT = 65535;
|
|
5805
|
+
var PROBE_TIMEOUT_MS = 5e3;
|
|
5806
|
+
function portNumber(raw) {
|
|
5807
|
+
if (!/^\d{1,5}$/.test(raw)) return null;
|
|
5808
|
+
const port = Number(raw);
|
|
5809
|
+
return port >= 1 && port <= MAX_PREVIEW_PORT ? port : null;
|
|
5810
|
+
}
|
|
5811
|
+
function portFromSetting(kind, raw) {
|
|
5812
|
+
const value = raw.trim();
|
|
5813
|
+
if (!value) return null;
|
|
5814
|
+
if (kind === "port") return portNumber(value);
|
|
5815
|
+
if (kind === "bind") return portNumber(value.slice(value.lastIndexOf(":") + 1));
|
|
5816
|
+
try {
|
|
5817
|
+
const url = new URL(value);
|
|
5818
|
+
return portNumber(url.port || (url.protocol === "https:" ? "443" : url.protocol === "http:" ? "80" : ""));
|
|
5819
|
+
} catch {
|
|
5820
|
+
return null;
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
function reservedPreviewPorts(env = process.env) {
|
|
5824
|
+
const denied = /* @__PURE__ */ new Set();
|
|
5825
|
+
for (const entry of RESERVED_PORTS) {
|
|
5826
|
+
denied.add(entry.port);
|
|
5827
|
+
const raw = entry.env ? env[entry.env] : void 0;
|
|
5828
|
+
const port = entry.kind && raw ? portFromSetting(entry.kind, raw) : null;
|
|
5829
|
+
if (port !== null) denied.add(port);
|
|
5830
|
+
}
|
|
5831
|
+
for (const raw of (env[PREVIEW_DENY_PORTS_ENV] ?? "").split(",")) {
|
|
5832
|
+
if (!raw.trim()) continue;
|
|
5833
|
+
const port = portNumber(raw.trim());
|
|
5834
|
+
if (port === null) {
|
|
5835
|
+
throw new PreviewCommandError(
|
|
5836
|
+
`${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.`,
|
|
5837
|
+
PREVIEW_EXIT_PORT_NOT_ALLOWED
|
|
5838
|
+
);
|
|
5839
|
+
}
|
|
5840
|
+
denied.add(port);
|
|
5841
|
+
}
|
|
5842
|
+
return denied;
|
|
5843
|
+
}
|
|
5844
|
+
function parsePreviewPort(raw, env = process.env) {
|
|
5845
|
+
const value = typeof raw === "number" ? raw : Number(String(raw ?? "").trim());
|
|
5846
|
+
if (!Number.isInteger(value) || value < MIN_PREVIEW_PORT || value > MAX_PREVIEW_PORT) {
|
|
5847
|
+
throw new PreviewCommandError(
|
|
5848
|
+
`Port must be a number from ${MIN_PREVIEW_PORT} to ${MAX_PREVIEW_PORT}.`,
|
|
5849
|
+
PREVIEW_EXIT_PORT_NOT_ALLOWED
|
|
5850
|
+
);
|
|
5851
|
+
}
|
|
5852
|
+
if (reservedPreviewPorts(env).has(value)) {
|
|
5853
|
+
throw new PreviewCommandError(
|
|
5854
|
+
`Port ${value} is reserved for Instafy and cannot be previewed. Serve the site on another port.`,
|
|
5855
|
+
PREVIEW_EXIT_PORT_NOT_ALLOWED
|
|
5856
|
+
);
|
|
5857
|
+
}
|
|
5858
|
+
return value;
|
|
5859
|
+
}
|
|
5860
|
+
function noAnswerMessage(port, probe, where, retry) {
|
|
5861
|
+
if (probe.timedOut) {
|
|
5862
|
+
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}.`;
|
|
5863
|
+
}
|
|
5864
|
+
return `Nothing is answering on ${where} (${probe.reason ?? "no answer"}). Start the site first, then ${retry}.`;
|
|
5865
|
+
}
|
|
5866
|
+
var LOOPBACK_HOSTS2 = ["127.0.0.1", "::1"];
|
|
5867
|
+
function urlHost(host) {
|
|
5868
|
+
return host.includes(":") ? `[${host}]` : host;
|
|
5869
|
+
}
|
|
5870
|
+
function probeHost(host, port, sitePath, timeoutMs) {
|
|
5871
|
+
return new Promise((resolve) => {
|
|
5872
|
+
const request = http2.get({ host, port, path: sitePath, agent: false, timeout: timeoutMs }, (response) => {
|
|
5873
|
+
const status = response.statusCode ?? 0;
|
|
5874
|
+
response.resume();
|
|
5875
|
+
request.destroy();
|
|
5876
|
+
resolve({ listening: status > 0 && status < 500, status, reason: `HTTP ${status}`, host: urlHost(host) });
|
|
5877
|
+
});
|
|
5878
|
+
request.on("timeout", () => {
|
|
5879
|
+
request.destroy();
|
|
5880
|
+
resolve({ listening: false, status: null, reason: `no answer within ${timeoutMs / 1e3} s`, host: null, code: "TIMEOUT" });
|
|
5881
|
+
});
|
|
5882
|
+
request.on("error", (error) => {
|
|
5883
|
+
const code = error.code;
|
|
5884
|
+
resolve({ listening: false, status: null, reason: code ?? error.message, host: null, code });
|
|
5885
|
+
});
|
|
5886
|
+
});
|
|
5887
|
+
}
|
|
5888
|
+
async function probeLocalSite(port, sitePath, timeoutMs = PROBE_TIMEOUT_MS, hosts = LOOPBACK_HOSTS2) {
|
|
5889
|
+
const tried = [];
|
|
5890
|
+
let last = { listening: false, status: null, reason: "no answer", host: null };
|
|
5891
|
+
for (const host of hosts) {
|
|
5892
|
+
const { code, ...result } = await probeHost(host, port, sitePath, timeoutMs);
|
|
5893
|
+
if (result.status !== null) return result;
|
|
5894
|
+
tried.push(`${urlHost(host)}: ${result.reason}`);
|
|
5895
|
+
last = { ...result, reason: tried.join(", ") };
|
|
5896
|
+
if (code === "TIMEOUT") return { ...last, timedOut: true };
|
|
5897
|
+
}
|
|
5898
|
+
return last;
|
|
5899
|
+
}
|
|
5900
|
+
|
|
5901
|
+
// src/preview.ts
|
|
5902
|
+
var PREVIEW_EXIT_UNAVAILABLE = 5;
|
|
5903
|
+
var PREVIEWS_UNAVAILABLE_MESSAGE = "Previews are not available on this server";
|
|
5904
|
+
var RATHOLE_LOCAL_HOSTS = ["127.0.0.1"];
|
|
5905
|
+
var MAX_TITLE_LENGTH = 120;
|
|
5906
|
+
var MAX_PATH_LENGTH = 512;
|
|
5907
|
+
var STATE_LOCK_WAIT_MS = 12e4;
|
|
5908
|
+
var STATE_LOCK_STALE_MS = 10 * 6e4;
|
|
5909
|
+
function instafyDir() {
|
|
5910
|
+
return path9.join(os5.homedir(), ".instafy");
|
|
5911
|
+
}
|
|
5912
|
+
function previewStateFile() {
|
|
5913
|
+
return path9.join(instafyDir(), "cli-preview-state.json");
|
|
5914
|
+
}
|
|
5915
|
+
function previewStateLockFile() {
|
|
5916
|
+
return path9.join(instafyDir(), "cli-preview-state.lock");
|
|
5917
|
+
}
|
|
5918
|
+
function workdirsRoot() {
|
|
5919
|
+
return path9.join(instafyDir(), "cli-preview-workdirs");
|
|
5920
|
+
}
|
|
5921
|
+
function workdirFor(previewId) {
|
|
5922
|
+
return path9.join(workdirsRoot(), safePathSegment2(previewId));
|
|
5923
|
+
}
|
|
5924
|
+
var HOST_SUFFIX_RE = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*(?::\d{1,5})?$/;
|
|
5925
|
+
function parseViewerHostSuffix(raw) {
|
|
5926
|
+
if (typeof raw !== "string") return void 0;
|
|
5927
|
+
const suffix = raw.trim().toLowerCase();
|
|
5928
|
+
return suffix.length <= 253 && HOST_SUFFIX_RE.test(suffix) ? suffix : void 0;
|
|
5929
|
+
}
|
|
5930
|
+
function parseStudioOrigin(raw) {
|
|
5931
|
+
if (typeof raw !== "string" || raw.length > 300) return void 0;
|
|
5932
|
+
let url;
|
|
5933
|
+
try {
|
|
5934
|
+
url = new URL(raw.trim());
|
|
5935
|
+
} catch {
|
|
5936
|
+
return void 0;
|
|
5937
|
+
}
|
|
5938
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
|
|
5939
|
+
if (url.username || url.password || url.search || url.hash || url.pathname !== "/" && url.pathname !== "") return void 0;
|
|
5940
|
+
return url.origin;
|
|
5941
|
+
}
|
|
5942
|
+
var CONTROL_CHARS_RE = /[\u0000-\u001f\u007f-\u009f]/;
|
|
5943
|
+
function parsePreviewTitle(raw) {
|
|
5944
|
+
const title = (raw ?? "").replace(/\s+/g, " ").trim();
|
|
5945
|
+
if (!title) {
|
|
5946
|
+
throw new Error('A title is required: --title "<name>".');
|
|
5947
|
+
}
|
|
5948
|
+
if ([...title].length > MAX_TITLE_LENGTH) {
|
|
5949
|
+
throw new Error(`Title must be at most ${MAX_TITLE_LENGTH} characters.`);
|
|
5950
|
+
}
|
|
5951
|
+
if (CONTROL_CHARS_RE.test(title)) {
|
|
5952
|
+
throw new Error("Title must not contain control characters.");
|
|
5953
|
+
}
|
|
5954
|
+
return title;
|
|
5955
|
+
}
|
|
5956
|
+
function parsePreviewPath(raw) {
|
|
5957
|
+
const value = raw?.trim() || "/";
|
|
5958
|
+
let printable = true;
|
|
5959
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
5960
|
+
const code = value.charCodeAt(index);
|
|
5961
|
+
if (code < 33 || code > 126 || code === 92 || code === 35) {
|
|
5962
|
+
printable = false;
|
|
5963
|
+
break;
|
|
5964
|
+
}
|
|
5965
|
+
}
|
|
5966
|
+
if (!value.startsWith("/") || value.startsWith("//") || value.length > MAX_PATH_LENGTH || !printable) {
|
|
5967
|
+
throw new Error(
|
|
5968
|
+
`Path must start with / (not //) and be at most ${MAX_PATH_LENGTH} printable ASCII characters with no spaces, backslashes or #.`
|
|
5969
|
+
);
|
|
5970
|
+
}
|
|
5971
|
+
return value;
|
|
5972
|
+
}
|
|
5973
|
+
function readStateFile2() {
|
|
5974
|
+
try {
|
|
5975
|
+
const parsed = JSON.parse(fs8.readFileSync(previewStateFile(), "utf8"));
|
|
5976
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.previews)) {
|
|
5977
|
+
return { version: 1, previews: [] };
|
|
5978
|
+
}
|
|
5979
|
+
return { version: 1, previews: parsed.previews.filter(Boolean) };
|
|
5980
|
+
} catch {
|
|
5981
|
+
return { version: 1, previews: [] };
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5984
|
+
function writeStateFile2(state) {
|
|
5985
|
+
fs8.mkdirSync(instafyDir(), { recursive: true });
|
|
5986
|
+
fs8.writeFileSync(previewStateFile(), JSON.stringify(state, null, 2), { encoding: "utf8", mode: 384 });
|
|
5987
|
+
}
|
|
5988
|
+
function upsertPreviewState(entry) {
|
|
5989
|
+
const state = readStateFile2();
|
|
5990
|
+
const next = state.previews.filter((preview) => preview.previewId !== entry.previewId);
|
|
5991
|
+
next.unshift(entry);
|
|
5992
|
+
writeStateFile2({ version: 1, previews: next });
|
|
5993
|
+
}
|
|
5994
|
+
function removePreviewState(previewId) {
|
|
5995
|
+
const state = readStateFile2();
|
|
5996
|
+
const existing = state.previews.find((preview) => preview.previewId === previewId) ?? null;
|
|
5997
|
+
writeStateFile2({ version: 1, previews: state.previews.filter((preview) => preview.previewId !== previewId) });
|
|
5998
|
+
return existing;
|
|
5999
|
+
}
|
|
6000
|
+
function removeWorkdir(previewId) {
|
|
6001
|
+
fs8.rmSync(workdirFor(previewId), { recursive: true, force: true });
|
|
6002
|
+
}
|
|
6003
|
+
function forgetPreview(previewId) {
|
|
6004
|
+
const entry = removePreviewState(previewId);
|
|
6005
|
+
removeWorkdir(previewId);
|
|
6006
|
+
return entry;
|
|
6007
|
+
}
|
|
6008
|
+
function sleep(ms) {
|
|
6009
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6010
|
+
}
|
|
6011
|
+
function lockHolderIsGone(lockFile) {
|
|
6012
|
+
try {
|
|
6013
|
+
const stat = fs8.statSync(lockFile);
|
|
6014
|
+
if (Date.now() - stat.mtimeMs > STATE_LOCK_STALE_MS) return true;
|
|
6015
|
+
const pid = Number.parseInt(fs8.readFileSync(lockFile, "utf8").trim(), 10);
|
|
6016
|
+
if (!Number.isInteger(pid) || pid <= 0) return Date.now() - stat.mtimeMs > 5e3;
|
|
6017
|
+
try {
|
|
6018
|
+
process.kill(pid, 0);
|
|
6019
|
+
return false;
|
|
6020
|
+
} catch (error) {
|
|
6021
|
+
return error.code === "ESRCH";
|
|
6022
|
+
}
|
|
6023
|
+
} catch {
|
|
6024
|
+
return false;
|
|
6025
|
+
}
|
|
6026
|
+
}
|
|
6027
|
+
async function withStateLock(fn) {
|
|
6028
|
+
const lockFile = previewStateLockFile();
|
|
6029
|
+
fs8.mkdirSync(instafyDir(), { recursive: true });
|
|
6030
|
+
const deadline = Date.now() + STATE_LOCK_WAIT_MS;
|
|
6031
|
+
for (; ; ) {
|
|
6032
|
+
try {
|
|
6033
|
+
const fd = fs8.openSync(lockFile, "wx", 384);
|
|
6034
|
+
fs8.writeSync(fd, String(process.pid));
|
|
6035
|
+
fs8.closeSync(fd);
|
|
6036
|
+
break;
|
|
6037
|
+
} catch (error) {
|
|
6038
|
+
if (error.code !== "EEXIST") throw error;
|
|
6039
|
+
if (lockHolderIsGone(lockFile)) {
|
|
6040
|
+
fs8.rmSync(lockFile, { force: true });
|
|
6041
|
+
continue;
|
|
6042
|
+
}
|
|
6043
|
+
if (Date.now() > deadline) {
|
|
6044
|
+
throw new Error("Another instafy preview command on this machine is still running. Retry when it is done.");
|
|
6045
|
+
}
|
|
6046
|
+
await sleep(100);
|
|
6047
|
+
}
|
|
6048
|
+
}
|
|
6049
|
+
try {
|
|
6050
|
+
return await fn();
|
|
6051
|
+
} finally {
|
|
6052
|
+
fs8.rmSync(lockFile, { force: true });
|
|
6053
|
+
}
|
|
6054
|
+
}
|
|
6055
|
+
function configPathFor(workdir) {
|
|
6056
|
+
return path9.join(workdir, "rathole.toml");
|
|
6057
|
+
}
|
|
6058
|
+
async function stopClient(entry) {
|
|
6059
|
+
await stopRatholeClient(entry.pid, configPathFor(entry.workdir), entry.binary);
|
|
6060
|
+
}
|
|
6061
|
+
function shellQuote2(value) {
|
|
6062
|
+
return `'${value.split("'").join(`'\\''`)}'`;
|
|
6063
|
+
}
|
|
6064
|
+
function safePathSegment2(value) {
|
|
6065
|
+
return value.trim().replace(/[^a-zA-Z0-9._-]/g, "_") || "preview";
|
|
6066
|
+
}
|
|
6067
|
+
function stringField(record2, ...keys) {
|
|
6068
|
+
for (const key of keys) {
|
|
6069
|
+
const value = record2[key];
|
|
6070
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
6071
|
+
}
|
|
6072
|
+
return null;
|
|
6073
|
+
}
|
|
6074
|
+
function numberField(record2, ...keys) {
|
|
6075
|
+
for (const key of keys) {
|
|
6076
|
+
const value = record2[key];
|
|
6077
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
6078
|
+
}
|
|
6079
|
+
return null;
|
|
6080
|
+
}
|
|
6081
|
+
function previewsUnavailable() {
|
|
6082
|
+
return new PreviewCommandError(`${PREVIEWS_UNAVAILABLE_MESSAGE}.`, PREVIEW_EXIT_UNAVAILABLE);
|
|
6083
|
+
}
|
|
6084
|
+
async function controllerRequest(params) {
|
|
6085
|
+
const { response } = await fetchWithControllerAuth({
|
|
6086
|
+
url: `${cleanUrl(params.controllerUrl)}${params.route}`,
|
|
6087
|
+
init: {
|
|
6088
|
+
method: params.method,
|
|
6089
|
+
headers: {
|
|
6090
|
+
accept: "application/json",
|
|
6091
|
+
...params.body === void 0 ? {} : { "content-type": "application/json" }
|
|
6092
|
+
},
|
|
6093
|
+
body: params.body === void 0 ? void 0 : JSON.stringify(params.body)
|
|
6094
|
+
},
|
|
6095
|
+
accessToken: params.auth.token,
|
|
6096
|
+
tokenSource: params.auth.source,
|
|
6097
|
+
profile: params.auth.profile,
|
|
6098
|
+
cwd: process.cwd()
|
|
6099
|
+
});
|
|
6100
|
+
const text2 = await response.text().catch(() => "");
|
|
6101
|
+
if (!response.ok) {
|
|
6102
|
+
const code = errorCode(text2);
|
|
6103
|
+
if (response.status === 503 && code === "previews_unavailable" || response.status === 404 && params.notFoundMeansUnavailable && !isStructuredError(text2)) {
|
|
6104
|
+
throw previewsUnavailable();
|
|
6105
|
+
}
|
|
6106
|
+
if (response.status === 401 || response.status === 403) {
|
|
6107
|
+
throw formatAuthRejectedError({
|
|
6108
|
+
status: response.status,
|
|
6109
|
+
responseBody: text2,
|
|
6110
|
+
retryCommand: params.retryCommand
|
|
6111
|
+
});
|
|
6112
|
+
}
|
|
6113
|
+
if (response.status === 400 && code === "port_not_allowed") {
|
|
6114
|
+
throw new PreviewCommandError(
|
|
6115
|
+
`The server does not allow previews on this port: ${extractControllerErrorMessage(text2) ?? "port_not_allowed"}`,
|
|
6116
|
+
PREVIEW_EXIT_PORT_NOT_ALLOWED
|
|
6117
|
+
);
|
|
6118
|
+
}
|
|
6119
|
+
const message = extractControllerErrorMessage(text2) ?? response.statusText;
|
|
6120
|
+
throw new Error(`Preview request failed (${response.status}): ${message}`);
|
|
6121
|
+
}
|
|
6122
|
+
if (!text2.trim()) return { status: response.status, body: null };
|
|
6123
|
+
try {
|
|
6124
|
+
return { status: response.status, body: JSON.parse(text2) };
|
|
6125
|
+
} catch {
|
|
6126
|
+
throw new Error(`Preview request returned invalid JSON (${response.status}).`);
|
|
6127
|
+
}
|
|
6128
|
+
}
|
|
6129
|
+
function isStructuredError(body) {
|
|
6130
|
+
try {
|
|
6131
|
+
const parsed = JSON.parse(body);
|
|
6132
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
6133
|
+
const record2 = parsed;
|
|
6134
|
+
return ["message", "code", "error"].some((key) => typeof record2[key] === "string" && String(record2[key]).trim() !== "");
|
|
6135
|
+
} catch {
|
|
6136
|
+
return false;
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
6139
|
+
function errorCode(body) {
|
|
6140
|
+
try {
|
|
6141
|
+
const parsed = JSON.parse(body);
|
|
6142
|
+
for (const key of ["code", "error"]) {
|
|
6143
|
+
const value = parsed?.[key];
|
|
6144
|
+
if (typeof value === "string" && /^[a-z0-9_]{1,64}$/.test(value)) return value;
|
|
6145
|
+
}
|
|
6146
|
+
} catch {
|
|
6147
|
+
}
|
|
6148
|
+
return null;
|
|
6149
|
+
}
|
|
6150
|
+
function resolveAuth(opts, retryCommand) {
|
|
6151
|
+
const auth = resolveControllerToken({ controllerToken: opts.controllerToken }, retryCommand);
|
|
6152
|
+
const controllerUrl = resolveTunnelControllerUrl({ controllerUrl: opts.controllerUrl }, auth);
|
|
6153
|
+
return { auth, controllerUrl };
|
|
6154
|
+
}
|
|
6155
|
+
function previewsRoute(projectId2, previewId, action) {
|
|
6156
|
+
const base = `/projects/${encodeURIComponent(projectId2)}/previews`;
|
|
6157
|
+
if (!previewId) return base;
|
|
6158
|
+
return `${base}/${encodeURIComponent(previewId)}${action ? `/${action}` : ""}`;
|
|
6159
|
+
}
|
|
6160
|
+
async function closePreviewOnServer(controllerUrl, auth, projectId2, previewId) {
|
|
6161
|
+
await controllerRequest({
|
|
6162
|
+
controllerUrl,
|
|
6163
|
+
auth,
|
|
6164
|
+
method: "POST",
|
|
6165
|
+
route: previewsRoute(projectId2, previewId, "close"),
|
|
6166
|
+
body: {},
|
|
6167
|
+
retryCommand: `instafy preview close ${previewId}`
|
|
6168
|
+
});
|
|
6169
|
+
}
|
|
6170
|
+
async function resolveRathole(ratholeBin, log) {
|
|
6171
|
+
if (ratholeBin) {
|
|
6172
|
+
process.env.RATHOLE_BIN = ratholeBin;
|
|
6173
|
+
}
|
|
6174
|
+
const rathole = await resolveRatholeBinaryForCli({
|
|
6175
|
+
env: process.env,
|
|
6176
|
+
version: process.env.RATHOLE_VERSION ?? null,
|
|
6177
|
+
cacheDir: process.env.RATHOLE_CACHE_DIR ?? null,
|
|
6178
|
+
logger: (message) => log(`[rathole] ${message}`),
|
|
6179
|
+
warn: (message) => console.error(kleur8.yellow(message))
|
|
6180
|
+
});
|
|
6181
|
+
if (!rathole) {
|
|
6182
|
+
throw new Error("rathole is required to open a preview. Set RATHOLE_BIN or ensure it is on PATH.");
|
|
6183
|
+
}
|
|
6184
|
+
return rathole;
|
|
6185
|
+
}
|
|
6186
|
+
function readFileOrNull(file) {
|
|
6187
|
+
try {
|
|
6188
|
+
return fs8.readFileSync(file, "utf8");
|
|
6189
|
+
} catch {
|
|
6190
|
+
return null;
|
|
6191
|
+
}
|
|
6192
|
+
}
|
|
6193
|
+
function readyHealth(readyBody) {
|
|
6194
|
+
return readyBody["health"] && typeof readyBody["health"] === "object" && !Array.isArray(readyBody["health"]) ? readyBody["health"] : null;
|
|
6195
|
+
}
|
|
6196
|
+
function previewStatusFromHealth(readyBody) {
|
|
6197
|
+
const health = readyHealth(readyBody);
|
|
6198
|
+
if (health?.["listening"] === true) return "live";
|
|
6199
|
+
if (health?.["listening"] === false) return "not_responding";
|
|
6200
|
+
return "unknown";
|
|
6201
|
+
}
|
|
6202
|
+
function previewHttpStatusFromHealth(readyBody) {
|
|
6203
|
+
const status = readyHealth(readyBody)?.["status"];
|
|
6204
|
+
return typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599 ? status : null;
|
|
6205
|
+
}
|
|
6206
|
+
async function notListeningError(port, sitePath, probe) {
|
|
6207
|
+
const where = `http://127.0.0.1:${port}${sitePath}`;
|
|
6208
|
+
if (probe.status !== null) {
|
|
6209
|
+
return new PreviewCommandError(
|
|
6210
|
+
`The site at ${where} answered HTTP ${probe.status}. Fix the server error, then retry.`,
|
|
6211
|
+
PREVIEW_EXIT_NOT_LISTENING
|
|
6212
|
+
);
|
|
6213
|
+
}
|
|
6214
|
+
if (probe.timedOut) {
|
|
6215
|
+
return new PreviewCommandError(noAnswerMessage(port, probe, where, "retry"), PREVIEW_EXIT_NOT_LISTENING);
|
|
6216
|
+
}
|
|
6217
|
+
const ipv6 = await probeLocalSite(port, sitePath, void 0, ["::1"]);
|
|
6218
|
+
if (ipv6.status !== null) {
|
|
6219
|
+
return new PreviewCommandError(
|
|
6220
|
+
`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.`,
|
|
6221
|
+
PREVIEW_EXIT_NOT_LISTENING
|
|
6222
|
+
);
|
|
6223
|
+
}
|
|
6224
|
+
return new PreviewCommandError(noAnswerMessage(port, probe, where, "retry"), PREVIEW_EXIT_NOT_LISTENING);
|
|
6225
|
+
}
|
|
6226
|
+
var SERVE_MAX_ARGS = 64;
|
|
6227
|
+
var SERVE_MAX_ARG_LENGTH = 2048;
|
|
6228
|
+
var EPHEMERAL_PREFIXES = ["/tmp/", "/var/tmp/", "/dev/shm/", "/run/"];
|
|
6229
|
+
function parseServeCommand(raw) {
|
|
6230
|
+
const argv = [];
|
|
6231
|
+
let current = "";
|
|
6232
|
+
let inWord = false;
|
|
6233
|
+
let quote = null;
|
|
6234
|
+
const text2 = String(raw ?? "");
|
|
6235
|
+
for (let index = 0; index < text2.length; index += 1) {
|
|
6236
|
+
const ch = text2[index];
|
|
6237
|
+
if (quote === "'") {
|
|
6238
|
+
if (ch === "'") quote = null;
|
|
6239
|
+
else current += ch;
|
|
6240
|
+
continue;
|
|
6241
|
+
}
|
|
6242
|
+
if (quote === '"') {
|
|
6243
|
+
if (ch === '"') {
|
|
6244
|
+
quote = null;
|
|
6245
|
+
} else if (ch === "\\" && index + 1 < text2.length && '"\\$`'.includes(text2[index + 1])) {
|
|
6246
|
+
current += text2[index + 1];
|
|
6247
|
+
index += 1;
|
|
6248
|
+
} else if (ch === "$" || ch === "`") {
|
|
6249
|
+
throw new Error(`--serve-cmd takes one command and its arguments, not shell syntax (found ${ch} in quotes).`);
|
|
6250
|
+
} else {
|
|
6251
|
+
current += ch;
|
|
6252
|
+
}
|
|
6253
|
+
continue;
|
|
6254
|
+
}
|
|
6255
|
+
if (ch === "'" || ch === '"') {
|
|
6256
|
+
quote = ch;
|
|
6257
|
+
inWord = true;
|
|
6258
|
+
continue;
|
|
6259
|
+
}
|
|
6260
|
+
if (ch === "\\") {
|
|
6261
|
+
if (index + 1 >= text2.length) throw new Error("--serve-cmd ends with a lone backslash.");
|
|
6262
|
+
current += text2[index + 1];
|
|
6263
|
+
index += 1;
|
|
6264
|
+
inWord = true;
|
|
6265
|
+
continue;
|
|
6266
|
+
}
|
|
6267
|
+
if (/\s/.test(ch)) {
|
|
6268
|
+
if (ch === "\n" || ch === "\r") {
|
|
6269
|
+
throw new Error("--serve-cmd takes one command on one line.");
|
|
6270
|
+
}
|
|
6271
|
+
if (inWord) {
|
|
6272
|
+
argv.push(current);
|
|
6273
|
+
current = "";
|
|
6274
|
+
inWord = false;
|
|
6275
|
+
}
|
|
6276
|
+
continue;
|
|
6277
|
+
}
|
|
6278
|
+
if ("|&;<>()$`".includes(ch)) {
|
|
6279
|
+
throw new Error(
|
|
6280
|
+
`--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.`
|
|
6281
|
+
);
|
|
6282
|
+
}
|
|
6283
|
+
current += ch;
|
|
6284
|
+
inWord = true;
|
|
6285
|
+
}
|
|
6286
|
+
if (quote) throw new Error("--serve-cmd has an unterminated quote.");
|
|
6287
|
+
if (inWord) argv.push(current);
|
|
6288
|
+
if (argv.length === 0) throw new Error("--serve-cmd is empty.");
|
|
6289
|
+
if (argv.length > SERVE_MAX_ARGS || argv.some((arg) => arg.length > SERVE_MAX_ARG_LENGTH || arg.includes("\0"))) {
|
|
6290
|
+
throw new Error(`--serve-cmd must have at most ${SERVE_MAX_ARGS} arguments of at most ${SERVE_MAX_ARG_LENGTH} characters.`);
|
|
6291
|
+
}
|
|
6292
|
+
return argv;
|
|
6293
|
+
}
|
|
6294
|
+
function realDir(dir) {
|
|
6295
|
+
try {
|
|
6296
|
+
const resolved = fs8.realpathSync(dir);
|
|
6297
|
+
return fs8.statSync(resolved).isDirectory() ? resolved : null;
|
|
6298
|
+
} catch {
|
|
6299
|
+
return null;
|
|
6300
|
+
}
|
|
6301
|
+
}
|
|
6302
|
+
function resolveWorkspaceRoot(env, projectId2) {
|
|
6303
|
+
const explicit = env["WORKSPACE_PROJECT_DIR"]?.trim();
|
|
6304
|
+
if (explicit) return realDir(explicit);
|
|
6305
|
+
const base = env["WORKSPACE_DIR"]?.trim();
|
|
6306
|
+
if (!base || !projectId2) return null;
|
|
6307
|
+
return realDir(path9.join(base, projectId2));
|
|
6308
|
+
}
|
|
6309
|
+
function workspaceRelativeDir(root, dir) {
|
|
6310
|
+
const resolved = realDir(dir);
|
|
6311
|
+
if (!resolved) return null;
|
|
6312
|
+
const relative = path9.relative(root, resolved);
|
|
6313
|
+
if (relative === "") return ".";
|
|
6314
|
+
if (relative.startsWith("..") || path9.isAbsolute(relative)) return null;
|
|
6315
|
+
return relative.split(path9.sep).join("/");
|
|
6316
|
+
}
|
|
6317
|
+
function listeningSocketInodes(procRoot, port) {
|
|
6318
|
+
const inodes = /* @__PURE__ */ new Set();
|
|
6319
|
+
for (const table of ["net/tcp", "net/tcp6"]) {
|
|
6320
|
+
let text2 = "";
|
|
6321
|
+
try {
|
|
6322
|
+
text2 = fs8.readFileSync(path9.join(procRoot, table), "utf8");
|
|
6323
|
+
} catch {
|
|
6324
|
+
continue;
|
|
6325
|
+
}
|
|
6326
|
+
for (const line of text2.split("\n").slice(1)) {
|
|
6327
|
+
const fields = line.trim().split(/\s+/);
|
|
6328
|
+
if (fields.length < 10) continue;
|
|
6329
|
+
const local = fields[1] ?? "";
|
|
6330
|
+
const portHex = local.slice(local.lastIndexOf(":") + 1);
|
|
6331
|
+
if (fields[3] === "0A" && Number.parseInt(portHex, 16) === port && fields[9] !== "0") {
|
|
6332
|
+
inodes.add(fields[9]);
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
6335
|
+
}
|
|
6336
|
+
return inodes;
|
|
6337
|
+
}
|
|
6338
|
+
function detectListeningProcess(port, procRoot = "/proc") {
|
|
6339
|
+
const inodes = listeningSocketInodes(procRoot, port);
|
|
6340
|
+
if (inodes.size === 0) return null;
|
|
6341
|
+
let pids = [];
|
|
6342
|
+
try {
|
|
6343
|
+
pids = fs8.readdirSync(procRoot).filter((name) => /^\d+$/.test(name)).map(Number).sort((a, b) => a - b);
|
|
6344
|
+
} catch {
|
|
6345
|
+
return null;
|
|
6346
|
+
}
|
|
6347
|
+
for (const pid of pids) {
|
|
6348
|
+
let fds = [];
|
|
6349
|
+
try {
|
|
6350
|
+
fds = fs8.readdirSync(path9.join(procRoot, String(pid), "fd"));
|
|
6351
|
+
} catch {
|
|
6352
|
+
continue;
|
|
6353
|
+
}
|
|
6354
|
+
const holds = fds.some((fd) => {
|
|
6355
|
+
try {
|
|
6356
|
+
const target = fs8.readlinkSync(path9.join(procRoot, String(pid), "fd", fd));
|
|
6357
|
+
const match = /^socket:\[(\d+)\]$/.exec(target);
|
|
6358
|
+
return Boolean(match && inodes.has(match[1]));
|
|
6359
|
+
} catch {
|
|
6360
|
+
return false;
|
|
6361
|
+
}
|
|
6362
|
+
});
|
|
6363
|
+
if (!holds) continue;
|
|
6364
|
+
try {
|
|
6365
|
+
const argv = fs8.readFileSync(path9.join(procRoot, String(pid), "cmdline"), "utf8").split("\0");
|
|
6366
|
+
while (argv.length > 0 && argv[argv.length - 1] === "") argv.pop();
|
|
6367
|
+
const cwd = fs8.readlinkSync(path9.join(procRoot, String(pid), "cwd"));
|
|
6368
|
+
if (argv.length === 0) continue;
|
|
6369
|
+
return { pid, argv, cwd };
|
|
6370
|
+
} catch {
|
|
6371
|
+
continue;
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6374
|
+
return null;
|
|
6375
|
+
}
|
|
6376
|
+
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;
|
|
6377
|
+
var SECRET_ENV = /^(?=[A-Za-z_])\w*(TOKEN|SECRET|PASSWORD|PASSWD|API_?KEY|ACCESS_?KEY|PRIVATE_?KEY|CREDENTIALS?)[\w]*=./i;
|
|
6378
|
+
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,}\./;
|
|
6379
|
+
var URL_USERINFO = /^[a-z][a-z0-9+.-]*:\/\/[^/\s@]*:[^/\s@]*@/i;
|
|
6380
|
+
function secretInServeCommand(argv) {
|
|
6381
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
6382
|
+
const arg = argv[i];
|
|
6383
|
+
const eq = arg.indexOf("=");
|
|
6384
|
+
const flag = arg.startsWith("-") ? eq > 0 ? arg.slice(0, eq) : arg : null;
|
|
6385
|
+
if (flag && SECRET_FLAG.test(flag)) {
|
|
6386
|
+
const value = eq > 0 ? arg.slice(eq + 1) : argv[i + 1];
|
|
6387
|
+
if (value && !value.startsWith("-")) return flag;
|
|
6388
|
+
}
|
|
6389
|
+
if (SECRET_ENV.test(arg)) return arg.slice(0, arg.indexOf("="));
|
|
6390
|
+
const bare = eq > 0 && arg.startsWith("-") ? arg.slice(eq + 1) : arg;
|
|
6391
|
+
if (URL_USERINFO.test(bare)) return "a URL with a password";
|
|
6392
|
+
if (SECRET_VALUE.test(bare)) return "a key";
|
|
6393
|
+
}
|
|
6394
|
+
return null;
|
|
6395
|
+
}
|
|
6396
|
+
function parseExplicitServeCommand(raw) {
|
|
6397
|
+
const argv = parseServeCommand(raw);
|
|
6398
|
+
const secret = secretInServeCommand(argv);
|
|
6399
|
+
if (secret) {
|
|
6400
|
+
throw new Error(
|
|
6401
|
+
`--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.`
|
|
6402
|
+
);
|
|
6403
|
+
}
|
|
6404
|
+
return argv;
|
|
6405
|
+
}
|
|
6406
|
+
function resolvePreviewServe(params) {
|
|
6407
|
+
const env = params.env ?? process.env;
|
|
6408
|
+
const root = resolveWorkspaceRoot(env, params.projectId);
|
|
6409
|
+
const explicit = params.serveCmd?.trim();
|
|
6410
|
+
if (explicit) {
|
|
6411
|
+
const argv = parseExplicitServeCommand(explicit);
|
|
6412
|
+
if (!root) {
|
|
6413
|
+
throw new Error("--serve-cmd needs the space's workspace (WORKSPACE_DIR); run it inside the space.");
|
|
6414
|
+
}
|
|
6415
|
+
const cwd2 = workspaceRelativeDir(root, params.cwd ?? process.cwd());
|
|
6416
|
+
if (!cwd2) {
|
|
6417
|
+
throw new Error(`--serve-cmd runs from the current directory, which must be inside the space's workspace (${root}).`);
|
|
6418
|
+
}
|
|
6419
|
+
return { serve: { argv, cwd: cwd2 } };
|
|
6420
|
+
}
|
|
6421
|
+
const notRestartable = (note) => ({ serve: null, note });
|
|
6422
|
+
const platform = params.platform ?? process.platform;
|
|
6423
|
+
if (platform !== "linux" && !params.procRoot) {
|
|
6424
|
+
return notRestartable("the start command can only be read on a Linux space; pass --serve-cmd");
|
|
6425
|
+
}
|
|
6426
|
+
if (!root) return notRestartable("this is not running inside a space's workspace");
|
|
6427
|
+
const found = detectListeningProcess(params.port, params.procRoot);
|
|
6428
|
+
if (!found) return notRestartable(`could not see which process listens on port ${params.port}; pass --serve-cmd`);
|
|
6429
|
+
const cwd = workspaceRelativeDir(root, found.cwd);
|
|
6430
|
+
if (!cwd) {
|
|
6431
|
+
return notRestartable(`the site runs from ${found.cwd}, outside the workspace, which does not survive a sleep`);
|
|
6432
|
+
}
|
|
6433
|
+
const ephemeral = found.argv.find((arg) => EPHEMERAL_PREFIXES.some((prefix) => arg.startsWith(prefix)));
|
|
6434
|
+
if (ephemeral) {
|
|
6435
|
+
return notRestartable(`its command uses ${ephemeral}, which does not survive a sleep; pass --serve-cmd`);
|
|
6436
|
+
}
|
|
6437
|
+
if (found.argv.length > SERVE_MAX_ARGS || found.argv.some((arg) => arg.length > SERVE_MAX_ARG_LENGTH)) {
|
|
6438
|
+
return notRestartable("its command line is too long to record; pass --serve-cmd");
|
|
6439
|
+
}
|
|
6440
|
+
const secret = secretInServeCommand(found.argv);
|
|
6441
|
+
if (secret) {
|
|
6442
|
+
return notRestartable(
|
|
6443
|
+
`its command line carries what looks like a secret (${secret}), which is not recorded; read it from an env file and pass --serve-cmd`
|
|
6444
|
+
);
|
|
6445
|
+
}
|
|
6446
|
+
return { serve: { argv: found.argv, cwd } };
|
|
6447
|
+
}
|
|
6448
|
+
async function createAndStartClient(params) {
|
|
6449
|
+
const { opts, port, title, sitePath, projectId: projectId2, retryCommand, auth, controllerUrl, serverUrl, log, serve } = params;
|
|
6450
|
+
const conversationId = opts.conversationId?.trim() || void 0;
|
|
6451
|
+
const created = await controllerRequest({
|
|
6452
|
+
controllerUrl,
|
|
6453
|
+
auth,
|
|
6454
|
+
method: "POST",
|
|
6455
|
+
route: previewsRoute(projectId2),
|
|
6456
|
+
body: {
|
|
6457
|
+
runtimeId: readEnv("RUNTIME_ID"),
|
|
6458
|
+
runtimeLeaseId: readEnv("RUNTIME_LEASE_ID"),
|
|
6459
|
+
conversationId,
|
|
6460
|
+
port,
|
|
6461
|
+
path: sitePath,
|
|
6462
|
+
title,
|
|
6463
|
+
metadata: { source: "instafy-cli" },
|
|
6464
|
+
...serve ? { serve } : {}
|
|
6465
|
+
},
|
|
6466
|
+
retryCommand,
|
|
6467
|
+
notFoundMeansUnavailable: true
|
|
6468
|
+
});
|
|
6469
|
+
const grant = created.body && !Array.isArray(created.body) ? created.body : {};
|
|
6470
|
+
const previewId = stringField(grant, "previewId");
|
|
6471
|
+
if (!previewId) {
|
|
6472
|
+
throw new Error("Preview response is missing previewId.");
|
|
6473
|
+
}
|
|
6474
|
+
const workdir = workdirFor(previewId);
|
|
6475
|
+
const logFile = path9.join(instafyDir(), "cli-preview-logs", `preview-${safePathSegment2(previewId)}.log`);
|
|
6476
|
+
const configPath2 = configPathFor(workdir);
|
|
6477
|
+
const hostSuffix = parseViewerHostSuffix(grant["viewerHostSuffix"]);
|
|
6478
|
+
const studioOrigin = parseStudioOrigin(grant["studioOrigin"]);
|
|
6479
|
+
const state = readStateFile2();
|
|
6480
|
+
const current = state.previews.find((preview) => preview.previewId === previewId) ?? null;
|
|
6481
|
+
let pid = null;
|
|
6482
|
+
let binary = current?.binary;
|
|
6483
|
+
try {
|
|
6484
|
+
const credentials = grant["credentials"] && typeof grant["credentials"] === "object" ? grant["credentials"] : {};
|
|
6485
|
+
const configBody = buildRatholeConfig(credentials, port);
|
|
6486
|
+
fs8.mkdirSync(workdir, { recursive: true });
|
|
6487
|
+
fs8.mkdirSync(path9.dirname(logFile), { recursive: true });
|
|
6488
|
+
if (current && readFileOrNull(configPath2) === configBody && isRatholeClientFor(current.pid, configPath2, current.binary)) {
|
|
6489
|
+
pid = current.pid;
|
|
6490
|
+
}
|
|
6491
|
+
for (const stale of state.previews) {
|
|
6492
|
+
if (stale.previewId === previewId) {
|
|
6493
|
+
if (pid === null) await stopClient(stale);
|
|
6494
|
+
continue;
|
|
6495
|
+
}
|
|
6496
|
+
if (stale.localPort !== port) continue;
|
|
6497
|
+
await stopClient(stale);
|
|
6498
|
+
forgetPreview(stale.previewId);
|
|
6499
|
+
if (cleanUrl(stale.controllerUrl) === serverUrl) {
|
|
6500
|
+
await closePreviewOnServer(controllerUrl, auth, stale.projectId, stale.previewId).catch(() => {
|
|
6501
|
+
});
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
if (pid === null) {
|
|
6505
|
+
const rathole = await resolveRathole(opts.ratholeBin, log);
|
|
6506
|
+
fs8.writeFileSync(configPath2, configBody, { encoding: "utf8", mode: 384 });
|
|
6507
|
+
binary = rathole;
|
|
6508
|
+
const started = await spawnRatholeDetached({ rathole, configPath: configPath2, workdir, logFile });
|
|
6509
|
+
if (started.exit !== null) {
|
|
6510
|
+
const suffix = started.logTail ? `
|
|
6511
|
+
|
|
6512
|
+
Last logs:
|
|
6513
|
+
${started.logTail}` : "";
|
|
6514
|
+
throw new Error(`Preview tunnel exited (${started.exit}).${suffix}`);
|
|
6515
|
+
}
|
|
6516
|
+
pid = started.pid;
|
|
6517
|
+
} else {
|
|
6518
|
+
log(`Reusing the running preview client (pid ${pid}).`);
|
|
6519
|
+
}
|
|
6520
|
+
} catch (error) {
|
|
6521
|
+
if (pid !== null) await stopRatholeClient(pid, configPath2, binary);
|
|
6522
|
+
forgetPreview(previewId);
|
|
6523
|
+
await closePreviewOnServer(controllerUrl, auth, projectId2, previewId).catch(() => {
|
|
6524
|
+
});
|
|
6525
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6526
|
+
const [first, ...rest] = message.split("\n");
|
|
6527
|
+
const closed = [`${first} The preview was closed.`, ...rest].join("\n");
|
|
6528
|
+
if (error instanceof PreviewCommandError) throw new PreviewCommandError(closed, error.exitCode);
|
|
6529
|
+
throw new Error(closed);
|
|
6530
|
+
}
|
|
6531
|
+
const entry = {
|
|
6532
|
+
previewId,
|
|
6533
|
+
projectId: projectId2,
|
|
6534
|
+
title,
|
|
6535
|
+
localPort: port,
|
|
6536
|
+
path: sitePath,
|
|
6537
|
+
controllerUrl: serverUrl,
|
|
6538
|
+
pid,
|
|
6539
|
+
logFile,
|
|
6540
|
+
workdir,
|
|
6541
|
+
startedAt: current?.pid === pid ? current.startedAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
6542
|
+
...binary ? { binary } : {},
|
|
6543
|
+
...hostSuffix ? { hostSuffix } : {},
|
|
6544
|
+
...studioOrigin ? { studioOrigin } : {}
|
|
6545
|
+
};
|
|
6546
|
+
upsertPreviewState(entry);
|
|
6547
|
+
return { grant, previewId, entry, current };
|
|
6548
|
+
}
|
|
6549
|
+
async function previewOpen(opts) {
|
|
6550
|
+
const port = parsePreviewPort(opts.port);
|
|
6551
|
+
const title = parsePreviewTitle(opts.title);
|
|
6552
|
+
const sitePath = parsePreviewPath(opts.path);
|
|
6553
|
+
if (opts.serveCmd?.trim()) parseExplicitServeCommand(opts.serveCmd);
|
|
6554
|
+
const projectId2 = resolveProject({ project: opts.project });
|
|
6555
|
+
const retryCommand = `instafy preview open --port ${port} --title ${shellQuote2(title)}`;
|
|
6556
|
+
const { auth, controllerUrl } = resolveAuth(opts, retryCommand);
|
|
6557
|
+
const serverUrl = cleanUrl(controllerUrl);
|
|
6558
|
+
const log = (message) => console.error(kleur8.cyan(message));
|
|
6559
|
+
const probe = await probeLocalSite(port, sitePath, void 0, RATHOLE_LOCAL_HOSTS);
|
|
6560
|
+
if (!probe.listening) {
|
|
6561
|
+
throw await notListeningError(port, sitePath, probe);
|
|
6562
|
+
}
|
|
6563
|
+
const { serve, note: restartNote } = resolvePreviewServe({ port, serveCmd: opts.serveCmd, projectId: projectId2, cwd: opts.cwd });
|
|
6564
|
+
const { grant, previewId, entry } = await withStateLock(
|
|
6565
|
+
() => createAndStartClient({ opts, port, title, sitePath, projectId: projectId2, retryCommand, auth, controllerUrl, serverUrl, log, serve })
|
|
6566
|
+
);
|
|
6567
|
+
let ready;
|
|
6568
|
+
try {
|
|
6569
|
+
ready = await controllerRequest({
|
|
6570
|
+
controllerUrl,
|
|
6571
|
+
auth,
|
|
6572
|
+
method: "POST",
|
|
6573
|
+
route: previewsRoute(projectId2, previewId, "ready"),
|
|
6574
|
+
body: {},
|
|
6575
|
+
retryCommand
|
|
6576
|
+
});
|
|
6577
|
+
} catch (error) {
|
|
6578
|
+
if (error instanceof PreviewCommandError && error.exitCode === PREVIEW_EXIT_UNAVAILABLE) {
|
|
6579
|
+
await withStateLock(async () => {
|
|
6580
|
+
await stopClient(entry);
|
|
6581
|
+
forgetPreview(previewId);
|
|
6582
|
+
});
|
|
6583
|
+
}
|
|
6584
|
+
throw error;
|
|
6585
|
+
}
|
|
6586
|
+
const readyBody = ready.body && !Array.isArray(ready.body) ? ready.body : {};
|
|
6587
|
+
const restartable = serve !== null && grant["restartable"] === true;
|
|
6588
|
+
return {
|
|
6589
|
+
previewId,
|
|
6590
|
+
title,
|
|
6591
|
+
port,
|
|
6592
|
+
path: sitePath,
|
|
6593
|
+
status: previewStatusFromHealth(readyBody),
|
|
6594
|
+
httpStatus: previewHttpStatusFromHealth(readyBody),
|
|
6595
|
+
messageId: stringField(readyBody, "messageId"),
|
|
6596
|
+
viewerHostSuffix: parseViewerHostSuffix(grant["viewerHostSuffix"]) ?? null,
|
|
6597
|
+
restartable,
|
|
6598
|
+
...restartable ? {} : { restartNote: restartNote ?? "the server did not record a start command" }
|
|
6599
|
+
};
|
|
6600
|
+
}
|
|
6601
|
+
function toPreviewSummary(raw) {
|
|
6602
|
+
if (!raw || typeof raw !== "object") return null;
|
|
6603
|
+
const record2 = raw;
|
|
6604
|
+
const previewId = stringField(record2, "previewId", "id");
|
|
6605
|
+
if (!previewId) return null;
|
|
6606
|
+
return {
|
|
6607
|
+
previewId,
|
|
6608
|
+
title: stringField(record2, "title") ?? "",
|
|
6609
|
+
port: numberField(record2, "port", "localPort") ?? 0,
|
|
6610
|
+
path: stringField(record2, "path") ?? "/",
|
|
6611
|
+
status: stringField(record2, "status") ?? "unknown"
|
|
6612
|
+
};
|
|
6613
|
+
}
|
|
6614
|
+
async function previewList(opts) {
|
|
6615
|
+
const projectId2 = resolveProject({ project: opts.project });
|
|
6616
|
+
const { auth, controllerUrl } = resolveAuth(opts, "instafy preview list");
|
|
6617
|
+
const listed = await controllerRequest({
|
|
6618
|
+
controllerUrl,
|
|
6619
|
+
auth,
|
|
6620
|
+
method: "GET",
|
|
6621
|
+
route: previewsRoute(projectId2),
|
|
6622
|
+
retryCommand: "instafy preview list",
|
|
6623
|
+
notFoundMeansUnavailable: true
|
|
6624
|
+
});
|
|
6625
|
+
const rows = Array.isArray(listed.body) ? listed.body : listed.body && Array.isArray(listed.body["previews"]) ? listed.body["previews"] : [];
|
|
6626
|
+
return rows.map(toPreviewSummary).filter((row) => row !== null);
|
|
6627
|
+
}
|
|
6628
|
+
async function previewClose(opts) {
|
|
6629
|
+
const previewId = opts.previewId?.trim() ?? "";
|
|
6630
|
+
if (!previewId) {
|
|
6631
|
+
throw new Error("Preview id is required. Use `instafy preview list` to find it.");
|
|
6632
|
+
}
|
|
6633
|
+
const entry = await withStateLock(async () => {
|
|
6634
|
+
const removed = removePreviewState(previewId);
|
|
6635
|
+
if (removed) await stopClient(removed);
|
|
6636
|
+
return removed;
|
|
6637
|
+
});
|
|
6638
|
+
const projectId2 = entry?.projectId ?? resolveProject({ project: opts.project });
|
|
6639
|
+
try {
|
|
6640
|
+
const { auth, controllerUrl } = resolveAuth(
|
|
6641
|
+
{ ...opts, controllerUrl: opts.controllerUrl ?? entry?.controllerUrl },
|
|
6642
|
+
`instafy preview close ${previewId}`
|
|
6643
|
+
);
|
|
6644
|
+
await closePreviewOnServer(controllerUrl, auth, projectId2, previewId);
|
|
6645
|
+
} catch (error) {
|
|
6646
|
+
if (entry) await withStateLock(async () => upsertPreviewState({ ...entry, pid: -1 }));
|
|
6647
|
+
throw error;
|
|
6648
|
+
}
|
|
6649
|
+
removeWorkdir(previewId);
|
|
6650
|
+
return { ok: true, previewId };
|
|
6651
|
+
}
|
|
6652
|
+
function printPreviewOpen(result, json) {
|
|
6653
|
+
if (json) {
|
|
6654
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6655
|
+
return;
|
|
6656
|
+
}
|
|
6657
|
+
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)");
|
|
6658
|
+
console.log(`${label}: ${result.title} (port ${result.port}). Open it from the card in the conversation.`);
|
|
6659
|
+
if (result.restartable) {
|
|
6660
|
+
console.log(kleur8.gray("Open starts the site again after the space sleeps."));
|
|
6661
|
+
} else {
|
|
6662
|
+
console.log(kleur8.yellow(`After the space sleeps, Open cannot start the site by itself: ${result.restartNote}.`));
|
|
6663
|
+
}
|
|
6664
|
+
console.log(kleur8.gray(`previewId=${result.previewId}`));
|
|
6665
|
+
if (result.httpStatus !== null && result.httpStatus >= 400) {
|
|
6666
|
+
console.log(kleur8.yellow(`The site answered HTTP ${result.httpStatus} through the preview.`));
|
|
6667
|
+
}
|
|
6668
|
+
if (result.viewerHostSuffix) {
|
|
6669
|
+
console.log(kleur8.gray(`viewerHostSuffix=${result.viewerHostSuffix}`));
|
|
6670
|
+
}
|
|
6671
|
+
console.log("");
|
|
6672
|
+
console.log("Next:");
|
|
6673
|
+
console.log(`- ${kleur8.cyan("instafy preview list")}`);
|
|
6674
|
+
console.log(`- ${kleur8.cyan(`instafy preview close ${result.previewId}`)}`);
|
|
6675
|
+
}
|
|
6676
|
+
function printPreviewList(previews, json) {
|
|
6677
|
+
if (json) {
|
|
6678
|
+
console.log(JSON.stringify(previews, null, 2));
|
|
6679
|
+
return;
|
|
6680
|
+
}
|
|
6681
|
+
if (previews.length === 0) {
|
|
6682
|
+
console.log(kleur8.yellow("No previews found."));
|
|
6683
|
+
return;
|
|
6684
|
+
}
|
|
6685
|
+
for (const preview of previews) {
|
|
6686
|
+
console.log(
|
|
6687
|
+
`${preview.previewId} \xB7 ${preview.title || "(untitled)"} \xB7 port=${preview.port}${preview.path === "/" ? "" : ` \xB7 path=${preview.path}`} \xB7 ${preview.status}`
|
|
6688
|
+
);
|
|
6689
|
+
}
|
|
6690
|
+
}
|
|
6691
|
+
|
|
5666
6692
|
// src/config-command.ts
|
|
5667
6693
|
init_config();
|
|
5668
6694
|
init_config();
|
|
5669
|
-
import
|
|
6695
|
+
import kleur9 from "kleur";
|
|
5670
6696
|
function normalizeKey(raw) {
|
|
5671
6697
|
const key = raw.trim().toLowerCase().replace(/_/g, "-");
|
|
5672
6698
|
if (key === "controller-url" || key === "controllerurl" || key === "server-url") {
|
|
@@ -5708,12 +6734,12 @@ function clearConfig(key) {
|
|
|
5708
6734
|
}
|
|
5709
6735
|
}
|
|
5710
6736
|
function configPath(options) {
|
|
5711
|
-
const
|
|
6737
|
+
const path19 = getInstafyConfigPath();
|
|
5712
6738
|
if (options?.json) {
|
|
5713
|
-
console.log(JSON.stringify({ path:
|
|
6739
|
+
console.log(JSON.stringify({ path: path19 }, null, 2));
|
|
5714
6740
|
return;
|
|
5715
6741
|
}
|
|
5716
|
-
console.log(
|
|
6742
|
+
console.log(path19);
|
|
5717
6743
|
}
|
|
5718
6744
|
function configList(options) {
|
|
5719
6745
|
const config = readInstafyCliConfig();
|
|
@@ -5730,13 +6756,13 @@ function configList(options) {
|
|
|
5730
6756
|
console.log(JSON.stringify(payload, null, 2));
|
|
5731
6757
|
return;
|
|
5732
6758
|
}
|
|
5733
|
-
console.log(
|
|
6759
|
+
console.log(kleur9.green("Instafy CLI config"));
|
|
5734
6760
|
console.log(`Path: ${payload.path}`);
|
|
5735
|
-
console.log(`controller-url: ${payload.controllerUrl ??
|
|
6761
|
+
console.log(`controller-url: ${payload.controllerUrl ?? kleur9.yellow("(not set)")}`);
|
|
5736
6762
|
console.log(`controller-url (effective): ${payload.effectiveControllerUrl}`);
|
|
5737
|
-
console.log(`studio-url: ${payload.studioUrl ??
|
|
5738
|
-
console.log(`access-token: ${payload.accessTokenSet ?
|
|
5739
|
-
console.log(`refresh-token: ${payload.refreshTokenSet ?
|
|
6763
|
+
console.log(`studio-url: ${payload.studioUrl ?? kleur9.yellow("(not set)")}`);
|
|
6764
|
+
console.log(`access-token: ${payload.accessTokenSet ? kleur9.green("(set)") : kleur9.yellow("(not set)")}`);
|
|
6765
|
+
console.log(`refresh-token: ${payload.refreshTokenSet ? kleur9.green("(set)") : kleur9.yellow("(not set)")}`);
|
|
5740
6766
|
if (payload.updatedAt) {
|
|
5741
6767
|
console.log(`updated-at: ${payload.updatedAt}`);
|
|
5742
6768
|
}
|
|
@@ -5765,7 +6791,7 @@ function configSet(params) {
|
|
|
5765
6791
|
console.log(JSON.stringify({ key, value, path: getInstafyConfigPath() }, null, 2));
|
|
5766
6792
|
return;
|
|
5767
6793
|
}
|
|
5768
|
-
console.log(
|
|
6794
|
+
console.log(kleur9.green(`Set ${key}`));
|
|
5769
6795
|
console.log(value);
|
|
5770
6796
|
}
|
|
5771
6797
|
function configUnset(params) {
|
|
@@ -5775,7 +6801,7 @@ function configUnset(params) {
|
|
|
5775
6801
|
console.log(JSON.stringify({ ok: true, key }, null, 2));
|
|
5776
6802
|
return;
|
|
5777
6803
|
}
|
|
5778
|
-
console.log(
|
|
6804
|
+
console.log(kleur9.green(`Unset ${key}`));
|
|
5779
6805
|
}
|
|
5780
6806
|
|
|
5781
6807
|
// src/index.ts
|
|
@@ -5784,27 +6810,27 @@ init_config();
|
|
|
5784
6810
|
// src/git-wrapper.ts
|
|
5785
6811
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
5786
6812
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
5787
|
-
import
|
|
5788
|
-
import
|
|
5789
|
-
import
|
|
6813
|
+
import fs9 from "node:fs";
|
|
6814
|
+
import path10 from "node:path";
|
|
6815
|
+
import kleur10 from "kleur";
|
|
5790
6816
|
function pathExists(candidate) {
|
|
5791
6817
|
try {
|
|
5792
|
-
|
|
6818
|
+
fs9.statSync(candidate);
|
|
5793
6819
|
return true;
|
|
5794
6820
|
} catch {
|
|
5795
6821
|
return false;
|
|
5796
6822
|
}
|
|
5797
6823
|
}
|
|
5798
6824
|
function findInstafyGitContext(startDir) {
|
|
5799
|
-
let current =
|
|
5800
|
-
const root =
|
|
6825
|
+
let current = path10.resolve(startDir);
|
|
6826
|
+
const root = path10.parse(current).root;
|
|
5801
6827
|
while (true) {
|
|
5802
|
-
const gitDir =
|
|
6828
|
+
const gitDir = path10.join(current, ".instafy", ".git");
|
|
5803
6829
|
if (pathExists(gitDir)) {
|
|
5804
6830
|
return { workTree: current, gitDir };
|
|
5805
6831
|
}
|
|
5806
6832
|
if (current === root) break;
|
|
5807
|
-
current =
|
|
6833
|
+
current = path10.dirname(current);
|
|
5808
6834
|
}
|
|
5809
6835
|
return null;
|
|
5810
6836
|
}
|
|
@@ -5851,9 +6877,9 @@ function validateGitArgs(userArgs) {
|
|
|
5851
6877
|
}
|
|
5852
6878
|
if (arg === "--git-dir" || arg.startsWith("--git-dir=") || arg === "--work-tree" || arg.startsWith("--work-tree=") || arg === "-C") {
|
|
5853
6879
|
throw new Error(
|
|
5854
|
-
`Unsupported argument "${arg}". Use ${
|
|
6880
|
+
`Unsupported argument "${arg}". Use ${kleur10.cyan(
|
|
5855
6881
|
"cd"
|
|
5856
|
-
)} instead; ${
|
|
6882
|
+
)} instead; ${kleur10.cyan("instafy git")} manages --git-dir/--work-tree automatically.`
|
|
5857
6883
|
);
|
|
5858
6884
|
}
|
|
5859
6885
|
if (arg === "-c" || arg === "--config-env" || arg === "--exec-path" || arg === "--namespace") {
|
|
@@ -5960,7 +6986,7 @@ function collectDirtyPaths(context, options) {
|
|
|
5960
6986
|
return unique;
|
|
5961
6987
|
}
|
|
5962
6988
|
function findEmbeddedGitDirs(context, pathSpecs) {
|
|
5963
|
-
const instafyGitDir =
|
|
6989
|
+
const instafyGitDir = path10.resolve(context.gitDir);
|
|
5964
6990
|
const seen = /* @__PURE__ */ new Set();
|
|
5965
6991
|
for (const rawPath of pathSpecs) {
|
|
5966
6992
|
const normalized = normalizePathSpec(rawPath).replace(/\/+$/, "");
|
|
@@ -5969,11 +6995,11 @@ function findEmbeddedGitDirs(context, pathSpecs) {
|
|
|
5969
6995
|
}
|
|
5970
6996
|
let cursor = normalized;
|
|
5971
6997
|
while (cursor && cursor !== ".") {
|
|
5972
|
-
const candidate =
|
|
5973
|
-
if (pathExists(candidate) &&
|
|
5974
|
-
seen.add(
|
|
6998
|
+
const candidate = path10.join(context.workTree, ...cursor.split("/"), ".git");
|
|
6999
|
+
if (pathExists(candidate) && path10.resolve(candidate) !== instafyGitDir) {
|
|
7000
|
+
seen.add(path10.resolve(candidate));
|
|
5975
7001
|
}
|
|
5976
|
-
const next =
|
|
7002
|
+
const next = path10.posix.dirname(cursor);
|
|
5977
7003
|
if (next === "." || next === cursor) {
|
|
5978
7004
|
break;
|
|
5979
7005
|
}
|
|
@@ -5982,8 +7008,8 @@ function findEmbeddedGitDirs(context, pathSpecs) {
|
|
|
5982
7008
|
}
|
|
5983
7009
|
const candidates = Array.from(seen);
|
|
5984
7010
|
candidates.sort((a, b) => {
|
|
5985
|
-
const depthA = a.split(
|
|
5986
|
-
const depthB = b.split(
|
|
7011
|
+
const depthA = a.split(path10.sep).length;
|
|
7012
|
+
const depthB = b.split(path10.sep).length;
|
|
5987
7013
|
return depthB - depthA;
|
|
5988
7014
|
});
|
|
5989
7015
|
return candidates;
|
|
@@ -5993,16 +7019,16 @@ function hideEmbeddedGitDirs(context, pathSpecs) {
|
|
|
5993
7019
|
if (candidates.length === 0) {
|
|
5994
7020
|
return [];
|
|
5995
7021
|
}
|
|
5996
|
-
const stagingBase =
|
|
5997
|
-
|
|
7022
|
+
const stagingBase = path10.join(context.workTree, ".instafy", "origin-staging", "embedded-git");
|
|
7023
|
+
fs9.mkdirSync(stagingBase, { recursive: true });
|
|
5998
7024
|
const renames = [];
|
|
5999
7025
|
for (const candidate of candidates) {
|
|
6000
|
-
let hidden =
|
|
7026
|
+
let hidden = path10.join(stagingBase, randomUUID2());
|
|
6001
7027
|
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
6002
7028
|
if (!pathExists(hidden)) break;
|
|
6003
|
-
hidden =
|
|
7029
|
+
hidden = path10.join(stagingBase, randomUUID2());
|
|
6004
7030
|
}
|
|
6005
|
-
|
|
7031
|
+
fs9.renameSync(candidate, hidden);
|
|
6006
7032
|
renames.push({ original: candidate, hidden });
|
|
6007
7033
|
}
|
|
6008
7034
|
return renames;
|
|
@@ -6012,7 +7038,7 @@ function restoreEmbeddedGitDirs(renames) {
|
|
|
6012
7038
|
const rename = renames[index];
|
|
6013
7039
|
if (!rename) continue;
|
|
6014
7040
|
try {
|
|
6015
|
-
|
|
7041
|
+
fs9.renameSync(rename.hidden, rename.original);
|
|
6016
7042
|
} catch {
|
|
6017
7043
|
}
|
|
6018
7044
|
}
|
|
@@ -6082,15 +7108,15 @@ function runInstafyGit(userArgs, options) {
|
|
|
6082
7108
|
console.log("instafy git <git-args...>");
|
|
6083
7109
|
console.log("");
|
|
6084
7110
|
console.log(
|
|
6085
|
-
`Runs ${
|
|
7111
|
+
`Runs ${kleur10.cyan("git")} against the Instafy canonical repo at ${kleur10.cyan(
|
|
6086
7112
|
".instafy/.git"
|
|
6087
7113
|
)} (auto-detected by walking up from cwd).`
|
|
6088
7114
|
);
|
|
6089
7115
|
console.log("");
|
|
6090
7116
|
console.log("Example:");
|
|
6091
|
-
console.log(` ${
|
|
6092
|
-
console.log(` ${
|
|
6093
|
-
console.log(` ${
|
|
7117
|
+
console.log(` ${kleur10.cyan("instafy git status")}`);
|
|
7118
|
+
console.log(` ${kleur10.cyan("instafy git add -A")}`);
|
|
7119
|
+
console.log(` ${kleur10.cyan('instafy git commit -am "instafy: checkpoint"')}`);
|
|
6094
7120
|
return 0;
|
|
6095
7121
|
}
|
|
6096
7122
|
const context = findInstafyGitContext(cwd);
|
|
@@ -6100,8 +7126,8 @@ function runInstafyGit(userArgs, options) {
|
|
|
6100
7126
|
"No Instafy canonical git checkout found (expected .instafy/.git).",
|
|
6101
7127
|
"",
|
|
6102
7128
|
"Tips:",
|
|
6103
|
-
`- Run this inside a git-canonical workspace (where ${
|
|
6104
|
-
`- If you're trying to operate on a user repo, use normal ${
|
|
7129
|
+
`- Run this inside a git-canonical workspace (where ${kleur10.cyan(".instafy/.git")} exists).`,
|
|
7130
|
+
`- If you're trying to operate on a user repo, use normal ${kleur10.cyan("git")} (it uses .git).`
|
|
6105
7131
|
].join("\n")
|
|
6106
7132
|
);
|
|
6107
7133
|
}
|
|
@@ -6376,7 +7402,7 @@ async function runInstafyGitSync(args, options) {
|
|
|
6376
7402
|
}
|
|
6377
7403
|
} catch (error) {
|
|
6378
7404
|
const message = error instanceof Error ? error.message : String(error);
|
|
6379
|
-
console.warn(
|
|
7405
|
+
console.warn(kleur10.yellow(`[instafy git sync] ${message}; falling back to environment token.`));
|
|
6380
7406
|
}
|
|
6381
7407
|
}
|
|
6382
7408
|
if (!originToken) {
|
|
@@ -6424,7 +7450,7 @@ async function runInstafyGitSync(args, options) {
|
|
|
6424
7450
|
} catch (error) {
|
|
6425
7451
|
const message = error instanceof Error ? error.message : String(error);
|
|
6426
7452
|
console.warn(
|
|
6427
|
-
|
|
7453
|
+
kleur10.yellow(
|
|
6428
7454
|
`[instafy git sync] failed to recover from invalid origin token: ${message}`
|
|
6429
7455
|
)
|
|
6430
7456
|
);
|
|
@@ -6438,18 +7464,18 @@ async function runInstafyGitSync(args, options) {
|
|
|
6438
7464
|
console.log(JSON.stringify(saveReportJson(report, notSaved2), null, 2));
|
|
6439
7465
|
} else {
|
|
6440
7466
|
for (const sentence of notSaved2) {
|
|
6441
|
-
console.error(
|
|
7467
|
+
console.error(kleur10.red(`${sentence}.`));
|
|
6442
7468
|
}
|
|
6443
7469
|
}
|
|
6444
7470
|
return 1;
|
|
6445
7471
|
}
|
|
6446
7472
|
const suffix = text2.trim() ? `: ${text2.trim()}` : "";
|
|
6447
|
-
console.error(
|
|
7473
|
+
console.error(kleur10.red(`Origin git sync failed (${response.status} ${response.statusText})${suffix}`));
|
|
6448
7474
|
return 1;
|
|
6449
7475
|
}
|
|
6450
7476
|
const rev = report.rev ?? "";
|
|
6451
7477
|
if (!rev) {
|
|
6452
|
-
console.error(
|
|
7478
|
+
console.error(kleur10.red("Origin git sync response missing rev field."));
|
|
6453
7479
|
return 1;
|
|
6454
7480
|
}
|
|
6455
7481
|
const notSaved = notSavedSentences(report, { failedWhole: false });
|
|
@@ -6458,7 +7484,7 @@ async function runInstafyGitSync(args, options) {
|
|
|
6458
7484
|
} else {
|
|
6459
7485
|
console.log(rev);
|
|
6460
7486
|
for (const sentence of notSaved) {
|
|
6461
|
-
console.error(
|
|
7487
|
+
console.error(kleur10.yellow(`${sentence}.`));
|
|
6462
7488
|
}
|
|
6463
7489
|
}
|
|
6464
7490
|
return notSaved.length > 0 ? 1 : 0;
|
|
@@ -6477,21 +7503,21 @@ function parseSaveReport(text2) {
|
|
|
6477
7503
|
payload = {};
|
|
6478
7504
|
}
|
|
6479
7505
|
const conflictedPaths = Array.isArray(payload["conflictedPaths"]) ? payload["conflictedPaths"].flatMap((entry) => {
|
|
6480
|
-
const
|
|
6481
|
-
return
|
|
7506
|
+
const path19 = optionalString(entry);
|
|
7507
|
+
return path19 ? [path19] : [];
|
|
6482
7508
|
}) : [];
|
|
6483
7509
|
const rejectedPaths = Array.isArray(payload["rejectedPaths"]) ? payload["rejectedPaths"].flatMap((entry) => {
|
|
6484
7510
|
if (typeof entry === "string") {
|
|
6485
|
-
const
|
|
6486
|
-
return
|
|
7511
|
+
const path20 = optionalString(entry);
|
|
7512
|
+
return path20 ? [{ path: path20, reason: "", keptSavedVersion: false }] : [];
|
|
6487
7513
|
}
|
|
6488
7514
|
if (!entry || typeof entry !== "object") return [];
|
|
6489
7515
|
const record2 = entry;
|
|
6490
|
-
const
|
|
6491
|
-
if (!
|
|
7516
|
+
const path19 = optionalString(record2["path"]);
|
|
7517
|
+
if (!path19) return [];
|
|
6492
7518
|
return [
|
|
6493
7519
|
{
|
|
6494
|
-
path:
|
|
7520
|
+
path: path19,
|
|
6495
7521
|
reason: optionalString(record2["reason"]) ?? "",
|
|
6496
7522
|
keptSavedVersion: record2["keptSavedVersion"] === true
|
|
6497
7523
|
}
|
|
@@ -6571,11 +7597,554 @@ function saveReportJson(report, notSaved) {
|
|
|
6571
7597
|
};
|
|
6572
7598
|
}
|
|
6573
7599
|
|
|
7600
|
+
// src/bind.ts
|
|
7601
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
7602
|
+
import fs10 from "node:fs";
|
|
7603
|
+
import path11 from "node:path";
|
|
7604
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7605
|
+
var BIN_REL = path11.join("bin", "instafy-bind.mjs");
|
|
7606
|
+
function resolveBindBin(env = process.env) {
|
|
7607
|
+
const fromEnv = env.INSTAFY_BIND_BIN?.trim();
|
|
7608
|
+
if (fromEnv) return fs10.existsSync(fromEnv) ? path11.resolve(fromEnv) : null;
|
|
7609
|
+
const here = path11.dirname(fileURLToPath2(import.meta.url));
|
|
7610
|
+
for (const candidate of [path11.join(here, "bind", BIN_REL), path11.resolve(here, "..", "..", "instafy-bind", BIN_REL)]) {
|
|
7611
|
+
if (fs10.existsSync(candidate)) return candidate;
|
|
7612
|
+
}
|
|
7613
|
+
return null;
|
|
7614
|
+
}
|
|
7615
|
+
function runBind(args) {
|
|
7616
|
+
const bin = resolveBindBin();
|
|
7617
|
+
if (!bin) {
|
|
7618
|
+
process.stderr.write(
|
|
7619
|
+
"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"
|
|
7620
|
+
);
|
|
7621
|
+
return Promise.resolve(1);
|
|
7622
|
+
}
|
|
7623
|
+
return new Promise((resolve) => {
|
|
7624
|
+
const child = spawn4(process.execPath, [bin, ...args], { stdio: "inherit", env: process.env });
|
|
7625
|
+
const forward = (signal) => {
|
|
7626
|
+
if (!child.killed) child.kill(signal);
|
|
7627
|
+
};
|
|
7628
|
+
process.on("SIGINT", forward);
|
|
7629
|
+
process.on("SIGTERM", forward);
|
|
7630
|
+
child.on("error", (error) => {
|
|
7631
|
+
process.stderr.write(`instafy bind: ${error.message}
|
|
7632
|
+
`);
|
|
7633
|
+
resolve(1);
|
|
7634
|
+
});
|
|
7635
|
+
child.on("exit", (code, signal) => {
|
|
7636
|
+
process.off("SIGINT", forward);
|
|
7637
|
+
process.off("SIGTERM", forward);
|
|
7638
|
+
resolve(code ?? (signal ? 130 : 1));
|
|
7639
|
+
});
|
|
7640
|
+
});
|
|
7641
|
+
}
|
|
7642
|
+
|
|
7643
|
+
// src/site-check.ts
|
|
7644
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
7645
|
+
import { once } from "node:events";
|
|
7646
|
+
import fs11 from "node:fs";
|
|
7647
|
+
import http3 from "node:http";
|
|
7648
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
7649
|
+
import os6 from "node:os";
|
|
7650
|
+
import path12 from "node:path";
|
|
7651
|
+
var CHECK_EXIT_OK = 0;
|
|
7652
|
+
var CHECK_EXIT_PROBLEMS = 1;
|
|
7653
|
+
var CHECK_EXIT_NOT_LISTENING = PREVIEW_EXIT_NOT_LISTENING;
|
|
7654
|
+
var CHECK_EXIT_NO_BROWSER = 4;
|
|
7655
|
+
var CHECK_EXIT_USAGE = 64;
|
|
7656
|
+
var CHECK_EXIT_INTERNAL = 70;
|
|
7657
|
+
var MAX_PATH_LENGTH2 = 512;
|
|
7658
|
+
var MAX_LIST = 20;
|
|
7659
|
+
var MAX_TEXT = 300;
|
|
7660
|
+
var CHROMIUM_ARGS = ["--use-angle=swiftshader", "--enable-unsafe-swiftshader", "--ignore-gpu-blocklist", "--disable-dev-shm-usage"];
|
|
7661
|
+
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/;
|
|
7662
|
+
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";
|
|
7663
|
+
var VIEWPORTS = [
|
|
7664
|
+
{ name: "desktop", width: 1280, height: 800, mobile: false },
|
|
7665
|
+
{ name: "phone", width: 390, height: 844, mobile: true }
|
|
7666
|
+
];
|
|
7667
|
+
var TRUSTED_PLAYWRIGHT_PATH_ENV = "INSTAFY_SHARED_BROWSER_PLAYWRIGHT_PATH";
|
|
7668
|
+
var TRUSTED_NODE_MODULES_ROOT_ENV = "INSTAFY_SHARED_BROWSER_TRUSTED_NODE_MODULES_ROOT";
|
|
7669
|
+
function isPlaywrightDir(dir) {
|
|
7670
|
+
try {
|
|
7671
|
+
const pkg2 = JSON.parse(fs11.readFileSync(path12.join(dir, "package.json"), "utf8"));
|
|
7672
|
+
return pkg2.name === "playwright" || pkg2.name === "playwright-core";
|
|
7673
|
+
} catch {
|
|
7674
|
+
return false;
|
|
7675
|
+
}
|
|
7676
|
+
}
|
|
7677
|
+
function playwrightIn(nodeModules) {
|
|
7678
|
+
const found = [];
|
|
7679
|
+
for (const name of ["playwright", "playwright-core"]) {
|
|
7680
|
+
const dir = path12.join(nodeModules, name);
|
|
7681
|
+
if (isPlaywrightDir(dir)) found.push(dir);
|
|
7682
|
+
}
|
|
7683
|
+
try {
|
|
7684
|
+
const store = path12.join(nodeModules, ".pnpm");
|
|
7685
|
+
for (const entry of fs11.readdirSync(store).filter((e) => /^playwright(-core)?@\d/.test(e)).sort().reverse()) {
|
|
7686
|
+
const name = entry.startsWith("playwright-core@") ? "playwright-core" : "playwright";
|
|
7687
|
+
const dir = path12.join(store, entry, "node_modules", name);
|
|
7688
|
+
if (isPlaywrightDir(dir)) found.push(dir);
|
|
7689
|
+
}
|
|
7690
|
+
} catch {
|
|
7691
|
+
}
|
|
7692
|
+
return found;
|
|
7693
|
+
}
|
|
7694
|
+
var npmRootCache;
|
|
7695
|
+
function npmGlobalRoot() {
|
|
7696
|
+
if (npmRootCache !== void 0) return npmRootCache;
|
|
7697
|
+
try {
|
|
7698
|
+
const out = spawnSync5("npm", ["root", "-g"], { cwd: path12.parse(process.cwd()).root, encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] });
|
|
7699
|
+
npmRootCache = out.status === 0 && out.stdout.trim() ? out.stdout.trim() : null;
|
|
7700
|
+
} catch {
|
|
7701
|
+
npmRootCache = null;
|
|
7702
|
+
}
|
|
7703
|
+
return npmRootCache;
|
|
7704
|
+
}
|
|
7705
|
+
function defaultGlobalRoots(env = process.env) {
|
|
7706
|
+
const roots = [];
|
|
7707
|
+
for (const entry of (env.NODE_PATH ?? "").split(path12.delimiter)) if (path12.isAbsolute(entry.trim())) roots.push(entry.trim());
|
|
7708
|
+
const npmRoot = npmGlobalRoot();
|
|
7709
|
+
if (npmRoot && path12.isAbsolute(npmRoot)) roots.push(npmRoot);
|
|
7710
|
+
roots.push("/usr/lib/node_modules", "/usr/local/lib/node_modules", "/opt/homebrew/lib/node_modules");
|
|
7711
|
+
return [...new Set(roots)];
|
|
7712
|
+
}
|
|
7713
|
+
function locateBrowserEngine(o = {}) {
|
|
7714
|
+
const env = o.env ?? process.env;
|
|
7715
|
+
const tried = [];
|
|
7716
|
+
const trustedPath = env[TRUSTED_PLAYWRIGHT_PATH_ENV]?.trim();
|
|
7717
|
+
if (trustedPath) {
|
|
7718
|
+
if (path12.isAbsolute(trustedPath) && isPlaywrightDir(trustedPath)) return { engine: { playwright: trustedPath, source: "trusted" }, tried };
|
|
7719
|
+
tried.push(`$${TRUSTED_PLAYWRIGHT_PATH_ENV} is not an absolute path to a playwright package`);
|
|
7720
|
+
}
|
|
7721
|
+
const trustedRoot = env[TRUSTED_NODE_MODULES_ROOT_ENV]?.trim();
|
|
7722
|
+
if (trustedRoot) {
|
|
7723
|
+
const found = path12.isAbsolute(trustedRoot) ? playwrightIn(trustedRoot) : [];
|
|
7724
|
+
if (found.length) return { engine: { playwright: found[0], source: "trusted" }, tried };
|
|
7725
|
+
tried.push(`no playwright in $${TRUSTED_NODE_MODULES_ROOT_ENV}`);
|
|
7726
|
+
}
|
|
7727
|
+
for (const root of o.globalRoots ?? defaultGlobalRoots(env)) {
|
|
7728
|
+
const found = playwrightIn(root);
|
|
7729
|
+
if (found.length) return { engine: { playwright: found[0], source: "global" }, tried };
|
|
7730
|
+
tried.push(`none in ${root}`);
|
|
7731
|
+
}
|
|
7732
|
+
if (!o.workspace) {
|
|
7733
|
+
tried.push("the workspace was not searched (--workspace-playwright)");
|
|
7734
|
+
return { engine: null, tried };
|
|
7735
|
+
}
|
|
7736
|
+
let dir = path12.resolve(o.cwd ?? process.cwd());
|
|
7737
|
+
for (let i = 0; i < 16; i += 1) {
|
|
7738
|
+
const found = playwrightIn(path12.join(dir, "node_modules"));
|
|
7739
|
+
if (found.length) return { engine: { playwright: found[0], source: "workspace" }, tried };
|
|
7740
|
+
const up = path12.dirname(dir);
|
|
7741
|
+
if (up === dir) break;
|
|
7742
|
+
dir = up;
|
|
7743
|
+
}
|
|
7744
|
+
tried.push(`no node_modules/playwright from ${o.cwd ?? process.cwd()} upwards`);
|
|
7745
|
+
return { engine: null, tried };
|
|
7746
|
+
}
|
|
7747
|
+
function chromiumCandidates(env = process.env, exists = fs11.existsSync) {
|
|
7748
|
+
const out = [];
|
|
7749
|
+
const browsersRoots = [env.PLAYWRIGHT_BROWSERS_PATH, "/ms-playwright", path12.join(os6.homedir(), ".cache", "ms-playwright")].filter((x) => Boolean(x && x !== "0"));
|
|
7750
|
+
for (const root of [...new Set(browsersRoots)]) {
|
|
7751
|
+
let entries = [];
|
|
7752
|
+
try {
|
|
7753
|
+
entries = fs11.readdirSync(root).sort().reverse();
|
|
7754
|
+
} catch {
|
|
7755
|
+
continue;
|
|
7756
|
+
}
|
|
7757
|
+
for (const e of entries.filter((x) => x.startsWith("chromium_headless_shell-"))) {
|
|
7758
|
+
out.push(path12.join(root, e, "chrome-headless-shell-linux64", "chrome-headless-shell"));
|
|
7759
|
+
out.push(path12.join(root, e, "chrome-linux", "headless_shell"));
|
|
7760
|
+
}
|
|
7761
|
+
for (const e of entries.filter((x) => /^chromium-\d/.test(x))) {
|
|
7762
|
+
out.push(path12.join(root, e, "chrome-linux64", "chrome"), path12.join(root, e, "chrome-linux", "chrome"));
|
|
7763
|
+
}
|
|
7764
|
+
}
|
|
7765
|
+
out.push(
|
|
7766
|
+
"/usr/bin/chromium",
|
|
7767
|
+
"/usr/bin/chromium-browser",
|
|
7768
|
+
"/usr/bin/google-chrome",
|
|
7769
|
+
"/usr/bin/google-chrome-stable",
|
|
7770
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
7771
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium"
|
|
7772
|
+
);
|
|
7773
|
+
return out.filter((p) => exists(p));
|
|
7774
|
+
}
|
|
7775
|
+
function loadPlaywright(engine) {
|
|
7776
|
+
const req = createRequire3(path12.join(engine.playwright, "package.json"));
|
|
7777
|
+
const mod = req(engine.playwright);
|
|
7778
|
+
const pw = mod?.chromium ? mod : mod?.default;
|
|
7779
|
+
if (!pw?.chromium) throw new Error(`${engine.playwright} has no chromium export`);
|
|
7780
|
+
return pw;
|
|
7781
|
+
}
|
|
7782
|
+
async function launchChromium(pw, env = process.env) {
|
|
7783
|
+
const errors = [];
|
|
7784
|
+
try {
|
|
7785
|
+
const browser = await pw.chromium.launch({ headless: true, args: CHROMIUM_ARGS });
|
|
7786
|
+
return { browser, executable: "playwright" };
|
|
7787
|
+
} catch (error) {
|
|
7788
|
+
errors.push(firstLine(error));
|
|
7789
|
+
}
|
|
7790
|
+
for (const executablePath of chromiumCandidates(env)) {
|
|
7791
|
+
try {
|
|
7792
|
+
const browser = await pw.chromium.launch({ headless: true, executablePath, args: CHROMIUM_ARGS });
|
|
7793
|
+
return { browser, executable: executablePath };
|
|
7794
|
+
} catch (error) {
|
|
7795
|
+
errors.push(`${executablePath}: ${firstLine(error)}`);
|
|
7796
|
+
}
|
|
7797
|
+
}
|
|
7798
|
+
throw new NoBrowserError(`No Chromium would start (${errors.join("; ") || "none found"})`);
|
|
7799
|
+
}
|
|
7800
|
+
var NoBrowserError = class extends Error {
|
|
7801
|
+
};
|
|
7802
|
+
function firstLine(error) {
|
|
7803
|
+
return String(error?.message ?? error).split("\n")[0].slice(0, MAX_TEXT);
|
|
7804
|
+
}
|
|
7805
|
+
function parseCheckPath(raw) {
|
|
7806
|
+
const value = raw?.trim() || "/";
|
|
7807
|
+
if (!value.startsWith("/") || value.startsWith("//") || value.length > MAX_PATH_LENGTH2 || /[\s\\]/.test(value)) {
|
|
7808
|
+
throw new PreviewCommandError("Path must start with / and contain no spaces or backslashes (a ?query and #hash are fine).", CHECK_EXIT_USAGE);
|
|
7809
|
+
}
|
|
7810
|
+
return value;
|
|
7811
|
+
}
|
|
7812
|
+
function siteUrlFor(siteOrigin, sitePath) {
|
|
7813
|
+
const url = new URL(sitePath, siteOrigin);
|
|
7814
|
+
if (url.origin !== siteOrigin) throw new PreviewCommandError("Path must stay on the site's own origin.", CHECK_EXIT_USAGE);
|
|
7815
|
+
return url.href;
|
|
7816
|
+
}
|
|
7817
|
+
function isSiteUrl(url, siteOrigin) {
|
|
7818
|
+
try {
|
|
7819
|
+
return new URL(url).origin === siteOrigin;
|
|
7820
|
+
} catch {
|
|
7821
|
+
return false;
|
|
7822
|
+
}
|
|
7823
|
+
}
|
|
7824
|
+
function makeDefaultOutDir(port) {
|
|
7825
|
+
return fs11.mkdtempSync(path12.join(os6.tmpdir(), `instafy-preview-check-${port}-`));
|
|
7826
|
+
}
|
|
7827
|
+
function escapeAttribute(value) {
|
|
7828
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
7829
|
+
}
|
|
7830
|
+
function framePage(siteUrl) {
|
|
7831
|
+
return `<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7832
|
+
<title>instafy preview check</title>
|
|
7833
|
+
<style>html,body{margin:0;height:100%;background:#fff}iframe{display:block;border:0;width:100%;height:100%}</style>
|
|
7834
|
+
<iframe id="site" src="${escapeAttribute(siteUrl)}" allow="fullscreen; clipboard-write"></iframe>`;
|
|
7835
|
+
}
|
|
7836
|
+
async function startFrameHost(siteUrl) {
|
|
7837
|
+
const body = framePage(siteUrl);
|
|
7838
|
+
const server = http3.createServer((req, res) => {
|
|
7839
|
+
if (req.url !== "/") {
|
|
7840
|
+
res.writeHead(404, { "content-type": "text/plain" });
|
|
7841
|
+
res.end();
|
|
7842
|
+
return;
|
|
7843
|
+
}
|
|
7844
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
7845
|
+
res.end(body);
|
|
7846
|
+
});
|
|
7847
|
+
server.listen(0, "127.0.0.1");
|
|
7848
|
+
await once(server, "listening");
|
|
7849
|
+
const { port } = server.address();
|
|
7850
|
+
return { url: `http://localhost:${port}/`, close: () => server.close() };
|
|
7851
|
+
}
|
|
7852
|
+
function blankVerdict(input3) {
|
|
7853
|
+
if (input3.framed === "refused") {
|
|
7854
|
+
return { blank: true, reason: "the site refuses to be framed (X-Frame-Options or CSP frame-ancestors), so the frame stays empty" };
|
|
7855
|
+
}
|
|
7856
|
+
if (input3.framed === "missing") return { blank: true, reason: "the page did not load" };
|
|
7857
|
+
if (input3.dominant !== null && input3.dominant >= 0.995 && input3.text === 0) return { blank: true, reason: "the screen is a single colour" };
|
|
7858
|
+
if (input3.text === 0 && input3.media === 0 && input3.elements < 3) return { blank: true, reason: "the page has no text, images or canvas" };
|
|
7859
|
+
return { blank: false, reason: null };
|
|
7860
|
+
}
|
|
7861
|
+
async function measureDominantShare(measure) {
|
|
7862
|
+
try {
|
|
7863
|
+
const share = await measure();
|
|
7864
|
+
return Number.isFinite(share) && share >= 0 && share <= 1 ? share : null;
|
|
7865
|
+
} catch {
|
|
7866
|
+
return null;
|
|
7867
|
+
}
|
|
7868
|
+
}
|
|
7869
|
+
function incompleteBlankCheck(dominant, text2) {
|
|
7870
|
+
if (dominant !== null) return null;
|
|
7871
|
+
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" };
|
|
7872
|
+
}
|
|
7873
|
+
var FRAME_SNAPSHOT = () => {
|
|
7874
|
+
const visible = (el) => {
|
|
7875
|
+
const r = el.getBoundingClientRect();
|
|
7876
|
+
if (r.width < 2 || r.height < 2 || r.bottom < 0 || r.right < 0 || r.top > innerHeight || r.left > innerWidth) return false;
|
|
7877
|
+
const s = getComputedStyle(el);
|
|
7878
|
+
return s.visibility !== "hidden" && s.display !== "none" && Number(s.opacity) > 0.05;
|
|
7879
|
+
};
|
|
7880
|
+
const body = document.body;
|
|
7881
|
+
const text2 = (body?.innerText ?? "").replace(/\s+/g, " ").trim().length;
|
|
7882
|
+
let media = 0;
|
|
7883
|
+
let elements = 0;
|
|
7884
|
+
for (const el of Array.from(body?.querySelectorAll("*") ?? [])) {
|
|
7885
|
+
if (/^(SCRIPT|STYLE|NOSCRIPT|TEMPLATE|LINK|META)$/.test(el.tagName)) continue;
|
|
7886
|
+
if (!visible(el)) continue;
|
|
7887
|
+
elements += 1;
|
|
7888
|
+
if (/^(CANVAS|IMG|SVG|VIDEO|IFRAME|PICTURE|OBJECT|EMBED)$/i.test(el.tagName)) media += 1;
|
|
7889
|
+
}
|
|
7890
|
+
return { text: text2, media, elements, title: document.title };
|
|
7891
|
+
};
|
|
7892
|
+
var DOMINANT_COLOUR = async (b64) => {
|
|
7893
|
+
const img = new Image();
|
|
7894
|
+
img.src = `data:image/png;base64,${b64}`;
|
|
7895
|
+
await img.decode();
|
|
7896
|
+
const w = 160;
|
|
7897
|
+
const h = Math.max(1, Math.round(img.height / img.width * w));
|
|
7898
|
+
const c = document.createElement("canvas");
|
|
7899
|
+
c.width = w;
|
|
7900
|
+
c.height = h;
|
|
7901
|
+
const g = c.getContext("2d");
|
|
7902
|
+
g.imageSmoothingEnabled = false;
|
|
7903
|
+
g.drawImage(img, 0, 0, w, h);
|
|
7904
|
+
const data = g.getImageData(0, 0, w, h).data;
|
|
7905
|
+
const counts = /* @__PURE__ */ new Map();
|
|
7906
|
+
let max = 0;
|
|
7907
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
7908
|
+
const key = data[i] >> 3 << 10 | data[i + 1] >> 3 << 5 | data[i + 2] >> 3;
|
|
7909
|
+
const n = (counts.get(key) ?? 0) + 1;
|
|
7910
|
+
counts.set(key, n);
|
|
7911
|
+
if (n > max) max = n;
|
|
7912
|
+
}
|
|
7913
|
+
return max / (w * h);
|
|
7914
|
+
};
|
|
7915
|
+
async function checkViewport(browser, target, vp, siteOrigin, outDir, timeoutMs, waitMs) {
|
|
7916
|
+
const errors = [];
|
|
7917
|
+
const warnings = [];
|
|
7918
|
+
const context = await browser.newContext({
|
|
7919
|
+
viewport: { width: vp.width, height: vp.height },
|
|
7920
|
+
deviceScaleFactor: vp.mobile ? 2 : 1,
|
|
7921
|
+
isMobile: vp.mobile,
|
|
7922
|
+
hasTouch: vp.mobile,
|
|
7923
|
+
...vp.mobile ? { userAgent: PHONE_UA } : {},
|
|
7924
|
+
reducedMotion: "reduce",
|
|
7925
|
+
serviceWorkers: "block",
|
|
7926
|
+
acceptDownloads: false
|
|
7927
|
+
});
|
|
7928
|
+
const t0 = Date.now();
|
|
7929
|
+
let status = null;
|
|
7930
|
+
try {
|
|
7931
|
+
const page = await context.newPage();
|
|
7932
|
+
const ours = (u) => isSiteUrl(u, siteOrigin);
|
|
7933
|
+
const shortUrl = (u) => (ours(u) ? u.slice(siteOrigin.length) || "/" : u).slice(0, 200);
|
|
7934
|
+
page.on("console", (m) => {
|
|
7935
|
+
const text2 = String(m.text());
|
|
7936
|
+
if (/^Failed to load resource/.test(text2)) return;
|
|
7937
|
+
if (SOFTWARE_GL_NOISE.test(text2)) return;
|
|
7938
|
+
if (/^Refused to display .* in a frame/.test(text2)) return;
|
|
7939
|
+
if (m.type() === "error") errors.push(`console.error: ${text2.slice(0, MAX_TEXT)}`);
|
|
7940
|
+
});
|
|
7941
|
+
page.on("pageerror", (e) => errors.push(`uncaught: ${String(e?.message ?? e).split("\n")[0].slice(0, MAX_TEXT)}`));
|
|
7942
|
+
let refused = false;
|
|
7943
|
+
page.on("requestfailed", (r) => {
|
|
7944
|
+
const u = String(r.url());
|
|
7945
|
+
if (/\/favicon\.ico(\?|$)/.test(u)) return;
|
|
7946
|
+
const why = r.failure()?.errorText ?? "failed";
|
|
7947
|
+
if (target.framed && ours(u) && r.isNavigationRequest() && /ERR_BLOCKED_BY_RESPONSE/.test(why)) {
|
|
7948
|
+
refused = true;
|
|
7949
|
+
return;
|
|
7950
|
+
}
|
|
7951
|
+
if (/ERR_ABORTED/.test(why)) return;
|
|
7952
|
+
(ours(u) ? errors : warnings).push(`request failed: ${shortUrl(u)} (${why})`);
|
|
7953
|
+
});
|
|
7954
|
+
page.on("response", (r) => {
|
|
7955
|
+
const u = String(r.url());
|
|
7956
|
+
const s = Number(r.status());
|
|
7957
|
+
if (r.request().isNavigationRequest() && ours(u) && status === null) status = s;
|
|
7958
|
+
if (s < 400 || /\/favicon\.ico(\?|$)/.test(u)) return;
|
|
7959
|
+
(ours(u) ? errors : warnings).push(`HTTP ${s}: ${shortUrl(u)}`);
|
|
7960
|
+
});
|
|
7961
|
+
await page.goto(target.pageUrl, { waitUntil: "load", timeout: timeoutMs });
|
|
7962
|
+
await page.waitForLoadState("networkidle", { timeout: Math.min(8e3, timeoutMs) }).catch(() => {
|
|
7963
|
+
warnings.push("the network was still busy after 8 s (screenshot taken anyway)");
|
|
7964
|
+
});
|
|
7965
|
+
const frame = target.framed ? page.frames().find((f) => f !== page.mainFrame() && isSiteUrl(String(f.url()), siteOrigin)) : page.mainFrame();
|
|
7966
|
+
let framed = "ok";
|
|
7967
|
+
let snap = { text: 0, media: 0, elements: 0, title: "" };
|
|
7968
|
+
if (refused) framed = "refused";
|
|
7969
|
+
else if (!frame) framed = "missing";
|
|
7970
|
+
else {
|
|
7971
|
+
await frame.evaluate(() => document.fonts?.ready).catch(() => {
|
|
7972
|
+
});
|
|
7973
|
+
await page.waitForTimeout(waitMs);
|
|
7974
|
+
snap = await frame.evaluate(FRAME_SNAPSHOT).catch(() => snap);
|
|
7975
|
+
}
|
|
7976
|
+
if (status !== null && status >= 400) framed = "missing";
|
|
7977
|
+
const file = path12.join(outDir, `${vp.name}.png`);
|
|
7978
|
+
const png = await page.screenshot({ path: file });
|
|
7979
|
+
const dominant = await measureDominantShare(async () => {
|
|
7980
|
+
const helper = await context.newPage();
|
|
7981
|
+
try {
|
|
7982
|
+
return await helper.evaluate(DOMINANT_COLOUR, png.toString("base64"));
|
|
7983
|
+
} finally {
|
|
7984
|
+
await helper.close().catch(() => {
|
|
7985
|
+
});
|
|
7986
|
+
}
|
|
7987
|
+
});
|
|
7988
|
+
const blank = blankVerdict({ dominant, text: snap.text, media: snap.media, elements: snap.elements, framed });
|
|
7989
|
+
if (blank.blank) errors.push(`blank page: ${blank.reason}`);
|
|
7990
|
+
else {
|
|
7991
|
+
const incomplete = incompleteBlankCheck(dominant, snap.text);
|
|
7992
|
+
if (incomplete) (incomplete.error ? errors : warnings).push(incomplete.message);
|
|
7993
|
+
}
|
|
7994
|
+
return {
|
|
7995
|
+
name: vp.name,
|
|
7996
|
+
width: vp.width,
|
|
7997
|
+
height: vp.height,
|
|
7998
|
+
screenshot: file,
|
|
7999
|
+
title: snap.title.slice(0, 200),
|
|
8000
|
+
status,
|
|
8001
|
+
ms: Date.now() - t0,
|
|
8002
|
+
blank,
|
|
8003
|
+
errors: [...new Set(errors)].slice(0, MAX_LIST),
|
|
8004
|
+
warnings: [...new Set(warnings)].slice(0, MAX_LIST)
|
|
8005
|
+
};
|
|
8006
|
+
} catch (error) {
|
|
8007
|
+
errors.push(`the page did not load: ${firstLine(error)}`);
|
|
8008
|
+
return {
|
|
8009
|
+
name: vp.name,
|
|
8010
|
+
width: vp.width,
|
|
8011
|
+
height: vp.height,
|
|
8012
|
+
screenshot: null,
|
|
8013
|
+
title: "",
|
|
8014
|
+
status,
|
|
8015
|
+
ms: Date.now() - t0,
|
|
8016
|
+
blank: { blank: true, reason: "the page did not load" },
|
|
8017
|
+
errors: [...new Set(errors)].slice(0, MAX_LIST),
|
|
8018
|
+
warnings: [...new Set(warnings)].slice(0, MAX_LIST)
|
|
8019
|
+
};
|
|
8020
|
+
} finally {
|
|
8021
|
+
await context.close().catch(() => {
|
|
8022
|
+
});
|
|
8023
|
+
}
|
|
8024
|
+
}
|
|
8025
|
+
async function runSiteCheck(opts) {
|
|
8026
|
+
const env = opts.env ?? process.env;
|
|
8027
|
+
const port = parsePreviewPort(opts.port, env);
|
|
8028
|
+
const sitePath = parseCheckPath(opts.path);
|
|
8029
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
8030
|
+
const viewports = VIEWPORTS.filter((v) => !opts.phone || v.name === "phone");
|
|
8031
|
+
let outDir = null;
|
|
8032
|
+
if (opts.outDir !== void 0) {
|
|
8033
|
+
outDir = path12.resolve(opts.outDir);
|
|
8034
|
+
fs11.mkdirSync(outDir, { recursive: true });
|
|
8035
|
+
for (const v of VIEWPORTS) fs11.rmSync(path12.join(outDir, `${v.name}.png`), { force: true });
|
|
8036
|
+
}
|
|
8037
|
+
const probePath = sitePath.replace(/[?#].*$/, "") || "/";
|
|
8038
|
+
const probe = await probeLocalSite(port, probePath, timeoutMs);
|
|
8039
|
+
if (!probe.host) {
|
|
8040
|
+
return {
|
|
8041
|
+
code: CHECK_EXIT_NOT_LISTENING,
|
|
8042
|
+
report: null,
|
|
8043
|
+
message: noAnswerMessage(port, probe, `port ${port} at 127.0.0.1 or [::1]`, "check again")
|
|
8044
|
+
};
|
|
8045
|
+
}
|
|
8046
|
+
const siteOrigin = `http://${probe.host}:${port}`;
|
|
8047
|
+
const url = siteUrlFor(siteOrigin, sitePath);
|
|
8048
|
+
const { engine, tried } = locateBrowserEngine({ env, cwd: opts.cwd, globalRoots: opts.globalRoots, workspace: opts.workspacePlaywright });
|
|
8049
|
+
if (!engine) {
|
|
8050
|
+
return { code: CHECK_EXIT_NO_BROWSER, report: null, message: `SKIPPED, nothing was checked: Playwright not found (${tried.join("; ")}).` };
|
|
8051
|
+
}
|
|
8052
|
+
let launched;
|
|
8053
|
+
try {
|
|
8054
|
+
launched = await launchChromium(loadPlaywright(engine), env);
|
|
8055
|
+
} catch (error) {
|
|
8056
|
+
return { code: CHECK_EXIT_NO_BROWSER, report: null, message: `SKIPPED, nothing was checked: ${firstLine(error)}` };
|
|
8057
|
+
}
|
|
8058
|
+
const reports = [];
|
|
8059
|
+
let host = null;
|
|
8060
|
+
try {
|
|
8061
|
+
const shots = outDir ?? makeDefaultOutDir(port);
|
|
8062
|
+
host = opts.framed ? await startFrameHost(url) : null;
|
|
8063
|
+
const target = { pageUrl: host?.url ?? url, framed: Boolean(opts.framed) };
|
|
8064
|
+
for (const vp of viewports) {
|
|
8065
|
+
reports.push(await checkViewport(launched.browser, target, vp, siteOrigin, shots, timeoutMs, opts.waitMs ?? 600));
|
|
8066
|
+
}
|
|
8067
|
+
} finally {
|
|
8068
|
+
await launched.browser.close().catch(() => {
|
|
8069
|
+
});
|
|
8070
|
+
host?.close();
|
|
8071
|
+
}
|
|
8072
|
+
const problems = reports.reduce((n, r) => n + r.errors.length, 0);
|
|
8073
|
+
const report = {
|
|
8074
|
+
ok: problems === 0,
|
|
8075
|
+
url,
|
|
8076
|
+
title: reports.find((r) => r.title)?.title ?? "",
|
|
8077
|
+
framed: Boolean(opts.framed),
|
|
8078
|
+
engine: { playwright: engine.playwright, chromium: launched.executable },
|
|
8079
|
+
viewports: reports,
|
|
8080
|
+
problems
|
|
8081
|
+
};
|
|
8082
|
+
return { code: problems ? CHECK_EXIT_PROBLEMS : CHECK_EXIT_OK, report };
|
|
8083
|
+
}
|
|
8084
|
+
function formatSiteCheck(report) {
|
|
8085
|
+
const lines = [];
|
|
8086
|
+
lines.push(`${report.ok ? "\u2713" : "\u2717"} ${report.url}${report.framed ? " (framed)" : ""} \xB7 "${report.title || "(no title)"}"`);
|
|
8087
|
+
for (const r of report.viewports) {
|
|
8088
|
+
const timing = `${(r.ms / 1e3).toFixed(1)} s${r.status ? `, HTTP ${r.status}` : ""}`;
|
|
8089
|
+
lines.push(`${r.errors.length ? "\u2717" : "\u2713"} ${r.name} ${r.width}\xD7${r.height}: ${r.screenshot ?? "no screenshot"} (${timing})`);
|
|
8090
|
+
for (const e of r.errors) lines.push(` error: ${e}`);
|
|
8091
|
+
for (const w of r.warnings.slice(0, 8)) lines.push(` warn: ${w}`);
|
|
8092
|
+
}
|
|
8093
|
+
lines.push(
|
|
8094
|
+
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."
|
|
8095
|
+
);
|
|
8096
|
+
return lines.join("\n");
|
|
8097
|
+
}
|
|
8098
|
+
async function runPreviewCheckCli(opts, io = { out: console.log, err: console.error }) {
|
|
8099
|
+
const env = io.env ?? process.env;
|
|
8100
|
+
const num = (raw, name, min, max) => {
|
|
8101
|
+
if (raw === void 0) return void 0;
|
|
8102
|
+
const n = Number(raw);
|
|
8103
|
+
if (!Number.isInteger(n) || n < min || n > max) throw new PreviewCommandError(`${name} must be a whole number from ${min} to ${max}.`, CHECK_EXIT_USAGE);
|
|
8104
|
+
return n;
|
|
8105
|
+
};
|
|
8106
|
+
try {
|
|
8107
|
+
if (opts.port === void 0) throw new PreviewCommandError("--port <port> is required.", CHECK_EXIT_USAGE);
|
|
8108
|
+
const result = await runSiteCheck({
|
|
8109
|
+
port: parsePreviewPort(opts.port, env),
|
|
8110
|
+
path: opts.path,
|
|
8111
|
+
phone: opts.phone,
|
|
8112
|
+
framed: opts.framed,
|
|
8113
|
+
workspacePlaywright: opts.workspacePlaywright,
|
|
8114
|
+
outDir: opts.out,
|
|
8115
|
+
timeoutMs: num(opts.timeout, "--timeout", 1e3, 12e4),
|
|
8116
|
+
waitMs: num(opts.wait, "--wait", 0, 3e4),
|
|
8117
|
+
env
|
|
8118
|
+
});
|
|
8119
|
+
if (!result.report) {
|
|
8120
|
+
if (opts.json) io.out(JSON.stringify({ ok: false, exitCode: result.code, error: result.message, skipped: result.code === CHECK_EXIT_NO_BROWSER }));
|
|
8121
|
+
else io.err(result.message ?? "check failed");
|
|
8122
|
+
return result.code;
|
|
8123
|
+
}
|
|
8124
|
+
io.out(opts.json ? JSON.stringify(result.report) : formatSiteCheck(result.report));
|
|
8125
|
+
return result.code;
|
|
8126
|
+
} catch (error) {
|
|
8127
|
+
const known = error instanceof PreviewCommandError;
|
|
8128
|
+
const code = known ? error.exitCode : CHECK_EXIT_INTERNAL;
|
|
8129
|
+
const message = known ? firstLine(error) : `The check itself failed, so nothing was checked: ${firstLine(error)}`;
|
|
8130
|
+
if (opts.json) io.out(JSON.stringify({ ok: false, exitCode: code, error: message, skipped: !known }));
|
|
8131
|
+
else io.err(message);
|
|
8132
|
+
return code;
|
|
8133
|
+
}
|
|
8134
|
+
}
|
|
8135
|
+
function registerPreviewCheckCommand(preview) {
|
|
8136
|
+
return preview.command("check").description(
|
|
8137
|
+
"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)"
|
|
8138
|
+
).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) => {
|
|
8139
|
+
process.exitCode = await runPreviewCheckCli(opts);
|
|
8140
|
+
});
|
|
8141
|
+
}
|
|
8142
|
+
|
|
6574
8143
|
// src/api.ts
|
|
6575
8144
|
init_config();
|
|
6576
8145
|
init_errors();
|
|
6577
8146
|
init_controller_fetch();
|
|
6578
|
-
import
|
|
8147
|
+
import fs12 from "node:fs";
|
|
6579
8148
|
function normalizeUrl5(raw) {
|
|
6580
8149
|
const value = (raw ?? "").trim();
|
|
6581
8150
|
if (!value) return "http://127.0.0.1:8788";
|
|
@@ -6652,7 +8221,7 @@ function parseHeader(raw) {
|
|
|
6652
8221
|
}
|
|
6653
8222
|
function parseJsonBody(options) {
|
|
6654
8223
|
if (options.jsonFile) {
|
|
6655
|
-
const raw =
|
|
8224
|
+
const raw = fs12.readFileSync(options.jsonFile, "utf8");
|
|
6656
8225
|
const parsed = JSON.parse(raw);
|
|
6657
8226
|
return JSON.stringify(parsed);
|
|
6658
8227
|
}
|
|
@@ -6796,7 +8365,7 @@ async function requestControllerApi(options) {
|
|
|
6796
8365
|
|
|
6797
8366
|
// src/conversations.ts
|
|
6798
8367
|
init_project_manifest();
|
|
6799
|
-
import
|
|
8368
|
+
import kleur11 from "kleur";
|
|
6800
8369
|
var DEFAULT_CONVERSATION_LIMIT = 50;
|
|
6801
8370
|
var DEFAULT_MESSAGE_LIMIT = 80;
|
|
6802
8371
|
var MAX_LIMIT = 200;
|
|
@@ -7103,7 +8672,7 @@ async function buildSearchMatches(conversations, options) {
|
|
|
7103
8672
|
}));
|
|
7104
8673
|
}
|
|
7105
8674
|
function formatConversationMatch(match) {
|
|
7106
|
-
const lines = [`- ${
|
|
8675
|
+
const lines = [`- ${kleur11.green(match.title)} (${match.id})`];
|
|
7107
8676
|
const details = [];
|
|
7108
8677
|
if (match.matchedIn.length > 0) {
|
|
7109
8678
|
details.push(`match: ${match.matchedIn.join(", ")}`);
|
|
@@ -7115,7 +8684,7 @@ function formatConversationMatch(match) {
|
|
|
7115
8684
|
lines.push(` ${details.join(" \xB7 ")}`);
|
|
7116
8685
|
}
|
|
7117
8686
|
if (match.preview) {
|
|
7118
|
-
lines.push(` ${
|
|
8687
|
+
lines.push(` ${kleur11.gray(summarizeContent(match.preview))}`);
|
|
7119
8688
|
}
|
|
7120
8689
|
return lines.join("\n");
|
|
7121
8690
|
}
|
|
@@ -7174,16 +8743,16 @@ async function createConversation(options) {
|
|
|
7174
8743
|
console.log(JSON.stringify(payload, null, 2));
|
|
7175
8744
|
return;
|
|
7176
8745
|
}
|
|
7177
|
-
console.log(
|
|
7178
|
-
console.log(`${
|
|
8746
|
+
console.log(kleur11.green("Created conversation"));
|
|
8747
|
+
console.log(`${kleur11.gray("ID:")} ${conversationId}`);
|
|
7179
8748
|
if (title) {
|
|
7180
|
-
console.log(`${
|
|
8749
|
+
console.log(`${kleur11.gray("Title:")} ${title}`);
|
|
7181
8750
|
}
|
|
7182
8751
|
if (parentConversationId) {
|
|
7183
|
-
console.log(`${
|
|
8752
|
+
console.log(`${kleur11.gray("Parent:")} ${parentConversationId}`);
|
|
7184
8753
|
}
|
|
7185
8754
|
if (threadKind) {
|
|
7186
|
-
console.log(`${
|
|
8755
|
+
console.log(`${kleur11.gray("Thread kind:")} ${threadKind}`);
|
|
7187
8756
|
}
|
|
7188
8757
|
}
|
|
7189
8758
|
async function listConversations(options) {
|
|
@@ -7210,10 +8779,10 @@ async function listConversations(options) {
|
|
|
7210
8779
|
return;
|
|
7211
8780
|
}
|
|
7212
8781
|
if (items.length === 0) {
|
|
7213
|
-
console.log(
|
|
8782
|
+
console.log(kleur11.yellow("No conversations found for this space."));
|
|
7214
8783
|
return;
|
|
7215
8784
|
}
|
|
7216
|
-
console.log(
|
|
8785
|
+
console.log(kleur11.green(`Conversations (${items.length})`));
|
|
7217
8786
|
for (const item of items) {
|
|
7218
8787
|
console.log(formatConversationMatch({ ...item, score: 0, matchedIn: [] }));
|
|
7219
8788
|
}
|
|
@@ -7235,10 +8804,10 @@ async function searchConversations(options) {
|
|
|
7235
8804
|
return matches;
|
|
7236
8805
|
}
|
|
7237
8806
|
if (matches.length === 0) {
|
|
7238
|
-
console.log(
|
|
8807
|
+
console.log(kleur11.yellow(`No conversations matched "${options.query}".`));
|
|
7239
8808
|
return matches;
|
|
7240
8809
|
}
|
|
7241
|
-
console.log(
|
|
8810
|
+
console.log(kleur11.green(`Matches for "${options.query}"`));
|
|
7242
8811
|
for (const match of matches.slice(0, Math.min(limit, matches.length))) {
|
|
7243
8812
|
console.log(formatConversationMatch(match));
|
|
7244
8813
|
}
|
|
@@ -7356,27 +8925,27 @@ async function showConversation(options) {
|
|
|
7356
8925
|
);
|
|
7357
8926
|
return;
|
|
7358
8927
|
}
|
|
7359
|
-
console.log(
|
|
7360
|
-
console.log(`${
|
|
8928
|
+
console.log(kleur11.green(selected.title));
|
|
8929
|
+
console.log(`${kleur11.gray("ID:")} ${selected.id}`);
|
|
7361
8930
|
if (selected.updatedAt) {
|
|
7362
|
-
console.log(`${
|
|
8931
|
+
console.log(`${kleur11.gray("Updated:")} ${selected.updatedAt}`);
|
|
7363
8932
|
}
|
|
7364
8933
|
if (selected.preview) {
|
|
7365
|
-
console.log(`${
|
|
8934
|
+
console.log(`${kleur11.gray("Preview:")} ${summarizeContent(selected.preview, 240)}`);
|
|
7366
8935
|
}
|
|
7367
8936
|
console.log("");
|
|
7368
8937
|
if (transcript?.hasMore) {
|
|
7369
|
-
console.log(
|
|
8938
|
+
console.log(kleur11.yellow(`Older rows remain; continue with --transcript --cursor ${transcript.nextCursor}.`));
|
|
7370
8939
|
}
|
|
7371
8940
|
if (messages.length === 0) {
|
|
7372
|
-
console.log(
|
|
8941
|
+
console.log(kleur11.yellow(transcript ? "No transcript messages in the scanned rows." : "No messages found in this conversation."));
|
|
7373
8942
|
return;
|
|
7374
8943
|
}
|
|
7375
8944
|
for (const message of messages.slice().reverse()) {
|
|
7376
8945
|
const role = (message.role ?? "unknown").trim().toLowerCase();
|
|
7377
8946
|
const createdAt = trimOrNull(message.createdAt) ?? trimOrNull(message.created_at);
|
|
7378
8947
|
const header = createdAt ? `${role} \xB7 ${createdAt}` : role;
|
|
7379
|
-
console.log(
|
|
8948
|
+
console.log(kleur11.cyan(`[${header}${transcript ? ` \xB7 ${message.id}` : ""}]`));
|
|
7380
8949
|
console.log(transcript ? message.content : summarizeContent(message.content ?? "", 4e3));
|
|
7381
8950
|
console.log("");
|
|
7382
8951
|
}
|
|
@@ -7473,9 +9042,9 @@ async function historyConversations(options) {
|
|
|
7473
9042
|
|
|
7474
9043
|
// src/hardware-bindings.ts
|
|
7475
9044
|
init_project_manifest();
|
|
7476
|
-
import
|
|
7477
|
-
import
|
|
7478
|
-
import
|
|
9045
|
+
import fs13 from "node:fs";
|
|
9046
|
+
import path13 from "node:path";
|
|
9047
|
+
import kleur12 from "kleur";
|
|
7479
9048
|
var SERIAL_HARDWARE_PROVIDER_ID = "hardware.serial";
|
|
7480
9049
|
var DEFAULT_SERIAL_CAPABILITIES = [
|
|
7481
9050
|
"hardware_serial_list",
|
|
@@ -7485,7 +9054,7 @@ var SUPPORTED_HARDWARE_CAPABILITIES = new Set(
|
|
|
7485
9054
|
DEFAULT_SERIAL_CAPABILITIES
|
|
7486
9055
|
);
|
|
7487
9056
|
function resolveHardwareBindingContext(startDir) {
|
|
7488
|
-
const lookup = findProjectManifest(
|
|
9057
|
+
const lookup = findProjectManifest(path13.resolve(startDir));
|
|
7489
9058
|
if (!lookup.manifest || !lookup.path) {
|
|
7490
9059
|
throw new Error(
|
|
7491
9060
|
"Linked space manifest required. Run `instafy space init` in this folder or pass --path <dir>."
|
|
@@ -7495,17 +9064,17 @@ function resolveHardwareBindingContext(startDir) {
|
|
|
7495
9064
|
if (!projectId2) {
|
|
7496
9065
|
throw new Error("Linked space manifest is missing a spaceId.");
|
|
7497
9066
|
}
|
|
7498
|
-
const
|
|
9067
|
+
const instafyDir2 = path13.dirname(lookup.path);
|
|
7499
9068
|
return {
|
|
7500
|
-
bindingsPath:
|
|
9069
|
+
bindingsPath: path13.join(instafyDir2, "hardware-bindings.json"),
|
|
7501
9070
|
projectId: projectId2
|
|
7502
9071
|
};
|
|
7503
9072
|
}
|
|
7504
9073
|
function readHardwareBindingStore(bindingsPath) {
|
|
7505
|
-
if (!
|
|
9074
|
+
if (!fs13.existsSync(bindingsPath)) {
|
|
7506
9075
|
return { version: 1, bindings: {} };
|
|
7507
9076
|
}
|
|
7508
|
-
const raw = JSON.parse(
|
|
9077
|
+
const raw = JSON.parse(fs13.readFileSync(bindingsPath, "utf8"));
|
|
7509
9078
|
const bindings = raw && typeof raw.bindings === "object" && raw.bindings !== null ? raw.bindings : {};
|
|
7510
9079
|
return {
|
|
7511
9080
|
version: 1,
|
|
@@ -7513,8 +9082,8 @@ function readHardwareBindingStore(bindingsPath) {
|
|
|
7513
9082
|
};
|
|
7514
9083
|
}
|
|
7515
9084
|
function writeHardwareBindingStore(bindingsPath, store) {
|
|
7516
|
-
|
|
7517
|
-
|
|
9085
|
+
fs13.mkdirSync(path13.dirname(bindingsPath), { recursive: true });
|
|
9086
|
+
fs13.writeFileSync(bindingsPath, JSON.stringify(store, null, 2), "utf8");
|
|
7518
9087
|
}
|
|
7519
9088
|
function normalizeCapabilities(providerId, values) {
|
|
7520
9089
|
const requested = Array.isArray(values) && values.length > 0 ? values : providerId === SERIAL_HARDWARE_PROVIDER_ID ? DEFAULT_SERIAL_CAPABILITIES : [];
|
|
@@ -7542,18 +9111,18 @@ function normalizeDevices(values) {
|
|
|
7542
9111
|
if (!trimmed) {
|
|
7543
9112
|
continue;
|
|
7544
9113
|
}
|
|
7545
|
-
const resolved =
|
|
9114
|
+
const resolved = path13.resolve(trimmed);
|
|
7546
9115
|
unique.set(resolved, {
|
|
7547
9116
|
kind: "serial_device",
|
|
7548
9117
|
id: resolved,
|
|
7549
9118
|
path: resolved,
|
|
7550
|
-
displayName:
|
|
9119
|
+
displayName: path13.basename(resolved)
|
|
7551
9120
|
});
|
|
7552
9121
|
}
|
|
7553
9122
|
return [...unique.values()];
|
|
7554
9123
|
}
|
|
7555
9124
|
function printHardwareBinding(record2) {
|
|
7556
|
-
console.log(
|
|
9125
|
+
console.log(kleur12.cyan(`Hardware provider: ${record2.providerId}`));
|
|
7557
9126
|
console.log(`Status: ${record2.status}`);
|
|
7558
9127
|
console.log(`Project: ${record2.projectId}`);
|
|
7559
9128
|
console.log(`Capabilities: ${record2.grantedCapabilities.join(", ") || "(none)"}`);
|
|
@@ -7599,10 +9168,10 @@ function showHardwareBinding(options) {
|
|
|
7599
9168
|
return;
|
|
7600
9169
|
}
|
|
7601
9170
|
if (bindings.length === 0) {
|
|
7602
|
-
console.log(
|
|
9171
|
+
console.log(kleur12.yellow("No hardware bindings recorded for this space."));
|
|
7603
9172
|
return;
|
|
7604
9173
|
}
|
|
7605
|
-
console.log(
|
|
9174
|
+
console.log(kleur12.cyan(`Hardware bindings for ${context.projectId}:`));
|
|
7606
9175
|
for (const record2 of bindings) {
|
|
7607
9176
|
console.log(`- ${record2.providerId} (${record2.status})`);
|
|
7608
9177
|
}
|
|
@@ -7635,9 +9204,9 @@ function grantHardwareBinding(options) {
|
|
|
7635
9204
|
console.log(JSON.stringify(record2));
|
|
7636
9205
|
return;
|
|
7637
9206
|
}
|
|
7638
|
-
console.log(
|
|
9207
|
+
console.log(kleur12.green(`Hardware binding granted: ${providerId}`));
|
|
7639
9208
|
printHardwareBinding(record2);
|
|
7640
|
-
console.log(
|
|
9209
|
+
console.log(kleur12.cyan(`Stored at ${context.bindingsPath}`));
|
|
7641
9210
|
}
|
|
7642
9211
|
function revokeHardwareBinding(options) {
|
|
7643
9212
|
const providerId = options.providerId.trim();
|
|
@@ -7656,12 +9225,12 @@ function revokeHardwareBinding(options) {
|
|
|
7656
9225
|
console.log(JSON.stringify({ revoked: true, providerId }));
|
|
7657
9226
|
return;
|
|
7658
9227
|
}
|
|
7659
|
-
console.log(
|
|
7660
|
-
console.log(
|
|
9228
|
+
console.log(kleur12.green(`Hardware binding revoked: ${providerId}`));
|
|
9229
|
+
console.log(kleur12.cyan(`Updated ${context.bindingsPath}`));
|
|
7661
9230
|
}
|
|
7662
9231
|
|
|
7663
9232
|
// src/agents.ts
|
|
7664
|
-
import
|
|
9233
|
+
import kleur13 from "kleur";
|
|
7665
9234
|
init_project_manifest();
|
|
7666
9235
|
var DEFAULT_CONTEXT_LIMIT = 50;
|
|
7667
9236
|
var MAX_CONTEXT_LIMIT = 200;
|
|
@@ -7770,20 +9339,107 @@ async function agentsList(options) {
|
|
|
7770
9339
|
return;
|
|
7771
9340
|
}
|
|
7772
9341
|
if (!agents.length) {
|
|
7773
|
-
console.log(
|
|
9342
|
+
console.log(kleur13.yellow("No agents found."));
|
|
7774
9343
|
return;
|
|
7775
9344
|
}
|
|
7776
|
-
console.log(
|
|
9345
|
+
console.log(kleur13.bold("Agents"));
|
|
7777
9346
|
for (const agent of agents) {
|
|
7778
9347
|
const provider = trimOrNull2(agent.provider);
|
|
7779
9348
|
const model = trimOrNull2(agent.model);
|
|
7780
9349
|
const details = [provider, model].filter(Boolean).join(" \xB7 ");
|
|
7781
9350
|
console.log(
|
|
7782
|
-
`- ${
|
|
9351
|
+
`- ${kleur13.cyan(`@${agent.handle}`)} ${displayName(agent)}${details ? ` (${details})` : ""}`
|
|
7783
9352
|
);
|
|
7784
9353
|
console.log(` id: ${agent.id}`);
|
|
7785
9354
|
}
|
|
7786
9355
|
}
|
|
9356
|
+
var agentSettingFields = [
|
|
9357
|
+
{ key: "maxAgentsPerPlan", flag: "--max-agents-per-plan", label: "Maximum agents per plan" },
|
|
9358
|
+
{ key: "maxConcurrentAgents", flag: "--max-concurrent-agents", label: "Maximum concurrent team workers across this space" }
|
|
9359
|
+
];
|
|
9360
|
+
function parseAgentSetting(value, label) {
|
|
9361
|
+
if (!/^[1-9]\d*$/.test(value.trim()) || !Number.isSafeInteger(Number(value))) {
|
|
9362
|
+
throw new Error(`${label} must be a positive whole number.`);
|
|
9363
|
+
}
|
|
9364
|
+
return Number(value);
|
|
9365
|
+
}
|
|
9366
|
+
async function agentSettings(options) {
|
|
9367
|
+
const patch = {};
|
|
9368
|
+
for (const field of agentSettingFields) {
|
|
9369
|
+
if (options[field.key] !== void 0) {
|
|
9370
|
+
patch[field.key] = parseAgentSetting(options[field.key], field.flag);
|
|
9371
|
+
}
|
|
9372
|
+
}
|
|
9373
|
+
if (options.interactive) {
|
|
9374
|
+
if (options.json || Object.keys(patch).length) {
|
|
9375
|
+
throw new Error("Use --interactive without --json or setting flags.");
|
|
9376
|
+
}
|
|
9377
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || process.env.CI === "true") {
|
|
9378
|
+
throw new Error("--interactive requires a terminal. Use --max-agents-per-plan or --max-concurrent-agents instead.");
|
|
9379
|
+
}
|
|
9380
|
+
}
|
|
9381
|
+
const projectId2 = resolveProjectId6(options.project);
|
|
9382
|
+
const request = {
|
|
9383
|
+
path: `/projects/${encodeURIComponent(projectId2)}/multi-agent-settings`,
|
|
9384
|
+
controllerUrl: options.controllerUrl,
|
|
9385
|
+
accessToken: options.accessToken,
|
|
9386
|
+
serviceToken: options.serviceToken
|
|
9387
|
+
};
|
|
9388
|
+
let settings = await requestControllerApiJson({ ...request, method: "GET" });
|
|
9389
|
+
if ((options.interactive || Object.keys(patch).length) && !settings.canManage) {
|
|
9390
|
+
throw new Error("You do not have permission to manage this space's multi-agent settings.");
|
|
9391
|
+
}
|
|
9392
|
+
if (options.interactive) {
|
|
9393
|
+
const { isCancel, text: text2 } = await import("@clack/prompts");
|
|
9394
|
+
console.log("These settings are shared by everyone in this space. Runtime capacity can limit concurrency further; lead and root jobs are excluded.");
|
|
9395
|
+
for (const field of agentSettingFields) {
|
|
9396
|
+
const bounds = settings.limits[field.key];
|
|
9397
|
+
const entered = await text2({
|
|
9398
|
+
message: `${field.label} (${bounds.min}\u2013${bounds.max}; default ${bounds.default})`,
|
|
9399
|
+
placeholder: String(settings[field.key]),
|
|
9400
|
+
defaultValue: String(settings[field.key]),
|
|
9401
|
+
validate(value2) {
|
|
9402
|
+
try {
|
|
9403
|
+
const parsed = parseAgentSetting(value2 || String(settings[field.key]), field.label);
|
|
9404
|
+
if (parsed < bounds.min || parsed > bounds.max) {
|
|
9405
|
+
return `Enter a whole number from ${bounds.min} to ${bounds.max}.`;
|
|
9406
|
+
}
|
|
9407
|
+
} catch (error) {
|
|
9408
|
+
return error.message;
|
|
9409
|
+
}
|
|
9410
|
+
}
|
|
9411
|
+
});
|
|
9412
|
+
if (isCancel(entered)) throw new Error("Cancelled. No settings were changed.");
|
|
9413
|
+
const value = parseAgentSetting(String(entered), field.label);
|
|
9414
|
+
if (value !== settings[field.key]) patch[field.key] = value;
|
|
9415
|
+
}
|
|
9416
|
+
}
|
|
9417
|
+
for (const field of agentSettingFields) {
|
|
9418
|
+
const value = patch[field.key];
|
|
9419
|
+
const bounds = settings.limits[field.key];
|
|
9420
|
+
if (value !== void 0 && (value < bounds.min || value > bounds.max)) {
|
|
9421
|
+
throw new Error(`${field.flag} must be between ${bounds.min} and ${bounds.max}.`);
|
|
9422
|
+
}
|
|
9423
|
+
}
|
|
9424
|
+
if (Object.keys(patch).length) {
|
|
9425
|
+
settings = await requestControllerApiJson({
|
|
9426
|
+
...request,
|
|
9427
|
+
method: "PATCH",
|
|
9428
|
+
jsonBody: patch
|
|
9429
|
+
});
|
|
9430
|
+
}
|
|
9431
|
+
if (options.json) {
|
|
9432
|
+
console.log(JSON.stringify(settings, null, 2));
|
|
9433
|
+
return;
|
|
9434
|
+
}
|
|
9435
|
+
console.log(kleur13.bold(`Multi-agent settings for space ${projectId2}`));
|
|
9436
|
+
for (const field of agentSettingFields) {
|
|
9437
|
+
const bounds = settings.limits[field.key];
|
|
9438
|
+
console.log(`${field.label}: ${settings[field.key]} (range ${bounds.min}\u2013${bounds.max}; default ${bounds.default})`);
|
|
9439
|
+
}
|
|
9440
|
+
console.log("Shared across this space. Lead/root jobs are excluded from the concurrent worker limit; runtime capacity can limit concurrency further.");
|
|
9441
|
+
if (!settings.canManage) console.log("Read-only: your role cannot change these settings.");
|
|
9442
|
+
}
|
|
7787
9443
|
async function agentContextsList(options) {
|
|
7788
9444
|
const projectId2 = resolveProjectId6(options.project);
|
|
7789
9445
|
const contexts = await requestControllerApiJson({
|
|
@@ -7799,15 +9455,15 @@ async function agentContextsList(options) {
|
|
|
7799
9455
|
return;
|
|
7800
9456
|
}
|
|
7801
9457
|
if (!contexts.length) {
|
|
7802
|
-
console.log(
|
|
9458
|
+
console.log(kleur13.yellow("No agent context cards found."));
|
|
7803
9459
|
return;
|
|
7804
9460
|
}
|
|
7805
|
-
console.log(
|
|
9461
|
+
console.log(kleur13.bold("Agent context"));
|
|
7806
9462
|
for (const card of contexts) {
|
|
7807
9463
|
const title = trimOrNull2(card.title);
|
|
7808
9464
|
const updatedAt = trimOrNull2(card.updatedAt) ?? trimOrNull2(card.updated_at);
|
|
7809
9465
|
console.log(
|
|
7810
|
-
`- ${
|
|
9466
|
+
`- ${kleur13.cyan(contextAgentLabel(card))} ${kleur13.gray(`[${contextScope(card)}]`)}${title ? ` ${title}` : ""}`
|
|
7811
9467
|
);
|
|
7812
9468
|
console.log(` ${summarize(card.context)}`);
|
|
7813
9469
|
if (updatedAt) {
|
|
@@ -7844,7 +9500,7 @@ async function agentContextPut(options) {
|
|
|
7844
9500
|
return;
|
|
7845
9501
|
}
|
|
7846
9502
|
console.log(
|
|
7847
|
-
`${
|
|
9503
|
+
`${kleur13.green("Saved")} ${kleur13.cyan(contextAgentLabel(saved))} context for ${kleur13.gray(contextScope(saved))}.`
|
|
7848
9504
|
);
|
|
7849
9505
|
}
|
|
7850
9506
|
function workerLabel(worker) {
|
|
@@ -7870,7 +9526,7 @@ async function agentPlanGroupStatus(options) {
|
|
|
7870
9526
|
console.log(JSON.stringify(status, null, 2));
|
|
7871
9527
|
return;
|
|
7872
9528
|
}
|
|
7873
|
-
console.log(
|
|
9529
|
+
console.log(kleur13.bold(`Plan group ${trimOrNull2(status.groupId) ?? groupId}`));
|
|
7874
9530
|
const conversationId = trimOrNull2(status.conversationId);
|
|
7875
9531
|
if (conversationId) {
|
|
7876
9532
|
console.log(` conversation: ${conversationId}`);
|
|
@@ -7880,13 +9536,13 @@ async function agentPlanGroupStatus(options) {
|
|
|
7880
9536
|
);
|
|
7881
9537
|
const workers = status.workers ?? [];
|
|
7882
9538
|
if (!workers.length) {
|
|
7883
|
-
console.log(
|
|
9539
|
+
console.log(kleur13.yellow("No worker jobs found."));
|
|
7884
9540
|
return;
|
|
7885
9541
|
}
|
|
7886
9542
|
for (const worker of workers) {
|
|
7887
9543
|
const marker = worker.terminal ? "terminal" : "running";
|
|
7888
9544
|
const details = [trimOrNull2(worker.status) ?? "unknown", marker, trimOrNull2(worker.outcome)].filter(Boolean).join(" \xB7 ");
|
|
7889
|
-
console.log(`- ${
|
|
9545
|
+
console.log(`- ${kleur13.cyan(workerLabel(worker))} ${kleur13.gray(`[${details}]`)}`);
|
|
7890
9546
|
const scope = trimOrNull2(worker.scopeSummary);
|
|
7891
9547
|
if (scope) {
|
|
7892
9548
|
console.log(` scope: ${scope}`);
|
|
@@ -7897,7 +9553,7 @@ async function agentPlanGroupStatus(options) {
|
|
|
7897
9553
|
}
|
|
7898
9554
|
const errorMessage = trimOrNull2(worker.errorMessage);
|
|
7899
9555
|
if (errorMessage) {
|
|
7900
|
-
console.log(` ${
|
|
9556
|
+
console.log(` ${kleur13.red(`error: ${summarize(errorMessage)}`)}`);
|
|
7901
9557
|
}
|
|
7902
9558
|
}
|
|
7903
9559
|
}
|
|
@@ -7924,11 +9580,11 @@ async function agentJobsCancel(options) {
|
|
|
7924
9580
|
const canceledJobs = result.canceledJobIds?.length ?? 0;
|
|
7925
9581
|
const canceledRuns = result.canceledRunIds?.length ?? 0;
|
|
7926
9582
|
if (!canceledJobs && !canceledRuns) {
|
|
7927
|
-
console.log(
|
|
9583
|
+
console.log(kleur13.yellow(`Nothing to cancel for ${target} (no queued or leased jobs).`));
|
|
7928
9584
|
return;
|
|
7929
9585
|
}
|
|
7930
9586
|
console.log(
|
|
7931
|
-
`${
|
|
9587
|
+
`${kleur13.green("Canceled")} ${canceledJobs} job(s) and ${canceledRuns} run(s) for ${target}.`
|
|
7932
9588
|
);
|
|
7933
9589
|
}
|
|
7934
9590
|
|
|
@@ -8080,7 +9736,7 @@ function isAssistantReplyCandidate(message, runId, options) {
|
|
|
8080
9736
|
}
|
|
8081
9737
|
return typeof message.content === "string" && message.content.trim().length > 0;
|
|
8082
9738
|
}
|
|
8083
|
-
async function
|
|
9739
|
+
async function sleep2(ms) {
|
|
8084
9740
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
8085
9741
|
}
|
|
8086
9742
|
async function dispatchPrompt(options) {
|
|
@@ -8188,7 +9844,7 @@ async function waitForAssistantReply(conversationId, runId, options) {
|
|
|
8188
9844
|
break;
|
|
8189
9845
|
}
|
|
8190
9846
|
}
|
|
8191
|
-
await
|
|
9847
|
+
await sleep2(pollMs);
|
|
8192
9848
|
}
|
|
8193
9849
|
const finalMessages = await fetchConversationMessages2(conversationId, options);
|
|
8194
9850
|
const assistant = findLatestAssistantReply(finalMessages, runId, options);
|
|
@@ -8234,8 +9890,8 @@ async function chatPrompt(options) {
|
|
|
8234
9890
|
}
|
|
8235
9891
|
|
|
8236
9892
|
// src/recommendations.ts
|
|
8237
|
-
import
|
|
8238
|
-
import
|
|
9893
|
+
import fs14 from "node:fs";
|
|
9894
|
+
import path14 from "node:path";
|
|
8239
9895
|
init_project_manifest();
|
|
8240
9896
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
8241
9897
|
var MAX_FILE_BYTES = 65536;
|
|
@@ -8264,26 +9920,26 @@ function text(value, label, max) {
|
|
|
8264
9920
|
}
|
|
8265
9921
|
function readProposalFile(file) {
|
|
8266
9922
|
const manifestPath = findProjectManifest(process.cwd()).path;
|
|
8267
|
-
const root =
|
|
8268
|
-
const selected =
|
|
8269
|
-
const canonical =
|
|
8270
|
-
const relative =
|
|
8271
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
9923
|
+
const root = fs14.realpathSync(manifestPath ? path14.dirname(path14.dirname(manifestPath)) : process.cwd());
|
|
9924
|
+
const selected = path14.resolve(file);
|
|
9925
|
+
const canonical = path14.join(fs14.realpathSync(path14.dirname(selected)), path14.basename(selected));
|
|
9926
|
+
const relative = path14.relative(root, canonical);
|
|
9927
|
+
if (relative === ".." || relative.startsWith(`..${path14.sep}`) || path14.isAbsolute(relative)) {
|
|
8272
9928
|
throw new Error("Recommendation file must stay inside the active Instafy workspace.");
|
|
8273
9929
|
}
|
|
8274
|
-
const initial =
|
|
9930
|
+
const initial = fs14.lstatSync(canonical);
|
|
8275
9931
|
if (!initial.isFile() || initial.isSymbolicLink()) throw new Error("Recommendation file must be a regular file, not a symlink.");
|
|
8276
|
-
const descriptor =
|
|
9932
|
+
const descriptor = fs14.openSync(canonical, fs14.constants.O_RDONLY | (fs14.constants.O_NOFOLLOW ?? 0));
|
|
8277
9933
|
let bytes;
|
|
8278
9934
|
try {
|
|
8279
|
-
const opened =
|
|
9935
|
+
const opened = fs14.fstatSync(descriptor);
|
|
8280
9936
|
if (!opened.isFile() || opened.dev !== initial.dev || opened.ino !== initial.ino || opened.size > MAX_FILE_BYTES) {
|
|
8281
9937
|
throw new Error(`Recommendation file must be a regular file no larger than ${MAX_FILE_BYTES} bytes.`);
|
|
8282
9938
|
}
|
|
8283
|
-
bytes =
|
|
9939
|
+
bytes = fs14.readFileSync(descriptor);
|
|
8284
9940
|
if (bytes.length > MAX_FILE_BYTES) throw new Error(`Recommendation file must be ${MAX_FILE_BYTES} bytes or smaller.`);
|
|
8285
9941
|
} finally {
|
|
8286
|
-
|
|
9942
|
+
fs14.closeSync(descriptor);
|
|
8287
9943
|
}
|
|
8288
9944
|
return bytes;
|
|
8289
9945
|
}
|
|
@@ -8615,11 +10271,11 @@ async function showConversationContext(options) {
|
|
|
8615
10271
|
}
|
|
8616
10272
|
|
|
8617
10273
|
// src/invitations.ts
|
|
8618
|
-
import
|
|
8619
|
-
import
|
|
10274
|
+
import kleur14 from "kleur";
|
|
10275
|
+
import path15 from "node:path";
|
|
8620
10276
|
init_project_manifest();
|
|
8621
10277
|
function resolveStartDir(rawPath) {
|
|
8622
|
-
return
|
|
10278
|
+
return path15.resolve(rawPath ?? process.cwd());
|
|
8623
10279
|
}
|
|
8624
10280
|
function trimOrNull3(value) {
|
|
8625
10281
|
const trimmed = value?.trim() ?? "";
|
|
@@ -8761,7 +10417,7 @@ async function inviteSpaceMember(options) {
|
|
|
8761
10417
|
return normalized;
|
|
8762
10418
|
}
|
|
8763
10419
|
const scopeLabel = orgName ? `${orgName}` : "this space";
|
|
8764
|
-
console.log(
|
|
10420
|
+
console.log(kleur14.green(`Invitation sent to ${normalized.email}`));
|
|
8765
10421
|
console.log(`Scope: ${scopeLabel}`);
|
|
8766
10422
|
console.log(`Role: ${normalized.role}`);
|
|
8767
10423
|
if (normalized.expiresAt) {
|
|
@@ -8821,7 +10477,7 @@ async function setSpaceMemberRole(options) {
|
|
|
8821
10477
|
return normalized;
|
|
8822
10478
|
}
|
|
8823
10479
|
console.log(
|
|
8824
|
-
|
|
10480
|
+
kleur14.green(
|
|
8825
10481
|
normalized.status === "updated" ? `Updated ${normalized.email} to ${normalized.role}` : `${normalized.email} already has role ${normalized.role}`
|
|
8826
10482
|
)
|
|
8827
10483
|
);
|
|
@@ -8860,7 +10516,7 @@ async function setSpaceMemberRole(options) {
|
|
|
8860
10516
|
return normalized;
|
|
8861
10517
|
}
|
|
8862
10518
|
console.log(
|
|
8863
|
-
|
|
10519
|
+
kleur14.green(
|
|
8864
10520
|
normalized.status === "updated" ? `Updated pending invite for ${normalized.email} to ${normalized.role}` : `Pending invite for ${normalized.email} already has role ${normalized.role}`
|
|
8865
10521
|
)
|
|
8866
10522
|
);
|
|
@@ -8910,7 +10566,7 @@ async function sendSpaceInvitation(options) {
|
|
|
8910
10566
|
init_config();
|
|
8911
10567
|
init_controller_fetch();
|
|
8912
10568
|
init_errors();
|
|
8913
|
-
import
|
|
10569
|
+
import kleur15 from "kleur";
|
|
8914
10570
|
var DEFAULT_STUDIO_URL = "https://instafy.dev";
|
|
8915
10571
|
var ORG_ROLES = ["owner", "admin", "builder", "viewer"];
|
|
8916
10572
|
var INVITE_LINK_ROLES = ["builder", "viewer"];
|
|
@@ -9065,7 +10721,7 @@ function printTable(headers, rows) {
|
|
|
9065
10721
|
(header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length))
|
|
9066
10722
|
);
|
|
9067
10723
|
const format = (cells) => cells.map((cell, index) => (cell ?? "").padEnd(widths[index])).join(" ").trimEnd();
|
|
9068
|
-
console.log(
|
|
10724
|
+
console.log(kleur15.bold(format(headers)));
|
|
9069
10725
|
for (const row of rows) {
|
|
9070
10726
|
console.log(format(row));
|
|
9071
10727
|
}
|
|
@@ -9094,7 +10750,7 @@ async function teamMembersList(options) {
|
|
|
9094
10750
|
return;
|
|
9095
10751
|
}
|
|
9096
10752
|
if (members.length === 0) {
|
|
9097
|
-
console.log(
|
|
10753
|
+
console.log(kleur15.gray("No members."));
|
|
9098
10754
|
return;
|
|
9099
10755
|
}
|
|
9100
10756
|
const rows = members.map((member) => [
|
|
@@ -9150,7 +10806,7 @@ If they already have an Instafy account, add them directly with:
|
|
|
9150
10806
|
);
|
|
9151
10807
|
return;
|
|
9152
10808
|
}
|
|
9153
|
-
console.log(
|
|
10809
|
+
console.log(kleur15.green(`Invitation sent to ${invitation.email}`));
|
|
9154
10810
|
console.log(`Role: ${invitation.role}`);
|
|
9155
10811
|
if (invitation.expiresAt) {
|
|
9156
10812
|
console.log(`Expires: ${invitation.expiresAt}`);
|
|
@@ -9181,7 +10837,7 @@ async function teamInviteLink(options) {
|
|
|
9181
10837
|
console.log(JSON.stringify({ inviteLink, acceptUrl }, null, 2));
|
|
9182
10838
|
return;
|
|
9183
10839
|
}
|
|
9184
|
-
console.log(
|
|
10840
|
+
console.log(kleur15.green("Shareable invite link created."));
|
|
9185
10841
|
console.log(`Role: ${inviteLink.role}`);
|
|
9186
10842
|
console.log(`Link: ${acceptUrl}`);
|
|
9187
10843
|
console.log(`Token: ${inviteLink.token}`);
|
|
@@ -9213,11 +10869,11 @@ async function teamInvites(options) {
|
|
|
9213
10869
|
return;
|
|
9214
10870
|
}
|
|
9215
10871
|
if (invitations.length === 0 && inviteLinks.length === 0) {
|
|
9216
|
-
console.log(
|
|
10872
|
+
console.log(kleur15.gray("No pending invitations or invite links."));
|
|
9217
10873
|
return;
|
|
9218
10874
|
}
|
|
9219
10875
|
if (invitations.length > 0) {
|
|
9220
|
-
console.log(
|
|
10876
|
+
console.log(kleur15.bold("Email invitations"));
|
|
9221
10877
|
const rows = invitations.map((invitation) => [
|
|
9222
10878
|
shortId(invitation.id),
|
|
9223
10879
|
invitation.email,
|
|
@@ -9228,7 +10884,7 @@ async function teamInvites(options) {
|
|
|
9228
10884
|
}
|
|
9229
10885
|
if (inviteLinks.length > 0) {
|
|
9230
10886
|
if (invitations.length > 0) console.log("");
|
|
9231
|
-
console.log(
|
|
10887
|
+
console.log(kleur15.bold("Invite links (shareable, grant join on sign-in)"));
|
|
9232
10888
|
const rows = inviteLinks.map((link) => [
|
|
9233
10889
|
shortId(link.id),
|
|
9234
10890
|
link.role,
|
|
@@ -9259,7 +10915,7 @@ async function teamAddMember(options) {
|
|
|
9259
10915
|
return;
|
|
9260
10916
|
}
|
|
9261
10917
|
const label = (member.email ?? "").trim() || member.userId;
|
|
9262
|
-
console.log(
|
|
10918
|
+
console.log(kleur15.green(`Added ${label} as ${member.role}.`));
|
|
9263
10919
|
}
|
|
9264
10920
|
async function teamAccept(options) {
|
|
9265
10921
|
const auth = resolveControllerAuth4(options, "instafy team accept");
|
|
@@ -9274,7 +10930,7 @@ async function teamAccept(options) {
|
|
|
9274
10930
|
return;
|
|
9275
10931
|
}
|
|
9276
10932
|
const name = (payload?.orgName ?? "").trim() || payload?.orgSlug || payload?.orgId || "team";
|
|
9277
|
-
console.log(
|
|
10933
|
+
console.log(kleur15.green(`Joined ${name} as ${payload?.role ?? "member"}.`));
|
|
9278
10934
|
}
|
|
9279
10935
|
async function confirmDestructive(message, yes, json) {
|
|
9280
10936
|
if (yes) return;
|
|
@@ -9308,7 +10964,7 @@ async function teamRevokeInvite(options) {
|
|
|
9308
10964
|
console.log(JSON.stringify({ ok: true, invitationId }, null, 2));
|
|
9309
10965
|
return;
|
|
9310
10966
|
}
|
|
9311
|
-
console.log(
|
|
10967
|
+
console.log(kleur15.green(`Revoked invitation ${invitationId}.`));
|
|
9312
10968
|
}
|
|
9313
10969
|
async function teamRevokeLink(options) {
|
|
9314
10970
|
const auth = resolveControllerAuth4(options, "instafy team revoke-link");
|
|
@@ -9327,17 +10983,17 @@ async function teamRevokeLink(options) {
|
|
|
9327
10983
|
console.log(JSON.stringify({ ok: true, inviteLinkId }, null, 2));
|
|
9328
10984
|
return;
|
|
9329
10985
|
}
|
|
9330
|
-
console.log(
|
|
10986
|
+
console.log(kleur15.green(`Revoked invite link ${inviteLinkId}.`));
|
|
9331
10987
|
}
|
|
9332
10988
|
|
|
9333
10989
|
// src/provider-bindings.ts
|
|
9334
10990
|
init_project_manifest();
|
|
9335
|
-
import
|
|
9336
|
-
import
|
|
10991
|
+
import fs15 from "node:fs";
|
|
10992
|
+
import path16 from "node:path";
|
|
9337
10993
|
import { pathToFileURL } from "node:url";
|
|
9338
|
-
import
|
|
10994
|
+
import kleur16 from "kleur";
|
|
9339
10995
|
function resolveBindingContext(startDir) {
|
|
9340
|
-
const lookup = findProjectManifest(
|
|
10996
|
+
const lookup = findProjectManifest(path16.resolve(startDir));
|
|
9341
10997
|
if (!lookup.manifest || !lookup.path) {
|
|
9342
10998
|
throw new Error(
|
|
9343
10999
|
"Linked space manifest required. Run `instafy space init` in this folder or pass --path <dir>."
|
|
@@ -9347,19 +11003,19 @@ function resolveBindingContext(startDir) {
|
|
|
9347
11003
|
if (!projectId2) {
|
|
9348
11004
|
throw new Error("Linked space manifest is missing a spaceId.");
|
|
9349
11005
|
}
|
|
9350
|
-
const
|
|
9351
|
-
const projectRoot =
|
|
11006
|
+
const instafyDir2 = path16.dirname(lookup.path);
|
|
11007
|
+
const projectRoot = path16.dirname(instafyDir2);
|
|
9352
11008
|
return {
|
|
9353
|
-
bindingsPath:
|
|
11009
|
+
bindingsPath: path16.join(instafyDir2, "provider-bindings.json"),
|
|
9354
11010
|
projectId: projectId2,
|
|
9355
11011
|
projectRoot
|
|
9356
11012
|
};
|
|
9357
11013
|
}
|
|
9358
11014
|
function readBindingStore(bindingsPath) {
|
|
9359
|
-
if (!
|
|
11015
|
+
if (!fs15.existsSync(bindingsPath)) {
|
|
9360
11016
|
return { version: 1, bindings: {} };
|
|
9361
11017
|
}
|
|
9362
|
-
const raw = JSON.parse(
|
|
11018
|
+
const raw = JSON.parse(fs15.readFileSync(bindingsPath, "utf8"));
|
|
9363
11019
|
const bindings = raw && typeof raw.bindings === "object" && raw.bindings !== null ? raw.bindings : {};
|
|
9364
11020
|
return {
|
|
9365
11021
|
version: 1,
|
|
@@ -9367,8 +11023,8 @@ function readBindingStore(bindingsPath) {
|
|
|
9367
11023
|
};
|
|
9368
11024
|
}
|
|
9369
11025
|
function writeBindingStore(bindingsPath, store) {
|
|
9370
|
-
|
|
9371
|
-
|
|
11026
|
+
fs15.mkdirSync(path16.dirname(bindingsPath), { recursive: true });
|
|
11027
|
+
fs15.writeFileSync(bindingsPath, JSON.stringify(store, null, 2), "utf8");
|
|
9372
11028
|
}
|
|
9373
11029
|
function normalizeCapabilities2(values) {
|
|
9374
11030
|
const requested = Array.isArray(values) && values.length > 0 ? values : ["project_content_read", "project_content_write"];
|
|
@@ -9391,7 +11047,7 @@ function deriveStatus(capabilities) {
|
|
|
9391
11047
|
return "bound_read_only";
|
|
9392
11048
|
}
|
|
9393
11049
|
function printBinding(record2) {
|
|
9394
|
-
console.log(
|
|
11050
|
+
console.log(kleur16.cyan(`Provider: ${record2.providerId}`));
|
|
9395
11051
|
console.log(`Status: ${record2.status}`);
|
|
9396
11052
|
console.log(`Project: ${record2.projectId}`);
|
|
9397
11053
|
console.log(`Root URI: ${record2.rootUri}`);
|
|
@@ -9433,10 +11089,10 @@ function showProviderBinding(options) {
|
|
|
9433
11089
|
return;
|
|
9434
11090
|
}
|
|
9435
11091
|
if (bindings.length === 0) {
|
|
9436
|
-
console.log(
|
|
11092
|
+
console.log(kleur16.yellow("No provider bindings recorded for this space."));
|
|
9437
11093
|
return;
|
|
9438
11094
|
}
|
|
9439
|
-
console.log(
|
|
11095
|
+
console.log(kleur16.cyan(`Bindings for ${context.projectId}:`));
|
|
9440
11096
|
for (const record2 of bindings) {
|
|
9441
11097
|
console.log(`- ${record2.providerId} (${record2.status})`);
|
|
9442
11098
|
}
|
|
@@ -9468,9 +11124,9 @@ function grantProviderBinding(options) {
|
|
|
9468
11124
|
console.log(JSON.stringify(record2));
|
|
9469
11125
|
return;
|
|
9470
11126
|
}
|
|
9471
|
-
console.log(
|
|
11127
|
+
console.log(kleur16.green(`Provider binding granted: ${providerId}`));
|
|
9472
11128
|
printBinding(record2);
|
|
9473
|
-
console.log(
|
|
11129
|
+
console.log(kleur16.cyan(`Stored at ${context.bindingsPath}`));
|
|
9474
11130
|
}
|
|
9475
11131
|
function revokeProviderBinding(options) {
|
|
9476
11132
|
const providerId = options.providerId.trim();
|
|
@@ -9489,12 +11145,12 @@ function revokeProviderBinding(options) {
|
|
|
9489
11145
|
console.log(JSON.stringify({ revoked: true, providerId }));
|
|
9490
11146
|
return;
|
|
9491
11147
|
}
|
|
9492
|
-
console.log(
|
|
9493
|
-
console.log(
|
|
11148
|
+
console.log(kleur16.green(`Provider binding revoked: ${providerId}`));
|
|
11149
|
+
console.log(kleur16.cyan(`Updated ${context.bindingsPath}`));
|
|
9494
11150
|
}
|
|
9495
11151
|
|
|
9496
11152
|
// src/providers.ts
|
|
9497
|
-
import
|
|
11153
|
+
import kleur17 from "kleur";
|
|
9498
11154
|
|
|
9499
11155
|
// ../provider-client/index.js
|
|
9500
11156
|
var DEFAULT_PROVIDER_HOST_BASE_URL = "http://127.0.0.1:8797";
|
|
@@ -9566,12 +11222,12 @@ function resolveFetchImplementation(fetchImpl) {
|
|
|
9566
11222
|
function createProviderHostClient(options = {}) {
|
|
9567
11223
|
const fetchImpl = resolveFetchImplementation(options.fetch);
|
|
9568
11224
|
const baseUrl = normalizeProviderHostBaseUrl(options.baseUrl);
|
|
9569
|
-
async function jsonRequest(
|
|
9570
|
-
const response = await fetchImpl(`${baseUrl}${
|
|
11225
|
+
async function jsonRequest(path19, init) {
|
|
11226
|
+
const response = await fetchImpl(`${baseUrl}${path19}`, init);
|
|
9571
11227
|
const json = await response.json();
|
|
9572
11228
|
if (!response.ok || json?.ok === false) {
|
|
9573
11229
|
throw new Error(
|
|
9574
|
-
json?.error || json?.stderr || `provider host request failed for ${
|
|
11230
|
+
json?.error || json?.stderr || `provider host request failed for ${path19}`
|
|
9575
11231
|
);
|
|
9576
11232
|
}
|
|
9577
11233
|
return json;
|
|
@@ -9664,7 +11320,7 @@ function formatProviderLabel(provider) {
|
|
|
9664
11320
|
return provider.title?.trim().length ? provider.title.trim() : provider.id;
|
|
9665
11321
|
}
|
|
9666
11322
|
function printProviderSummary(provider) {
|
|
9667
|
-
console.log(
|
|
11323
|
+
console.log(kleur17.green(formatProviderLabel(provider)));
|
|
9668
11324
|
console.log(` id: ${provider.id}`);
|
|
9669
11325
|
if (provider.description) {
|
|
9670
11326
|
console.log(` description: ${provider.description}`);
|
|
@@ -9676,7 +11332,7 @@ function printProviderSummary(provider) {
|
|
|
9676
11332
|
console.log(` type: ${provider.providerType}`);
|
|
9677
11333
|
}
|
|
9678
11334
|
console.log(
|
|
9679
|
-
` discoverable: ${provider.discoverable !== false ?
|
|
11335
|
+
` discoverable: ${provider.discoverable !== false ? kleur17.green("yes") : kleur17.yellow("no")}`
|
|
9680
11336
|
);
|
|
9681
11337
|
if (provider.capabilityIds?.length) {
|
|
9682
11338
|
console.log(` capabilities: ${provider.capabilityIds.join(", ")}`);
|
|
@@ -9688,7 +11344,7 @@ function printProviderSummary(provider) {
|
|
|
9688
11344
|
console.log(` resources: ${provider.resourceUris.join(", ")}`);
|
|
9689
11345
|
}
|
|
9690
11346
|
if (provider.error) {
|
|
9691
|
-
console.log(` error: ${
|
|
11347
|
+
console.log(` error: ${kleur17.yellow(provider.error)}`);
|
|
9692
11348
|
}
|
|
9693
11349
|
}
|
|
9694
11350
|
function parseCommandJson(commandJson) {
|
|
@@ -9708,10 +11364,10 @@ async function providersList(options = {}) {
|
|
|
9708
11364
|
});
|
|
9709
11365
|
return;
|
|
9710
11366
|
}
|
|
9711
|
-
console.log(
|
|
11367
|
+
console.log(kleur17.green("Local providers"));
|
|
9712
11368
|
console.log(`Host: ${providerHostUrl}`);
|
|
9713
11369
|
if (!result.providers.length) {
|
|
9714
|
-
console.log(
|
|
11370
|
+
console.log(kleur17.yellow("No providers discovered."));
|
|
9715
11371
|
return;
|
|
9716
11372
|
}
|
|
9717
11373
|
for (const provider of result.providers) {
|
|
@@ -9728,7 +11384,7 @@ async function providersDiscover(params) {
|
|
|
9728
11384
|
});
|
|
9729
11385
|
return;
|
|
9730
11386
|
}
|
|
9731
|
-
console.log(
|
|
11387
|
+
console.log(kleur17.green(`Provider discovery: ${params.providerId}`));
|
|
9732
11388
|
console.log(`Host: ${providerHostUrl}`);
|
|
9733
11389
|
console.log(JSON.stringify(result.provider ?? result, null, 2));
|
|
9734
11390
|
}
|
|
@@ -9742,10 +11398,10 @@ async function providersRead(params) {
|
|
|
9742
11398
|
});
|
|
9743
11399
|
return;
|
|
9744
11400
|
}
|
|
9745
|
-
console.log(
|
|
11401
|
+
console.log(kleur17.green(`Provider resource: ${params.uri}`));
|
|
9746
11402
|
console.log(`Host: ${providerHostUrl}`);
|
|
9747
11403
|
if (result.exists === false) {
|
|
9748
|
-
console.log(
|
|
11404
|
+
console.log(kleur17.yellow("Resource does not exist."));
|
|
9749
11405
|
return;
|
|
9750
11406
|
}
|
|
9751
11407
|
console.log(JSON.stringify(result.value ?? result, null, 2));
|
|
@@ -9771,7 +11427,7 @@ async function providersProbe(params) {
|
|
|
9771
11427
|
});
|
|
9772
11428
|
return;
|
|
9773
11429
|
}
|
|
9774
|
-
console.log(
|
|
11430
|
+
console.log(kleur17.green(`Provider transport probe: ${params.providerId}`));
|
|
9775
11431
|
console.log(`Host: ${providerHostUrl}`);
|
|
9776
11432
|
console.log(JSON.stringify(result, null, 2));
|
|
9777
11433
|
}
|
|
@@ -9873,9 +11529,9 @@ async function diagnosticsRunResult(options) {
|
|
|
9873
11529
|
}
|
|
9874
11530
|
|
|
9875
11531
|
// src/support.ts
|
|
9876
|
-
import
|
|
11532
|
+
import fs16 from "node:fs";
|
|
9877
11533
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
9878
|
-
import
|
|
11534
|
+
import path17 from "node:path";
|
|
9879
11535
|
init_project_manifest();
|
|
9880
11536
|
var MAX_SUMMARY_CHARS = 500;
|
|
9881
11537
|
var MAX_DETAILS_BYTES = 2e4;
|
|
@@ -9924,34 +11580,34 @@ function requiredCursorUuid(value, flag) {
|
|
|
9924
11580
|
return id;
|
|
9925
11581
|
}
|
|
9926
11582
|
function readRegularFile(filePath, maxBytes, label) {
|
|
9927
|
-
const resolved =
|
|
11583
|
+
const resolved = path17.resolve(filePath);
|
|
9928
11584
|
const manifestPath = findProjectManifest(process.cwd()).path;
|
|
9929
|
-
const workspaceRoot = manifestPath ?
|
|
9930
|
-
const canonicalRoot =
|
|
9931
|
-
const canonicalParent =
|
|
9932
|
-
const canonicalTarget =
|
|
9933
|
-
const relativeTarget =
|
|
9934
|
-
if (relativeTarget === ".." || relativeTarget.startsWith(`..${
|
|
11585
|
+
const workspaceRoot = manifestPath ? path17.dirname(path17.dirname(manifestPath)) : process.cwd();
|
|
11586
|
+
const canonicalRoot = fs16.realpathSync(workspaceRoot);
|
|
11587
|
+
const canonicalParent = fs16.realpathSync(path17.dirname(resolved));
|
|
11588
|
+
const canonicalTarget = path17.join(canonicalParent, path17.basename(resolved));
|
|
11589
|
+
const relativeTarget = path17.relative(canonicalRoot, canonicalTarget);
|
|
11590
|
+
if (relativeTarget === ".." || relativeTarget.startsWith(`..${path17.sep}`) || path17.isAbsolute(relativeTarget)) {
|
|
9935
11591
|
throw new Error(`${label} must stay inside the active Instafy workspace.`);
|
|
9936
11592
|
}
|
|
9937
|
-
const initialStat =
|
|
11593
|
+
const initialStat = fs16.lstatSync(canonicalTarget);
|
|
9938
11594
|
if (initialStat.isSymbolicLink() || !initialStat.isFile()) {
|
|
9939
11595
|
throw new Error(`${label} must be a regular file, not a symlink.`);
|
|
9940
11596
|
}
|
|
9941
|
-
const noFollow = typeof
|
|
9942
|
-
const descriptor =
|
|
11597
|
+
const noFollow = typeof fs16.constants.O_NOFOLLOW === "number" ? fs16.constants.O_NOFOLLOW : 0;
|
|
11598
|
+
const descriptor = fs16.openSync(canonicalTarget, fs16.constants.O_RDONLY | noFollow);
|
|
9943
11599
|
try {
|
|
9944
|
-
const openedStat =
|
|
11600
|
+
const openedStat = fs16.fstatSync(descriptor);
|
|
9945
11601
|
if (!openedStat.isFile() || openedStat.dev !== initialStat.dev || openedStat.ino !== initialStat.ino || openedStat.size > maxBytes) {
|
|
9946
11602
|
throw new Error(`${label} must be a regular file no larger than ${maxBytes} bytes.`);
|
|
9947
11603
|
}
|
|
9948
|
-
const bytes =
|
|
11604
|
+
const bytes = fs16.readFileSync(descriptor);
|
|
9949
11605
|
if (bytes.length > maxBytes) {
|
|
9950
11606
|
throw new Error(`${label} must be ${maxBytes} bytes or smaller.`);
|
|
9951
11607
|
}
|
|
9952
11608
|
return bytes;
|
|
9953
11609
|
} finally {
|
|
9954
|
-
|
|
11610
|
+
fs16.closeSync(descriptor);
|
|
9955
11611
|
}
|
|
9956
11612
|
}
|
|
9957
11613
|
function readJsonFile(filePath, maxBytes, label) {
|
|
@@ -10025,7 +11681,7 @@ function resolveScreenshots(filePaths = []) {
|
|
|
10025
11681
|
);
|
|
10026
11682
|
}
|
|
10027
11683
|
return {
|
|
10028
|
-
fileName:
|
|
11684
|
+
fileName: path17.basename(filePath),
|
|
10029
11685
|
mediaType,
|
|
10030
11686
|
dataBase64: bytes.toString("base64"),
|
|
10031
11687
|
byteLength: bytes.length
|
|
@@ -10423,7 +12079,7 @@ function failWithGroupHelp(command) {
|
|
|
10423
12079
|
process.exitCode = 1;
|
|
10424
12080
|
});
|
|
10425
12081
|
}
|
|
10426
|
-
var require4 =
|
|
12082
|
+
var require4 = createRequire4(import.meta.url);
|
|
10427
12083
|
var pkg = require4("../package.json");
|
|
10428
12084
|
function addServerUrlOptions(command) {
|
|
10429
12085
|
return command.option(
|
|
@@ -10508,7 +12164,7 @@ program.command("login").description("Log in and save an access token for future
|
|
|
10508
12164
|
json: opts.json
|
|
10509
12165
|
});
|
|
10510
12166
|
} catch (error) {
|
|
10511
|
-
console.error(
|
|
12167
|
+
console.error(kleur20.red(String(error)));
|
|
10512
12168
|
process.exit(1);
|
|
10513
12169
|
}
|
|
10514
12170
|
});
|
|
@@ -10516,7 +12172,7 @@ program.command("logout").description("Clear the saved CLI access token").option
|
|
|
10516
12172
|
try {
|
|
10517
12173
|
await logout({ json: opts.json, profile: opts.profile });
|
|
10518
12174
|
} catch (error) {
|
|
10519
|
-
console.error(
|
|
12175
|
+
console.error(kleur20.red(String(error)));
|
|
10520
12176
|
process.exit(1);
|
|
10521
12177
|
}
|
|
10522
12178
|
});
|
|
@@ -10540,7 +12196,7 @@ chatCommand.option("--json", "Output JSON").action(async (promptParts, opts) =>
|
|
|
10540
12196
|
json: opts.json
|
|
10541
12197
|
});
|
|
10542
12198
|
} catch (error) {
|
|
10543
|
-
console.error(
|
|
12199
|
+
console.error(kleur20.red(String(error)));
|
|
10544
12200
|
process.exit(1);
|
|
10545
12201
|
}
|
|
10546
12202
|
});
|
|
@@ -10564,7 +12220,7 @@ function registerSpaceCommand(command) {
|
|
|
10564
12220
|
json: opts.json
|
|
10565
12221
|
});
|
|
10566
12222
|
} catch (error) {
|
|
10567
|
-
console.error(
|
|
12223
|
+
console.error(kleur20.red(String(error)));
|
|
10568
12224
|
process.exit(1);
|
|
10569
12225
|
}
|
|
10570
12226
|
});
|
|
@@ -10580,7 +12236,7 @@ function registerSpaceCommand(command) {
|
|
|
10580
12236
|
json: opts.json
|
|
10581
12237
|
});
|
|
10582
12238
|
} catch (error) {
|
|
10583
|
-
console.error(
|
|
12239
|
+
console.error(kleur20.red(String(error)));
|
|
10584
12240
|
process.exit(1);
|
|
10585
12241
|
}
|
|
10586
12242
|
});
|
|
@@ -10594,7 +12250,7 @@ function registerSpaceCommand(command) {
|
|
|
10594
12250
|
json: opts.json
|
|
10595
12251
|
});
|
|
10596
12252
|
} catch (error) {
|
|
10597
|
-
console.error(
|
|
12253
|
+
console.error(kleur20.red(String(error)));
|
|
10598
12254
|
process.exit(1);
|
|
10599
12255
|
}
|
|
10600
12256
|
});
|
|
@@ -10617,7 +12273,7 @@ function registerSpaceCommand(command) {
|
|
|
10617
12273
|
json: opts.json
|
|
10618
12274
|
});
|
|
10619
12275
|
} catch (error) {
|
|
10620
|
-
console.error(
|
|
12276
|
+
console.error(kleur20.red(String(error)));
|
|
10621
12277
|
process.exit(1);
|
|
10622
12278
|
}
|
|
10623
12279
|
});
|
|
@@ -10629,7 +12285,7 @@ function registerSpaceCommand(command) {
|
|
|
10629
12285
|
json: opts.json
|
|
10630
12286
|
});
|
|
10631
12287
|
} catch (error) {
|
|
10632
|
-
console.error(
|
|
12288
|
+
console.error(kleur20.red(String(error)));
|
|
10633
12289
|
process.exit(1);
|
|
10634
12290
|
}
|
|
10635
12291
|
});
|
|
@@ -10651,7 +12307,7 @@ function registerSpaceCommand(command) {
|
|
|
10651
12307
|
json: opts.json
|
|
10652
12308
|
});
|
|
10653
12309
|
} catch (error) {
|
|
10654
|
-
console.error(
|
|
12310
|
+
console.error(kleur20.red(String(error)));
|
|
10655
12311
|
process.exit(1);
|
|
10656
12312
|
}
|
|
10657
12313
|
});
|
|
@@ -10667,7 +12323,7 @@ function registerSpaceCommand(command) {
|
|
|
10667
12323
|
json: opts.json
|
|
10668
12324
|
});
|
|
10669
12325
|
} catch (error) {
|
|
10670
|
-
console.error(
|
|
12326
|
+
console.error(kleur20.red(String(error)));
|
|
10671
12327
|
process.exit(1);
|
|
10672
12328
|
}
|
|
10673
12329
|
});
|
|
@@ -10693,7 +12349,7 @@ function registerSpaceCommand(command) {
|
|
|
10693
12349
|
json: opts.json
|
|
10694
12350
|
});
|
|
10695
12351
|
} catch (error) {
|
|
10696
|
-
console.error(
|
|
12352
|
+
console.error(kleur20.red(String(error)));
|
|
10697
12353
|
process.exit(1);
|
|
10698
12354
|
}
|
|
10699
12355
|
});
|
|
@@ -10718,7 +12374,7 @@ function registerSpaceCommand(command) {
|
|
|
10718
12374
|
json: opts.json
|
|
10719
12375
|
});
|
|
10720
12376
|
} catch (error) {
|
|
10721
|
-
console.error(
|
|
12377
|
+
console.error(kleur20.red(String(error)));
|
|
10722
12378
|
process.exit(1);
|
|
10723
12379
|
}
|
|
10724
12380
|
});
|
|
@@ -10738,7 +12394,7 @@ hardwareBindingsCommand.command("show").description("Show hardware bindings for
|
|
|
10738
12394
|
json: opts.json
|
|
10739
12395
|
});
|
|
10740
12396
|
} catch (error) {
|
|
10741
|
-
console.error(
|
|
12397
|
+
console.error(kleur20.red(String(error)));
|
|
10742
12398
|
process.exit(1);
|
|
10743
12399
|
}
|
|
10744
12400
|
});
|
|
@@ -10758,7 +12414,7 @@ hardwareBindingsCommand.command("grant").description("Grant a local hardware pro
|
|
|
10758
12414
|
json: opts.json
|
|
10759
12415
|
});
|
|
10760
12416
|
} catch (error) {
|
|
10761
|
-
console.error(
|
|
12417
|
+
console.error(kleur20.red(String(error)));
|
|
10762
12418
|
process.exit(1);
|
|
10763
12419
|
}
|
|
10764
12420
|
});
|
|
@@ -10770,7 +12426,7 @@ hardwareBindingsCommand.command("revoke").description("Revoke a local hardware b
|
|
|
10770
12426
|
json: opts.json
|
|
10771
12427
|
});
|
|
10772
12428
|
} catch (error) {
|
|
10773
|
-
console.error(
|
|
12429
|
+
console.error(kleur20.red(String(error)));
|
|
10774
12430
|
process.exit(1);
|
|
10775
12431
|
}
|
|
10776
12432
|
});
|
|
@@ -10784,7 +12440,7 @@ hardwareSerialCommand.command("list").description("List local serial devices vis
|
|
|
10784
12440
|
scanDirs: opts.scanDir
|
|
10785
12441
|
});
|
|
10786
12442
|
} catch (error) {
|
|
10787
|
-
console.error(
|
|
12443
|
+
console.error(kleur20.red(String(error)));
|
|
10788
12444
|
process.exit(1);
|
|
10789
12445
|
}
|
|
10790
12446
|
});
|
|
@@ -10796,7 +12452,7 @@ hardwareSerialCommand.command("probe").description("Probe host permissions for o
|
|
|
10796
12452
|
json: opts.json
|
|
10797
12453
|
});
|
|
10798
12454
|
} catch (error) {
|
|
10799
|
-
console.error(
|
|
12455
|
+
console.error(kleur20.red(String(error)));
|
|
10800
12456
|
process.exit(1);
|
|
10801
12457
|
}
|
|
10802
12458
|
});
|
|
@@ -10808,7 +12464,7 @@ hardwareCommand.command("opportunities").description("List host-local IO opportu
|
|
|
10808
12464
|
scanDirs: opts.scanDir
|
|
10809
12465
|
});
|
|
10810
12466
|
} catch (error) {
|
|
10811
|
-
console.error(
|
|
12467
|
+
console.error(kleur20.red(String(error)));
|
|
10812
12468
|
process.exit(1);
|
|
10813
12469
|
}
|
|
10814
12470
|
});
|
|
@@ -10821,7 +12477,7 @@ hardwareCommand.command("run").description("Run one available host-local IO acti
|
|
|
10821
12477
|
json: opts.json
|
|
10822
12478
|
});
|
|
10823
12479
|
} catch (error) {
|
|
10824
|
-
console.error(
|
|
12480
|
+
console.error(kleur20.red(String(error)));
|
|
10825
12481
|
process.exit(1);
|
|
10826
12482
|
}
|
|
10827
12483
|
});
|
|
@@ -10836,7 +12492,7 @@ providersListCommand.option("--json", "Output JSON").action(async (opts) => {
|
|
|
10836
12492
|
json: opts.json
|
|
10837
12493
|
});
|
|
10838
12494
|
} catch (error) {
|
|
10839
|
-
console.error(
|
|
12495
|
+
console.error(kleur20.red(String(error)));
|
|
10840
12496
|
process.exit(1);
|
|
10841
12497
|
}
|
|
10842
12498
|
});
|
|
@@ -10850,7 +12506,7 @@ providersDiscoverCommand.option("--json", "Output JSON").action(async (providerI
|
|
|
10850
12506
|
json: opts.json
|
|
10851
12507
|
});
|
|
10852
12508
|
} catch (error) {
|
|
10853
|
-
console.error(
|
|
12509
|
+
console.error(kleur20.red(String(error)));
|
|
10854
12510
|
process.exit(1);
|
|
10855
12511
|
}
|
|
10856
12512
|
});
|
|
@@ -10865,7 +12521,7 @@ providersReadCommand.option("--json", "Output JSON").action(async (providerId, u
|
|
|
10865
12521
|
json: opts.json
|
|
10866
12522
|
});
|
|
10867
12523
|
} catch (error) {
|
|
10868
|
-
console.error(
|
|
12524
|
+
console.error(kleur20.red(String(error)));
|
|
10869
12525
|
process.exit(1);
|
|
10870
12526
|
}
|
|
10871
12527
|
});
|
|
@@ -10888,7 +12544,7 @@ providersProbeCommand.option("--json", "Output JSON").action(async (providerId,
|
|
|
10888
12544
|
json: opts.json
|
|
10889
12545
|
});
|
|
10890
12546
|
} catch (error) {
|
|
10891
|
-
console.error(
|
|
12547
|
+
console.error(kleur20.red(String(error)));
|
|
10892
12548
|
process.exit(1);
|
|
10893
12549
|
}
|
|
10894
12550
|
});
|
|
@@ -10908,7 +12564,7 @@ for (const operation of ["list", "submit"]) {
|
|
|
10908
12564
|
if (operation === "list") await recommendationsList({ ...common, limit: opts.limit });
|
|
10909
12565
|
else await recommendationsSubmit({ ...common, file: opts.file });
|
|
10910
12566
|
} catch (error) {
|
|
10911
|
-
console.error(
|
|
12567
|
+
console.error(kleur20.red(String(error)));
|
|
10912
12568
|
process.exit(1);
|
|
10913
12569
|
}
|
|
10914
12570
|
});
|
|
@@ -10932,13 +12588,33 @@ for (const operation of ["current", "dismiss", "remind"]) {
|
|
|
10932
12588
|
else if (operation === "dismiss") await recommendationsDismiss(common);
|
|
10933
12589
|
else await recommendationsRemind({ ...common, at: opts.at, timezone: opts.timezone });
|
|
10934
12590
|
} catch (error) {
|
|
10935
|
-
console.error(
|
|
12591
|
+
console.error(kleur20.red(String(error)));
|
|
10936
12592
|
process.exit(1);
|
|
10937
12593
|
}
|
|
10938
12594
|
});
|
|
10939
12595
|
}
|
|
10940
|
-
var agentsCommand = program.command("agents").description("Inspect agents and scoped agent context");
|
|
12596
|
+
var agentsCommand = program.command("agents").description("Inspect agents, shared multi-agent settings, and scoped agent context");
|
|
10941
12597
|
failWithGroupHelp(agentsCommand);
|
|
12598
|
+
var agentsSettingsCommand = agentsCommand.command("settings").description("Inspect or change the space's shared multi-agent planning and concurrency limits");
|
|
12599
|
+
addSpaceOption(agentsSettingsCommand, "Space UUID (defaults to .instafy/space.json or SPACE_ID)");
|
|
12600
|
+
addServerUrlOptions(agentsSettingsCommand);
|
|
12601
|
+
addAccessTokenOptions(agentsSettingsCommand, "Instafy access token");
|
|
12602
|
+
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) => {
|
|
12603
|
+
try {
|
|
12604
|
+
await agentSettings({
|
|
12605
|
+
project: resolveSpaceIdOption(opts),
|
|
12606
|
+
controllerUrl: opts.serverUrl,
|
|
12607
|
+
accessToken: opts.accessToken,
|
|
12608
|
+
maxAgentsPerPlan: opts.maxAgentsPerPlan,
|
|
12609
|
+
maxConcurrentAgents: opts.maxConcurrentAgents,
|
|
12610
|
+
interactive: opts.interactive,
|
|
12611
|
+
json: opts.json
|
|
12612
|
+
});
|
|
12613
|
+
} catch (error) {
|
|
12614
|
+
console.error(kleur20.red(String(error)));
|
|
12615
|
+
process.exit(1);
|
|
12616
|
+
}
|
|
12617
|
+
});
|
|
10942
12618
|
var agentsListCommand = agentsCommand.command("list").description("List agents available to this account");
|
|
10943
12619
|
addSpaceOption(
|
|
10944
12620
|
agentsListCommand,
|
|
@@ -10955,7 +12631,7 @@ agentsListCommand.option("--json", "Output JSON").action(async (opts) => {
|
|
|
10955
12631
|
json: opts.json
|
|
10956
12632
|
});
|
|
10957
12633
|
} catch (error) {
|
|
10958
|
-
console.error(
|
|
12634
|
+
console.error(kleur20.red(String(error)));
|
|
10959
12635
|
process.exit(1);
|
|
10960
12636
|
}
|
|
10961
12637
|
});
|
|
@@ -10983,7 +12659,7 @@ agentsContextListCommand.option("--agent <handle>", "Filter by agent handle (for
|
|
|
10983
12659
|
json: opts.json
|
|
10984
12660
|
});
|
|
10985
12661
|
} catch (error) {
|
|
10986
|
-
console.error(
|
|
12662
|
+
console.error(kleur20.red(String(error)));
|
|
10987
12663
|
process.exit(1);
|
|
10988
12664
|
}
|
|
10989
12665
|
});
|
|
@@ -11009,7 +12685,7 @@ agentsContextPutCommand.action(async (contextParts, opts) => {
|
|
|
11009
12685
|
json: opts.json
|
|
11010
12686
|
});
|
|
11011
12687
|
} catch (error) {
|
|
11012
|
-
console.error(
|
|
12688
|
+
console.error(kleur20.red(String(error)));
|
|
11013
12689
|
process.exit(1);
|
|
11014
12690
|
}
|
|
11015
12691
|
});
|
|
@@ -11025,7 +12701,7 @@ agentsStatusCommand.option("--json", "Output JSON").action(async (groupId, opts)
|
|
|
11025
12701
|
json: opts.json
|
|
11026
12702
|
});
|
|
11027
12703
|
} catch (error) {
|
|
11028
|
-
console.error(
|
|
12704
|
+
console.error(kleur20.red(String(error)));
|
|
11029
12705
|
process.exit(1);
|
|
11030
12706
|
}
|
|
11031
12707
|
});
|
|
@@ -11043,7 +12719,7 @@ agentsCancelCommand.option("--json", "Output JSON").action(async (opts) => {
|
|
|
11043
12719
|
json: opts.json
|
|
11044
12720
|
});
|
|
11045
12721
|
} catch (error) {
|
|
11046
|
-
console.error(
|
|
12722
|
+
console.error(kleur20.red(String(error)));
|
|
11047
12723
|
process.exit(1);
|
|
11048
12724
|
}
|
|
11049
12725
|
});
|
|
@@ -11065,7 +12741,7 @@ secretsListCommand.option("--json", "Output JSON").action(async (opts) => {
|
|
|
11065
12741
|
json: opts.json
|
|
11066
12742
|
});
|
|
11067
12743
|
} catch (error) {
|
|
11068
|
-
console.error(
|
|
12744
|
+
console.error(kleur20.red(String(error)));
|
|
11069
12745
|
process.exit(1);
|
|
11070
12746
|
}
|
|
11071
12747
|
});
|
|
@@ -11086,7 +12762,7 @@ secretsGetCommand.option("--json", "Output JSON").action(async (nameOrId, opts)
|
|
|
11086
12762
|
json: opts.json
|
|
11087
12763
|
});
|
|
11088
12764
|
} catch (error) {
|
|
11089
|
-
console.error(
|
|
12765
|
+
console.error(kleur20.red(String(error)));
|
|
11090
12766
|
process.exit(1);
|
|
11091
12767
|
}
|
|
11092
12768
|
});
|
|
@@ -11111,7 +12787,7 @@ secretsPutCommand.option("--json", "Output JSON").action(async (name, opts) => {
|
|
|
11111
12787
|
json: opts.json
|
|
11112
12788
|
});
|
|
11113
12789
|
} catch (error) {
|
|
11114
|
-
console.error(
|
|
12790
|
+
console.error(kleur20.red(String(error)));
|
|
11115
12791
|
process.exit(1);
|
|
11116
12792
|
}
|
|
11117
12793
|
});
|
|
@@ -11132,7 +12808,7 @@ secretsRevokeCommand.option("--json", "Output JSON").action(async (nameOrId, opt
|
|
|
11132
12808
|
json: opts.json
|
|
11133
12809
|
});
|
|
11134
12810
|
} catch (error) {
|
|
11135
|
-
console.error(
|
|
12811
|
+
console.error(kleur20.red(String(error)));
|
|
11136
12812
|
process.exit(1);
|
|
11137
12813
|
}
|
|
11138
12814
|
});
|
|
@@ -11166,7 +12842,7 @@ automationsListCommand.option("--json", "Output JSON").action(async (opts) => {
|
|
|
11166
12842
|
json: opts.json
|
|
11167
12843
|
});
|
|
11168
12844
|
} catch (error) {
|
|
11169
|
-
console.error(
|
|
12845
|
+
console.error(kleur20.red(String(error)));
|
|
11170
12846
|
process.exit(1);
|
|
11171
12847
|
}
|
|
11172
12848
|
});
|
|
@@ -11212,7 +12888,7 @@ automationsCreateCommand.option("--json", "Output JSON").action(async (opts) =>
|
|
|
11212
12888
|
json: opts.json
|
|
11213
12889
|
});
|
|
11214
12890
|
} catch (error) {
|
|
11215
|
-
console.error(
|
|
12891
|
+
console.error(kleur20.red(String(error)));
|
|
11216
12892
|
process.exit(1);
|
|
11217
12893
|
}
|
|
11218
12894
|
});
|
|
@@ -11258,7 +12934,7 @@ automationsUpdateCommand.option("--json", "Output JSON").action(async (automatio
|
|
|
11258
12934
|
json: opts.json
|
|
11259
12935
|
});
|
|
11260
12936
|
} catch (error) {
|
|
11261
|
-
console.error(
|
|
12937
|
+
console.error(kleur20.red(String(error)));
|
|
11262
12938
|
process.exit(1);
|
|
11263
12939
|
}
|
|
11264
12940
|
});
|
|
@@ -11275,7 +12951,7 @@ automationsPauseCommand.option("--json", "Output JSON").action(async (automation
|
|
|
11275
12951
|
json: opts.json
|
|
11276
12952
|
});
|
|
11277
12953
|
} catch (error) {
|
|
11278
|
-
console.error(
|
|
12954
|
+
console.error(kleur20.red(String(error)));
|
|
11279
12955
|
process.exit(1);
|
|
11280
12956
|
}
|
|
11281
12957
|
});
|
|
@@ -11292,7 +12968,7 @@ automationsResumeCommand.option("--json", "Output JSON").action(async (automatio
|
|
|
11292
12968
|
json: opts.json
|
|
11293
12969
|
});
|
|
11294
12970
|
} catch (error) {
|
|
11295
|
-
console.error(
|
|
12971
|
+
console.error(kleur20.red(String(error)));
|
|
11296
12972
|
process.exit(1);
|
|
11297
12973
|
}
|
|
11298
12974
|
});
|
|
@@ -11308,7 +12984,7 @@ automationsRunCommand.option("--json", "Output JSON").action(async (automationId
|
|
|
11308
12984
|
json: opts.json
|
|
11309
12985
|
});
|
|
11310
12986
|
} catch (error) {
|
|
11311
|
-
console.error(
|
|
12987
|
+
console.error(kleur20.red(String(error)));
|
|
11312
12988
|
process.exit(1);
|
|
11313
12989
|
}
|
|
11314
12990
|
});
|
|
@@ -11324,7 +13000,7 @@ automationsDeleteCommand.option("--json", "Output JSON").action(async (automatio
|
|
|
11324
13000
|
json: opts.json
|
|
11325
13001
|
});
|
|
11326
13002
|
} catch (error) {
|
|
11327
|
-
console.error(
|
|
13003
|
+
console.error(kleur20.red(String(error)));
|
|
11328
13004
|
process.exit(1);
|
|
11329
13005
|
}
|
|
11330
13006
|
});
|
|
@@ -11342,7 +13018,7 @@ credentialsListCommand.option("--json", "Output JSON").action(async (opts) => {
|
|
|
11342
13018
|
json: opts.json
|
|
11343
13019
|
});
|
|
11344
13020
|
} catch (error) {
|
|
11345
|
-
console.error(
|
|
13021
|
+
console.error(kleur20.red(String(error)));
|
|
11346
13022
|
process.exit(1);
|
|
11347
13023
|
}
|
|
11348
13024
|
});
|
|
@@ -11361,7 +13037,7 @@ credentialsTestCommand.option("--json", "Output JSON").action(async (idOrPrefix,
|
|
|
11361
13037
|
process.exitCode = 1;
|
|
11362
13038
|
}
|
|
11363
13039
|
} catch (error) {
|
|
11364
|
-
console.error(
|
|
13040
|
+
console.error(kleur20.red(String(error)));
|
|
11365
13041
|
process.exit(1);
|
|
11366
13042
|
}
|
|
11367
13043
|
});
|
|
@@ -11392,7 +13068,7 @@ credentialsDefaultCommand.option("--json", "Output JSON").action(async (idOrPref
|
|
|
11392
13068
|
json: opts.json
|
|
11393
13069
|
});
|
|
11394
13070
|
} catch (error) {
|
|
11395
|
-
console.error(
|
|
13071
|
+
console.error(kleur20.red(String(error)));
|
|
11396
13072
|
process.exit(1);
|
|
11397
13073
|
}
|
|
11398
13074
|
});
|
|
@@ -11409,7 +13085,7 @@ credentialsRevokeCommand.option("--json", "Output JSON").action(async (idOrPrefi
|
|
|
11409
13085
|
json: opts.json
|
|
11410
13086
|
});
|
|
11411
13087
|
} catch (error) {
|
|
11412
|
-
console.error(
|
|
13088
|
+
console.error(kleur20.red(String(error)));
|
|
11413
13089
|
process.exit(1);
|
|
11414
13090
|
}
|
|
11415
13091
|
});
|
|
@@ -11419,7 +13095,7 @@ configCommand.command("path").description("Print the config file path").option("
|
|
|
11419
13095
|
try {
|
|
11420
13096
|
configPath({ json: opts.json });
|
|
11421
13097
|
} catch (error) {
|
|
11422
|
-
console.error(
|
|
13098
|
+
console.error(kleur20.red(String(error)));
|
|
11423
13099
|
process.exit(1);
|
|
11424
13100
|
}
|
|
11425
13101
|
});
|
|
@@ -11427,7 +13103,7 @@ configCommand.command("list").description("List saved configuration").option("--
|
|
|
11427
13103
|
try {
|
|
11428
13104
|
configList({ json: opts.json });
|
|
11429
13105
|
} catch (error) {
|
|
11430
|
-
console.error(
|
|
13106
|
+
console.error(kleur20.red(String(error)));
|
|
11431
13107
|
process.exit(1);
|
|
11432
13108
|
}
|
|
11433
13109
|
});
|
|
@@ -11435,7 +13111,7 @@ configCommand.command("get").description("Get a config value (controller-url, st
|
|
|
11435
13111
|
try {
|
|
11436
13112
|
configGet({ key, json: opts.json });
|
|
11437
13113
|
} catch (error) {
|
|
11438
|
-
console.error(
|
|
13114
|
+
console.error(kleur20.red(String(error)));
|
|
11439
13115
|
process.exit(1);
|
|
11440
13116
|
}
|
|
11441
13117
|
});
|
|
@@ -11443,7 +13119,7 @@ configCommand.command("set").description("Set a config value (controller-url, st
|
|
|
11443
13119
|
try {
|
|
11444
13120
|
configSet({ key, value, json: opts.json });
|
|
11445
13121
|
} catch (error) {
|
|
11446
|
-
console.error(
|
|
13122
|
+
console.error(kleur20.red(String(error)));
|
|
11447
13123
|
process.exit(1);
|
|
11448
13124
|
}
|
|
11449
13125
|
});
|
|
@@ -11451,7 +13127,7 @@ configCommand.command("unset").description("Unset a config value (controller-url
|
|
|
11451
13127
|
try {
|
|
11452
13128
|
configUnset({ key, json: opts.json });
|
|
11453
13129
|
} catch (error) {
|
|
11454
|
-
console.error(
|
|
13130
|
+
console.error(kleur20.red(String(error)));
|
|
11455
13131
|
process.exit(1);
|
|
11456
13132
|
}
|
|
11457
13133
|
});
|
|
@@ -11471,7 +13147,7 @@ runtimeStartCommand.option("--supabase-access-token <token>", "Supabase session
|
|
|
11471
13147
|
controllerAccessToken: opts.accessToken
|
|
11472
13148
|
});
|
|
11473
13149
|
} catch (error) {
|
|
11474
|
-
console.error(
|
|
13150
|
+
console.error(kleur20.red(String(error)));
|
|
11475
13151
|
process.exit(1);
|
|
11476
13152
|
}
|
|
11477
13153
|
});
|
|
@@ -11479,7 +13155,7 @@ runtimeCommand.command("status").description("Show runtime health").option("--js
|
|
|
11479
13155
|
try {
|
|
11480
13156
|
await runtimeStatus({ json: opts.json });
|
|
11481
13157
|
} catch (error) {
|
|
11482
|
-
console.error(
|
|
13158
|
+
console.error(kleur20.red(String(error)));
|
|
11483
13159
|
process.exit(1);
|
|
11484
13160
|
}
|
|
11485
13161
|
});
|
|
@@ -11487,7 +13163,7 @@ runtimeCommand.command("stop").description("Stop the local Instafy runtime").opt
|
|
|
11487
13163
|
try {
|
|
11488
13164
|
await runtimeStop({ json: opts.json });
|
|
11489
13165
|
} catch (error) {
|
|
11490
|
-
console.error(
|
|
13166
|
+
console.error(kleur20.red(String(error)));
|
|
11491
13167
|
process.exit(1);
|
|
11492
13168
|
}
|
|
11493
13169
|
});
|
|
@@ -11513,7 +13189,7 @@ runtimeTokenCommand.option("--runtime-id <uuid>", "Runtime ID to bind token to")
|
|
|
11513
13189
|
json: opts.json
|
|
11514
13190
|
});
|
|
11515
13191
|
} catch (error) {
|
|
11516
|
-
console.error(
|
|
13192
|
+
console.error(kleur20.red(String(error)));
|
|
11517
13193
|
process.exit(1);
|
|
11518
13194
|
}
|
|
11519
13195
|
});
|
|
@@ -11535,7 +13211,7 @@ diagnosticsRuntimeEventsCommand.action(async (opts) => {
|
|
|
11535
13211
|
accessToken: opts.accessToken
|
|
11536
13212
|
});
|
|
11537
13213
|
} catch (error) {
|
|
11538
|
-
console.error(
|
|
13214
|
+
console.error(kleur20.red(String(error)));
|
|
11539
13215
|
process.exit(1);
|
|
11540
13216
|
}
|
|
11541
13217
|
});
|
|
@@ -11550,7 +13226,7 @@ diagnosticsRunResultCommand.action(async (runId, opts) => {
|
|
|
11550
13226
|
accessToken: opts.accessToken
|
|
11551
13227
|
});
|
|
11552
13228
|
} catch (error) {
|
|
11553
|
-
console.error(
|
|
13229
|
+
console.error(kleur20.red(String(error)));
|
|
11554
13230
|
process.exit(1);
|
|
11555
13231
|
}
|
|
11556
13232
|
});
|
|
@@ -11588,7 +13264,7 @@ supportReportCommand.action(async (summaryParts, opts) => {
|
|
|
11588
13264
|
json: opts.json
|
|
11589
13265
|
});
|
|
11590
13266
|
} catch (error) {
|
|
11591
|
-
console.error(
|
|
13267
|
+
console.error(kleur20.red(String(error)));
|
|
11592
13268
|
process.exit(1);
|
|
11593
13269
|
}
|
|
11594
13270
|
});
|
|
@@ -11623,7 +13299,7 @@ supportListCommand.action(async (opts) => {
|
|
|
11623
13299
|
json: opts.json
|
|
11624
13300
|
});
|
|
11625
13301
|
} catch (error) {
|
|
11626
|
-
console.error(
|
|
13302
|
+
console.error(kleur20.red(String(error)));
|
|
11627
13303
|
process.exit(1);
|
|
11628
13304
|
}
|
|
11629
13305
|
});
|
|
@@ -11639,7 +13315,7 @@ supportShowCommand.action(async (reportId, opts) => {
|
|
|
11639
13315
|
json: opts.json
|
|
11640
13316
|
});
|
|
11641
13317
|
} catch (error) {
|
|
11642
|
-
console.error(
|
|
13318
|
+
console.error(kleur20.red(String(error)));
|
|
11643
13319
|
process.exit(1);
|
|
11644
13320
|
}
|
|
11645
13321
|
});
|
|
@@ -11664,7 +13340,7 @@ supportMessagesCommand.action(async (reportId, opts) => {
|
|
|
11664
13340
|
json: opts.json
|
|
11665
13341
|
});
|
|
11666
13342
|
} catch (error) {
|
|
11667
|
-
console.error(
|
|
13343
|
+
console.error(kleur20.red(String(error)));
|
|
11668
13344
|
process.exit(1);
|
|
11669
13345
|
}
|
|
11670
13346
|
});
|
|
@@ -11685,11 +13361,12 @@ supportReplyCommand.action(async (reportId, messageParts, opts) => {
|
|
|
11685
13361
|
json: opts.json
|
|
11686
13362
|
});
|
|
11687
13363
|
} catch (error) {
|
|
11688
|
-
console.error(
|
|
13364
|
+
console.error(kleur20.red(String(error)));
|
|
11689
13365
|
process.exit(1);
|
|
11690
13366
|
}
|
|
11691
13367
|
});
|
|
11692
13368
|
program.command("git").description("Run git against the Instafy canonical checkout (.instafy/.git)").allowUnknownOption(true);
|
|
13369
|
+
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
13370
|
var tunnelCommand = program.command("tunnel").description("Create and manage shareable tunnels");
|
|
11694
13371
|
failWithGroupHelp(tunnelCommand);
|
|
11695
13372
|
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 +13407,19 @@ tunnelStartCommand.option("--no-detach", "Run in foreground until interrupted").
|
|
|
11730
13407
|
console.log(JSON.stringify(started, null, 2));
|
|
11731
13408
|
return;
|
|
11732
13409
|
}
|
|
11733
|
-
console.log(
|
|
11734
|
-
console.log(
|
|
11735
|
-
console.log(
|
|
13410
|
+
console.log(kleur20.green(`Tunnel started: ${started.url} (tunnelId=${started.tunnelId})`));
|
|
13411
|
+
console.log(kleur20.gray(`pid=${started.pid} \xB7 port=${started.localPort}`));
|
|
13412
|
+
console.log(kleur20.gray(`log: ${started.logFile}`));
|
|
11736
13413
|
console.log("");
|
|
11737
13414
|
console.log("Next:");
|
|
11738
|
-
console.log(`- ${
|
|
11739
|
-
console.log(`- ${
|
|
11740
|
-
console.log(`- ${
|
|
13415
|
+
console.log(`- ${kleur20.cyan(`instafy tunnel list`)}`);
|
|
13416
|
+
console.log(`- ${kleur20.cyan(`instafy tunnel logs ${started.tunnelId} --follow`)}`);
|
|
13417
|
+
console.log(`- ${kleur20.cyan(`instafy tunnel stop ${started.tunnelId}`)}`);
|
|
11741
13418
|
if (process.platform !== "win32") {
|
|
11742
|
-
console.log(
|
|
13419
|
+
console.log(kleur20.gray(`(or) tail -n 200 -f ${started.logFile}`));
|
|
11743
13420
|
}
|
|
11744
13421
|
} catch (error) {
|
|
11745
|
-
console.error(
|
|
13422
|
+
console.error(kleur20.red(String(error)));
|
|
11746
13423
|
process.exit(1);
|
|
11747
13424
|
}
|
|
11748
13425
|
});
|
|
@@ -11754,14 +13431,14 @@ tunnelCommand.command("list").description("List local tunnel sessions started by
|
|
|
11754
13431
|
return;
|
|
11755
13432
|
}
|
|
11756
13433
|
if (tunnels.length === 0) {
|
|
11757
|
-
console.log(
|
|
13434
|
+
console.log(kleur20.yellow("No tunnels found."));
|
|
11758
13435
|
return;
|
|
11759
13436
|
}
|
|
11760
13437
|
for (const tunnel of tunnels) {
|
|
11761
13438
|
console.log(`${tunnel.tunnelId} \xB7 ${tunnel.url} \xB7 port=${tunnel.localPort} \xB7 pid=${tunnel.pid}`);
|
|
11762
13439
|
}
|
|
11763
13440
|
} catch (error) {
|
|
11764
|
-
console.error(
|
|
13441
|
+
console.error(kleur20.red(String(error)));
|
|
11765
13442
|
process.exit(1);
|
|
11766
13443
|
}
|
|
11767
13444
|
});
|
|
@@ -11777,9 +13454,9 @@ tunnelCommand.command("stop").description("Stop a local tunnel session and revok
|
|
|
11777
13454
|
console.log(JSON.stringify(result, null, 2));
|
|
11778
13455
|
return;
|
|
11779
13456
|
}
|
|
11780
|
-
console.log(
|
|
13457
|
+
console.log(kleur20.green(`Tunnel stopped: ${result.tunnelId}`));
|
|
11781
13458
|
} catch (error) {
|
|
11782
|
-
console.error(
|
|
13459
|
+
console.error(kleur20.red(String(error)));
|
|
11783
13460
|
process.exit(1);
|
|
11784
13461
|
}
|
|
11785
13462
|
});
|
|
@@ -11793,10 +13470,81 @@ tunnelCommand.command("logs").description("Show logs for a local tunnel session"
|
|
|
11793
13470
|
json: Boolean(opts.json)
|
|
11794
13471
|
});
|
|
11795
13472
|
} catch (error) {
|
|
11796
|
-
console.error(
|
|
13473
|
+
console.error(kleur20.red(String(error)));
|
|
11797
13474
|
process.exit(1);
|
|
11798
13475
|
}
|
|
11799
13476
|
});
|
|
13477
|
+
function exitWithPreviewError(error) {
|
|
13478
|
+
console.error(kleur20.red(error instanceof Error ? error.message : String(error)));
|
|
13479
|
+
process.exit(error instanceof PreviewCommandError ? error.exitCode : 1);
|
|
13480
|
+
}
|
|
13481
|
+
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");
|
|
13482
|
+
failWithGroupHelp(previewCommand);
|
|
13483
|
+
registerPreviewCheckCommand(previewCommand);
|
|
13484
|
+
var previewOpenCommand = previewCommand.command("open").description(
|
|
13485
|
+
"Open a private preview of a local port (exit 2: nothing listening, 3: port not allowed, 5: previews not available)"
|
|
13486
|
+
).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 /)");
|
|
13487
|
+
addSpaceOption(previewOpenCommand, "Space UUID (defaults to .instafy/space.json or SPACE_ID)");
|
|
13488
|
+
addServerUrlOptions(previewOpenCommand);
|
|
13489
|
+
addAccessTokenOptions(previewOpenCommand, "Instafy access token (defaults to the job token, then the saved `instafy login` token)");
|
|
13490
|
+
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(
|
|
13491
|
+
"--serve-cmd <command>",
|
|
13492
|
+
"How to start the site again after the space sleeps, run from the current directory (one command, no shell); default: read from the process listening on the port"
|
|
13493
|
+
).option("--json", "Output JSON").action(async (opts) => {
|
|
13494
|
+
try {
|
|
13495
|
+
const result = await previewOpen({
|
|
13496
|
+
project: resolveSpaceIdOption(opts),
|
|
13497
|
+
controllerUrl: opts.serverUrl,
|
|
13498
|
+
controllerToken: opts.accessToken,
|
|
13499
|
+
conversationId: opts.conversation,
|
|
13500
|
+
port: opts.port,
|
|
13501
|
+
title: opts.title,
|
|
13502
|
+
path: opts.path,
|
|
13503
|
+
ratholeBin: opts.ratholeBin,
|
|
13504
|
+
serveCmd: opts.serveCmd
|
|
13505
|
+
});
|
|
13506
|
+
printPreviewOpen(result, Boolean(opts.json));
|
|
13507
|
+
} catch (error) {
|
|
13508
|
+
exitWithPreviewError(error);
|
|
13509
|
+
}
|
|
13510
|
+
});
|
|
13511
|
+
var previewListCommand = previewCommand.command("list").description("List this space's previews");
|
|
13512
|
+
addSpaceOption(previewListCommand, "Space UUID (defaults to .instafy/space.json or SPACE_ID)");
|
|
13513
|
+
addServerUrlOptions(previewListCommand);
|
|
13514
|
+
addAccessTokenOptions(previewListCommand, "Instafy access token (defaults to the job token, then the saved `instafy login` token)");
|
|
13515
|
+
previewListCommand.option("--json", "Output JSON").action(async (opts) => {
|
|
13516
|
+
try {
|
|
13517
|
+
const previews = await previewList({
|
|
13518
|
+
project: resolveSpaceIdOption(opts),
|
|
13519
|
+
controllerUrl: opts.serverUrl,
|
|
13520
|
+
controllerToken: opts.accessToken
|
|
13521
|
+
});
|
|
13522
|
+
printPreviewList(previews, Boolean(opts.json));
|
|
13523
|
+
} catch (error) {
|
|
13524
|
+
exitWithPreviewError(error);
|
|
13525
|
+
}
|
|
13526
|
+
});
|
|
13527
|
+
var previewCloseCommand = previewCommand.command("close").description("Close a preview: stop its local client and revoke it").argument("<previewId>", "Preview ID (from `instafy preview list`)");
|
|
13528
|
+
addSpaceOption(previewCloseCommand, "Space UUID (defaults to .instafy/space.json or SPACE_ID)");
|
|
13529
|
+
addServerUrlOptions(previewCloseCommand);
|
|
13530
|
+
addAccessTokenOptions(previewCloseCommand, "Instafy access token (defaults to the job token, then the saved `instafy login` token)");
|
|
13531
|
+
previewCloseCommand.option("--json", "Output JSON").action(async (previewId, opts) => {
|
|
13532
|
+
try {
|
|
13533
|
+
const result = await previewClose({
|
|
13534
|
+
previewId,
|
|
13535
|
+
project: resolveSpaceIdOption(opts),
|
|
13536
|
+
controllerUrl: opts.serverUrl,
|
|
13537
|
+
controllerToken: opts.accessToken
|
|
13538
|
+
});
|
|
13539
|
+
if (opts.json) {
|
|
13540
|
+
console.log(JSON.stringify(result, null, 2));
|
|
13541
|
+
return;
|
|
13542
|
+
}
|
|
13543
|
+
console.log(kleur20.green(`Preview closed: ${result.previewId}`));
|
|
13544
|
+
} catch (error) {
|
|
13545
|
+
exitWithPreviewError(error);
|
|
13546
|
+
}
|
|
13547
|
+
});
|
|
11800
13548
|
var orgCommand = program.command("team").alias("org").description("Team utilities");
|
|
11801
13549
|
failWithGroupHelp(orgCommand);
|
|
11802
13550
|
var orgListCommand = orgCommand.command("list").description("List teams for your account");
|
|
@@ -11809,7 +13557,7 @@ orgListCommand.option("--access-token <token>", "Instafy or Supabase access toke
|
|
|
11809
13557
|
json: opts.json
|
|
11810
13558
|
});
|
|
11811
13559
|
} catch (error) {
|
|
11812
|
-
console.error(
|
|
13560
|
+
console.error(kleur20.red(String(error)));
|
|
11813
13561
|
process.exit(1);
|
|
11814
13562
|
}
|
|
11815
13563
|
});
|
|
@@ -11830,7 +13578,7 @@ teamMembersCommand.action(async (opts) => {
|
|
|
11830
13578
|
json: opts.json
|
|
11831
13579
|
});
|
|
11832
13580
|
} catch (error) {
|
|
11833
|
-
console.error(
|
|
13581
|
+
console.error(kleur20.red(String(error)));
|
|
11834
13582
|
process.exit(1);
|
|
11835
13583
|
}
|
|
11836
13584
|
});
|
|
@@ -11849,7 +13597,7 @@ teamInviteCommand.option("--role <role>", "owner, admin, builder, or viewer (def
|
|
|
11849
13597
|
json: opts.json
|
|
11850
13598
|
});
|
|
11851
13599
|
} catch (error) {
|
|
11852
|
-
console.error(
|
|
13600
|
+
console.error(kleur20.red(String(error)));
|
|
11853
13601
|
process.exit(1);
|
|
11854
13602
|
}
|
|
11855
13603
|
});
|
|
@@ -11868,7 +13616,7 @@ teamInviteLinkCommand.option("--role <role>", "builder or viewer (default: build
|
|
|
11868
13616
|
json: opts.json
|
|
11869
13617
|
});
|
|
11870
13618
|
} catch (error) {
|
|
11871
|
-
console.error(
|
|
13619
|
+
console.error(kleur20.red(String(error)));
|
|
11872
13620
|
process.exit(1);
|
|
11873
13621
|
}
|
|
11874
13622
|
});
|
|
@@ -11885,7 +13633,7 @@ teamInvitesCommand.action(async (opts) => {
|
|
|
11885
13633
|
json: opts.json
|
|
11886
13634
|
});
|
|
11887
13635
|
} catch (error) {
|
|
11888
|
-
console.error(
|
|
13636
|
+
console.error(kleur20.red(String(error)));
|
|
11889
13637
|
process.exit(1);
|
|
11890
13638
|
}
|
|
11891
13639
|
});
|
|
@@ -11904,7 +13652,7 @@ teamAddMemberCommand.requiredOption("--user-id <uuid>", "User id of an existing
|
|
|
11904
13652
|
json: opts.json
|
|
11905
13653
|
});
|
|
11906
13654
|
} catch (error) {
|
|
11907
|
-
console.error(
|
|
13655
|
+
console.error(kleur20.red(String(error)));
|
|
11908
13656
|
process.exit(1);
|
|
11909
13657
|
}
|
|
11910
13658
|
});
|
|
@@ -11922,7 +13670,7 @@ teamAcceptCommand.action(async (token, opts) => {
|
|
|
11922
13670
|
json: opts.json
|
|
11923
13671
|
});
|
|
11924
13672
|
} catch (error) {
|
|
11925
|
-
console.error(
|
|
13673
|
+
console.error(kleur20.red(String(error)));
|
|
11926
13674
|
process.exit(1);
|
|
11927
13675
|
}
|
|
11928
13676
|
});
|
|
@@ -11941,7 +13689,7 @@ teamRevokeInviteCommand.option("--yes", "Skip the confirmation prompt").action(a
|
|
|
11941
13689
|
json: opts.json
|
|
11942
13690
|
});
|
|
11943
13691
|
} catch (error) {
|
|
11944
|
-
console.error(
|
|
13692
|
+
console.error(kleur20.red(String(error)));
|
|
11945
13693
|
process.exit(1);
|
|
11946
13694
|
}
|
|
11947
13695
|
});
|
|
@@ -11960,7 +13708,7 @@ teamRevokeLinkCommand.option("--yes", "Skip the confirmation prompt").action(asy
|
|
|
11960
13708
|
json: opts.json
|
|
11961
13709
|
});
|
|
11962
13710
|
} catch (error) {
|
|
11963
|
-
console.error(
|
|
13711
|
+
console.error(kleur20.red(String(error)));
|
|
11964
13712
|
process.exit(1);
|
|
11965
13713
|
}
|
|
11966
13714
|
});
|
|
@@ -11985,17 +13733,17 @@ profileCommand.command("list").description("List saved CLI profiles").option("--
|
|
|
11985
13733
|
return;
|
|
11986
13734
|
}
|
|
11987
13735
|
if (profiles.length === 0) {
|
|
11988
|
-
console.log(
|
|
11989
|
-
console.log(`Create one with: ${
|
|
13736
|
+
console.log(kleur20.yellow("No profiles found."));
|
|
13737
|
+
console.log(`Create one with: ${kleur20.cyan("instafy login --profile <name>")}`);
|
|
11990
13738
|
return;
|
|
11991
13739
|
}
|
|
11992
|
-
console.log(
|
|
13740
|
+
console.log(kleur20.green("Instafy CLI profiles"));
|
|
11993
13741
|
for (const profile of profiles) {
|
|
11994
|
-
const token = profile.accessTokenSet ?
|
|
11995
|
-
console.log(`- ${
|
|
13742
|
+
const token = profile.accessTokenSet ? kleur20.green("token") : kleur20.yellow("no-token");
|
|
13743
|
+
console.log(`- ${kleur20.cyan(profile.name)} (${token})`);
|
|
11996
13744
|
}
|
|
11997
13745
|
} catch (error) {
|
|
11998
|
-
console.error(
|
|
13746
|
+
console.error(kleur20.red(String(error)));
|
|
11999
13747
|
process.exit(1);
|
|
12000
13748
|
}
|
|
12001
13749
|
});
|
|
@@ -12015,7 +13763,7 @@ historyMessagesCommand.option("--limit <n>", "Max messages to return (1-200, def
|
|
|
12015
13763
|
pretty: opts.pretty
|
|
12016
13764
|
});
|
|
12017
13765
|
} catch (error) {
|
|
12018
|
-
console.error(
|
|
13766
|
+
console.error(kleur20.red(String(error)));
|
|
12019
13767
|
process.exit(1);
|
|
12020
13768
|
}
|
|
12021
13769
|
});
|
|
@@ -12032,7 +13780,7 @@ historyRunsCommand.option("--limit <n>", "Max runs to return (1-200, default: 50
|
|
|
12032
13780
|
pretty: opts.pretty
|
|
12033
13781
|
});
|
|
12034
13782
|
} catch (error) {
|
|
12035
|
-
console.error(
|
|
13783
|
+
console.error(kleur20.red(String(error)));
|
|
12036
13784
|
process.exit(1);
|
|
12037
13785
|
}
|
|
12038
13786
|
});
|
|
@@ -12049,7 +13797,7 @@ historyConversationsCommand.option("--limit <n>", "Max conversations to return (
|
|
|
12049
13797
|
pretty: opts.pretty
|
|
12050
13798
|
});
|
|
12051
13799
|
} catch (error) {
|
|
12052
|
-
console.error(
|
|
13800
|
+
console.error(kleur20.red(String(error)));
|
|
12053
13801
|
process.exit(1);
|
|
12054
13802
|
}
|
|
12055
13803
|
});
|
|
@@ -12070,7 +13818,7 @@ conversationCreateCommand.action(async (opts) => {
|
|
|
12070
13818
|
json: opts.json
|
|
12071
13819
|
});
|
|
12072
13820
|
} catch (error) {
|
|
12073
|
-
console.error(
|
|
13821
|
+
console.error(kleur20.red(String(error)));
|
|
12074
13822
|
process.exit(1);
|
|
12075
13823
|
}
|
|
12076
13824
|
});
|
|
@@ -12088,7 +13836,7 @@ conversationListCommand.action(async (opts) => {
|
|
|
12088
13836
|
json: opts.json
|
|
12089
13837
|
});
|
|
12090
13838
|
} catch (error) {
|
|
12091
|
-
console.error(
|
|
13839
|
+
console.error(kleur20.red(String(error)));
|
|
12092
13840
|
process.exit(1);
|
|
12093
13841
|
}
|
|
12094
13842
|
});
|
|
@@ -12107,7 +13855,7 @@ conversationSearchCommand.action(async (queryParts, opts) => {
|
|
|
12107
13855
|
json: opts.json
|
|
12108
13856
|
});
|
|
12109
13857
|
} catch (error) {
|
|
12110
|
-
console.error(
|
|
13858
|
+
console.error(kleur20.red(String(error)));
|
|
12111
13859
|
process.exit(1);
|
|
12112
13860
|
}
|
|
12113
13861
|
});
|
|
@@ -12131,7 +13879,7 @@ conversationGrepCommand.action(async (parts, opts) => {
|
|
|
12131
13879
|
});
|
|
12132
13880
|
process.exitCode = found ? 0 : 1;
|
|
12133
13881
|
} catch (error) {
|
|
12134
|
-
console.error(
|
|
13882
|
+
console.error(kleur20.red(String(error)));
|
|
12135
13883
|
process.exitCode = 2;
|
|
12136
13884
|
}
|
|
12137
13885
|
});
|
|
@@ -12151,7 +13899,7 @@ conversationContextCommand.action(async (conversationId, messageId, opts) => {
|
|
|
12151
13899
|
accessToken: opts.accessToken
|
|
12152
13900
|
});
|
|
12153
13901
|
} catch (error) {
|
|
12154
|
-
console.error(
|
|
13902
|
+
console.error(kleur20.red(String(error)));
|
|
12155
13903
|
process.exitCode = 2;
|
|
12156
13904
|
}
|
|
12157
13905
|
});
|
|
@@ -12173,7 +13921,7 @@ conversationShowCommand.action(async (targetParts, opts) => {
|
|
|
12173
13921
|
json: opts.json
|
|
12174
13922
|
});
|
|
12175
13923
|
} catch (error) {
|
|
12176
|
-
console.error(
|
|
13924
|
+
console.error(kleur20.red(String(error)));
|
|
12177
13925
|
process.exit(1);
|
|
12178
13926
|
}
|
|
12179
13927
|
});
|
|
@@ -12183,6 +13931,10 @@ async function runCli(argv = process.argv) {
|
|
|
12183
13931
|
return;
|
|
12184
13932
|
}
|
|
12185
13933
|
const args = argv.slice(2);
|
|
13934
|
+
if (args[0] === "bind") {
|
|
13935
|
+
process.exitCode = await runBind(args.slice(1));
|
|
13936
|
+
return;
|
|
13937
|
+
}
|
|
12186
13938
|
if (args[0] === "git") {
|
|
12187
13939
|
if (args[1] === "credential") {
|
|
12188
13940
|
try {
|