@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/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.750.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 \`tailscale serve --bg --https=${servePort} http://127.0.0.1:${servePort}\` on ${spec.name} (tailscale.exe under WSL)`;
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 chalk255 from "chalk";
47706
+ import chalk256 from "chalk";
47648
47707
 
47649
47708
  // src/commands/sessions/nodes/notifyDaemonLinks.ts
47650
- import chalk254 from "chalk";
47709
+ import chalk255 from "chalk";
47651
47710
  async function notifyDaemonLinks() {
47652
47711
  if (!await isDaemonRunning()) {
47653
47712
  console.log(
47654
- chalk254.yellow(
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(chalk254.dim("The sessions daemon reloaded its links."));
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
- chalk254.yellow(
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
- chalk255.green(`Linked ${name} (${describeTarget(toLinkSpec(link3))})`)
47831
+ chalk256.green(`Linked ${name} (${describeTarget(toLinkSpec(link3))})`)
47773
47832
  );
47774
47833
  await notifyDaemonLinks();
47775
47834
  }
47776
47835
 
47777
- // src/commands/sessions/nodes/listNodes.ts
47778
- import chalk256 from "chalk";
47779
- async function nodesReport() {
47780
- const live = await queryNodes();
47781
- if (live)
47782
- return { local: live.local, daemonRunning: true, links: live.links };
47783
- return {
47784
- local: resolveNodeName(),
47785
- daemonRunning: false,
47786
- links: loadLinkSpecs().map((link3) => ({ ...link3, state: "disconnected" }))
47787
- };
47788
- }
47789
- async function listNodes(options2) {
47790
- const report2 = await nodesReport();
47791
- if (options2.json) {
47792
- console.log(JSON.stringify(report2, null, 2));
47793
- return;
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/nodeLogs.ts
47803
- async function fetchLogTail(name, lines2) {
47804
- if (name === resolveNodeName())
47805
- return {
47806
- nodeName: name,
47807
- path: daemonPaths.log,
47808
- lines: readLogTail(daemonPaths.log, lines2)
47809
- };
47810
- const link3 = findLinkSpec(name);
47811
- if (!link3)
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
- `Could not read ${name}'s daemon log: ${describePeerError(error)}. Run assist sessions nodes doctor ${name}`
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
- async function nodeLogs(name, options2) {
47825
- const lines2 = Number.parseInt(options2.lines, 10);
47826
- if (!Number.isInteger(lines2) || lines2 < 1)
47827
- throw new Error(`-n must be a positive integer, got ${options2.lines}`);
47828
- const tail = await fetchLogTail(name, lines2);
47829
- if (options2.json) {
47830
- console.log(JSON.stringify(tail, null, 2));
47831
- return;
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
- for (const line of tail.lines) console.log(line);
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 chalk257 from "chalk";
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(chalk257.yellow(`No link named ${name}`));
47917
+ console.log(chalk258.yellow(`No link named ${name}`));
47843
47918
  return;
47844
47919
  }
47845
47920
  await writeLinks(remaining);
47846
- console.log(chalk257.green(`Unlinked ${name}`));
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.command("link <name> [url]").description(
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 chalk258 from "chalk";
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(chalk258.yellow("No sessions found."));
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(chalk258.green("All sessions already summarised."));
48119
+ console.log(chalk259.green("All sessions already summarised."));
48056
48120
  return;
48057
48121
  }
48058
48122
  console.log(
48059
- chalk258.cyan(
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
- chalk258.green(`Done: ${succeeded} summarised`) + (failed3 > 0 ? chalk258.yellow(`, ${failed3} skipped`) : "")
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(chalk258.dim(` [${i + 1}/${files.length}] `));
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(`${chalk258.green("\u2713")} ${summary}
48154
+ process.stdout.write(`${chalk259.green("\u2713")} ${summary}
48091
48155
  `);
48092
48156
  } else {
48093
48157
  failed3++;
48094
- process.stdout.write(` ${chalk258.yellow("skip")}
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 chalk259 from "chalk";
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(chalk259.red(error.message));
48191
+ console.error(chalk260.red(error.message));
48128
48192
  } else {
48129
48193
  console.error(error);
48130
48194
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.750.0",
3
+ "version": "0.751.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {