@zswarm/core 0.8.0 → 0.8.1
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/host/gate.js +5 -13
- package/dist/ops/gate.d.ts +5 -3
- package/dist/ops/gate.js +8 -7
- package/dist/zellij/client.js +22 -3
- package/package.json +1 -1
package/dist/host/gate.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { ZellijError } from "../errors.js";
|
|
4
4
|
import { dispatchZswarm } from "../ops/dispatch.js";
|
|
@@ -61,15 +61,6 @@ async function readStdin(limit) {
|
|
|
61
61
|
}
|
|
62
62
|
return Buffer.concat(chunks).toString("utf8");
|
|
63
63
|
}
|
|
64
|
-
function cliPath() {
|
|
65
|
-
const script = process.argv[1] ?? "";
|
|
66
|
-
try {
|
|
67
|
-
return realpathSync(script);
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
return script;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
64
|
const s = (v) => (typeof v === "string" ? v : undefined);
|
|
74
65
|
export function gateAllow(id, flags, env = process.env) {
|
|
75
66
|
id = validateGateId(id);
|
|
@@ -83,7 +74,7 @@ export function gateAllow(id, flags, env = process.env) {
|
|
|
83
74
|
const key = s(flags["--key"]) ?? (keyFile ? readFileSync(keyFile, "utf8") : undefined);
|
|
84
75
|
if (!key)
|
|
85
76
|
throw new ZellijError("usage", "gate allow needs --key 'ssh-ed25519 AAAA…' or --key-file PATH (the sender's public key)");
|
|
86
|
-
const command = gateForcedCommand({ id,
|
|
77
|
+
const command = gateForcedCommand({ id, bin: s(flags["--bin"]), env });
|
|
87
78
|
const line = gateKeyLine({ id, key, command, from: s(flags["--from"]) });
|
|
88
79
|
const keysPath = s(flags["--authorized-keys"]) ?? defaultAuthorizedKeysPath(env);
|
|
89
80
|
mkdirSync(dirname(keysPath), { recursive: true, mode: 0o700 });
|
|
@@ -119,10 +110,11 @@ export function gateList(flags, env = process.env) {
|
|
|
119
110
|
};
|
|
120
111
|
}
|
|
121
112
|
export const GATE_USAGE = [
|
|
122
|
-
"zswarm gate allow ID (--key 'ssh-ed25519 AAAA…' | --key-file PUB) --session S --pane P [--from ADDRS] [--authorized-keys PATH]",
|
|
113
|
+
"zswarm gate allow ID (--key 'ssh-ed25519 AAAA…' | --key-file PUB) --session S --pane P [--from ADDRS] [--bin PATH] [--authorized-keys PATH]",
|
|
123
114
|
" on the receiving machine: let the key send to pane P in session S as ID. Writes a forced-command line",
|
|
124
115
|
" (send only: no shell, no pty, no forwarding) to authorized_keys and the target to gate.json (ZSWARM_GATE_CONFIG,",
|
|
125
|
-
" default ~/.zswarm/gate.json); --from limits the source addresses (sshd from= patterns)",
|
|
116
|
+
" default ~/.zswarm/gate.json); --from limits the source addresses (sshd from= patterns); the command runs",
|
|
117
|
+
" `zswarm` from this shell's PATH (kept across upgrades), or --bin PATH",
|
|
126
118
|
"zswarm gate revoke ID [--session S --pane P] drop ID from one target or all; the key line goes when nothing is left",
|
|
127
119
|
"zswarm gate list targets, allowed ids and gate keys",
|
|
128
120
|
"zswarm gate ID the forced command itself: reads one request on stdin and sends locally (sshd runs this)",
|
package/dist/ops/gate.d.ts
CHANGED
|
@@ -67,12 +67,14 @@ export declare function authorizeGateRequest(config: GateConfig, id: string, raw
|
|
|
67
67
|
/**
|
|
68
68
|
* The forced command for one id. sshd gives it a bare environment, so the PATH
|
|
69
69
|
* (and on macOS the per-user TMPDIR that Zellij keeps its sockets under) of the
|
|
70
|
-
* process running `gate allow` is written into the command.
|
|
70
|
+
* process running `gate allow` is written into the command. The launcher is
|
|
71
|
+
* `zswarm` looked up on that PATH, not the resolved script: package managers
|
|
72
|
+
* install each version under its own directory, so a resolved path breaks on
|
|
73
|
+
* the next upgrade. `bin` pins an explicit launcher instead.
|
|
71
74
|
*/
|
|
72
75
|
export declare function gateForcedCommand(input: {
|
|
73
76
|
id: string;
|
|
74
|
-
|
|
75
|
-
cli: string;
|
|
77
|
+
bin?: string;
|
|
76
78
|
platform?: NodeJS.Platform;
|
|
77
79
|
env?: NodeJS.ProcessEnv;
|
|
78
80
|
}): string;
|
package/dist/ops/gate.js
CHANGED
|
@@ -223,19 +223,20 @@ function posixQuote(arg) {
|
|
|
223
223
|
/**
|
|
224
224
|
* The forced command for one id. sshd gives it a bare environment, so the PATH
|
|
225
225
|
* (and on macOS the per-user TMPDIR that Zellij keeps its sockets under) of the
|
|
226
|
-
* process running `gate allow` is written into the command.
|
|
226
|
+
* process running `gate allow` is written into the command. The launcher is
|
|
227
|
+
* `zswarm` looked up on that PATH, not the resolved script: package managers
|
|
228
|
+
* install each version under its own directory, so a resolved path breaks on
|
|
229
|
+
* the next upgrade. `bin` pins an explicit launcher instead.
|
|
227
230
|
*/
|
|
228
231
|
export function gateForcedCommand(input) {
|
|
229
232
|
const platform = input.platform ?? process.platform;
|
|
230
233
|
const env = input.env ?? process.env;
|
|
234
|
+
const bin = input.bin?.trim() || "zswarm";
|
|
231
235
|
if (platform === "win32") {
|
|
232
236
|
// Unquoted paths run the same in cmd and PowerShell (OpenSSH's DefaultShell
|
|
233
237
|
// may be either); a path with spaces needs quoting, which only cmd accepts.
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
return /\s/.test(p) ? `"${p}"` : p;
|
|
237
|
-
};
|
|
238
|
-
return `${q(input.node)} ${q(input.cli)} gate ${input.id}`;
|
|
238
|
+
const p = bin.replace(/\\/g, "/");
|
|
239
|
+
return `${/\s/.test(p) ? `"${p}"` : p} gate ${input.id}`;
|
|
239
240
|
}
|
|
240
241
|
const vars = [];
|
|
241
242
|
if (env.PATH)
|
|
@@ -246,7 +247,7 @@ export function gateForcedCommand(input) {
|
|
|
246
247
|
vars.push(`ZSWARM_STATE_DIR=${posixQuote(env.ZSWARM_STATE_DIR)}`);
|
|
247
248
|
if (env.ZSWARM_GATE_CONFIG)
|
|
248
249
|
vars.push(`ZSWARM_GATE_CONFIG=${posixQuote(env.ZSWARM_GATE_CONFIG)}`);
|
|
249
|
-
return ["env", ...vars, posixQuote(
|
|
250
|
+
return ["env", ...vars, posixQuote(bin), "gate", input.id].join(" ");
|
|
250
251
|
}
|
|
251
252
|
const KEY_TYPES = /^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp(256|384|521)|sk-ssh-ed25519@openssh\.com|sk-ecdsa-sha2-nistp256@openssh\.com)$/;
|
|
252
253
|
/** `type base64 [comment]` → `type base64`; anything else is refused. */
|
package/dist/zellij/client.js
CHANGED
|
@@ -323,8 +323,15 @@ export function createZellijClient(options = {}) {
|
|
|
323
323
|
creation: server.creationName,
|
|
324
324
|
at: now(),
|
|
325
325
|
};
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
// Remember only names that no live session carries. A live name that
|
|
327
|
+
// resolved elsewhere was reused, or came from a process whose env is
|
|
328
|
+
// stale (a daemon started from another session's pane); recording it
|
|
329
|
+
// would send every explicit --session NAME to the wrong session
|
|
330
|
+
// (bacloud, 2026-10-06: cratchet -> astro-dogster).
|
|
331
|
+
const liveName = (name) => live.some((session) => session.name === name);
|
|
332
|
+
if (!liveName(candidate))
|
|
333
|
+
aliases.recordSessionAlias(candidate, entry);
|
|
334
|
+
if (server.creationName && server.creationName !== candidate && !liveName(server.creationName)) {
|
|
328
335
|
aliases.recordSessionAlias(server.creationName, entry);
|
|
329
336
|
}
|
|
330
337
|
}
|
|
@@ -345,9 +352,21 @@ export function createZellijClient(options = {}) {
|
|
|
345
352
|
if (!isSelf) {
|
|
346
353
|
if (aliases) {
|
|
347
354
|
const entry = aliases.readSessionAliases()[candidate];
|
|
348
|
-
if (entry?.serverPid != null) {
|
|
355
|
+
if (entry?.serverPid != null && entry.current !== candidate) {
|
|
349
356
|
const table = await processTable();
|
|
350
357
|
if (liveServers(table).some((server) => server.pid === entry.serverPid)) {
|
|
358
|
+
// A live session with exactly the requested name wins over a
|
|
359
|
+
// remembered rename; the listing is only paid when an alias would
|
|
360
|
+
// redirect. If the listing fails, the alias still applies.
|
|
361
|
+
try {
|
|
362
|
+
const liveNow = await listSessions(callTimeoutMs);
|
|
363
|
+
if (liveNow.some((session) => !session.exited && session.name === candidate)) {
|
|
364
|
+
return fromEnv;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
// fall through to the alias
|
|
369
|
+
}
|
|
351
370
|
return { session: entry.current, source: "alias", requested: candidate };
|
|
352
371
|
}
|
|
353
372
|
}
|