@zswarm/core 0.1.7 → 0.1.9

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.
@@ -1,9 +1,16 @@
1
+ import { ZellijError } from "../errors.js";
1
2
  import { createSshExec, NOT_FOUND_EXIT, type ExecFn, type ExecResult, type SshTarget } from "../exec.js";
2
3
  export { createSshExec, type SshTarget };
3
4
  export type ZellijExecResult = ExecResult;
4
5
  export type ZellijExecFn = ExecFn;
6
+ /** Where a missing/wrong-Zellij failure was produced. */
7
+ export type ZellijFailureOrigin = "local_spawn" | "local_preflight" | "remote";
5
8
  export declare const DEFAULT_TIMEOUT_MS = 15000;
6
9
  export { NOT_FOUND_EXIT };
10
+ /** Local Node spawn failure vs a process that actually started. */
11
+ export declare function originFromExecResult(result: ExecResult): Exclude<ZellijFailureOrigin, "local_preflight">;
12
+ export declare function zellijExecDetails(result: ExecResult): Record<string, unknown>;
13
+ export declare function zellijMissingError(zellijPath: string, result: ExecResult): ZellijError;
7
14
  /** Expand a leading `~/` or `~\` using USERPROFILE/HOME. */
8
15
  export declare function expandHomePath(input: string, env?: NodeJS.ProcessEnv): string;
9
16
  /**
@@ -7,6 +7,19 @@ import { applyIpcTmpEnv } from "./ipc.js";
7
7
  export { createSshExec };
8
8
  export const DEFAULT_TIMEOUT_MS = 15_000;
9
9
  export { NOT_FOUND_EXIT };
10
+ /** Local Node spawn failure vs a process that actually started. */
11
+ export function originFromExecResult(result) {
12
+ return result.spawn?.origin === "local" ? "local_spawn" : "remote";
13
+ }
14
+ export function zellijExecDetails(result) {
15
+ const origin = originFromExecResult(result);
16
+ return result.spawn
17
+ ? { origin, spawn: { origin: result.spawn.origin, errno: result.spawn.errno, bin: result.spawn.bin } }
18
+ : { origin };
19
+ }
20
+ export function zellijMissingError(zellijPath, result) {
21
+ return new ZellijError("zellij_missing", `zellij binary not found (${zellijPath}); install Zellij ≥ 0.42, add it to PATH, or set ZSWARM_BIN / ZSWARM_PATH`, zellijExecDetails(result));
22
+ }
10
23
  /** Expand a leading `~/` or `~\` using USERPROFILE/HOME. */
