nixamp 0.5.2 → 0.5.3

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/daemon.d.ts CHANGED
@@ -8,6 +8,20 @@ export interface DaemonState {
8
8
  startedAt: number;
9
9
  /** Where its output went, for when it died and you want to know why. */
10
10
  log: string;
11
+ /**
12
+ * The labelled addresses the server itself worked out, share key not applied.
13
+ *
14
+ * Recorded rather than recomputed because only the server knows them: a
15
+ * daemon bound to every interface has no single host to print, and the
16
+ * public one may be a tunnel it was told about rather than an interface
17
+ * anybody here can see. Absent on a state file written by an older nixamp.
18
+ */
19
+ urls?: {
20
+ label: string;
21
+ url: string;
22
+ }[];
23
+ /** The firewall standing between this port and the rest of the network. */
24
+ firewall?: string | null;
11
25
  }
12
26
  /** XDG, with the usual fallback. One daemon per user, which is one too few for nobody. */
13
27
  export declare function stateDir(): string;
@@ -29,6 +43,17 @@ export declare function status(): {
29
43
  };
30
44
  /** The URL an admin client should talk to. */
31
45
  export declare function daemonUrl(state: DaemonState): string;
46
+ /**
47
+ * What `nixamp daemon start` prints, as lines, so it can be tested without
48
+ * starting a daemon.
49
+ *
50
+ * Every address the server found, not one loopback link: the point of a daemon
51
+ * is the phone in the other room, and 127.0.0.1 is the single address that
52
+ * cannot be handed to anybody. The firewall warning comes with it because the
53
+ * server writes that into a log file nobody reads, not to the person who just
54
+ * typed the command.
55
+ */
56
+ export declare function daemonLines(state: DaemonState): string[];
32
57
  /**
33
58
  * Start one, detached, and wait until it is actually answering before saying
34
59
  * it started. Reporting success and leaving the user to discover a crash in a
package/dist/daemon.js CHANGED
@@ -10,6 +10,7 @@ import { spawn } from "node:child_process";
10
10
  import { existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
11
11
  import { homedir } from "node:os";
12
12
  import { dirname, join } from "node:path";
13
+ import { portCommands } from "./share.js";
13
14
  /** XDG, with the usual fallback. One daemon per user, which is one too few for nobody. */
