@staff0rd/assist 0.750.0 → 0.751.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 +8 -6
- package/dist/commands/sessions/web/bundle.js +1 -1
- package/dist/index.js +152 -88
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@staff0rd/assist",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.751.0",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -10696,7 +10696,7 @@ function startWebServer(label2, port, handler, initialPath, open = true) {
|
|
|
10696
10696
|
const server = createServer((req, res) => {
|
|
10697
10697
|
runHandler(handler, req, res, port);
|
|
10698
10698
|
});
|
|
10699
|
-
server.listen(port, () => {
|
|
10699
|
+
server.listen(port, "127.0.0.1", () => {
|
|
10700
10700
|
console.log(chalk103.green(`${label2}: ${url}`));
|
|
10701
10701
|
console.log(chalk103.dim("Press Ctrl+C to stop"));
|
|
10702
10702
|
if (open) {
|
|
@@ -47380,7 +47380,7 @@ function tailscaleServeRemediation(spec, error) {
|
|
|
47380
47380
|
if (status3 !== void 0 && status3 !== 502) return void 0;
|
|
47381
47381
|
const { hostname: hostname2, port } = new URL(spec.url);
|
|
47382
47382
|
const servePort = port || "443";
|
|
47383
|
-
return status3 === 502 ? `tailscale serve on ${hostname2} answered but nothing listens on 127.0.0.1:${servePort} behind it \u2014 is ${spec.name}'s web server running (project-switch webservers)?` : `nothing serves https on ${servePort} on ${hostname2} \u2014 run \`
|
|
47383
|
+
return status3 === 502 ? `tailscale serve on ${hostname2} answered but nothing listens on 127.0.0.1:${servePort} behind it \u2014 is ${spec.name}'s web server running (project-switch webservers)?` : `nothing serves https on ${servePort} on ${hostname2} \u2014 run \`assist sessions nodes serve --port ${servePort}\` on ${spec.name}`;
|
|
47384
47384
|
}
|
|
47385
47385
|
|
|
47386
47386
|
// src/commands/sessions/nodes/isTailscaleLink.ts
|
|
@@ -47643,15 +47643,74 @@ async function doctorNodes(name, options2) {
|
|
|
47643
47643
|
else printReport(report2);
|
|
47644
47644
|
}
|
|
47645
47645
|
|
|
47646
|
+
// src/commands/sessions/nodes/listNodes.ts
|
|
47647
|
+
import chalk254 from "chalk";
|
|
47648
|
+
async function nodesReport() {
|
|
47649
|
+
const live = await queryNodes();
|
|
47650
|
+
if (live)
|
|
47651
|
+
return { local: live.local, daemonRunning: true, links: live.links };
|
|
47652
|
+
return {
|
|
47653
|
+
local: resolveNodeName(),
|
|
47654
|
+
daemonRunning: false,
|
|
47655
|
+
links: loadLinkSpecs().map((link3) => ({ ...link3, state: "disconnected" }))
|
|
47656
|
+
};
|
|
47657
|
+
}
|
|
47658
|
+
async function listNodes(options2) {
|
|
47659
|
+
const report2 = await nodesReport();
|
|
47660
|
+
if (options2.json) {
|
|
47661
|
+
console.log(JSON.stringify(report2, null, 2));
|
|
47662
|
+
return;
|
|
47663
|
+
}
|
|
47664
|
+
console.log(`${report2.local} ${chalk254.dim("(this node)")}`);
|
|
47665
|
+
if (!report2.daemonRunning)
|
|
47666
|
+
console.log(chalk254.dim(" daemon not running; showing configured links"));
|
|
47667
|
+
if (report2.links.length === 0) console.log(chalk254.dim(" no links"));
|
|
47668
|
+
for (const link3 of report2.links) console.log(formatLink(link3));
|
|
47669
|
+
}
|
|
47670
|
+
|
|
47671
|
+
// src/commands/sessions/nodes/nodeLogs.ts
|
|
47672
|
+
async function fetchLogTail(name, lines2) {
|
|
47673
|
+
if (name === resolveNodeName())
|
|
47674
|
+
return {
|
|
47675
|
+
nodeName: name,
|
|
47676
|
+
path: daemonPaths.log,
|
|
47677
|
+
lines: readLogTail(daemonPaths.log, lines2)
|
|
47678
|
+
};
|
|
47679
|
+
const link3 = findLinkSpec(name);
|
|
47680
|
+
if (!link3)
|
|
47681
|
+
throw new Error(`No link named ${name}; see assist sessions nodes`);
|
|
47682
|
+
try {
|
|
47683
|
+
return await fetchPeerJson(
|
|
47684
|
+
link3.url,
|
|
47685
|
+
`/api/daemon-log?lines=${lines2}`
|
|
47686
|
+
);
|
|
47687
|
+
} catch (error) {
|
|
47688
|
+
throw new Error(
|
|
47689
|
+
`Could not read ${name}'s daemon log: ${describePeerError(error)}. Run assist sessions nodes doctor ${name}`
|
|
47690
|
+
);
|
|
47691
|
+
}
|
|
47692
|
+
}
|
|
47693
|
+
async function nodeLogs(name, options2) {
|
|
47694
|
+
const lines2 = Number.parseInt(options2.lines, 10);
|
|
47695
|
+
if (!Number.isInteger(lines2) || lines2 < 1)
|
|
47696
|
+
throw new Error(`-n must be a positive integer, got ${options2.lines}`);
|
|
47697
|
+
const tail = await fetchLogTail(name, lines2);
|
|
47698
|
+
if (options2.json) {
|
|
47699
|
+
console.log(JSON.stringify(tail, null, 2));
|
|
47700
|
+
return;
|
|
47701
|
+
}
|
|
47702
|
+
for (const line of tail.lines) console.log(line);
|
|
47703
|
+
}
|
|
47704
|
+
|
|
47646
47705
|
// src/commands/sessions/nodes/linkNode.ts
|
|
47647
|
-
import
|
|
47706
|
+
import chalk256 from "chalk";
|
|
47648
47707
|
|
|
47649
47708
|
// src/commands/sessions/nodes/notifyDaemonLinks.ts
|
|
47650
|
-
import
|
|
47709
|
+
import chalk255 from "chalk";
|
|
47651
47710
|
async function notifyDaemonLinks() {
|
|
47652
47711
|
if (!await isDaemonRunning()) {
|
|
47653
47712
|
console.log(
|
|
47654
|
-
|
|
47713
|
+
chalk255.yellow(
|
|
47655
47714
|
"The sessions daemon is not running; it loads the links when it starts."
|
|
47656
47715
|
)
|
|
47657
47716
|
);
|
|
@@ -47659,11 +47718,11 @@ async function notifyDaemonLinks() {
|
|
|
47659
47718
|
}
|
|
47660
47719
|
try {
|
|
47661
47720
|
await sendToDaemonAwaitAck({ type: "reload-links" });
|
|
47662
|
-
console.log(
|
|
47721
|
+
console.log(chalk255.dim("The sessions daemon reloaded its links."));
|
|
47663
47722
|
} catch (error) {
|
|
47664
47723
|
const message3 = error instanceof Error ? error.message : String(error);
|
|
47665
47724
|
console.log(
|
|
47666
|
-
|
|
47725
|
+
chalk255.yellow(
|
|
47667
47726
|
`Could not notify the sessions daemon (${message3}); run \`assist daemon restart\` to apply the change.`
|
|
47668
47727
|
)
|
|
47669
47728
|
);
|
|
@@ -47769,103 +47828,108 @@ async function linkNode(name, url, options2) {
|
|
|
47769
47828
|
const link3 = await buildLink(name, url, options2, others);
|
|
47770
47829
|
await writeLinks([...others, link3]);
|
|
47771
47830
|
console.log(
|
|
47772
|
-
|
|
47831
|
+
chalk256.green(`Linked ${name} (${describeTarget(toLinkSpec(link3))})`)
|
|
47773
47832
|
);
|
|
47774
47833
|
await notifyDaemonLinks();
|
|
47775
47834
|
}
|
|
47776
47835
|
|
|
47777
|
-
// src/commands/sessions/nodes/
|
|
47778
|
-
|
|
47779
|
-
|
|
47780
|
-
|
|
47781
|
-
|
|
47782
|
-
|
|
47783
|
-
|
|
47784
|
-
|
|
47785
|
-
|
|
47786
|
-
|
|
47787
|
-
|
|
47788
|
-
|
|
47789
|
-
|
|
47790
|
-
|
|
47791
|
-
|
|
47792
|
-
|
|
47793
|
-
|
|
47794
|
-
}
|
|
47795
|
-
console.log(`${report2.local} ${chalk256.dim("(this node)")}`);
|
|
47796
|
-
if (!report2.daemonRunning)
|
|
47797
|
-
console.log(chalk256.dim(" daemon not running; showing configured links"));
|
|
47798
|
-
if (report2.links.length === 0) console.log(chalk256.dim(" no links"));
|
|
47799
|
-
for (const link3 of report2.links) console.log(formatLink(link3));
|
|
47836
|
+
// src/commands/sessions/nodes/registerLinkCommand.ts
|
|
47837
|
+
function registerLinkCommand(nodes) {
|
|
47838
|
+
nodes.command("link <name> [url]").description(
|
|
47839
|
+
"Link a peer node by its web server URL, by its Tailscale name with --tailscale and --port, or over ssh with --ssh and --port (name must match the peer's sessions.nodeName)"
|
|
47840
|
+
).option(
|
|
47841
|
+
"--tailscale <host>",
|
|
47842
|
+
"Dial the peer at https://<host>.<tailnet>.ts.net:<port> (the peer runs tailscale serve for its web server port)"
|
|
47843
|
+
).option(
|
|
47844
|
+
"--ssh <alias>",
|
|
47845
|
+
"Tunnel to the peer through this ~/.ssh/config alias"
|
|
47846
|
+
).option(
|
|
47847
|
+
"--port <port>",
|
|
47848
|
+
"The peer's web server port (with --tailscale or --ssh)"
|
|
47849
|
+
).option(
|
|
47850
|
+
"--local-port <port>",
|
|
47851
|
+
"Local end of the ssh tunnel (default: 43000 + port % 1000, bumped past other links)"
|
|
47852
|
+
).action(linkNode);
|
|
47800
47853
|
}
|
|
47801
47854
|
|
|
47802
|
-
// src/commands/sessions/nodes/
|
|
47803
|
-
|
|
47804
|
-
|
|
47805
|
-
|
|
47806
|
-
|
|
47807
|
-
|
|
47808
|
-
|
|
47809
|
-
|
|
47810
|
-
const
|
|
47811
|
-
if (!
|
|
47812
|
-
throw new Error(`No link named ${name}; see assist sessions nodes`);
|
|
47813
|
-
try {
|
|
47814
|
-
return await fetchPeerJson(
|
|
47815
|
-
link3.url,
|
|
47816
|
-
`/api/daemon-log?lines=${lines2}`
|
|
47817
|
-
);
|
|
47818
|
-
} catch (error) {
|
|
47855
|
+
// src/commands/sessions/nodes/serveNode.ts
|
|
47856
|
+
import { execFile as execFile14, spawnSync as spawnSync12 } from "child_process";
|
|
47857
|
+
import { promisify as promisify13 } from "util";
|
|
47858
|
+
import chalk257 from "chalk";
|
|
47859
|
+
|
|
47860
|
+
// src/commands/sessions/nodes/planServe.ts
|
|
47861
|
+
function planServe(status3, serveConfig2, port) {
|
|
47862
|
+
tailnetSuffix(status3);
|
|
47863
|
+
const fqdn = status3.Self?.DNSName?.replace(/\.$/, "");
|
|
47864
|
+
if (!fqdn)
|
|
47819
47865
|
throw new Error(
|
|
47820
|
-
|
|
47866
|
+
`\`${tailscaleCli()} status --json\` reports no DNS name for this machine`
|
|
47821
47867
|
);
|
|
47822
|
-
}
|
|
47868
|
+
const target = `http://127.0.0.1:${port}`;
|
|
47869
|
+
const served = serveConfig2.Web?.[`${fqdn}:${port}`]?.Handlers?.["/"]?.Proxy;
|
|
47870
|
+
return {
|
|
47871
|
+
host: fqdn.split(".")[0],
|
|
47872
|
+
url: `https://${fqdn}:${port}`,
|
|
47873
|
+
target,
|
|
47874
|
+
alreadyServing: served?.replace(/\/$/, "") === target,
|
|
47875
|
+
args: ["serve", "--bg", `--https=${port}`, target]
|
|
47876
|
+
};
|
|
47823
47877
|
}
|
|
47824
|
-
|
|
47825
|
-
|
|
47826
|
-
|
|
47827
|
-
|
|
47828
|
-
const
|
|
47829
|
-
|
|
47830
|
-
|
|
47831
|
-
|
|
47878
|
+
|
|
47879
|
+
// src/commands/sessions/nodes/serveNode.ts
|
|
47880
|
+
var execFileAsync12 = promisify13(execFile14);
|
|
47881
|
+
async function serveConfig() {
|
|
47882
|
+
const { stdout } = await execFileAsync12(
|
|
47883
|
+
tailscaleCli(),
|
|
47884
|
+
["serve", "status", "--json"],
|
|
47885
|
+
{ encoding: "utf8", timeout: 1e4, windowsHide: true }
|
|
47886
|
+
);
|
|
47887
|
+
return stdout.trim() ? JSON.parse(stdout) : {};
|
|
47888
|
+
}
|
|
47889
|
+
function runServe(args) {
|
|
47890
|
+
console.log(chalk257.dim(`${tailscaleCli()} ${args.join(" ")}`));
|
|
47891
|
+
const result = spawnSync12(tailscaleCli(), args, { stdio: "inherit" });
|
|
47892
|
+
if (result.error) throw result.error;
|
|
47893
|
+
if (result.status !== 0)
|
|
47894
|
+
throw new Error(`tailscale serve exited with ${result.status}`);
|
|
47895
|
+
}
|
|
47896
|
+
async function serveNode(options2) {
|
|
47897
|
+
const port = parsePort("--port", options2.port);
|
|
47898
|
+
const plan2 = planServe(await tailscaleStatus(), await serveConfig(), port);
|
|
47899
|
+
if (plan2.alreadyServing) {
|
|
47900
|
+
console.log(chalk257.dim(`${plan2.url} already serves ${plan2.target}`));
|
|
47901
|
+
} else {
|
|
47902
|
+
runServe(plan2.args);
|
|
47903
|
+
console.log(chalk257.green(`Serving ${plan2.target} at ${plan2.url}`));
|
|
47832
47904
|
}
|
|
47833
|
-
|
|
47905
|
+
console.log("Link this node from another node with:");
|
|
47906
|
+
console.log(
|
|
47907
|
+
` assist sessions nodes link ${resolveNodeName()} --tailscale ${plan2.host} --port ${port}`
|
|
47908
|
+
);
|
|
47834
47909
|
}
|
|
47835
47910
|
|
|
47836
47911
|
// src/commands/sessions/nodes/unlinkNode.ts
|
|
47837
|
-
import
|
|
47912
|
+
import chalk258 from "chalk";
|
|
47838
47913
|
async function unlinkNode(name) {
|
|
47839
47914
|
const links2 = readLinks();
|
|
47840
47915
|
const remaining = links2.filter((link3) => link3.name !== name);
|
|
47841
47916
|
if (remaining.length === links2.length) {
|
|
47842
|
-
console.log(
|
|
47917
|
+
console.log(chalk258.yellow(`No link named ${name}`));
|
|
47843
47918
|
return;
|
|
47844
47919
|
}
|
|
47845
47920
|
await writeLinks(remaining);
|
|
47846
|
-
console.log(
|
|
47921
|
+
console.log(chalk258.green(`Unlinked ${name}`));
|
|
47847
47922
|
await notifyDaemonLinks();
|
|
47848
47923
|
}
|
|
47849
47924
|
|
|
47850
47925
|
// src/commands/sessions/nodes/registerNodes.ts
|
|
47851
47926
|
function registerNodes(sessions) {
|
|
47852
47927
|
const cmd = sessions.command("nodes").description("List this node and every linked node with its link state").option("--json", "Output as JSON").action(listNodes);
|
|
47853
|
-
cmd
|
|
47854
|
-
"Link a peer node by its web server URL, by its Tailscale name with --tailscale and --port, or over ssh with --ssh and --port (name must match the peer's sessions.nodeName)"
|
|
47855
|
-
).option(
|
|
47856
|
-
"--tailscale <host>",
|
|
47857
|
-
"Dial the peer at https://<host>.<tailnet>.ts.net:<port> (the peer runs tailscale serve for its web server port)"
|
|
47858
|
-
).option(
|
|
47859
|
-
"--ssh <alias>",
|
|
47860
|
-
"Tunnel to the peer through this ~/.ssh/config alias"
|
|
47861
|
-
).option(
|
|
47862
|
-
"--port <port>",
|
|
47863
|
-
"The peer's web server port (with --tailscale or --ssh)"
|
|
47864
|
-
).option(
|
|
47865
|
-
"--local-port <port>",
|
|
47866
|
-
"Local end of the ssh tunnel (default: 43000 + port % 1000, bumped past other links)"
|
|
47867
|
-
).action(linkNode);
|
|
47928
|
+
registerLinkCommand(cmd);
|
|
47868
47929
|
cmd.command("unlink <name>").description("Remove a linked node").action(unlinkNode);
|
|
47930
|
+
cmd.command("serve").description(
|
|
47931
|
+
"Expose this node's web server on the tailnet with tailscale serve (idempotent) and print the command that links it"
|
|
47932
|
+
).option("-p, --port <port>", "This node's web server port", "3100").action(serveNode);
|
|
47869
47933
|
cmd.command("doctor [name]").description(
|
|
47870
47934
|
"Probe each hop of every link (or one) in order and stop at the first failure with a remediation"
|
|
47871
47935
|
).option("--json", "Output as JSON").action(
|
|
@@ -47988,7 +48052,7 @@ async function renameSession(title) {
|
|
|
47988
48052
|
|
|
47989
48053
|
// src/commands/sessions/summarise.ts
|
|
47990
48054
|
import * as fs58 from "fs";
|
|
47991
|
-
import
|
|
48055
|
+
import chalk259 from "chalk";
|
|
47992
48056
|
|
|
47993
48057
|
// src/commands/sessions/summarise/shared.ts
|
|
47994
48058
|
import * as fs57 from "fs";
|
|
@@ -48047,22 +48111,22 @@ ${firstMessage}`);
|
|
|
48047
48111
|
async function summarise2(options2) {
|
|
48048
48112
|
const files = await discoverSessionFiles();
|
|
48049
48113
|
if (files.length === 0) {
|
|
48050
|
-
console.log(
|
|
48114
|
+
console.log(chalk259.yellow("No sessions found."));
|
|
48051
48115
|
return;
|
|
48052
48116
|
}
|
|
48053
48117
|
const toProcess = selectCandidates(files, options2);
|
|
48054
48118
|
if (toProcess.length === 0) {
|
|
48055
|
-
console.log(
|
|
48119
|
+
console.log(chalk259.green("All sessions already summarised."));
|
|
48056
48120
|
return;
|
|
48057
48121
|
}
|
|
48058
48122
|
console.log(
|
|
48059
|
-
|
|
48123
|
+
chalk259.cyan(
|
|
48060
48124
|
`Summarising ${toProcess.length} session(s) (${files.length} total)\u2026`
|
|
48061
48125
|
)
|
|
48062
48126
|
);
|
|
48063
48127
|
const { succeeded, failed: failed3 } = processSessions(toProcess);
|
|
48064
48128
|
console.log(
|
|
48065
|
-
|
|
48129
|
+
chalk259.green(`Done: ${succeeded} summarised`) + (failed3 > 0 ? chalk259.yellow(`, ${failed3} skipped`) : "")
|
|
48066
48130
|
);
|
|
48067
48131
|
}
|
|
48068
48132
|
function selectCandidates(files, options2) {
|
|
@@ -48082,16 +48146,16 @@ function processSessions(files) {
|
|
|
48082
48146
|
let failed3 = 0;
|
|
48083
48147
|
for (let i = 0; i < files.length; i++) {
|
|
48084
48148
|
const file = files[i];
|
|
48085
|
-
process.stdout.write(
|
|
48149
|
+
process.stdout.write(chalk259.dim(` [${i + 1}/${files.length}] `));
|
|
48086
48150
|
const summary = summariseSession(file);
|
|
48087
48151
|
if (summary) {
|
|
48088
48152
|
writeSummary(file, summary);
|
|
48089
48153
|
succeeded++;
|
|
48090
|
-
process.stdout.write(`${
|
|
48154
|
+
process.stdout.write(`${chalk259.green("\u2713")} ${summary}
|
|
48091
48155
|
`);
|
|
48092
48156
|
} else {
|
|
48093
48157
|
failed3++;
|
|
48094
|
-
process.stdout.write(` ${
|
|
48158
|
+
process.stdout.write(` ${chalk259.yellow("skip")}
|
|
48095
48159
|
`);
|
|
48096
48160
|
}
|
|
48097
48161
|
}
|
|
@@ -48121,10 +48185,10 @@ function registerSessions(program2) {
|
|
|
48121
48185
|
}
|
|
48122
48186
|
|
|
48123
48187
|
// src/reportCliError.ts
|
|
48124
|
-
import
|
|
48188
|
+
import chalk260 from "chalk";
|
|
48125
48189
|
function reportCliError(error) {
|
|
48126
48190
|
if (error instanceof InvalidItemIdError || error instanceof AmbiguousRepoConfigError || error instanceof UnknownRepoConfigError || error instanceof MissingRunCwdError || error instanceof MiroExtractError) {
|
|
48127
|
-
console.error(
|
|
48191
|
+
console.error(chalk260.red(error.message));
|
|
48128
48192
|
} else {
|
|
48129
48193
|
console.error(error);
|
|
48130
48194
|
}
|