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