14
15
  export function stateDir() {
15
16
  const base = process.env["XDG_STATE_HOME"] || join(homedir(), ".local", "state");
@@ -63,6 +64,36 @@ export function daemonUrl(state) {
63
64
  const host = state.host === "0.0.0.0" || state.host === "::" ? "127.0.0.1" : state.host;
64
65
  return `http://${host.includes(":") ? `[${host}]` : host}:${state.port}`;
65
66
  }
67
+ /**
68
+ * What `nixamp daemon start` prints, as lines, so it can be tested without
69
+ * starting a daemon.
70
+ *
71
+ * Every address the server found, not one loopback link: the point of a daemon
72
+ * is the phone in the other room, and 127.0.0.1 is the single address that
73
+ * cannot be handed to anybody. The firewall warning comes with it because the
74
+ * server writes that into a log file nobody reads, not to the person who just
75
+ * typed the command.
76
+ */
77
+ export function daemonLines(state) {
78
+ const link = (url) => (state.key ? `${url}/s/${state.key}` : url);
79
+ // A state file written by an older nixamp has no list, so host and port
80
+ // still stand in rather than printing nothing at all.
81
+ const addresses = state.urls ?? [{ label: "here", url: daemonUrl(state) }];
82
+ const width = Math.max(...addresses.map((a) => a.label.length), "source".length);
83
+ const lines = [`nixamp daemon running (pid ${state.pid})`];
84
+ for (const { label, url } of addresses)
85
+ lines.push(` ${label.padEnd(width)} ${link(url)}`);
86
+ lines.push(` ${"source".padEnd(width)} ${state.source}`);
87
+ if (state.firewall) {
88
+ const { open } = portCommands(state.firewall, state.port);
89
+ lines.push("", ` ${state.firewall} is running, so nothing else can reach port ${state.port} yet:`, ` sudo ${open.join(" ")}`, " or `nixamp daemon stop` and start again with --open-port.");
90
+ }
91
+ if (!addresses.some((a) => a.label === "on the internet")) {
92
+ lines.push("", " None of those work from outside this network. If it should:", " nixamp daemon start ... --public-url https://your-tunnel.example.com");
93
+ }
94
+ lines.push("", " nixamp attach the player, in front of it", " nixamp admin who is connected", " nixamp daemon stop when you are done");
95
+ return lines;
96
+ }
66
97
  /**
67
98
  * Start one, detached, and wait until it is actually answering before saying
68
99
  * it started. Reporting success and leaving the user to discover a crash in a
@@ -119,11 +150,14 @@ async function waitForAnnounce(log, timeoutMs) {
119
150
  try {
120
151
  const parsed = JSON.parse(line);
121
152
  if (parsed["nixamp"] === "listening") {
153
+ const urls = parsed["urls"];
122
154
  return {
123
155
  host: String(parsed["host"]),
124
156
  port: Number(parsed["port"]),
125
157
  key: parsed["key"] ?? null,
126
158
  source: String(parsed["source"]),
159
+ ...(Array.isArray(urls) ? { urls: urls } : {}),
160
+ ...(typeof parsed["firewall"] === "string" ? { firewall: parsed["firewall"] } : {}),
127
161
  };
128
162
  }
129
163
  }
package/dist/main.js CHANGED
@@ -195,13 +195,8 @@ async function runDaemon(argv) {
195
195
  if (action === "start") {
196
196
  try {
197
197
  const state = await d.start(rest, entry);
198
- console.log(`nixamp daemon running (pid ${state.pid})`);
199
- const url = d.daemonUrl(state);
200
- console.log(` ${state.key ? `${url}/s/${state.key}` : url}`);
201
- console.log(` ${state.source}`);
202
- console.log("");
203
- console.log(" nixamp admin who is connected");
204
- console.log(" nixamp daemon stop when you are done");
198
+ for (const line of d.daemonLines(state))
199
+ console.log(line);
205
200
  return 0;
206
201
  }
207
202
  catch (error) {
package/dist/server.js CHANGED
@@ -2085,8 +2085,30 @@ export async function serve(argv, version = "0.1.0") {
2085
2085
  return { status: done.status, stdout: done.stdout ?? "" };
2086
2086
  },
2087
2087
  };
2088
+ // The link, not the address. Without the key the address is a 401, so
2089
+ // printing a bare host:port would be printing something that does not work.
2090
+ const addresses = reachableAddresses(options.host, port, options.publicUrl);
2091
+ // Listening on every interface proves the socket is open here and nothing
2092
+ // about the path between here and the phone.
2093
+ const listening = options.host === "0.0.0.0" || options.host === "::";
2094
+ const firewall = listening ? firewallInUse(io) : null;
2088
2095
  if (options.announce) {
2089
- console.log(JSON.stringify({ nixamp: "listening", host: options.host, port, key, source: root }));
2096
+ // Everything `nixamp daemon start` needs to print what this would have
2097
+ // printed. Without the addresses it could only reconstruct host and port,
2098
+ // which for a server bound to every interface means it printed 127.0.0.1 --
2099
+ // an address that works on exactly the machine you are already sitting at.
2100
+ // The firewall matters for the same reason: the warning was going into a
2101
+ // log file nobody reads instead of to the person who just typed the
2102
+ // command.
2103
+ console.log(JSON.stringify({
2104
+ nixamp: "listening",
2105
+ host: options.host,
2106
+ port,
2107
+ key,
2108
+ source: root,
2109
+ urls: addresses,
2110
+ firewall,
2111
+ }));
2090
2112
  }
2091
2113
  // The other half of "names now, tags later". It runs while the banner is
2092
2114
  // printed and while the publish prompt waits, and it is deliberately not
@@ -2102,9 +2124,6 @@ export async function serve(argv, version = "0.1.0") {
2102
2124
  }
2103
2125
  console.log(`nixamp serve — ${tracks.length} tracks under ${root}`);
2104
2126
  console.log("");
2105
- // The link, not the address. Without the key the address is a 401, so
2106
- // printing a bare host:port would be printing something that does not work.
2107
- const addresses = reachableAddresses(options.host, port, options.publicUrl);
2108
2127
  const width = Math.max(...addresses.map((a) => a.label.length));
2109
2128
  for (const { label, url } of addresses) {
2110
2129
  console.log(` ${label.padEnd(width)} ${shareLink(url, key)}`);
@@ -2161,10 +2180,6 @@ export async function serve(argv, version = "0.1.0") {
2161
2180
  }
2162
2181
  if (web === null)
2163
2182
  console.log(" No built PWA found, so / has nothing to serve: run `bun run web:build`.");
2164
- // Listening on every interface proves the socket is open here and nothing
2165
- // about the path between here and the phone.
2166
- const listening = options.host === "0.0.0.0" || options.host === "::";
2167
- const firewall = listening ? firewallInUse(io) : null;
2168
2183
  let closePort = null;
2169
2184
  if (firewall !== null) {
2170
2185
  const { open, close } = portCommands(firewall, port);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nixamp",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "It really whips the terminal's ass. A Winamp-shaped audio player for your terminal.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/daemon.ts CHANGED
@@ -10,6 +10,7 @@ import { spawn } from "node:child_process";
10
10
  import { existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
11
11
  import { homedir } from "node:os";
12
12
  import { dirname, join } from "node:path";
13
+ import { type Firewall, portCommands } from "./share.ts";
13
14
 
14
15
  export interface DaemonState {
15
16
  pid: number;
@@ -21,6 +22,17 @@ export interface DaemonState {
21
22
  startedAt: number;
22
23
  /** Where its output went, for when it died and you want to know why. */
23
24
  log: string;
25
+ /**
26
+ * The labelled addresses the server itself worked out, share key not applied.
27
+ *
28
+ * Recorded rather than recomputed because only the server knows them: a
29
+ * daemon bound to every interface has no single host to print, and the
30
+ * public one may be a tunnel it was told about rather than an interface
31
+ * anybody here can see. Absent on a state file written by an older nixamp.
32
+ */
33
+ urls?: { label: string; url: string }[];
34
+ /** The firewall standing between this port and the rest of the network. */
35
+ firewall?: string | null;
24
36
  }
