loreli 0.0.0 → 2.0.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 (104) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +710 -97
  3. package/bin/loreli.js +89 -0
  4. package/package.json +77 -14
  5. package/packages/README.md +101 -0
  6. package/packages/action/README.md +98 -0
  7. package/packages/action/prompts/action.md +172 -0
  8. package/packages/action/src/index.js +684 -0
  9. package/packages/agent/README.md +606 -0
  10. package/packages/agent/src/backends/claude.js +387 -0
  11. package/packages/agent/src/backends/codex.js +351 -0
  12. package/packages/agent/src/backends/cursor.js +371 -0
  13. package/packages/agent/src/backends/index.js +486 -0
  14. package/packages/agent/src/base.js +138 -0
  15. package/packages/agent/src/cli.js +275 -0
  16. package/packages/agent/src/discover.js +396 -0
  17. package/packages/agent/src/factory.js +124 -0
  18. package/packages/agent/src/index.js +12 -0
  19. package/packages/agent/src/models.js +159 -0
  20. package/packages/agent/src/output.js +62 -0
  21. package/packages/agent/src/session.js +162 -0
  22. package/packages/agent/src/trace.js +186 -0
  23. package/packages/classify/README.md +136 -0
  24. package/packages/classify/prompts/blocker.md +12 -0
  25. package/packages/classify/prompts/feedback.md +14 -0
  26. package/packages/classify/prompts/pane-state.md +20 -0
  27. package/packages/classify/src/index.js +81 -0
  28. package/packages/config/README.md +898 -0
  29. package/packages/config/src/defaults.js +145 -0
  30. package/packages/config/src/index.js +223 -0
  31. package/packages/config/src/schema.js +291 -0
  32. package/packages/config/src/validate.js +160 -0
  33. package/packages/context/README.md +165 -0
  34. package/packages/context/src/index.js +198 -0
  35. package/packages/hub/README.md +338 -0
  36. package/packages/hub/src/base.js +154 -0
  37. package/packages/hub/src/github.js +1597 -0
  38. package/packages/hub/src/index.js +79 -0
  39. package/packages/hub/src/labels.js +48 -0
  40. package/packages/identity/README.md +288 -0
  41. package/packages/identity/src/index.js +620 -0
  42. package/packages/identity/src/themes/avatar.js +217 -0
  43. package/packages/identity/src/themes/digimon.js +217 -0
  44. package/packages/identity/src/themes/dragonball.js +217 -0
  45. package/packages/identity/src/themes/lotr.js +217 -0
  46. package/packages/identity/src/themes/marvel.js +217 -0
  47. package/packages/identity/src/themes/pokemon.js +217 -0
  48. package/packages/identity/src/themes/starwars.js +217 -0
  49. package/packages/identity/src/themes/transformers.js +217 -0
  50. package/packages/identity/src/themes/zelda.js +217 -0
  51. package/packages/knowledge/README.md +217 -0
  52. package/packages/knowledge/src/index.js +243 -0
  53. package/packages/log/README.md +93 -0
  54. package/packages/log/src/index.js +252 -0
  55. package/packages/marker/README.md +200 -0
  56. package/packages/marker/src/index.js +184 -0
  57. package/packages/mcp/README.md +323 -0
  58. package/packages/mcp/instructions.md +126 -0
  59. package/packages/mcp/scaffolding/.agents/skills/loreli-context/SKILL.md +89 -0
  60. package/packages/mcp/scaffolding/ISSUE_TEMPLATE/config.yml +2 -0
  61. package/packages/mcp/scaffolding/ISSUE_TEMPLATE/loreli.yml +83 -0
  62. package/packages/mcp/scaffolding/loreli.yml +491 -0
  63. package/packages/mcp/scaffolding/mcp-configs/.codex/config.toml +4 -0
  64. package/packages/mcp/scaffolding/mcp-configs/.cursor/mcp.json +14 -0
  65. package/packages/mcp/scaffolding/mcp-configs/.mcp.json +14 -0
  66. package/packages/mcp/scaffolding/pull-request.md +23 -0
  67. package/packages/mcp/src/index.js +600 -0
  68. package/packages/mcp/src/tools/agent-context.js +44 -0
  69. package/packages/mcp/src/tools/agents.js +450 -0
  70. package/packages/mcp/src/tools/context.js +200 -0
  71. package/packages/mcp/src/tools/github.js +1163 -0
  72. package/packages/mcp/src/tools/hitl.js +162 -0
  73. package/packages/mcp/src/tools/index.js +18 -0
  74. package/packages/mcp/src/tools/refactor.js +227 -0
  75. package/packages/mcp/src/tools/repo.js +44 -0
  76. package/packages/mcp/src/tools/start.js +904 -0
  77. package/packages/mcp/src/tools/status.js +149 -0
  78. package/packages/mcp/src/tools/work.js +134 -0
  79. package/packages/orchestrator/README.md +192 -0
  80. package/packages/orchestrator/src/index.js +1492 -0
  81. package/packages/planner/README.md +251 -0
  82. package/packages/planner/prompts/plan-reviewer.md +109 -0
  83. package/packages/planner/prompts/planner.md +191 -0
  84. package/packages/planner/prompts/tiebreaker-reviewer.md +71 -0
  85. package/packages/planner/src/index.js +1381 -0
  86. package/packages/review/README.md +129 -0
  87. package/packages/review/prompts/reviewer.md +158 -0
  88. package/packages/review/src/index.js +1403 -0
  89. package/packages/risk/README.md +178 -0
  90. package/packages/risk/prompts/risk.md +272 -0
  91. package/packages/risk/src/index.js +439 -0
  92. package/packages/session/README.md +165 -0
  93. package/packages/session/src/index.js +215 -0
  94. package/packages/test-utils/README.md +96 -0
  95. package/packages/test-utils/src/index.js +354 -0
  96. package/packages/tmux/README.md +261 -0
  97. package/packages/tmux/src/index.js +501 -0
  98. package/packages/workflow/README.md +317 -0
  99. package/packages/workflow/prompts/preamble.md +14 -0
  100. package/packages/workflow/src/index.js +660 -0
  101. package/packages/workflow/src/proof-of-life.js +74 -0
  102. package/packages/workspace/README.md +143 -0
  103. package/packages/workspace/src/index.js +1127 -0
  104. package/index.js +0 -8
