@zswarm/core 0.1.4 → 0.1.6
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 +4 -1
- package/dist/exec.d.ts +14 -1
- package/dist/exec.js +107 -14
- package/dist/harness.js +12 -7
- package/dist/index.d.ts +11 -10
- package/dist/index.js +8 -8
- package/dist/ops/bus.d.ts +16 -10
- package/dist/ops/bus.js +164 -98
- package/dist/ops/delivery.d.ts +3 -3
- package/dist/ops/delivery.js +4 -7
- package/dist/ops/dispatch.d.ts +7 -1
- package/dist/ops/dispatch.js +208 -68
- package/dist/ops/guards.d.ts +2 -0
- package/dist/ops/guards.js +11 -1
- package/dist/ops/observation.d.ts +8 -0
- package/dist/ops/observation.js +26 -0
- package/dist/ops/routing.d.ts +13 -0
- package/dist/ops/routing.js +21 -0
- package/dist/ops/serve.d.ts +1 -0
- package/dist/ops/serve.js +7 -1
- package/dist/ops/spawn.d.ts +3 -3
- package/dist/ops/spawn.js +130 -64
- package/dist/ops/status.d.ts +30 -5
- package/dist/ops/status.js +231 -91
- package/dist/ops/types.d.ts +7 -1
- package/dist/ops/util.d.ts +4 -0
- package/dist/ops/util.js +7 -1
- package/dist/ops/wait.d.ts +8 -1
- package/dist/ops/wait.js +7 -3
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +50 -8
- package/dist/state.d.ts +7 -6
- package/dist/state.js +77 -17
- package/dist/zellij/args.d.ts +4 -0
- package/dist/zellij/args.js +1 -0
- package/dist/zellij/binary.d.ts +34 -0
- package/dist/zellij/binary.js +169 -8
- package/dist/zellij/bus.d.ts +11 -0
- package/dist/zellij/bus.js +22 -1
- package/dist/zellij/client.d.ts +23 -5
- package/dist/zellij/client.js +99 -33
- package/dist/zellij/panes.js +3 -0
- package/dist/zellij/session.d.ts +19 -3
- package/dist/zellij/session.js +39 -9
- package/package.json +3 -3
package/dist/zellij/client.d.ts
CHANGED
|
@@ -1,23 +1,38 @@
|
|
|
1
1
|
import { buildClosePaneArgs, buildDumpArgs, buildListPanesArgs, buildNewPaneArgs, buildNewTabArgs, buildPasteArgs, buildSendEnterArgs, buildSendKeysArgs, buildWriteCharsArgs, type WaitRequest, type LaunchPluginInput, type NewPaneInput, type NewTabInput, type PipeInput } from "./args.js";
|
|
2
2
|
import { resolveTab, type ZellijTab } from "./tabs.js";
|
|
3
|
+
import { type IpcDiscoveryState } from "../exec.js";
|
|
3
4
|
import { type ZellijExecFn } from "./binary.js";
|
|
4
5
|
import { normalizePaneId, resolvePane, type ZellijPane } from "./panes.js";
|
|
5
|
-
import { type ZellijSessionResolve } from "./session.js";
|
|
6
|
+
import { type ZellijSession, type ZellijSessionResolve } from "./session.js";
|
|
6
7
|
export type ZellijClientOptions = {
|
|
7
8
|
exec?: ZellijExecFn;
|
|
8
9
|
zellijPath?: string;
|
|
9
10
|
timeoutMs?: number;
|
|
10
11
|
env?: NodeJS.ProcessEnv;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
/** Skip the one-time `zellij --version` identity probe (tests). */
|
|
14
|
+
skipIdentityProbe?: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type ZellijTransport = {
|
|
17
|
+
kind: "local" | "ssh";
|
|
18
|
+
mode: "local" | "ssh" | "interactive";
|
|
19
|
+
host?: string;
|
|
20
|
+
remoteBin?: string;
|
|
21
|
+
/** Configured or resolved IPC temp (`auto` until discovery succeeds). */
|
|
22
|
+
tmp?: string;
|
|
23
|
+
/** Outcome of `ZSWARM_TMP=auto` discovery when SSH routing is active. */
|
|
24
|
+
ipc?: IpcDiscoveryState;
|
|
11
25
|
};
|
|
12
26
|
/** Thin, stateless wrapper over the `zellij` binary. */
|
|
13
27
|
export declare function createZellijClient(options?: ZellijClientOptions): {
|
|
14
28
|
zellijPath: string;
|
|
15
29
|
selfPaneId: string | null;
|
|
30
|
+
readonly transport: ZellijTransport;
|
|
16
31
|
/** A `file:` plugin url only names a path on the machine running Zellij. */
|
|
17
32
|
remote: boolean;
|
|
18
|
-
listSessions: () => Promise<
|
|
19
|
-
resolveSession: (explicit?: string | null) => Promise<ZellijSessionResolve>;
|
|
20
|
-
listPanes: (session: string) => Promise<ZellijPane[]>;
|
|
33
|
+
listSessions: (callTimeoutMs?: number) => Promise<ZellijSession[]>;
|
|
34
|
+
resolveSession: (explicit?: string | null, callTimeoutMs?: number) => Promise<ZellijSessionResolve>;
|
|
35
|
+
listPanes: (session: string, callTimeoutMs?: number) => Promise<ZellijPane[]>;
|
|
21
36
|
resolvePane: typeof resolvePane;
|
|
22
37
|
injectPane: (input: {
|
|
23
38
|
session: string;
|
|
@@ -31,6 +46,7 @@ export declare function createZellijClient(options?: ZellijClientOptions): {
|
|
|
31
46
|
session: string;
|
|
32
47
|
paneId: string;
|
|
33
48
|
full?: boolean;
|
|
49
|
+
timeoutMs?: number;
|
|
34
50
|
}) => Promise<{
|
|
35
51
|
paneId: string;
|
|
36
52
|
session: string;
|
|
@@ -74,6 +90,7 @@ export declare function createZellijClient(options?: ZellijClientOptions): {
|
|
|
74
90
|
session: string;
|
|
75
91
|
paneId: string;
|
|
76
92
|
name: string;
|
|
93
|
+
timeoutMs?: number;
|
|
77
94
|
}) => Promise<{
|
|
78
95
|
paneId: string;
|
|
79
96
|
session: string;
|
|
@@ -95,7 +112,7 @@ export declare function createZellijClient(options?: ZellijClientOptions): {
|
|
|
95
112
|
paneId: string;
|
|
96
113
|
session: string;
|
|
97
114
|
}>;
|
|
98
|
-
listTabs: (session: string) => Promise<ZellijTab[]>;
|
|
115
|
+
listTabs: (session: string, callTimeoutMs?: number) => Promise<ZellijTab[]>;
|
|
99
116
|
dumpLayout: (session: string) => Promise<string>;
|
|
100
117
|
stackPanes: (input: {
|
|
101
118
|
session: string;
|
|
@@ -137,6 +154,7 @@ export declare function createZellijClient(options?: ZellijClientOptions): {
|
|
|
137
154
|
url: string;
|
|
138
155
|
configKey: string;
|
|
139
156
|
panes: string[];
|
|
157
|
+
timeoutMs?: number;
|
|
140
158
|
}) => Promise<{
|
|
141
159
|
code: number;
|
|
142
160
|
stdout: string;
|
package/dist/zellij/client.js
CHANGED
|
@@ -3,54 +3,115 @@ import { buildClosePaneArgs, buildDumpArgs, buildDumpLayoutArgs, buildFocusPaneA
|
|
|
3
3
|
import { DEFAULT_BUS_TIMEOUT_MS, parseBusReply, parseChangedReply, parseScrollbackReply, parseWaitReply, } from "./bus.js";
|
|
4
4
|
import { parseTabList, resolveTab } from "./tabs.js";
|
|
5
5
|
import { createSshExec } from "../exec.js";
|
|
6
|
-
import { DEFAULT_TIMEOUT_MS, NOT_FOUND_EXIT, defaultExec, resolveSshTarget, resolveZellijBinary, sanitizeZellijEnv, } from "./binary.js";
|
|
6
|
+
import { DEFAULT_TIMEOUT_MS, NOT_FOUND_EXIT, defaultExec, ensureZellijCapabilities, ensureZellijIdentity, identityCacheKey, resolveSshTarget, resolveZellijBinary, sanitizeZellijEnv, } from "./binary.js";
|
|
7
7
|
import { normalizePaneId, parsePaneList, resolvePane, } from "./panes.js";
|
|
8
8
|
import { isZellijNoSessionsOutput, parseSessionList, resolveSelfPaneId, sessionFromEnv, sessionFromList, } from "./session.js";
|
|
9
9
|
/** Thin, stateless wrapper over the `zellij` binary. */
|
|
10
10
|
export function createZellijClient(options = {}) {
|
|
11
11
|
const env = options.env ?? process.env;
|
|
12
|
-
// A remote crew never resolves a local binary.
|
|
12
|
+
// A remote crew never resolves a local binary. An injected exec is a unit
|
|
13
|
+
// test (or in-process stand-in) — skip PATH/env resolution so a poisoned
|
|
14
|
+
// ZSWARM_BIN in the host environment cannot fail client construction.
|
|
13
15
|
const ssh = options.exec ? null : resolveSshTarget(env);
|
|
14
16
|
const zellijPath = options.zellijPath ??
|
|
15
|
-
(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
(options.exec
|
|
18
|
+
? "zellij"
|
|
19
|
+
: ssh
|
|
20
|
+
? `ssh://${ssh.host}/${ssh.remoteBin}`
|
|
21
|
+
: resolveZellijBinary(env));
|
|
22
|
+
const sshExec = options.exec || !ssh
|
|
23
|
+
? null
|
|
24
|
+
: createSshExec(ssh, sanitizeZellijEnv(env));
|
|
25
|
+
const rawExec = options.exec ??
|
|
26
|
+
(sshExec ? sshExec : defaultExec(zellijPath, env));
|
|
27
|
+
const exec = (args, opts) => rawExec(args, { ...opts, signal: opts.signal ?? options.signal });
|
|
20
28
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
21
29
|
const selfPaneId = resolveSelfPaneId(env);
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
const probeKey = identityCacheKey(zellijPath, ssh);
|
|
31
|
+
function readTransport() {
|
|
32
|
+
if (!ssh)
|
|
33
|
+
return { kind: "local", mode: "local" };
|
|
34
|
+
const ipc = sshExec?.ipcState;
|
|
35
|
+
return {
|
|
36
|
+
kind: "ssh",
|
|
37
|
+
mode: ssh.mode ?? "ssh",
|
|
38
|
+
host: ssh.host,
|
|
39
|
+
remoteBin: ssh.remoteBin,
|
|
40
|
+
tmp: ipc?.tmp ?? ssh.tmp,
|
|
41
|
+
ipc: ipc
|
|
42
|
+
? {
|
|
43
|
+
requested: ipc.requested,
|
|
44
|
+
status: ipc.status,
|
|
45
|
+
tmp: ipc.tmp,
|
|
46
|
+
socketDir: ipc.socketDir,
|
|
47
|
+
}
|
|
48
|
+
: undefined,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function operationBudget(budget, label) {
|
|
52
|
+
const deadline = Date.now() + budget;
|
|
53
|
+
return () => {
|
|
54
|
+
if (options.signal?.aborted) {
|
|
55
|
+
throw new ZellijError("cancelled", "operation cancelled");
|
|
56
|
+
}
|
|
57
|
+
const left = deadline - Date.now();
|
|
58
|
+
if (left <= 0) {
|
|
59
|
+
throw new ZellijError("zellij_failed", `${label} timed out after ${budget}ms`);
|
|
60
|
+
}
|
|
61
|
+
return left;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function ensureIdentity(remaining) {
|
|
65
|
+
if (options.skipIdentityProbe || options.exec)
|
|
66
|
+
return;
|
|
67
|
+
// Only the helpers' positively verified results are cached. In-flight or
|
|
68
|
+
// unresolved probes must not tie this call to another operation's budget.
|
|
69
|
+
await ensureZellijIdentity(exec, zellijPath, remaining(), probeKey);
|
|
70
|
+
await ensureZellijCapabilities(exec, zellijPath, remaining(), probeKey);
|
|
71
|
+
}
|
|
72
|
+
async function run(args, label, callTimeoutMs = timeoutMs) {
|
|
73
|
+
const remaining = operationBudget(callTimeoutMs, label);
|
|
74
|
+
await ensureIdentity(remaining);
|
|
75
|
+
const result = await exec(args, { timeoutMs: remaining() });
|
|
24
76
|
if (result.code === NOT_FOUND_EXIT) {
|
|
25
77
|
throw new ZellijError("zellij_missing", `zellij binary not found (${zellijPath}); install Zellij ≥ 0.42, add it to PATH, or set ZSWARM_BIN / ZSWARM_PATH`);
|
|
26
78
|
}
|
|
27
79
|
if (result.code !== 0) {
|
|
28
|
-
|
|
80
|
+
const detail = result.stderr.trim() || result.stdout.trim() || "no output";
|
|
81
|
+
if (/usage:\s*zswarm/i.test(detail) || /unknown arg:/i.test(detail)) {
|
|
82
|
+
throw new ZellijError("zellij_wrong_bin", `resolved binary looks like zswarm, not Zellij (${zellijPath}): ${detail}`);
|
|
83
|
+
}
|
|
84
|
+
throw new ZellijError("zellij_failed", `${label} failed (exit ${result.code}): ${detail}`);
|
|
29
85
|
}
|
|
30
86
|
return result;
|
|
31
87
|
}
|
|
32
|
-
async function listSessions() {
|
|
33
|
-
const
|
|
34
|
-
|
|
88
|
+
async function listSessions(callTimeoutMs = timeoutMs) {
|
|
89
|
+
const remaining = operationBudget(callTimeoutMs, "zellij list-sessions");
|
|
90
|
+
await ensureIdentity(remaining);
|
|
91
|
+
// Keep annotations (EXITED / current); --short drops them.
|
|
92
|
+
const result = await exec(["list-sessions", "--no-formatting"], {
|
|
93
|
+
timeoutMs: remaining(),
|
|
35
94
|
});
|
|
36
95
|
if (result.code === NOT_FOUND_EXIT) {
|
|
37
96
|
throw new ZellijError("zellij_missing", `zellij binary not found (${zellijPath}); install Zellij ≥ 0.42, add it to PATH, or set ZSWARM_BIN / ZSWARM_PATH`);
|
|
38
97
|
}
|
|
39
98
|
if (result.code !== 0) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
99
|
+
const detail = result.stderr.trim() || result.stdout.trim() || "no output";
|
|
100
|
+
if (/usage:\s*zswarm/i.test(detail) || /unknown arg:/i.test(detail)) {
|
|
101
|
+
throw new ZellijError("zellij_wrong_bin", `resolved binary looks like zswarm, not Zellij (${zellijPath}): ${detail}`);
|
|
102
|
+
}
|
|
43
103
|
if (isZellijNoSessionsOutput(result.stdout, result.stderr))
|
|
44
104
|
return [];
|
|
45
|
-
throw new ZellijError("zellij_failed", `zellij list-sessions failed (exit ${result.code}): ${
|
|
105
|
+
throw new ZellijError("zellij_failed", `zellij list-sessions failed (exit ${result.code}): ${detail}`);
|
|
46
106
|
}
|
|
47
107
|
return parseSessionList(result.stdout);
|
|
48
108
|
}
|
|
49
|
-
async function resolveSession(explicit) {
|
|
50
|
-
return sessionFromEnv(env, explicit) ??
|
|
109
|
+
async function resolveSession(explicit, callTimeoutMs = timeoutMs) {
|
|
110
|
+
return (sessionFromEnv(env, explicit) ??
|
|
111
|
+
sessionFromList(await listSessions(callTimeoutMs)));
|
|
51
112
|
}
|
|
52
|
-
async function listPanes(session) {
|
|
53
|
-
const result = await run(buildListPanesArgs(session), "zellij action list-panes");
|
|
113
|
+
async function listPanes(session, callTimeoutMs = timeoutMs) {
|
|
114
|
+
const result = await run(buildListPanesArgs(session), "zellij action list-panes", callTimeoutMs);
|
|
54
115
|
return parsePaneList(result.stdout);
|
|
55
116
|
}
|
|
56
117
|
async function injectPane(input) {
|
|
@@ -64,7 +125,7 @@ export function createZellijClient(options = {}) {
|
|
|
64
125
|
}
|
|
65
126
|
async function dumpPane(input) {
|
|
66
127
|
const paneId = normalizePaneId(input.paneId);
|
|
67
|
-
const result = await run(buildDumpArgs(input.session, paneId, input.full), "zellij action dump-screen");
|
|
128
|
+
const result = await run(buildDumpArgs(input.session, paneId, input.full), "zellij action dump-screen", input.timeoutMs ?? timeoutMs);
|
|
68
129
|
return { paneId, session: input.session, text: result.stdout };
|
|
69
130
|
}
|
|
70
131
|
async function sendKeys(input) {
|
|
@@ -88,7 +149,7 @@ export function createZellijClient(options = {}) {
|
|
|
88
149
|
if (!input.name.trim()) {
|
|
89
150
|
throw new ZellijError("missing_name", "name required");
|
|
90
151
|
}
|
|
91
|
-
await run(buildRenamePaneArgs(input.session, paneId, input.name), "zellij action rename-pane");
|
|
152
|
+
await run(buildRenamePaneArgs(input.session, paneId, input.name), "zellij action rename-pane", input.timeoutMs ?? timeoutMs);
|
|
92
153
|
return { paneId, session: input.session, name: input.name };
|
|
93
154
|
}
|
|
94
155
|
async function renameTab(input) {
|
|
@@ -103,8 +164,8 @@ export function createZellijClient(options = {}) {
|
|
|
103
164
|
await run(buildFocusPaneArgs(input.session, paneId), "zellij action focus-pane-id");
|
|
104
165
|
return { paneId, session: input.session };
|
|
105
166
|
}
|
|
106
|
-
async function listTabs(session) {
|
|
107
|
-
const result = await run(buildListTabsArgs(session), "zellij action list-tabs");
|
|
167
|
+
async function listTabs(session, callTimeoutMs = timeoutMs) {
|
|
168
|
+
const result = await run(buildListTabsArgs(session), "zellij action list-tabs", callTimeoutMs);
|
|
108
169
|
return parseTabList(result.stdout);
|
|
109
170
|
}
|
|
110
171
|
async function dumpLayout(session) {
|
|
@@ -128,11 +189,12 @@ export function createZellijClient(options = {}) {
|
|
|
128
189
|
}
|
|
129
190
|
/** `new-pane` prints the created pane id; `new-tab` prints a tab id instead. */
|
|
130
191
|
function parseCreatedPaneId(stdout) {
|
|
131
|
-
const
|
|
132
|
-
|
|
192
|
+
const ids = new Set(stdout.split(/\r?\n/).map((line) => line.trim().toLowerCase())
|
|
193
|
+
.filter((line) => /^(terminal|plugin)_\d+$/.test(line)));
|
|
194
|
+
return ids.size === 1 ? [...ids][0] : null;
|
|
133
195
|
}
|
|
134
196
|
async function newPane(input) {
|
|
135
|
-
const result = await run(buildNewPaneArgs(input), "zellij action new-pane");
|
|
197
|
+
const result = await run(buildNewPaneArgs(input), "zellij action new-pane", input.timeoutMs ?? timeoutMs);
|
|
136
198
|
return {
|
|
137
199
|
session: input.session,
|
|
138
200
|
paneId: parseCreatedPaneId(result.stdout),
|
|
@@ -140,11 +202,12 @@ export function createZellijClient(options = {}) {
|
|
|
140
202
|
};
|
|
141
203
|
}
|
|
142
204
|
async function newTab(input) {
|
|
143
|
-
const result = await run(buildNewTabArgs(input), "zellij action new-tab");
|
|
144
|
-
const
|
|
205
|
+
const result = await run(buildNewTabArgs(input), "zellij action new-tab", input.timeoutMs ?? timeoutMs);
|
|
206
|
+
const ids = new Set(result.stdout.split(/\r?\n/).map((line) => line.trim())
|
|
207
|
+
.filter((line) => /^\d+$/.test(line)).map(Number).filter(Number.isSafeInteger));
|
|
145
208
|
return {
|
|
146
209
|
session: input.session,
|
|
147
|
-
tabId:
|
|
210
|
+
tabId: ids.size === 1 ? [...ids][0] : null,
|
|
148
211
|
stdout: result.stdout.trim(),
|
|
149
212
|
};
|
|
150
213
|
}
|
|
@@ -200,7 +263,7 @@ export function createZellijClient(options = {}) {
|
|
|
200
263
|
configKey: input.configKey,
|
|
201
264
|
payload: changedPayload(input.panes),
|
|
202
265
|
}), {
|
|
203
|
-
timeoutMs: DEFAULT_BUS_TIMEOUT_MS,
|
|
266
|
+
timeoutMs: input.timeoutMs ?? DEFAULT_BUS_TIMEOUT_MS,
|
|
204
267
|
until: (stdout) => parseChangedReply(stdout) !== null,
|
|
205
268
|
});
|
|
206
269
|
}
|
|
@@ -214,11 +277,14 @@ export function createZellijClient(options = {}) {
|
|
|
214
277
|
/** Visible prefix so peer CLIs can tell zSwarm injects from human prompts. */
|
|
215
278
|
function formatPeerMessage(from, body) {
|
|
216
279
|
const sender = from.trim() || "swarm";
|
|
217
|
-
return `[zswarm from=${sender}]\n${body
|
|
280
|
+
return `[zswarm from=${sender}]\n${body}`;
|
|
218
281
|
}
|
|
219
282
|
return {
|
|
220
283
|
zellijPath,
|
|
221
284
|
selfPaneId,
|
|
285
|
+
get transport() {
|
|
286
|
+
return readTransport();
|
|
287
|
+
},
|
|
222
288
|
/** A `file:` plugin url only names a path on the machine running Zellij. */
|
|
223
289
|
remote: ssh !== null,
|
|
224
290
|
listSessions,
|
package/dist/zellij/panes.js
CHANGED
|
@@ -68,6 +68,9 @@ export function resolvePane(panes, to) {
|
|
|
68
68
|
const byTypedId = panes.find((p) => p.id === key.toLowerCase());
|
|
69
69
|
if (byTypedId)
|
|
70
70
|
return byTypedId;
|
|
71
|
+
if (/^(terminal|plugin)_\d+$/i.test(key)) {
|
|
72
|
+
throw new ZellijError("peer_not_found", `pane ${key} is not in the current listing`);
|
|
73
|
+
}
|
|
71
74
|
if (/^\d+$/.test(key)) {
|
|
72
75
|
const terminals = panes.filter((p) => !p.isPlugin && String(p.numericId) === key);
|
|
73
76
|
if (terminals.length === 1)
|
package/dist/zellij/session.d.ts
CHANGED
|
@@ -2,15 +2,31 @@ export type ZellijSessionResolve = {
|
|
|
2
2
|
session: string;
|
|
3
3
|
source: "arg" | "env_zswarm" | "env_zellij" | "sole_live";
|
|
4
4
|
};
|
|
5
|
+
/** One row from `zellij list-sessions --no-formatting` (not `--short`). */
|
|
6
|
+
export type ZellijSession = {
|
|
7
|
+
name: string;
|
|
8
|
+
/** True when Zellij marks the row EXITED (resurrectable, no live server). */
|
|
9
|
+
exited: boolean;
|
|
10
|
+
/** True when the listing marks this session as the caller's current one. */
|
|
11
|
+
current: boolean;
|
|
12
|
+
};
|
|
5
13
|
/**
|
|
6
14
|
* Pane hosting the caller, so writes can refuse to loop back into it.
|
|
7
15
|
* `ZSWARM_SELF_PANE` wins; Zellij exports `ZELLIJ_PANE_ID` inside a pane.
|
|
8
16
|
*/
|
|
9
17
|
export declare function resolveSelfPaneId(env?: NodeJS.ProcessEnv): string | null;
|
|
10
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Parse `list-sessions --no-formatting` output.
|
|
20
|
+
*
|
|
21
|
+
* Annotated form: `name [Created …] [(current)| (EXITED - …)]`.
|
|
22
|
+
* Bare names (from `--short` or test mocks) are treated as live.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseSessionList(stdout: string): ZellijSession[];
|
|
25
|
+
/** Live session names only — used when auto-picking a sole session. */
|
|
26
|
+
export declare function liveSessionNames(sessions: ZellijSession[]): string[];
|
|
11
27
|
/** Zellij exits 1 and prints this when the machine simply has no sessions. */
|
|
12
28
|
export declare function isZellijNoSessionsOutput(stdout: string, stderr: string): boolean;
|
|
13
29
|
/** Session from an explicit argument or the environment, before asking Zellij. */
|
|
14
30
|
export declare function sessionFromEnv(env: NodeJS.ProcessEnv, explicit?: string | null): ZellijSessionResolve | null;
|
|
15
|
-
/** Pick a session when the environment gave no answer. */
|
|
16
|
-
export declare function sessionFromList(sessions:
|
|
31
|
+
/** Pick a session when the environment gave no answer. EXITED rows do not count. */
|
|
32
|
+
export declare function sessionFromList(sessions: ZellijSession[]): ZellijSessionResolve;
|
package/dist/zellij/session.js
CHANGED
|
@@ -13,11 +13,40 @@ export function resolveSelfPaneId(env = process.env) {
|
|
|
13
13
|
return raw.toLowerCase();
|
|
14
14
|
return null;
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse `list-sessions --no-formatting` output.
|
|
18
|
+
*
|
|
19
|
+
* Annotated form: `name [Created …] [(current)| (EXITED - …)]`.
|
|
20
|
+
* Bare names (from `--short` or test mocks) are treated as live.
|
|
21
|
+
*/
|
|
16
22
|
export function parseSessionList(stdout) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
.
|
|
23
|
+
const sessions = [];
|
|
24
|
+
for (const raw of stdout.split(/\r?\n/)) {
|
|
25
|
+
// Drop ANSI in case a caller forgot --no-formatting.
|
|
26
|
+
const line = raw.replace(/\x1B\[[0-9;]*m/g, "").trim();
|
|
27
|
+
if (!line)
|
|
28
|
+
continue;
|
|
29
|
+
const created = line.indexOf(" [Created ");
|
|
30
|
+
if (created >= 0) {
|
|
31
|
+
const name = line.slice(0, created).trim();
|
|
32
|
+
if (!name)
|
|
33
|
+
continue;
|
|
34
|
+
const suffix = line.slice(created);
|
|
35
|
+
sessions.push({
|
|
36
|
+
name,
|
|
37
|
+
exited: /\(EXITED\b/i.test(suffix),
|
|
38
|
+
current: /\(current\)/i.test(suffix),
|
|
39
|
+
});
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
// Bare name from --short or a mock.
|
|
43
|
+
sessions.push({ name: line, exited: false, current: false });
|
|
44
|
+
}
|
|
45
|
+
return sessions;
|
|
46
|
+
}
|
|
47
|
+
/** Live session names only — used when auto-picking a sole session. */
|
|
48
|
+
export function liveSessionNames(sessions) {
|
|
49
|
+
return sessions.filter((s) => !s.exited).map((s) => s.name);
|
|
21
50
|
}
|
|
22
51
|
/** Zellij exits 1 and prints this when the machine simply has no sessions. */
|
|
23
52
|
export function isZellijNoSessionsOutput(stdout, stderr) {
|
|
@@ -36,13 +65,14 @@ export function sessionFromEnv(env, explicit) {
|
|
|
36
65
|
return { session: zellijEnv, source: "env_zellij" };
|
|
37
66
|
return null;
|
|
38
67
|
}
|
|
39
|
-
/** Pick a session when the environment gave no answer. */
|
|
68
|
+
/** Pick a session when the environment gave no answer. EXITED rows do not count. */
|
|
40
69
|
export function sessionFromList(sessions) {
|
|
41
|
-
|
|
42
|
-
|
|
70
|
+
const live = liveSessionNames(sessions);
|
|
71
|
+
if (live.length === 1) {
|
|
72
|
+
return { session: live[0], source: "sole_live" };
|
|
43
73
|
}
|
|
44
|
-
if (
|
|
74
|
+
if (live.length === 0) {
|
|
45
75
|
throw new ZellijError("zellij_no_session", "no live Zellij sessions; start zellij or pass session=");
|
|
46
76
|
}
|
|
47
|
-
throw new ZellijError("zellij_session_ambiguous", `multiple Zellij sessions (${
|
|
77
|
+
throw new ZellijError("zellij_session_ambiguous", `multiple Zellij sessions (${live.join(", ")}); pass session=`);
|
|
48
78
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zswarm/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "zSwarm Zellij client and shared ops dispatch",
|
|
6
6
|
"exports": {
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@zswarm/wasm": "0.1.
|
|
28
|
+
"@zswarm/wasm": "0.1.3"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsc -p tsconfig.json",
|
|
32
|
-
"test": "pnpm run build && node --test"
|
|
32
|
+
"test": "pnpm run build && node --import ./test-support/clean-env.mjs --test"
|
|
33
33
|
}
|
|
34
34
|
}
|