11
24
  export function expandHomePath(input, env = process.env) {
12
25
  const trimmed = input.trim();
@@ -51,7 +64,7 @@ export function looksLikeZswarmBinary(path) {
51
64
  export function assertZellijBinaryPath(path) {
52
65
  if (!looksLikeZswarmBinary(path))
53
66
  return;
54
- throw new ZellijError("zellij_wrong_bin", `ZSWARM_BIN/ZSWARM_PATH points at zswarm (${path}), not Zellij. Set it to the zellij binary (e.g. ~/.local/bin/zellij)`);
67
+ throw new ZellijError("zellij_wrong_bin", `ZSWARM_BIN/ZSWARM_PATH points at zswarm (${path}), not Zellij. Set it to the zellij binary (e.g. ~/.local/bin/zellij)`, { origin: "local_preflight", bin: path });
55
68
  }
56
69
  /**
57
70
  * ZSWARM_SSH is a destination, not a full ssh argv.
@@ -127,10 +140,10 @@ export async function ensureZellijIdentity(exec, zellijPath, timeoutMs = 3_000,
127
140
  });
128
141
  const text = `${result.stdout}\n${result.stderr}`;
129
142
  if (/usage:\s*zswarm/i.test(text) || /unknown arg:/i.test(text)) {
130
- throw new ZellijError("zellij_wrong_bin", `resolved binary is zswarm, not Zellij (${zellijPath}). Set ZSWARM_BIN to the zellij executable`);
143
+ throw new ZellijError("zellij_wrong_bin", `resolved binary is zswarm, not Zellij (${zellijPath}). Set ZSWARM_BIN to the zellij executable`, zellijExecDetails(result));
131
144
  }
132
145
  if (result.code === NOT_FOUND_EXIT) {
133
- throw new ZellijError("zellij_missing", `zellij binary not found (${zellijPath}); install Zellij ≥ 0.42, add it to PATH, or set ZSWARM_BIN / ZSWARM_PATH`);
146
+ throw zellijMissingError(zellijPath, result);
134
147
  }
135
148
  if (result.code === 0) {
136
149
  if (signal?.aborted)
@@ -139,7 +152,7 @@ export async function ensureZellijIdentity(exec, zellijPath, timeoutMs = 3_000,
139
152
  identityCache.add(cacheKey);
140
153
  return true;
141
154
  }
142
- throw new ZellijError("zellij_wrong_bin", `resolved binary is not Zellij (${zellijPath}); --version returned: ${result.stdout.trim() || result.stderr.trim() || "empty"}`);
155
+ throw new ZellijError("zellij_wrong_bin", `resolved binary is not Zellij (${zellijPath}); --version returned: ${result.stdout.trim() || result.stderr.trim() || "empty"}`, zellijExecDetails(result));
143
156
  }
144
157
  // Timeout / transport failure: leave unresolved so a later call retries.
145
158
  return false;
@@ -161,10 +174,10 @@ export async function ensureZellijCapabilities(exec, zellijPath, timeoutMs = 3_0
161
174
  });
162
175
  const text = `${result.stdout}\n${result.stderr}`;
163
176
  if (/usage:\s*zswarm/i.test(text)) {
164
- throw new ZellijError("zellij_wrong_bin", `resolved binary is zswarm, not Zellij (${zellijPath})`);
177
+ throw new ZellijError("zellij_wrong_bin", `resolved binary is zswarm, not Zellij (${zellijPath})`, zellijExecDetails(result));
165
178
  }
166
179
  if (result.code === NOT_FOUND_EXIT) {
167
- throw new ZellijError("zellij_missing", `zellij binary not found (${zellijPath})`);
180
+ throw zellijMissingError(zellijPath, result);
168
181
  }
169
182
  // Require the flags this client always passes.
170
183
  if (result.code === 0 &&
@@ -176,7 +189,7 @@ export async function ensureZellijCapabilities(exec, zellijPath, timeoutMs = 3_0
176
189
  return true;
177
190
  }
178
191
  if (result.code === 0) {
179
- throw new ZellijError("zellij_incompatible", `Zellij at ${zellijPath} does not advertise list-sessions --no-formatting; upgrade Zellij (≥ 0.42) or zswarm`);
192
+ throw new ZellijError("zellij_incompatible", `Zellij at ${zellijPath} does not advertise list-sessions --no-formatting; upgrade Zellij (≥ 0.42) or zswarm`, zellijExecDetails(result));
180
193
  }
181
194
  // Soft: leave unresolved on transport failure.
182
195
  return false;
@@ -4,7 +4,7 @@ import { buildClosePaneArgs, buildDumpArgs, buildDumpLayoutArgs, buildFocusPaneA
4
4
  import { DEFAULT_BUS_TIMEOUT_MS, parseBusReply, parseChangedReply, parseScrollbackReply, parseWaitReply, } from "./bus.js";
5
5
  import { parseTabList, resolveTab } from "./tabs.js";
6
6
  import { createSshExec } from "../exec.js";
7
- import { DEFAULT_TIMEOUT_MS, NOT_FOUND_EXIT, defaultExec, ensureZellijProbes, identityCacheKey, resolveSshTarget, resolveZellijBinary, sanitizeZellijEnv, } from "./binary.js";
7
+ import { DEFAULT_TIMEOUT_MS, NOT_FOUND_EXIT, defaultExec, ensureZellijProbes, identityCacheKey, resolveSshTarget, resolveZellijBinary, sanitizeZellijEnv, zellijExecDetails, zellijMissingError, } from "./binary.js";
8
8
  import { normalizePaneId, parsePaneList, resolvePane, } from "./panes.js";
9
9
  import { isZellijNoSessionsOutput, parseSessionList, resolveSelfPaneId, sessionFromEnv, sessionFromList, } from "./session.js";
10
10
  /** Zellij wrapper with bounded caches of completed discovery observations. */
@@ -119,14 +119,14 @@ export function createZellijClient(options = {}) {
119
119
  await ensureIdentity(remaining);
120
120
  const result = await exec(args, { timeoutMs: remaining() });
121
121
  if (result.code === NOT_FOUND_EXIT) {
122
- throw new ZellijError("zellij_missing", `zellij binary not found (${zellijPath}); install Zellij ≥ 0.42, add it to PATH, or set ZSWARM_BIN / ZSWARM_PATH`);
122
+ throw zellijMissingError(zellijPath, result);
123
123
  }
124
124
  if (result.code !== 0) {
125
125
  const detail = result.stderr.trim() || result.stdout.trim() || "no output";
126
126
  if (/usage:\s*zswarm/i.test(detail) || /unknown arg:/i.test(detail)) {
127
- throw new ZellijError("zellij_wrong_bin", `resolved binary looks like zswarm, not Zellij (${zellijPath}): ${detail}`);
127
+ throw new ZellijError("zellij_wrong_bin", `resolved binary looks like zswarm, not Zellij (${zellijPath}): ${detail}`, zellijExecDetails(result));
128
128
  }
129
- throw new ZellijError("zellij_failed", `${label} failed (exit ${result.code}): ${detail}`);
129
+ throw new ZellijError("zellij_failed", `${label} failed (exit ${result.code}): ${detail}`, zellijExecDetails(result));
130
130
  }
131
131
  return result;
132
132
  }
@@ -159,16 +159,16 @@ export function createZellijClient(options = {}) {
159
159
  timeoutMs: remaining(),
160
160
  });
161
161
  if (result.code === NOT_FOUND_EXIT) {
162
- throw new ZellijError("zellij_missing", `zellij binary not found (${zellijPath}); install Zellij ≥ 0.42, add it to PATH, or set ZSWARM_BIN / ZSWARM_PATH`);
162
+ throw zellijMissingError(zellijPath, result);
163
163
  }
164
164
  if (result.code !== 0) {
165
165
  const detail = result.stderr.trim() || result.stdout.trim() || "no output";
166
166
  if (/usage:\s*zswarm/i.test(detail) || /unknown arg:/i.test(detail)) {
167
- throw new ZellijError("zellij_wrong_bin", `resolved binary looks like zswarm, not Zellij (${zellijPath}): ${detail}`);
167
+ throw new ZellijError("zellij_wrong_bin", `resolved binary looks like zswarm, not Zellij (${zellijPath}): ${detail}`, zellijExecDetails(result));
168
168
  }
169
169
  if (isZellijNoSessionsOutput(result.stdout, result.stderr))
170
170
  return [];
171
- throw new ZellijError("zellij_failed", `zellij list-sessions failed (exit ${result.code}): ${detail}`);
171
+ throw new ZellijError("zellij_failed", `zellij list-sessions failed (exit ${result.code}): ${detail}`, zellijExecDetails(result));
172
172
  }
173
173
  return parseSessionList(result.stdout);
174
174
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zswarm/core",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "type": "module",
5
5
  "description": "zSwarm Zellij client and shared ops dispatch",
6
6
  "exports": {