@@ -0,0 +1,261 @@
1
+ # loreli/tmux
2
+
3
+ Zero-dependency Node.js wrapper for tmux with pane-level control and automatic session lifecycle management.
4
+
5
+ This package exists because no adequate Node.js tmux library provides pane-level management. It wraps the `tmux` CLI binary with a clean async API, mapping each method ~1:1 to a tmux command.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add loreli
11
+ ```
12
+
13
+ Requires `tmux` to be installed on the system (`brew install tmux` on macOS, `apt install tmux` on Linux).
14
+
15
+ ## Session Lifecycle
16
+
17
+ Sessions created via `create()` are tracked in an in-memory set. This enables batch cleanup via `cleanup()` when the orchestrator shuts down gracefully. For crash recovery where in-memory state is lost, `prune()` queries tmux directly by session-name prefix.
18
+
19
+ The key design insight is **no garbage default windows**. When `create()` is called with a `command` option, that command becomes the initial window's process. tmux auto-destroys a session when its last window dies, so when all agents exit, the session disappears naturally — no orphaned sessions.
20
+
21
+ The following example shows the recommended spawn pattern used by Loreli's backends. The first agent creates the session, and subsequent agents join as additional windows.
22
+
23
+ ```js
24
+ import { Tmux } from 'loreli/tmux';
25
+
26
+ const tmux = new Tmux();
27
+
28
+ // First agent IS the session's initial window — no garbage default window
29
+ const pane1 = await tmux.create('loreli', { cwd: '/tmp', command: './agent1.sh' });
30
+
31
+ // Second agent joins as an additional window
32
+ const pane2 = await tmux.window('loreli', { cwd: '/tmp', command: './agent2.sh' });
33
+
34
+ // Both agents die → tmux auto-destroys the session. No orphans.
35
+ ```
36
+
37
+ ## API Reference
38
+
39
+ ### `Tmux.available()`
40
+
41
+ Static method. Returns `true` if tmux is on PATH, `false` otherwise. Does not throw.
42
+
43
+ ```js
44
+ import { Tmux } from 'loreli/tmux';
45
+
46
+ if (Tmux.available()) {
47
+ const tmux = new Tmux();
48
+ }
49
+ ```
50
+
51
+ ### `new Tmux()`
52
+
53
+ Creates a new Tmux instance. All subsequent methods are called on this instance. Each instance maintains its own set of tracked sessions.
54
+
55
+ ### Session Management
56
+
57
+ #### `tmux.create(name, opts?)` → `Promise<string|void>`
58
+
59
+ Create a new detached tmux session. Throws if a session with the same name already exists.
60
+
61
+ When `opts.command` is provided, it becomes the initial window's process and the pane ID is returned. When no command is provided, a bare session with an empty default window is created (useful for test sessions with specific dimensions).
62
+
63
+ - `name` `{string}` — The session name.
64
+ - `opts.width` `{number}` — Initial window width (tmux `-x` flag).
65
+ - `opts.height` `{number}` — Initial window height (tmux `-y` flag).
66
+ - `opts.cwd` `{string}` — Working directory for the initial window.
67
+ - `opts.command` `{string}` — Command for the initial window.
68
+
69
+ The following example shows creating a session with an agent command as its initial window. The pane ID is returned so you can capture output or set options on the pane.
70
+
71
+ ```js
72
+ // Agent command IS the initial window — no garbage default window
73
+ const paneId = await tmux.create('loreli', {
74
+ cwd: '/tmp/workspace',
75
+ command: '/path/to/launch.sh'
76
+ });
77
+
78
+ // Test session with specific dimensions (no command — bare session)
79
+ await tmux.create('test-session', { width: 200, height: 50 });
80
+ ```
81
+
82
+ #### `tmux.kill(name)` → `Promise<void>`
83
+
84
+ Kill (destroy) a tmux session. Throws if the session does not exist. Removes the session from the tracked set.
85
+
86
+ #### `tmux.list()` → `Promise<string[]>`
87
+
88
+ List all active tmux session names. Returns an empty array when no server is running.
89
+
90
+ #### `tmux.has(name)` → `Promise<boolean>`
91
+
92
+ Check whether a session with the given name exists.
93
+
94
+ #### `tmux.owned()` → `string[]`
95
+
96
+ Return session names created by this Tmux instance. Synchronous — reads from the in-memory tracking set.
97
+
98
+ ```js
99
+ const tmux = new Tmux();
100
+ await tmux.create('session-a');
101
+ await tmux.create('session-b');
102
+ tmux.owned(); // ['session-a', 'session-b']
103
+ ```
104
+
105
+ #### `tmux.cleanup()` → `Promise<void>`
106
+
107
+ Kill all sessions created by this instance. Best-effort — errors on individual sessions are swallowed so one stuck session does not prevent others from being cleaned. Clears the tracking set.
108
+
109
+ This is the recommended path for graceful orchestrator shutdown.
110
+
111
+ ```js
112
+ // In graceful shutdown handler
113
+ await tmux.cleanup();
114
+ ```
115
+
116
+ #### `tmux.prune(prefix?)` → `Promise<string[]>`
117
+
118
+ Kill all tmux sessions whose name starts with the given prefix (default: `'loreli'`). Queries tmux directly — does not rely on in-memory tracking. Returns the names of sessions that were killed.
119
+
120
+ This is the crash-recovery / manual-reset path. It is destructive: any matching session is killed regardless of whether its agents are still doing useful work.
121
+
122
+ ```js
123
+ // After a crash, clean up all loreli-prefixed sessions
124
+ const killed = await tmux.prune('loreli');
125
+ // ['loreli', 'loreli-debug', 'loreli-agent-test-12345']
126
+ ```
127
+
128
+ ### Stale Session Detection
129
+
130
+ #### `tmux.stale(name)` → `Promise<boolean>`
131
+
132
+ Check whether a session exists but was not created by this instance. A session is stale when it survives from a previous MCP server process — the current Tmux instance has no record of it because in-memory tracking is per-process. Stale sessions contain agent panes running with outdated environment variables (API keys, budgets, tokens).
133
+
134
+ The following example shows the pattern start uses to detect leftover sessions before spawning new agents.
135
+
136
+ ```js
137
+ const tmux = new Tmux();
138
+ if (await tmux.stale('loreli')) {
139
+ // Session exists from a dead MCP server — agents inside have stale env
140
+ }
141
+ ```
142
+
143
+ #### `tmux.reap(name)` → `Promise<{killed: boolean, panes: number}>`
144
+
145
+ Destroy a stale session and report what was killed. Only acts on sessions not owned by this instance — owned sessions are left untouched, preventing accidental self-destruction during a start re-run within the same process.
146
+
147
+ Returns `{killed: true, panes: N}` when a stale session was destroyed, or `{killed: false, panes: 0}` when the session doesn't exist or belongs to this instance.
148
+
149
+ The following example shows how start reaps stale tmux sessions during its cleanup phase. The pane count is logged so operators know how many ghost agents were terminated.
150
+
151
+ ```js
152
+ const tmux = new Tmux();
153
+ const result = await tmux.reap('loreli');
154
+ if (result.killed) {
155
+ console.log(`reaped stale session (${result.panes} panes)`);
156
+ }
157
+ ```
158
+
159
+ ### Window Management
160
+
161
+ #### `tmux.window(session, opts?)` → `Promise<string>`
162
+
163
+ Create a new window in an existing session and return its pane ID (e.g. `%3`). Each agent gets its own full-size window instead of splitting an existing one, avoiding the `no space for new pane` error.
164
+
165
+ When `opts.command` is provided, the process runs directly without an intermediate shell, avoiding `.zshrc` initialization prompts.
166
+
167
+ - `session` `{string}` — The target session name.
168
+ - `opts.cwd` `{string}` — Working directory for the new window.
169
+ - `opts.command` `{string}` — Shell command to run directly.
170
+
171
+ ```js
172
+ const paneId = await tmux.window('loreli', {
173
+ cwd: '/tmp/workspace',
174
+ command: '/path/to/launch.sh'
175
+ });
176
+ ```
177
+
178
+ ### Pane Management
179
+
180
+ #### `tmux.split(session, opts?)` → `Promise<string>`
181
+
182
+ Split the current window into a new pane. Returns the new pane ID (e.g. `%3`).
183
+
184
+ - `opts.cwd` `{string}` — Working directory for the new pane.
185
+ - `opts.vertical` `{boolean}` — Split vertically instead of horizontally.
186
+
187
+ #### `tmux.send(target, text)` → `Promise<void>`
188
+
189
+ Send keystrokes followed by Enter to a pane. The `target` is a pane ID (e.g. `%3`).
190
+
191
+ #### `tmux.keys(target, ...keys)` → `Promise<void>`
192
+
193
+ Send raw key sequences to a pane without appending Enter. Useful for TUI navigation (arrow keys, Escape, Tab). Each argument is a tmux key name: `Down`, `Up`, `Enter`, `Escape`, etc.
194
+
195
+ The following example shows navigating a TUI dialog, which is how the Claude backend accepts its bypass-permissions confirmation.
196
+
197
+ ```js
198
+ await tmux.keys(paneId, 'Down'); // Move to "Yes, I accept"
199
+ await tmux.keys(paneId, 'Enter'); // Confirm selection
200
+ ```
201
+
202
+ #### `tmux.capture(target, lines?)` → `Promise<string>`
203
+
204
+ Capture the visible content of a pane. Uses `-J` to join wrapped lines. Default capture depth is 500 lines.
205
+
206
+ #### `tmux.command(target)` → `Promise<string>`
207
+
208
+ Query the current foreground command running in a pane. Returns the process name (e.g. `'zsh'`, `'claude'`, `'codex'`), or empty string if the pane is dead.
209
+
210
+ ```js
211
+ const cmd = await tmux.command(paneId);
212
+ if (cmd === 'claude') { /* agent is still running */ }
213
+ ```
214
+
215
+ #### `tmux.panes(session)` → `Promise<Array<{id, pid, active, title}>>`
216
+
217
+ List panes in a session with metadata.
218
+
219
+ #### `tmux.alive(target)` → `Promise<boolean>`
220
+
221
+ Check whether a specific pane still exists and its process is running. Uses `list-panes` with filtering for reliable detection.
222
+
223
+ #### `tmux.killPane(target)` → `Promise<void>`
224
+
225
+ Kill a specific pane by ID.
226
+
227
+ #### `tmux.set(target, option, value)` → `Promise<void>`
228
+
229
+ Set a pane-level option. Commonly used with `remain-on-exit` for agent backends:
230
+
231
+ ```js
232
+ await tmux.set(paneId, 'remain-on-exit', 'on');
233
+ ```
234
+
235
+ ## Error Handling
236
+
237
+ | Scenario | Error |
238
+ |----------|-------|
239
+ | tmux not installed | `Tmux.available()` returns `false`; instance methods throw with the underlying stderr message |
240
+ | Session already exists | `create()` throws `Session "<name>" already exists` |
241
+ | Session not found | `kill()` throws with tmux's error message |
242
+ | Pane not found | `killPane()`, `send()`, `capture()` throw with tmux's error message |
243
+
244
+ ## Configuration via loreli/config
245
+
246
+ When used through Loreli's orchestration layer, the following tmux settings are configurable via `loreli.yml`:
247
+
248
+ | Config Key | Default | Description |
249
+ |------------|---------|-------------|
250
+ | `tmux.session` | `loreli` | Tmux session name used by `CliAgent` |
251
+ | `tmux.capture` | `500` | Number of history lines captured by `capture()` |
252
+
253
+ These values are resolved through `loreli/config`'s four-layer resolution (start params > `loreli.yml` > env vars > defaults). When using `loreli/tmux` directly without the orchestration layer, the defaults above apply.
254
+
255
+ ## Testing
256
+
257
+ ```bash
258
+ node --test packages/tmux/test/index.test.js
259
+ ```
260
+
261
+ Tests require tmux to be installed on the system.