25
37
 
26
38
  /** XDG, with the usual fallback. One daemon per user, which is one too few for nobody. */
@@ -82,6 +94,53 @@ export function daemonUrl(state: DaemonState): string {
82
94
  return `http://${host.includes(":") ? `[${host}]` : host}:${state.port}`;
83
95
  }
84
96
 
97
+ /**
98
+ * What `nixamp daemon start` prints, as lines, so it can be tested without
99
+ * starting a daemon.
100
+ *
101
+ * Every address the server found, not one loopback link: the point of a daemon
102
+ * is the phone in the other room, and 127.0.0.1 is the single address that
103
+ * cannot be handed to anybody. The firewall warning comes with it because the
104
+ * server writes that into a log file nobody reads, not to the person who just
105
+ * typed the command.
106
+ */
107
+ export function daemonLines(state: DaemonState): string[] {
108
+ const link = (url: string): string => (state.key ? `${url}/s/${state.key}` : url);
109
+ // A state file written by an older nixamp has no list, so host and port
110
+ // still stand in rather than printing nothing at all.
111
+ const addresses = state.urls ?? [{ label: "here", url: daemonUrl(state) }];
112
+ const width = Math.max(...addresses.map((a) => a.label.length), "source".length);
113
+
114
+ const lines = [`nixamp daemon running (pid ${state.pid})`];
115
+ for (const { label, url } of addresses) lines.push(` ${label.padEnd(width)} ${link(url)}`);
116
+ lines.push(` ${"source".padEnd(width)} ${state.source}`);
117
+
118
+ if (state.firewall) {
119
+ const { open } = portCommands(state.firewall as Firewall, state.port);
120
+ lines.push(
121
+ "",
122
+ ` ${state.firewall} is running, so nothing else can reach port ${state.port} yet:`,
123
+ ` sudo ${open.join(" ")}`,
124
+ " or `nixamp daemon stop` and start again with --open-port.",
125
+ );
126
+ }
127
+ if (!addresses.some((a) => a.label === "on the internet")) {
128
+ lines.push(
129
+ "",
130
+ " None of those work from outside this network. If it should:",
131
+ " nixamp daemon start ... --public-url https://your-tunnel.example.com",
132
+ );
133
+ }
134
+
135
+ lines.push(
136
+ "",
137
+ " nixamp attach the player, in front of it",
138
+ " nixamp admin who is connected",
139
+ " nixamp daemon stop when you are done",
140
+ );
141
+ return lines;
142
+ }
143
+
85
144
  /**
86
145
  * Start one, detached, and wait until it is actually answering before saying
87
146
  * it started. Reporting success and leaving the user to discover a crash in a
@@ -142,11 +201,14 @@ async function waitForAnnounce(
142
201
  try {
143
202
  const parsed = JSON.parse(line) as { nixamp?: string } & Record<string, unknown>;
144
203
  if (parsed["nixamp"] === "listening") {
204
+ const urls = parsed["urls"];
145
205
  return {
146
206
  host: String(parsed["host"]),
147
207
  port: Number(parsed["port"]),
148
208
  key: (parsed["key"] as string | null) ?? null,
149
209
  source: String(parsed["source"]),
210
+ ...(Array.isArray(urls) ? { urls: urls as { label: string; url: string }[] } : {}),
211
+ ...(typeof parsed["firewall"] === "string" ? { firewall: parsed["firewall"] } : {}),
150
212
  };
151
213
  }
152
214
  } catch {
package/src/main.ts CHANGED
@@ -224,13 +224,7 @@ async function runDaemon(argv: string[]): Promise<number> {
224
224
  if (action === "start") {
225
225
  try {
226
226
  const state = await d.start(rest, entry);
227
- console.log(`nixamp daemon running (pid ${state.pid})`);
228
- const url = d.daemonUrl(state);
229
- console.log(` ${state.key ? `${url}/s/${state.key}` : url}`);
230
- console.log(` ${state.source}`);
231
- console.log("");
232
- console.log(" nixamp admin who is connected");
233
- console.log(" nixamp daemon stop when you are done");
227
+ for (const line of d.daemonLines(state)) console.log(line);
234
228
  return 0;
235
229
  } catch (error) {
236
230
  console.error((error as Error).message);
package/src/server.ts CHANGED
@@ -2390,8 +2390,34 @@ export async function serve(argv: string[], version = "0.1.0"): Promise<void> {
2390
2390
  },
2391
2391
  };
2392
2392
 
2393
+ // The link, not the address. Without the key the address is a 401, so
2394
+ // printing a bare host:port would be printing something that does not work.
2395
+ const addresses = reachableAddresses(options.host, port, options.publicUrl);
2396
+
2397
+ // Listening on every interface proves the socket is open here and nothing
2398
+ // about the path between here and the phone.
2399
+ const listening = options.host === "0.0.0.0" || options.host === "::";
2400
+ const firewall = listening ? firewallInUse(io) : null;
2401
+
2393
2402
  if (options.announce) {
2394
- console.log(JSON.stringify({ nixamp: "listening", host: options.host, port, key, source: root }));
2403
+ // Everything `nixamp daemon start` needs to print what this would have
2404
+ // printed. Without the addresses it could only reconstruct host and port,
2405
+ // which for a server bound to every interface means it printed 127.0.0.1 --
2406
+ // an address that works on exactly the machine you are already sitting at.
2407
+ // The firewall matters for the same reason: the warning was going into a
2408
+ // log file nobody reads instead of to the person who just typed the
2409
+ // command.
2410
+ console.log(
2411
+ JSON.stringify({
2412
+ nixamp: "listening",
2413
+ host: options.host,
2414
+ port,
2415
+ key,
2416
+ source: root,
2417
+ urls: addresses,
2418
+ firewall,
2419
+ }),
2420
+ );
2395
2421
  }
2396
2422
 
2397
2423
  // The other half of "names now, tags later". It runs while the banner is
@@ -2410,9 +2436,6 @@ export async function serve(argv: string[], version = "0.1.0"): Promise<void> {
2410
2436
  console.log(`nixamp serve — ${tracks.length} tracks under ${root}`);
2411
2437
  console.log("");
2412
2438
 
2413
- // The link, not the address. Without the key the address is a 401, so
2414
- // printing a bare host:port would be printing something that does not work.
2415
- const addresses = reachableAddresses(options.host, port, options.publicUrl);
2416
2439
  const width = Math.max(...addresses.map((a) => a.label.length));
2417
2440
  for (const { label, url } of addresses) {
2418
2441
  console.log(` ${label.padEnd(width)} ${shareLink(url, key)}`);
@@ -2469,10 +2492,6 @@ export async function serve(argv: string[], version = "0.1.0"): Promise<void> {
2469
2492
  }
2470
2493
  if (web === null) console.log(" No built PWA found, so / has nothing to serve: run `bun run web:build`.");
2471
2494
 
2472
- // Listening on every interface proves the socket is open here and nothing
2473
- // about the path between here and the phone.
2474
- const listening = options.host === "0.0.0.0" || options.host === "::";
2475
- const firewall = listening ? firewallInUse(io) : null;
2476
2495
  let closePort: (() => void) | null = null;
2477
2496
 
2478
2497
  if (firewall !== null) {
package/web/dist/sw.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /* nixamp service worker — generated, do not edit */
2
- const CACHE = "nixamp-1788936569431";
2
+ const CACHE = "nixamp-1788937268228";
3
3
  const PRECACHE = [
4
4
  "/",
5
5
  "/apple-touch-icon.png",