alexandr 0.1.1 → 0.1.2

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/bin.js CHANGED
@@ -1,7 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  // Public entrypoint for the `alexandr` CLI. Keep this a thin shim; logic lives
3
- // in ./src. Plain Node (no Bun, no deps) so it runs anywhere Node 20+ is.
4
- import { run } from "./src/cli.js";
3
+ // in ./src. Plain Node (no Bun, no deps) so it runs anywhere Node 18+ is.
4
+
5
+ // Our own version guard, so nobody meets npm's EBADENGINE wall: package.json
6
+ // declares >=18 (what Ubuntu LTS's apt ships, and provably sufficient — the CLI
7
+ // is dependency-free), and anything older gets ONE styled line instead of a trace.
8
+ const major = Number(process.versions.node.split(".")[0]);
9
+ if (major < 18) {
10
+ process.stderr.write(
11
+ `\x1b[31m✗\x1b[0m alexandr needs Node 18 or newer — this is Node ${process.versions.node}.\n` +
12
+ ` On Ubuntu/Debian: apt install -y npm (or use nodesource for a current LTS)\n`,
13
+ );
14
+ process.exit(1);
15
+ }
16
+
17
+ const { run } = await import("./src/cli.js");
5
18
 
6
19
  run().catch((err) => {
7
20
  process.stderr.write(`${err?.stack || err}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alexandr",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Run the alexandr workspace runtime locally — a thin Docker front door (npx alexandr up). Pulls + boots the published kernel image.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,7 @@
17
17
  "LICENSE"
18
18
  ],
19
19
  "engines": {
20
- "node": ">=20"
20
+ "node": ">=18"
21
21
  },
22
22
  "license": "Apache-2.0",
23
23
  "repository": {
package/src/commands.js CHANGED
@@ -173,6 +173,12 @@ export async function up(flags) {
173
173
 
174
174
  if (flags.port) setEnv(inst.dir, "ALEXANDR_KERNEL_PORT", String(flags.port));
175
175
  if (flags.domain) setEnv(inst.dir, "ALEXANDR_DOMAIN", String(flags.domain));
176
+ // Persist the display name so a RETRIED ceremony reuses the wizard's answer — the
177
+ // first attempt on the aos box lost "AOS" to the default because it lived only in
178
+ // that run's memory (the ceremony it fed timed out).
179
+ if (typeof flags.name === "string" && flags.name.trim()) {
180
+ setEnv(inst.dir, "ALEXANDR_WORKSPACE_NAME", flags.name.trim());
181
+ }
176
182
 
177
183
  // Sign-in comes FIRST (account-required-runtimes D3) — now literally: the entitlement
178
184
  // gate fires before anything system-mutating, so a refused account walks away from a
package/src/link.js CHANGED
@@ -101,7 +101,9 @@ export async function runLinkCeremony(inst, flags) {
101
101
  ? `https://${String(domain).replace(/^https?:\/\//, "").replace(/\/+$/, "")}`
102
102
  : kernelUrl(kernelPort(inst.dir));
103
103
  const name =
104
- typeof flags.name === "string" && flags.name.trim() ? flags.name.trim() : "My self-hosted workspace";
104
+ (typeof flags.name === "string" && flags.name.trim()) ||
105
+ (readEnv(inst.dir).ALEXANDR_WORKSPACE_NAME || "").trim() ||
106
+ "My self-hosted workspace";
105
107
 
106
108
  // PKCE (S256) + a CSRF state for the loopback redirect.
107
109
  const verifier = b64url(crypto.randomBytes(32));
@@ -309,7 +311,7 @@ export async function presentAuthUrl(authUrl, { port, domain, headless = isHeadl
309
311
  }
310
312
 
311
313
  /** Start a loopback listener for the OAuth redirect. Resolves {code,state} when /callback is hit. */
312
- function startLoopback() {
314
+ export function startLoopback() {
313
315
  let resolveFn, rejectFn;
314
316
  const done = new Promise((res, rej) => {
315
317
  resolveFn = res;
@@ -317,6 +319,20 @@ function startLoopback() {
317
319
  });
318
320
  const server = http.createServer((req, res) => {
319
321
  const u = new URL(req.url, "http://127.0.0.1");
322
+ // Reachability probe for the consent page: /cli-auth pings this before the user
323
+ // clicks Link, so a missing ssh tunnel becomes a guided "start the tunnel" notice
324
+ // instead of a dead browser error page after the click. The PNA header answers
325
+ // Chrome's public→loopback preflight; ACAO lets the page read the success.
326
+ if (u.pathname === "/ping") {
327
+ res.writeHead(204, {
328
+ "access-control-allow-origin": "*",
329
+ "access-control-allow-methods": "GET, OPTIONS",
330
+ "access-control-allow-headers": "*",
331
+ "access-control-allow-private-network": "true",
332
+ });
333
+ res.end();
334
+ return;
335
+ }
320
336
  if (u.pathname !== "/callback") {
321
337
  res.writeHead(404);
322
338
  res.end();
@@ -21,6 +21,8 @@
21
21
  # ALEXANDR_INSTANCE_ID=
22
22
  # ALEXANDR_RUNTIME_SECRET=
23
23
  # ALEXANDR_WORKSPACE_ID= # account-side record id (used by `destroy --unlink`)
24
+ # ALEXANDR_WORKSPACE_NAME= # display name for registration (the wizard writes it,
25
+ # # so a retried sign-in reuses your answer)
24
26
 
25
27
  # Public domain for Caddy auto-HTTPS. Setting this starts the "public" profile — the
26
28
  # supported way to reach the runtime from another machine. Without a domain the