@zswarm/core 0.1.0

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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -0
  3. package/dist/errors.d.ts +5 -0
  4. package/dist/errors.js +9 -0
  5. package/dist/exec.d.ts +32 -0
  6. package/dist/exec.js +118 -0
  7. package/dist/git.d.ts +51 -0
  8. package/dist/git.js +190 -0
  9. package/dist/harness.d.ts +21 -0
  10. package/dist/harness.js +98 -0
  11. package/dist/index.d.ts +23 -0
  12. package/dist/index.js +22 -0
  13. package/dist/keys.d.ts +15 -0
  14. package/dist/keys.js +168 -0
  15. package/dist/ops/broadcast.d.ts +19 -0
  16. package/dist/ops/broadcast.js +113 -0
  17. package/dist/ops/bus.d.ts +59 -0
  18. package/dist/ops/bus.js +284 -0
  19. package/dist/ops/delivery.d.ts +43 -0
  20. package/dist/ops/delivery.js +191 -0
  21. package/dist/ops/dispatch.d.ts +4 -0
  22. package/dist/ops/dispatch.js +331 -0
  23. package/dist/ops/guards.d.ts +15 -0
  24. package/dist/ops/guards.js +36 -0
  25. package/dist/ops/log.d.ts +4 -0
  26. package/dist/ops/log.js +26 -0
  27. package/dist/ops/panes.d.ts +13 -0
  28. package/dist/ops/panes.js +111 -0
  29. package/dist/ops/review.d.ts +9 -0
  30. package/dist/ops/review.js +108 -0
  31. package/dist/ops/signals.d.ts +10 -0
  32. package/dist/ops/signals.js +86 -0
  33. package/dist/ops/spawn.d.ts +5 -0
  34. package/dist/ops/spawn.js +98 -0
  35. package/dist/ops/status.d.ts +36 -0
  36. package/dist/ops/status.js +192 -0
  37. package/dist/ops/tail.d.ts +19 -0
  38. package/dist/ops/tail.js +59 -0
  39. package/dist/ops/types.d.ts +26 -0
  40. package/dist/ops/types.js +1 -0
  41. package/dist/ops/util.d.ts +48 -0
  42. package/dist/ops/util.js +85 -0
  43. package/dist/ops/wait.d.ts +19 -0
  44. package/dist/ops/wait.js +138 -0
  45. package/dist/ops/worktree.d.ts +21 -0
  46. package/dist/ops/worktree.js +144 -0
  47. package/dist/policy.d.ts +15 -0
  48. package/dist/policy.js +75 -0
  49. package/dist/schema.d.ts +26 -0
  50. package/dist/schema.js +489 -0
  51. package/dist/state.d.ts +52 -0
  52. package/dist/state.js +150 -0
  53. package/dist/zellij/args.d.ts +93 -0
  54. package/dist/zellij/args.js +261 -0
  55. package/dist/zellij/binary.d.ts +17 -0
  56. package/dist/zellij/binary.js +96 -0
  57. package/dist/zellij/bus.d.ts +91 -0
  58. package/dist/zellij/bus.js +279 -0
  59. package/dist/zellij/client.d.ts +162 -0
  60. package/dist/zellij/client.js +246 -0
  61. package/dist/zellij/panes.d.ts +21 -0
  62. package/dist/zellij/panes.js +110 -0
  63. package/dist/zellij/session.d.ts +14 -0
  64. package/dist/zellij/session.js +44 -0
  65. package/dist/zellij/tabs.d.ts +14 -0
  66. package/dist/zellij/tabs.js +56 -0
  67. package/package.json +34 -0
@@ -0,0 +1,21 @@
1
+ export type ZellijPane = {
2
+ id: string;
3
+ numericId: number;
4
+ isPlugin: boolean;
5
+ title: string;
6
+ command?: string | null;
7
+ cwd?: string | null;
8
+ tabName?: string | null;
9
+ tabId?: number | null;
10
+ focused: boolean;
11
+ exited: boolean;
12
+ floating: boolean;
13
+ };
14
+ export declare function normalizePaneId(raw: string, isPlugin?: boolean): string;
15
+ /** Parse `list-panes --json` output. */
16
+ export declare function parsePaneList(stdout: string): ZellijPane[];
17
+ /**
18
+ * Find a pane by typed id, bare number, exact title, command, then partial
19
+ * title. Ambiguity is an error rather than a guess.
20
+ */
21
+ export declare function resolvePane(panes: ZellijPane[], to: string): ZellijPane;
@@ -0,0 +1,110 @@
1
+ import { ZellijError } from "../errors.js";
2
+ export function normalizePaneId(raw, isPlugin = false) {
3
+ const t = raw.trim();
4
+ if (!t)
5
+ throw new ZellijError("invalid_pane", "empty pane id");
6
+ if (/^(terminal|plugin)_\d+$/i.test(t))
7
+ return t.toLowerCase();
8
+ if (/^\d+$/.test(t)) {
9
+ return isPlugin ? `plugin_${t}` : `terminal_${t}`;
10
+ }
11
+ return t;
12
+ }
13
+ function parsePaneRow(row) {
14
+ const numericId = Number(row.id);
15
+ if (!Number.isFinite(numericId))
16
+ return null;
17
+ const isPlugin = row.is_plugin === true;
18
+ const id = isPlugin ? `plugin_${numericId}` : `terminal_${numericId}`;
19
+ const title = String(row.title ?? "");
20
+ const command = (typeof row.pane_command === "string" && row.pane_command) ||
21
+ (typeof row.terminal_command === "string" && row.terminal_command) ||
22
+ null;
23
+ return {
24
+ id,
25
+ numericId,
26
+ isPlugin,
27
+ title,
28
+ command,
29
+ cwd: typeof row.pane_cwd === "string" ? row.pane_cwd : null,
30
+ tabName: typeof row.tab_name === "string" ? row.tab_name : null,
31
+ tabId: typeof row.tab_id === "number" ? row.tab_id : null,
32
+ focused: row.is_focused === true,
33
+ exited: row.exited === true,
34
+ floating: row.is_floating === true,
35
+ };
36
+ }
37
+ /** Parse `list-panes --json` output. */
38
+ export function parsePaneList(stdout) {
39
+ let parsed;
40
+ try {
41
+ parsed = JSON.parse(stdout);
42
+ }
43
+ catch {
44
+ throw new ZellijError("zellij_failed", "list-panes returned non-JSON output");
45
+ }
46
+ if (!Array.isArray(parsed)) {
47
+ throw new ZellijError("zellij_failed", "list-panes JSON was not an array");
48
+ }
49
+ const panes = [];
50
+ for (const row of parsed) {
51
+ if (row && typeof row === "object") {
52
+ const pane = parsePaneRow(row);
53
+ if (pane)
54
+ panes.push(pane);
55
+ }
56
+ }
57
+ return panes;
58
+ }
59
+ /**
60
+ * Find a pane by typed id, bare number, exact title, command, then partial
61
+ * title. Ambiguity is an error rather than a guess.
62
+ */
63
+ export function resolvePane(panes, to) {
64
+ const key = to.trim();
65
+ if (!key) {
66
+ throw new ZellijError("peer_not_found", "to (pane id or title) required");
67
+ }
68
+ const byTypedId = panes.find((p) => p.id === key.toLowerCase());
69
+ if (byTypedId)
70
+ return byTypedId;
71
+ if (/^\d+$/.test(key)) {
72
+ const terminals = panes.filter((p) => !p.isPlugin && String(p.numericId) === key);
73
+ if (terminals.length === 1)
74
+ return terminals[0];
75
+ const any = panes.filter((p) => String(p.numericId) === key);
76
+ if (any.length === 1)
77
+ return any[0];
78
+ if (any.length > 1) {
79
+ throw new ZellijError("peer_ambiguous", `pane id ${key} matches both terminal and plugin; use terminal_${key} or plugin_${key}`);
80
+ }
81
+ }
82
+ const lowered = key.toLowerCase();
83
+ const titleMatches = panes.filter((p) => !p.isPlugin && p.title.toLowerCase() === lowered);
84
+ if (titleMatches.length === 1)
85
+ return titleMatches[0];
86
+ if (titleMatches.length > 1) {
87
+ throw new ZellijError("peer_ambiguous", `multiple panes titled "${key}"; use pane id from list`);
88
+ }
89
+ const cmdMatches = panes.filter((p) => {
90
+ if (p.isPlugin || !p.command)
91
+ return false;
92
+ const base = p.command.replace(/\\/g, "/").split("/").pop() ?? "";
93
+ const baseNoExt = base.replace(/\.(exe|cmd|bat)$/i, "");
94
+ return (base.toLowerCase() === lowered ||
95
+ baseNoExt.toLowerCase() === lowered ||
96
+ p.command.toLowerCase().includes(lowered));
97
+ });
98
+ if (cmdMatches.length === 1)
99
+ return cmdMatches[0];
100
+ if (cmdMatches.length > 1) {
101
+ throw new ZellijError("peer_ambiguous", `multiple panes match command "${key}"; use pane id from list`);
102
+ }
103
+ const titlePartial = panes.filter((p) => !p.isPlugin && p.title.toLowerCase().includes(lowered));
104
+ if (titlePartial.length === 1)
105
+ return titlePartial[0];
106
+ if (titlePartial.length > 1) {
107
+ throw new ZellijError("peer_ambiguous", `multiple panes match "${key}"; use pane id from list`);
108
+ }
109
+ throw new ZellijError("peer_not_found", `no Zellij pane matching "${key}"; zswarm({op:"list"}) to list`);
110
+ }
@@ -0,0 +1,14 @@
1
+ export type ZellijSessionResolve = {
2
+ session: string;
3
+ source: "arg" | "env_zswarm" | "env_zellij" | "sole_live";
4
+ };
5
+ /**
6
+ * Pane hosting the caller, so writes can refuse to loop back into it.
7
+ * `ZSWARM_SELF_PANE` wins; Zellij exports `ZELLIJ_PANE_ID` inside a pane.
8
+ */
9
+ export declare function resolveSelfPaneId(env?: NodeJS.ProcessEnv): string | null;
10
+ export declare function parseSessionList(stdout: string): string[];
11
+ /** Session from an explicit argument or the environment, before asking Zellij. */
12
+ export declare function sessionFromEnv(env: NodeJS.ProcessEnv, explicit?: string | null): ZellijSessionResolve | null;
13
+ /** Pick a session when the environment gave no answer. */
14
+ export declare function sessionFromList(sessions: string[]): ZellijSessionResolve;
@@ -0,0 +1,44 @@
1
+ import { ZellijError } from "../errors.js";
2
+ /**
3
+ * Pane hosting the caller, so writes can refuse to loop back into it.
4
+ * `ZSWARM_SELF_PANE` wins; Zellij exports `ZELLIJ_PANE_ID` inside a pane.
5
+ */
6
+ export function resolveSelfPaneId(env = process.env) {
7
+ const raw = (env.ZSWARM_SELF_PANE ?? env.ZELLIJ_PANE_ID ?? "").trim();
8
+ if (!raw || raw.toLowerCase() === "none")
9
+ return null;
10
+ if (/^\d+$/.test(raw))
11
+ return `terminal_${raw}`;
12
+ if (/^(terminal|plugin)_\d+$/i.test(raw))
13
+ return raw.toLowerCase();
14
+ return null;
15
+ }
16
+ export function parseSessionList(stdout) {
17
+ return stdout
18
+ .split(/\r?\n/)
19
+ .map((l) => l.trim())
20
+ .filter(Boolean);
21
+ }
22
+ /** Session from an explicit argument or the environment, before asking Zellij. */
23
+ export function sessionFromEnv(env, explicit) {
24
+ const arg = explicit?.trim();
25
+ if (arg)
26
+ return { session: arg, source: "arg" };
27
+ const swarmEnv = env.ZSWARM_SESSION?.trim();
28
+ if (swarmEnv)
29
+ return { session: swarmEnv, source: "env_zswarm" };
30
+ const zellijEnv = env.ZELLIJ_SESSION_NAME?.trim();
31
+ if (zellijEnv)
32
+ return { session: zellijEnv, source: "env_zellij" };
33
+ return null;
34
+ }
35
+ /** Pick a session when the environment gave no answer. */
36
+ export function sessionFromList(sessions) {
37
+ if (sessions.length === 1) {
38
+ return { session: sessions[0], source: "sole_live" };
39
+ }
40
+ if (sessions.length === 0) {
41
+ throw new ZellijError("zellij_no_session", "no live Zellij sessions; start zellij or pass session=");
42
+ }
43
+ throw new ZellijError("zellij_session_ambiguous", `multiple Zellij sessions (${sessions.join(", ")}); pass session=`);
44
+ }
@@ -0,0 +1,14 @@
1
+ export type ZellijTab = {
2
+ id: number;
3
+ position: number;
4
+ name: string;
5
+ active: boolean;
6
+ panes: number;
7
+ fullscreen: boolean;
8
+ sync: boolean;
9
+ layout: string | null;
10
+ };
11
+ /** Parse `list-tabs --json` output. */
12
+ export declare function parseTabList(stdout: string): ZellijTab[];
13
+ /** Find a tab by name (case-insensitive), position, or stable id. */
14
+ export declare function resolveTab(tabs: ZellijTab[], key: string): ZellijTab;
@@ -0,0 +1,56 @@
1
+ import { ZellijError } from "../errors.js";
2
+ /** Parse `list-tabs --json` output. */
3
+ export function parseTabList(stdout) {
4
+ let parsed;
5
+ try {
6
+ parsed = JSON.parse(stdout);
7
+ }
8
+ catch {
9
+ throw new ZellijError("zellij_failed", "list-tabs returned non-JSON output");
10
+ }
11
+ if (!Array.isArray(parsed)) {
12
+ throw new ZellijError("zellij_failed", "list-tabs JSON was not an array");
13
+ }
14
+ const tabs = [];
15
+ for (const entry of parsed) {
16
+ if (!entry || typeof entry !== "object")
17
+ continue;
18
+ const row = entry;
19
+ const position = Number(row.position);
20
+ if (!Number.isFinite(position))
21
+ continue;
22
+ const id = Number(row.tab_id);
23
+ tabs.push({
24
+ id: Number.isFinite(id) ? id : position,
25
+ position,
26
+ name: String(row.name ?? ""),
27
+ active: row.active === true,
28
+ panes: Number(row.selectable_tiled_panes_count ?? 0),
29
+ fullscreen: row.is_fullscreen_active === true,
30
+ sync: row.is_sync_panes_active === true,
31
+ layout: typeof row.active_swap_layout_name === "string"
32
+ ? row.active_swap_layout_name
33
+ : null,
34
+ });
35
+ }
36
+ return tabs;
37
+ }
38
+ /** Find a tab by name (case-insensitive), position, or stable id. */
39
+ export function resolveTab(tabs, key) {
40
+ const wanted = key.trim();
41
+ if (!wanted)
42
+ throw new ZellijError("tab_not_found", "tab name or id required");
43
+ const byName = tabs.filter((t) => t.name.toLowerCase() === wanted.toLowerCase());
44
+ if (byName.length === 1)
45
+ return byName[0];
46
+ if (byName.length > 1) {
47
+ throw new ZellijError("tab_ambiguous", `multiple tabs named "${key}"; use the tab id`);
48
+ }
49
+ if (/^\d+$/.test(wanted)) {
50
+ const n = Number(wanted);
51
+ const byId = tabs.find((t) => t.id === n) ?? tabs.find((t) => t.position === n);
52
+ if (byId)
53
+ return byId;
54
+ }
55
+ throw new ZellijError("tab_not_found", `no tab matching "${key}"`);
56
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@zswarm/core",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "zSwarm Zellij client and shared ops dispatch",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ }
11
+ },
12
+ "devDependencies": {
13
+ "@types/node": "22.14.1",
14
+ "typescript": "5.8.3"
15
+ },
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "license": "MIT",
20
+ "files": [
21
+ "dist",
22
+ "README.md"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "dependencies": {
28
+ "@zswarm/wasm": "0.1.0"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "test": "pnpm run build && node --test tests/*.test.mjs"
33
+ }
34
+ }