editprompt 1.5.0 → 1.6.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 (3) hide show
  1. package/README.md +45 -10
  2. package/dist/index.js +641 -193
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,15 +2,33 @@
2
2
  import { configureSync, getAnsiColorFormatter, getLogger, getTextFormatter, parseLogLevel, withFilter } from "@logtape/logtape";
3
3
  import { cli, define } from "gunshi";
4
4
  import { appendFileSync } from "node:fs";
5
+ import { createHash, randomBytes } from "node:crypto";
6
+ import { createConnection } from "node:net";
7
+ import Conf from "conf";
5
8
  import { exec, spawn } from "node:child_process";
6
9
  import { promisify } from "node:util";
7
- import Conf from "conf";
8
10
  import clipboardy from "clipboardy";
9
11
  import { mkdir, readFile, writeFile } from "node:fs/promises";
10
- import { randomBytes } from "node:crypto";
11
12
  import { tmpdir } from "node:os";
12
13
  import { join } from "node:path";
13
14
 
15
+ //#region \0rolldown/runtime.js
16
+ var __defProp = Object.defineProperty;
17
+ var __exportAll = (all, no_symbols) => {
18
+ let target = {};
19
+ for (var name in all) {
20
+ __defProp(target, name, {
21
+ get: all[name],
22
+ enumerable: true
23
+ });
24
+ }
25
+ if (!no_symbols) {
26
+ __defProp(target, Symbol.toStringTag, { value: "Module" });
27
+ }
28
+ return target;
29
+ };
30
+
31
+ //#endregion
14
32
  //#region src/modules/logger.ts
15
33
  function resolveLogFilePath(options) {
16
34
  if (options.logFile) return options.logFile;
@@ -72,7 +90,7 @@ function setupLogger(options = {}) {
72
90
 
73
91
  //#endregion
74
92
  //#region package.json
75
- var version = "1.5.0";
93
+ var version = "1.6.0";
76
94
 
77
95
  //#endregion
78
96
  //#region src/config/constants.ts
@@ -82,6 +100,8 @@ const DEFAULT_EDITOR = "vim";
82
100
  const TMUX_IMSG_LIMIT_BYTES = 16 * 1024;
83
101
  const TMUX_SEND_CHUNK_BYTES = TMUX_IMSG_LIMIT_BYTES / 2;
84
102
  const WEZTERM_SEND_CHUNK_BYTES = TMUX_SEND_CHUNK_BYTES;
103
+ const HERDR_REQUEST_LIMIT_BYTES = 1024 * 1024;
104
+ const HERDR_SEND_CHUNK_BYTES = HERDR_REQUEST_LIMIT_BYTES / 8;
85
105
 
86
106
  //#endregion
87
107
  //#region src/utils/contentChunker.ts
@@ -111,10 +131,282 @@ function splitByByteSize(content, maxBytes) {
111
131
  return chunks;
112
132
  }
113
133
 
134
+ //#endregion
135
+ //#region src/modules/conf.ts
136
+ const projectName = process.env.NODE_ENV === "test" ? "editprompt-test" : "editprompt";
137
+ const conf = new Conf({ projectName });
138
+
139
+ //#endregion
140
+ //#region src/modules/herdr.ts
141
+ var herdr_exports = /* @__PURE__ */ __exportAll({
142
+ appendToQuoteText: () => appendToQuoteText$1,
143
+ calculateEditorSplitRatio: () => calculateEditorSplitRatio,
144
+ checkPaneExists: () => checkPaneExists$2,
145
+ clearEditorPaneId: () => clearEditorPaneId$2,
146
+ clearQuoteText: () => clearQuoteText$1,
147
+ closePane: () => closePane,
148
+ focusPane: () => focusPane$2,
149
+ getCurrentPaneId: () => getCurrentPaneId$2,
150
+ getEditorPaneId: () => getEditorPaneId$2,
151
+ getQuoteText: () => getQuoteText$1,
152
+ getStorageNamespace: () => getStorageNamespace,
153
+ getTargetPaneIds: () => getTargetPaneIds$2,
154
+ inputToHerdrPane: () => inputToHerdrPane,
155
+ isEditorPaneFromConf: () => isEditorPaneFromConf$1,
156
+ markAsEditorPane: () => markAsEditorPane$2,
157
+ resumeEditorPane: () => resumeEditorPane,
158
+ runCommandInPane: () => runCommandInPane,
159
+ saveEditorPaneId: () => saveEditorPaneId$2,
160
+ sendKeyToHerdrPane: () => sendKeyToHerdrPane,
161
+ splitEditorPane: () => splitEditorPane
162
+ });
163
+ const logger$13 = getLogger(["editprompt", "herdr"]);
164
+ const HERDR_REQUEST_TIMEOUT_MS = 6e3;
165
+ let requestSequence = 0;
166
+ function getSocketPath() {
167
+ const socketPath = process.env.HERDR_SOCKET_PATH?.trim();
168
+ if (!socketPath) throw new Error("HERDR_SOCKET_PATH is not set");
169
+ return socketPath;
170
+ }
171
+ async function sendRequest(method, params) {
172
+ const id = `editprompt_${process.pid}_${++requestSequence}`;
173
+ const request = `${JSON.stringify({
174
+ id,
175
+ method,
176
+ params
177
+ })}\n`;
178
+ return new Promise((resolve, reject) => {
179
+ const socket = createConnection(getSocketPath());
180
+ let buffer = "";
181
+ let settled = false;
182
+ const finish = (callback) => {
183
+ if (settled) return;
184
+ settled = true;
185
+ socket.end();
186
+ callback();
187
+ };
188
+ socket.setEncoding("utf8");
189
+ socket.setTimeout(HERDR_REQUEST_TIMEOUT_MS);
190
+ socket.on("connect", () => {
191
+ socket.write(request);
192
+ });
193
+ socket.on("data", (chunk) => {
194
+ buffer += chunk;
195
+ const newlineIndex = buffer.indexOf("\n");
196
+ if (newlineIndex === -1) return;
197
+ const line = buffer.slice(0, newlineIndex);
198
+ let response;
199
+ try {
200
+ response = JSON.parse(line);
201
+ } catch (error) {
202
+ finish(() => {
203
+ reject(/* @__PURE__ */ new Error(`Invalid response from Herdr: ${error instanceof Error ? error.message : "Unknown error"}`));
204
+ });
205
+ return;
206
+ }
207
+ if (response.id !== id) {
208
+ finish(() => {
209
+ reject(/* @__PURE__ */ new Error(`Unexpected response ID from Herdr: ${response.id}`));
210
+ });
211
+ return;
212
+ }
213
+ if (response.error) {
214
+ finish(() => {
215
+ reject(/* @__PURE__ */ new Error(`Herdr ${response.error?.code}: ${response.error?.message}`));
216
+ });
217
+ return;
218
+ }
219
+ finish(() => {
220
+ resolve(response.result);
221
+ });
222
+ });
223
+ socket.on("error", (error) => {
224
+ finish(() => {
225
+ reject(/* @__PURE__ */ new Error(`Failed to communicate with Herdr: ${error.message}`));
226
+ });
227
+ });
228
+ socket.on("timeout", () => {
229
+ if (settled) return;
230
+ settled = true;
231
+ socket.destroy();
232
+ reject(/* @__PURE__ */ new Error(`Timed out waiting for Herdr response after ${HERDR_REQUEST_TIMEOUT_MS}ms`));
233
+ });
234
+ socket.on("end", () => {
235
+ if (!settled) finish(() => {
236
+ reject(/* @__PURE__ */ new Error("Herdr closed the socket before sending a response"));
237
+ });
238
+ });
239
+ });
240
+ }
241
+ function getStorageNamespace() {
242
+ return `herdr.session_${createHash("sha256").update(getSocketPath()).digest("hex").slice(0, 16)}`;
243
+ }
244
+ function targetPaneKey(paneId) {
245
+ return `${getStorageNamespace()}.targetPane.pane_${paneId}`;
246
+ }
247
+ function editorPaneKey(paneId) {
248
+ return `${getStorageNamespace()}.editorPane.pane_${paneId}`;
249
+ }
250
+ async function getCurrentPaneId$2() {
251
+ const paneId = process.env.HERDR_PANE_ID?.trim() || process.env.HERDR_ACTIVE_PANE_ID?.trim();
252
+ if (!paneId) throw new Error("HERDR_PANE_ID or HERDR_ACTIVE_PANE_ID is not set");
253
+ return paneId;
254
+ }
255
+ async function checkPaneExists$2(paneId) {
256
+ try {
257
+ await sendRequest("pane.get", { pane_id: paneId });
258
+ return true;
259
+ } catch (error) {
260
+ logger$13.debug("checkPaneExists failed: {error}", { error });
261
+ return false;
262
+ }
263
+ }
264
+ async function focusPane$2(paneId) {
265
+ await sendRequest("pane.focus", { pane_id: paneId });
266
+ }
267
+ function calculateEditorSplitRatio(paneHeight, editorPaneRows) {
268
+ if (!Number.isInteger(paneHeight) || paneHeight <= 1) throw new Error(`Invalid Herdr pane height: ${paneHeight}`);
269
+ if (!Number.isInteger(editorPaneRows) || editorPaneRows <= 0) throw new Error(`Invalid Herdr editor pane rows: ${editorPaneRows}`);
270
+ return Math.min(.9, Math.max(.1, 1 - editorPaneRows / paneHeight));
271
+ }
272
+ async function splitEditorPane(targetPaneId, editorPaneRows, cwd, env = {}) {
273
+ const paneHeight = ((await sendRequest("pane.layout", { pane_id: targetPaneId })).layout?.panes?.find((pane) => pane.pane_id === targetPaneId))?.rect?.height;
274
+ if (typeof paneHeight !== "number") throw new Error(`Herdr pane.layout did not return a height for pane ${targetPaneId}`);
275
+ const params = {
276
+ target_pane_id: targetPaneId,
277
+ direction: "down",
278
+ ratio: calculateEditorSplitRatio(paneHeight, editorPaneRows),
279
+ focus: true,
280
+ env
281
+ };
282
+ if (cwd?.trim()) params.cwd = cwd;
283
+ const editorPaneId = (await sendRequest("pane.split", params)).pane?.pane_id;
284
+ if (typeof editorPaneId !== "string" || editorPaneId === "") throw new Error("Herdr pane.split did not return the new pane ID");
285
+ return editorPaneId;
286
+ }
287
+ function quoteShellWord(value) {
288
+ return `'${value.replace(/'/g, "'\\''")}'`;
289
+ }
290
+ async function runCommandInPane(paneId, argv) {
291
+ if (argv.length === 0) throw new Error("Cannot run an empty command in a Herdr pane");
292
+ await sendRequest("pane.send_input", {
293
+ pane_id: paneId,
294
+ text: `exec ${argv.map(quoteShellWord).join(" ")}`,
295
+ keys: ["Enter"]
296
+ });
297
+ }
298
+ async function closePane(paneId) {
299
+ await sendRequest("pane.close", { pane_id: paneId });
300
+ }
301
+ async function inputToHerdrPane(paneId, content) {
302
+ const chunks = splitByByteSize(content, HERDR_SEND_CHUNK_BYTES);
303
+ for (const chunk of chunks) await sendRequest("pane.send_text", {
304
+ pane_id: paneId,
305
+ text: chunk
306
+ });
307
+ logger$13.debug("Content sent to Herdr pane: {paneId} ({chunkCount} chunks)", {
308
+ paneId,
309
+ chunkCount: chunks.length
310
+ });
311
+ }
312
+ async function sendKeyToHerdrPane(paneId, key, delay = 1e3) {
313
+ await new Promise((resolve) => setTimeout(resolve, delay));
314
+ await sendRequest("pane.send_keys", {
315
+ pane_id: paneId,
316
+ keys: [key]
317
+ });
318
+ }
319
+ async function saveEditorPaneId$2(targetPaneId, editorPaneId) {
320
+ const data = conf.get(targetPaneKey(targetPaneId));
321
+ const existing = typeof data === "object" && data !== null ? data : {};
322
+ conf.set(targetPaneKey(targetPaneId), {
323
+ ...existing,
324
+ editorPaneId
325
+ });
326
+ }
327
+ async function getEditorPaneId$2(targetPaneId) {
328
+ const data = conf.get(targetPaneKey(targetPaneId));
329
+ if (typeof data === "object" && data !== null && "editorPaneId" in data) return String(data.editorPaneId);
330
+ return "";
331
+ }
332
+ async function clearEditorPaneId$2(targetPaneId, expectedEditorPaneId) {
333
+ const editorPaneId = await getEditorPaneId$2(targetPaneId);
334
+ if (expectedEditorPaneId && editorPaneId !== expectedEditorPaneId) {
335
+ conf.delete(editorPaneKey(expectedEditorPaneId));
336
+ return;
337
+ }
338
+ const data = conf.get(targetPaneKey(targetPaneId));
339
+ if (typeof data === "object" && data !== null && "editorPaneId" in data) {
340
+ const { editorPaneId: _, ...remaining } = data;
341
+ if (Object.keys(remaining).length === 0) conf.delete(targetPaneKey(targetPaneId));
342
+ else conf.set(targetPaneKey(targetPaneId), remaining);
343
+ }
344
+ if (editorPaneId) conf.delete(editorPaneKey(editorPaneId));
345
+ }
346
+ async function markAsEditorPane$2(editorPaneId, targetPaneIds) {
347
+ const uniqueTargetPaneIds = [...new Set(targetPaneIds)];
348
+ conf.set(editorPaneKey(editorPaneId), { targetPaneIds: uniqueTargetPaneIds });
349
+ for (const targetPaneId of uniqueTargetPaneIds) await saveEditorPaneId$2(targetPaneId, editorPaneId);
350
+ }
351
+ async function getTargetPaneIds$2(editorPaneId) {
352
+ const data = conf.get(editorPaneKey(editorPaneId));
353
+ if (typeof data === "object" && data !== null && "targetPaneIds" in data) {
354
+ const targetPaneIds = data.targetPaneIds;
355
+ if (Array.isArray(targetPaneIds)) return targetPaneIds.map((id) => String(id));
356
+ }
357
+ return [];
358
+ }
359
+ function isEditorPaneFromConf$1(paneId) {
360
+ return conf.has(editorPaneKey(paneId));
361
+ }
362
+ async function resumeEditorPane(targetPaneId) {
363
+ const currentPaneId = await getCurrentPaneId$2();
364
+ const editorPaneId = isEditorPaneFromConf$1(currentPaneId) ? currentPaneId : isEditorPaneFromConf$1(targetPaneId) ? targetPaneId : "";
365
+ if (editorPaneId) {
366
+ const targetPaneIds = await getTargetPaneIds$2(editorPaneId);
367
+ for (const paneId of targetPaneIds) if (await checkPaneExists$2(paneId)) {
368
+ await focusPane$2(paneId);
369
+ return true;
370
+ }
371
+ return false;
372
+ }
373
+ const registeredEditorPaneId = await getEditorPaneId$2(targetPaneId);
374
+ if (registeredEditorPaneId === "") return false;
375
+ if (!await checkPaneExists$2(registeredEditorPaneId)) {
376
+ await clearEditorPaneId$2(targetPaneId);
377
+ return false;
378
+ }
379
+ await focusPane$2(registeredEditorPaneId);
380
+ return true;
381
+ }
382
+ async function appendToQuoteText$1(paneId, content) {
383
+ const data = conf.get(targetPaneKey(paneId));
384
+ const existing = typeof data === "object" && data !== null ? data : {};
385
+ const existingQuoteText = "quote_text" in existing ? String(existing.quote_text) : "";
386
+ const quoteText = existingQuoteText.trim() !== "" ? `${existingQuoteText}\n\n${content}` : content;
387
+ conf.set(targetPaneKey(paneId), {
388
+ ...existing,
389
+ quote_text: quoteText
390
+ });
391
+ }
392
+ async function getQuoteText$1(paneId) {
393
+ const data = conf.get(targetPaneKey(paneId));
394
+ if (typeof data === "object" && data !== null && "quote_text" in data) return String(data.quote_text);
395
+ return "";
396
+ }
397
+ async function clearQuoteText$1(paneId) {
398
+ const data = conf.get(targetPaneKey(paneId));
399
+ if (typeof data === "object" && data !== null && "quote_text" in data) {
400
+ const { quote_text: _, ...remaining } = data;
401
+ if (Object.keys(remaining).length === 0) conf.delete(targetPaneKey(paneId));
402
+ else conf.set(targetPaneKey(paneId), remaining);
403
+ }
404
+ }
405
+
114
406
  //#endregion
115
407
  //#region src/modules/tmux.ts
116
408
  const execAsync$1 = promisify(exec);
117
- const logger$11 = getLogger(["editprompt", "tmux"]);
409
+ const logger$12 = getLogger(["editprompt", "tmux"]);
118
410
  async function getCurrentPaneId$1() {
119
411
  const envPaneId = process.env.TMUX_PANE?.trim();
120
412
  if (envPaneId) return envPaneId;
@@ -196,20 +488,15 @@ async function inputToTmuxPane(paneId, content) {
196
488
  await execAsync$1(`tmux if-shell -t '${paneId}' '[ "#{pane_in_mode}" = "1" ]' "copy-mode -q -t '${paneId}'"`);
197
489
  const chunks = splitByByteSize(content, TMUX_SEND_CHUNK_BYTES);
198
490
  for (const chunk of chunks) await execAsync$1(`tmux send-keys -t '${paneId}' -- '${chunk.replace(/'/g, "'\\''")}'`);
199
- logger$11.debug("Content sent to tmux pane: {paneId} ({chunks} chunk(s))", {
491
+ logger$12.debug("Content sent to tmux pane: {paneId} ({chunks} chunk(s))", {
200
492
  paneId,
201
493
  chunks: chunks.length
202
494
  });
203
495
  }
204
496
 
205
- //#endregion
206
- //#region src/modules/conf.ts
207
- const projectName = process.env.NODE_ENV === "test" ? "editprompt-test" : "editprompt";
208
- const conf = new Conf({ projectName });
209
-
210
497
  //#endregion
211
498
  //#region src/modules/wezterm.ts
212
- const logger$10 = getLogger(["editprompt", "wezterm"]);
499
+ const logger$11 = getLogger(["editprompt", "wezterm"]);
213
500
  const execAsync = promisify(exec);
214
501
  async function getCurrentPaneId() {
215
502
  try {
@@ -217,26 +504,26 @@ async function getCurrentPaneId() {
217
504
  const activePane = JSON.parse(stdout).find((pane) => pane.is_active === true);
218
505
  return String(activePane?.pane_id);
219
506
  } catch (error) {
220
- logger$10.debug("getCurrentPaneId failed: {error}", { error });
507
+ logger$11.debug("getCurrentPaneId failed: {error}", { error });
221
508
  return "";
222
509
  }
223
510
  }
224
511
  async function checkPaneExists(paneId) {
225
512
  try {
226
513
  const { stdout } = await execAsync("wezterm cli list --format json");
227
- logger$10.debug("wezterm cli list output: {stdout}", { stdout });
514
+ logger$11.debug("wezterm cli list output: {stdout}", { stdout });
228
515
  return JSON.parse(stdout).some((pane) => String(pane.pane_id) === paneId);
229
516
  } catch (error) {
230
- logger$10.debug("checkPaneExists failed: {error}", { error });
517
+ logger$11.debug("checkPaneExists failed: {error}", { error });
231
518
  return false;
232
519
  }
233
520
  }
234
521
  async function saveEditorPaneId(targetPaneId, editorPaneId) {
235
- logger$10.debug("Saving editor pane ID to conf key: wezterm.targetPane.pane_{targetPaneId}", { targetPaneId });
522
+ logger$11.debug("Saving editor pane ID to conf key: wezterm.targetPane.pane_{targetPaneId}", { targetPaneId });
236
523
  try {
237
524
  conf.set(`wezterm.targetPane.pane_${targetPaneId}`, { editorPaneId });
238
525
  } catch (error) {
239
- logger$10.debug("saveEditorPaneId failed: {error}", { error });
526
+ logger$11.debug("saveEditorPaneId failed: {error}", { error });
240
527
  }
241
528
  }
242
529
  async function getEditorPaneId(targetPaneId) {
@@ -245,7 +532,7 @@ async function getEditorPaneId(targetPaneId) {
245
532
  if (typeof data === "object" && data !== null && "editorPaneId" in data) return String(data.editorPaneId);
246
533
  return "";
247
534
  } catch (error) {
248
- logger$10.debug("getEditorPaneId failed: {error}", { error });
535
+ logger$11.debug("getEditorPaneId failed: {error}", { error });
249
536
  return "";
250
537
  }
251
538
  }
@@ -255,7 +542,7 @@ async function clearEditorPaneId(targetPaneId) {
255
542
  conf.delete(`wezterm.targetPane.pane_${targetPaneId}`);
256
543
  if (editorPaneId) conf.delete(`wezterm.editorPane.pane_${editorPaneId}`);
257
544
  } catch (error) {
258
- logger$10.debug("clearEditorPaneId failed: {error}", { error });
545
+ logger$11.debug("clearEditorPaneId failed: {error}", { error });
259
546
  }
260
547
  }
261
548
  async function focusPane(paneId) {
@@ -267,7 +554,7 @@ async function markAsEditorPane(editorPaneId, targetPaneIds) {
267
554
  conf.set(`wezterm.editorPane.pane_${editorPaneId}`, { targetPaneIds: uniqueTargetPaneIds });
268
555
  for (const targetPaneId of uniqueTargetPaneIds) await saveEditorPaneId(targetPaneId, editorPaneId);
269
556
  } catch (error) {
270
- logger$10.debug("markAsEditorPane failed: {error}", { error });
557
+ logger$11.debug("markAsEditorPane failed: {error}", { error });
271
558
  }
272
559
  }
273
560
  async function getTargetPaneIds(editorPaneId) {
@@ -279,7 +566,7 @@ async function getTargetPaneIds(editorPaneId) {
279
566
  }
280
567
  return [];
281
568
  } catch (error) {
282
- logger$10.debug("getTargetPaneIds failed: {error}", { error });
569
+ logger$11.debug("getTargetPaneIds failed: {error}", { error });
283
570
  return [];
284
571
  }
285
572
  }
@@ -287,7 +574,7 @@ function isEditorPaneFromConf(paneId) {
287
574
  try {
288
575
  return conf.has(`wezterm.editorPane.pane_${paneId}`);
289
576
  } catch (error) {
290
- logger$10.debug("isEditorPaneFromConf failed: {error}", { error });
577
+ logger$11.debug("isEditorPaneFromConf failed: {error}", { error });
291
578
  return false;
292
579
  }
293
580
  }
@@ -305,7 +592,7 @@ async function appendToQuoteText(paneId, content) {
305
592
  } else newData = { quote_text: content };
306
593
  conf.set(`wezterm.targetPane.pane_${paneId}`, newData);
307
594
  } catch (error) {
308
- logger$10.debug("appendToQuoteText failed: {error}", { error });
595
+ logger$11.debug("appendToQuoteText failed: {error}", { error });
309
596
  }
310
597
  }
311
598
  async function getQuoteText(paneId) {
@@ -314,7 +601,7 @@ async function getQuoteText(paneId) {
314
601
  if (typeof data === "object" && data !== null && "quote_text" in data) return String(data.quote_text);
315
602
  return "";
316
603
  } catch (error) {
317
- logger$10.debug("getQuoteText failed: {error}", { error });
604
+ logger$11.debug("getQuoteText failed: {error}", { error });
318
605
  return "";
319
606
  }
320
607
  }
@@ -323,7 +610,7 @@ async function clearQuoteText(paneId) {
323
610
  const key = `wezterm.targetPane.pane_${paneId}.quote_text`;
324
611
  if (conf.has(key)) conf.delete(key);
325
612
  } catch (error) {
326
- logger$10.debug("clearQuoteText failed: {error}", { error });
613
+ logger$11.debug("clearQuoteText failed: {error}", { error });
327
614
  }
328
615
  }
329
616
  async function sendKeyToWeztermPane(paneId, key, delay = 1e3) {
@@ -333,7 +620,7 @@ async function sendKeyToWeztermPane(paneId, key, delay = 1e3) {
333
620
  async function inputToWeztermPane(paneId, content) {
334
621
  const chunks = splitByByteSize(content, WEZTERM_SEND_CHUNK_BYTES);
335
622
  for (const chunk of chunks) await execAsync(`wezterm cli send-text --no-paste --pane-id '${paneId}' -- '${chunk.replace(/'/g, "'\\''")}'`);
336
- logger$10.debug("Content sent to wezterm pane: {paneId} ({chunks} chunk(s))", {
623
+ logger$11.debug("Content sent to wezterm pane: {paneId} ({chunks} chunk(s))", {
337
624
  paneId,
338
625
  chunks: chunks.length
339
626
  });
@@ -477,87 +764,36 @@ function processQuoteText(text, options) {
477
764
  }
478
765
 
479
766
  //#endregion
480
- //#region src/modes/common.ts
481
- const logger$9 = getLogger(["editprompt", "delivery"]);
767
+ //#region src/utils/mux.ts
768
+ const SUPPORTED_MUXES = [
769
+ "tmux",
770
+ "wezterm",
771
+ "herdr"
772
+ ];
482
773
  function isMuxType(value) {
483
- return value === "tmux" || value === "wezterm";
774
+ return value === "tmux" || value === "wezterm" || value === "herdr";
484
775
  }
485
- const SUPPORTED_MUXES = ["tmux", "wezterm"];
486
- async function copyToClipboard(content) {
487
- await clipboardy.write(content);
488
- }
489
- async function inputContentToPane(content, mux, targetPaneId) {
490
- if (mux === "wezterm") await inputToWeztermPane(targetPaneId, content);
491
- else await inputToTmuxPane(targetPaneId, content);
776
+ function hasValue(value) {
777
+ return typeof value === "string" && value.trim() !== "";
492
778
  }
493
- async function focusFirstSuccessPane(mux, targetPanes, failedPanes) {
494
- const firstSuccessPane = targetPanes.find((p) => !failedPanes.includes(p));
495
- if (firstSuccessPane) if (mux === "tmux") await focusPane$1(firstSuccessPane);
496
- else await focusPane(firstSuccessPane);
779
+ function isHerdrEnvironment(env = process.env) {
780
+ return hasValue(env.HERDR_SOCKET_PATH) && (hasValue(env.HERDR_PANE_ID) || hasValue(env.HERDR_ACTIVE_PANE_ID));
497
781
  }
498
- async function handleContentDelivery(content, mux, targetPanes) {
499
- if (!content) return {
500
- successCount: 0,
501
- totalCount: 0,
502
- allSuccess: true,
503
- allFailed: false,
504
- failedPanes: []
505
- };
506
- if (targetPanes.length === 0) {
507
- try {
508
- await copyToClipboard(content);
509
- logger$9.info("Content copied to clipboard.");
510
- } catch (error) {
511
- logger$9.warn(`Failed to copy to clipboard: ${error instanceof Error ? error.message : "Unknown error"}`);
512
- }
513
- return {
514
- successCount: 0,
515
- totalCount: 0,
516
- allSuccess: true,
517
- allFailed: false,
518
- failedPanes: []
519
- };
520
- }
521
- const results = [];
522
- for (const targetPane of targetPanes) try {
523
- await inputContentToPane(content, mux, targetPane);
524
- results.push({
525
- pane: targetPane,
526
- success: true
527
- });
528
- } catch (error) {
529
- logger$9.warn(`Failed to send to pane ${targetPane}: ${error instanceof Error ? error.message : "Unknown error"}`);
530
- results.push({
531
- pane: targetPane,
532
- success: false
533
- });
782
+ function resolveMux(explicitMux, env = process.env) {
783
+ const muxValue = explicitMux || env.EDITPROMPT_MUX || (isHerdrEnvironment(env) ? "herdr" : "tmux");
784
+ if (!isMuxType(muxValue)) {
785
+ const displayValue = typeof muxValue === "string" ? muxValue : `<${typeof muxValue}>`;
786
+ throw new Error(`Invalid multiplexer type '${displayValue}'. Supported values: ${SUPPORTED_MUXES.join(", ")}`);
534
787
  }
535
- const successCount = results.filter((r) => r.success).length;
536
- const failedPanes = results.filter((r) => !r.success).map((r) => r.pane);
537
- const allSuccess = successCount === targetPanes.length;
538
- const allFailed = successCount === 0;
539
- if (allSuccess) logger$9.info("Content sent successfully to all panes!");
540
- else if (allFailed) {
541
- logger$9.error("All target panes failed to receive content.");
542
- logger$9.info("Falling back to clipboard...");
543
- await copyToClipboard(content);
544
- logger$9.info("Content copied to clipboard.");
545
- } else logger$9.warn(`Content sent to ${successCount}/${targetPanes.length} panes. Failed panes: ${failedPanes.join(", ")}`);
546
- return {
547
- successCount,
548
- totalCount: targetPanes.length,
549
- allSuccess,
550
- allFailed,
551
- failedPanes
552
- };
788
+ return muxValue;
553
789
  }
554
790
 
555
791
  //#endregion
556
792
  //#region src/modes/args.ts
557
- const logger$8 = getLogger(["editprompt"]);
793
+ const logger$10 = getLogger(["editprompt"]);
558
794
  const ARG_MUX = {
559
795
  short: "m",
560
- description: "Multiplexer type (tmux or wezterm, default: tmux)",
796
+ description: "Multiplexer type (tmux, wezterm, or herdr; auto-detects Herdr, otherwise tmux)",
561
797
  type: "string"
562
798
  };
563
799
  const ARG_TARGET_PANE_SINGLE = {
@@ -603,17 +839,23 @@ const ARG_VERBOSE = {
603
839
  description: "Enable debug-level log output",
604
840
  type: "boolean"
605
841
  };
842
+ const ARG_ENV = {
843
+ short: "E",
844
+ description: "Environment variables to set (e.g., KEY=VALUE)",
845
+ type: "string",
846
+ multiple: true
847
+ };
606
848
  function validateMux(value) {
607
- const muxValue = value || "tmux";
608
- if (!isMuxType(muxValue)) {
609
- logger$8.error(`Invalid multiplexer type '${muxValue}'. Supported values: ${SUPPORTED_MUXES.join(", ")}`);
849
+ try {
850
+ return resolveMux(value);
851
+ } catch (error) {
852
+ logger$10.error(error instanceof Error ? error.message : String(error));
610
853
  process.exit(1);
611
854
  }
612
- return muxValue;
613
855
  }
614
856
  function validateTargetPane(value, commandName) {
615
857
  if (!value || typeof value !== "string") {
616
- logger$8.error(`--target-pane is required for ${commandName} command`);
858
+ logger$10.error(`--target-pane is required for ${commandName} command`);
617
859
  process.exit(1);
618
860
  }
619
861
  return value;
@@ -626,7 +868,7 @@ function normalizeTargetPanes(value) {
626
868
 
627
869
  //#endregion
628
870
  //#region src/modes/collect.ts
629
- const logger$7 = getLogger(["editprompt", "collect"]);
871
+ const logger$9 = getLogger(["editprompt", "collect"]);
630
872
  const SUPPORTED_OUTPUTS = ["buffer", "stdout"];
631
873
  async function readStdin() {
632
874
  return new Promise((resolve, reject) => {
@@ -650,7 +892,7 @@ function normalizeCollectOutputs(value) {
650
892
  const uniqueOutputs = [...new Set(outputs)];
651
893
  const invalid = uniqueOutputs.filter((v) => !SUPPORTED_OUTPUTS.includes(v));
652
894
  if (invalid.length > 0) {
653
- logger$7.error(`Invalid output(s) '${invalid.join(", ")}'. Supported values: ${SUPPORTED_OUTPUTS.join(", ")}`);
895
+ logger$9.error(`Invalid output(s) '${invalid.join(", ")}'. Supported values: ${SUPPORTED_OUTPUTS.join(", ")}`);
654
896
  process.exit(1);
655
897
  }
656
898
  return uniqueOutputs;
@@ -661,12 +903,12 @@ async function runCollectMode(mux, targetPaneId, rawContent, outputs = ["buffer"
661
903
  if (rawContent !== void 0) selection = rawContent;
662
904
  else selection = await readStdin();
663
905
  const processedText = processQuoteText(selection, { withQuote });
664
- for (const output of outputs) if (output === "buffer") {
665
- if (mux === "tmux") await appendToQuoteVariable(targetPaneId, processedText);
666
- else if (mux === "wezterm") await appendToQuoteText(targetPaneId, processedText);
667
- } else if (output === "stdout") process.stdout.write(processedText);
906
+ for (const output of outputs) if (output === "buffer") if (mux === "tmux") await appendToQuoteVariable(targetPaneId, processedText);
907
+ else if (mux === "wezterm") await appendToQuoteText(targetPaneId, processedText);
908
+ else await appendToQuoteText$1(targetPaneId, processedText);
909
+ else if (output === "stdout") process.stdout.write(processedText);
668
910
  } catch (error) {
669
- logger$7.error(`${error instanceof Error ? error.message : "Unknown error"}`);
911
+ logger$9.error(`${error instanceof Error ? error.message : "Unknown error"}`);
670
912
  process.exit(1);
671
913
  }
672
914
  }
@@ -693,10 +935,10 @@ const collectCommand = define({
693
935
  const outputs = normalizeCollectOutputs(ctx.values.output);
694
936
  const withQuote = !ctx.values["no-quote"];
695
937
  let rawContent;
696
- if (mux === "wezterm") {
938
+ if (mux === "wezterm" || mux === "herdr") {
697
939
  rawContent = extractRawContent(ctx.rest, ctx.positionals);
698
940
  if (rawContent === void 0) {
699
- logger$7.error("Text content is required for collect mode with wezterm. Use: editprompt collect --mux wezterm --target-pane <id> -- \"<text>\"");
941
+ logger$9.error(`Text content is required for collect mode with ${mux}. Use: editprompt collect --mux ${mux} --target-pane <id> -- "<text>"`);
700
942
  process.exit(1);
701
943
  }
702
944
  }
@@ -706,11 +948,8 @@ const collectCommand = define({
706
948
 
707
949
  //#endregion
708
950
  //#region src/utils/sendConfig.ts
709
- const VALID_MUX_TYPES = ["tmux", "wezterm"];
710
951
  function readSendConfig() {
711
- const muxValue = process.env.EDITPROMPT_MUX || "tmux";
712
- if (!VALID_MUX_TYPES.includes(muxValue)) throw new Error(`Invalid EDITPROMPT_MUX value: ${muxValue}. Must be one of: ${VALID_MUX_TYPES.join(", ")}`);
713
- const mux = muxValue;
952
+ const mux = resolveMux();
714
953
  const alwaysCopy = process.env.EDITPROMPT_ALWAYS_COPY === "1";
715
954
  const delayValue = process.env.EDITPROMPT_SEND_KEY_DELAY;
716
955
  const parsedDelay = delayValue ? Number.parseInt(delayValue, 10) : NaN;
@@ -723,7 +962,7 @@ function readSendConfig() {
723
962
 
724
963
  //#endregion
725
964
  //#region src/modes/dump.ts
726
- const logger$6 = getLogger(["editprompt", "dump"]);
965
+ const logger$8 = getLogger(["editprompt", "dump"]);
727
966
  async function runDumpMode() {
728
967
  try {
729
968
  const config = readSendConfig();
@@ -732,19 +971,23 @@ async function runDumpMode() {
732
971
  if (config.mux === "tmux") {
733
972
  currentPaneId = await getCurrentPaneId$1();
734
973
  isEditor = await isEditorPane(currentPaneId);
735
- } else {
974
+ } else if (config.mux === "wezterm") {
736
975
  currentPaneId = await getCurrentPaneId();
737
976
  isEditor = isEditorPaneFromConf(currentPaneId);
977
+ } else {
978
+ currentPaneId = await getCurrentPaneId$2();
979
+ isEditor = isEditorPaneFromConf$1(currentPaneId);
738
980
  }
739
981
  if (!isEditor) {
740
- logger$6.error("Current pane is not an editor pane");
982
+ logger$8.error("Current pane is not an editor pane");
741
983
  process.exit(1);
742
984
  }
743
985
  let targetPanes;
744
986
  if (config.mux === "tmux") targetPanes = await getTargetPaneIds$1(currentPaneId);
745
- else targetPanes = await getTargetPaneIds(currentPaneId);
987
+ else if (config.mux === "wezterm") targetPanes = await getTargetPaneIds(currentPaneId);
988
+ else targetPanes = await getTargetPaneIds$2(currentPaneId);
746
989
  if (targetPanes.length === 0) {
747
- logger$6.error("No target panes registered for this editor pane");
990
+ logger$8.error("No target panes registered for this editor pane");
748
991
  process.exit(1);
749
992
  }
750
993
  const quoteContents = [];
@@ -753,9 +996,12 @@ async function runDumpMode() {
753
996
  if (config.mux === "tmux") {
754
997
  content = await getQuoteVariableContent(targetPane);
755
998
  await clearQuoteVariable(targetPane);
756
- } else {
999
+ } else if (config.mux === "wezterm") {
757
1000
  content = await getQuoteText(targetPane);
758
1001
  await clearQuoteText(targetPane);
1002
+ } else {
1003
+ content = await getQuoteText$1(targetPane);
1004
+ await clearQuoteText$1(targetPane);
759
1005
  }
760
1006
  if (content.trim() !== "") quoteContents.push(content);
761
1007
  }
@@ -763,7 +1009,7 @@ async function runDumpMode() {
763
1009
  process.stdout.write(combinedContent.replace(/\n{3,}$/, "\n\n"));
764
1010
  process.exit(0);
765
1011
  } catch (error) {
766
- logger$6.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1012
+ logger$8.error(`${error instanceof Error ? error.message : "Unknown error"}`);
767
1013
  process.exit(1);
768
1014
  }
769
1015
  }
@@ -793,13 +1039,87 @@ function processContent(content) {
793
1039
  return processed;
794
1040
  }
795
1041
 
1042
+ //#endregion
1043
+ //#region src/modes/common.ts
1044
+ const logger$7 = getLogger(["editprompt", "delivery"]);
1045
+ async function copyToClipboard(content) {
1046
+ await clipboardy.write(content);
1047
+ }
1048
+ async function inputContentToPane(content, mux, targetPaneId) {
1049
+ if (mux === "wezterm") await inputToWeztermPane(targetPaneId, content);
1050
+ else if (mux === "herdr") await inputToHerdrPane(targetPaneId, content);
1051
+ else await inputToTmuxPane(targetPaneId, content);
1052
+ }
1053
+ async function focusFirstSuccessPane(mux, targetPanes, failedPanes) {
1054
+ const firstSuccessPane = targetPanes.find((p) => !failedPanes.includes(p));
1055
+ if (firstSuccessPane) if (mux === "tmux") await focusPane$1(firstSuccessPane);
1056
+ else if (mux === "wezterm") await focusPane(firstSuccessPane);
1057
+ else await focusPane$2(firstSuccessPane);
1058
+ }
1059
+ async function handleContentDelivery(content, mux, targetPanes) {
1060
+ if (!content) return {
1061
+ successCount: 0,
1062
+ totalCount: 0,
1063
+ allSuccess: true,
1064
+ allFailed: false,
1065
+ failedPanes: []
1066
+ };
1067
+ if (targetPanes.length === 0) {
1068
+ try {
1069
+ await copyToClipboard(content);
1070
+ logger$7.info("Content copied to clipboard.");
1071
+ } catch (error) {
1072
+ logger$7.warn(`Failed to copy to clipboard: ${error instanceof Error ? error.message : "Unknown error"}`);
1073
+ }
1074
+ return {
1075
+ successCount: 0,
1076
+ totalCount: 0,
1077
+ allSuccess: true,
1078
+ allFailed: false,
1079
+ failedPanes: []
1080
+ };
1081
+ }
1082
+ const results = [];
1083
+ for (const targetPane of targetPanes) try {
1084
+ await inputContentToPane(content, mux, targetPane);
1085
+ results.push({
1086
+ pane: targetPane,
1087
+ success: true
1088
+ });
1089
+ } catch (error) {
1090
+ logger$7.warn(`Failed to send to pane ${targetPane}: ${error instanceof Error ? error.message : "Unknown error"}`);
1091
+ results.push({
1092
+ pane: targetPane,
1093
+ success: false
1094
+ });
1095
+ }
1096
+ const successCount = results.filter((r) => r.success).length;
1097
+ const failedPanes = results.filter((r) => !r.success).map((r) => r.pane);
1098
+ const allSuccess = successCount === targetPanes.length;
1099
+ const allFailed = successCount === 0;
1100
+ if (allSuccess) logger$7.info("Content sent successfully to all panes!");
1101
+ else if (allFailed) {
1102
+ logger$7.error("All target panes failed to receive content.");
1103
+ logger$7.info("Falling back to clipboard...");
1104
+ await copyToClipboard(content);
1105
+ logger$7.info("Content copied to clipboard.");
1106
+ } else logger$7.warn(`Content sent to ${successCount}/${targetPanes.length} panes. Failed panes: ${failedPanes.join(", ")}`);
1107
+ return {
1108
+ successCount,
1109
+ totalCount: targetPanes.length,
1110
+ allSuccess,
1111
+ allFailed,
1112
+ failedPanes
1113
+ };
1114
+ }
1115
+
796
1116
  //#endregion
797
1117
  //#region src/modes/input.ts
798
- const logger$5 = getLogger(["editprompt", "input"]);
1118
+ const logger$6 = getLogger(["editprompt", "input"]);
799
1119
  async function runInputMode(rawContent, autoSend, sendKey, sendKeyDelay) {
800
1120
  const content = processContent(rawContent);
801
1121
  if (!content) {
802
- logger$5.info("No content to send. Exiting.");
1122
+ logger$6.info("No content to send. Exiting.");
803
1123
  return;
804
1124
  }
805
1125
  const config = readSendConfig();
@@ -808,44 +1128,51 @@ async function runInputMode(rawContent, autoSend, sendKey, sendKeyDelay) {
808
1128
  if (config.mux === "tmux") {
809
1129
  currentPaneId = await getCurrentPaneId$1();
810
1130
  isEditor = await isEditorPane(currentPaneId);
811
- } else {
1131
+ } else if (config.mux === "wezterm") {
812
1132
  currentPaneId = await getCurrentPaneId();
813
1133
  isEditor = isEditorPaneFromConf(currentPaneId);
1134
+ } else {
1135
+ currentPaneId = await getCurrentPaneId$2();
1136
+ isEditor = isEditorPaneFromConf$1(currentPaneId);
814
1137
  }
815
1138
  if (!isEditor) {
816
- logger$5.error("Current pane is not an editor pane");
1139
+ logger$6.error("Current pane is not an editor pane");
817
1140
  process.exit(1);
818
1141
  }
819
1142
  let targetPanes;
820
1143
  if (config.mux === "tmux") targetPanes = await getTargetPaneIds$1(currentPaneId);
821
- else targetPanes = await getTargetPaneIds(currentPaneId);
1144
+ else if (config.mux === "wezterm") targetPanes = await getTargetPaneIds(currentPaneId);
1145
+ else targetPanes = await getTargetPaneIds$2(currentPaneId);
822
1146
  if (targetPanes.length === 0) {
823
- logger$5.error("No target panes registered for this editor pane");
1147
+ logger$6.error("No target panes registered for this editor pane");
824
1148
  process.exit(1);
825
1149
  }
826
1150
  if (autoSend) {
827
- const key = sendKey || (config.mux === "wezterm" ? "\\r" : "C-m");
1151
+ const key = sendKey || (config.mux === "wezterm" ? "\\r" : config.mux === "herdr" ? "enter" : "C-m");
828
1152
  const delay = /\.(png|webp|avif|jpe?g|gif)\b/i.test(content) ? sendKeyDelay ?? 1e3 : 200;
829
1153
  let successCount = 0;
830
1154
  for (const targetPane of targetPanes) try {
831
1155
  if (config.mux === "wezterm") {
832
1156
  await inputToWeztermPane(targetPane, content);
833
1157
  await sendKeyToWeztermPane(targetPane, key, delay);
1158
+ } else if (config.mux === "herdr") {
1159
+ await inputToHerdrPane(targetPane, content);
1160
+ await sendKeyToHerdrPane(targetPane, key, delay);
834
1161
  } else {
835
1162
  await inputToTmuxPane(targetPane, content);
836
1163
  await sendKeyToTmuxPane(targetPane, key, delay);
837
1164
  }
838
1165
  successCount++;
839
1166
  } catch (error) {
840
- logger$5.error(`Failed to send to pane ${targetPane}: ${error instanceof Error ? error.message : "Unknown error"}`);
1167
+ logger$6.error(`Failed to send to pane ${targetPane}: ${error instanceof Error ? error.message : "Unknown error"}`);
841
1168
  }
842
1169
  if (config.alwaysCopy) {
843
1170
  await copyToClipboard(content);
844
- logger$5.info("Also copied to clipboard.");
1171
+ logger$6.info("Also copied to clipboard.");
845
1172
  }
846
- if (successCount > 0) logger$5.info("Content sent and submitted successfully!");
1173
+ if (successCount > 0) logger$6.info("Content sent and submitted successfully!");
847
1174
  else {
848
- logger$5.error("All target panes failed to receive content");
1175
+ logger$6.error("All target panes failed to receive content");
849
1176
  process.exit(1);
850
1177
  }
851
1178
  return;
@@ -854,12 +1181,12 @@ async function runInputMode(rawContent, autoSend, sendKey, sendKeyDelay) {
854
1181
  const result = await handleContentDelivery(content, config.mux, targetPanes);
855
1182
  if (config.alwaysCopy && !result.allFailed) {
856
1183
  await copyToClipboard(content);
857
- logger$5.info("Also copied to clipboard.");
1184
+ logger$6.info("Also copied to clipboard.");
858
1185
  }
859
1186
  if (result.successCount > 0) await focusFirstSuccessPane(config.mux, targetPanes, result.failedPanes);
860
1187
  if (result.allFailed) process.exit(1);
861
1188
  } catch (error) {
862
- logger$5.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1189
+ logger$6.error(`${error instanceof Error ? error.message : "Unknown error"}`);
863
1190
  process.exit(1);
864
1191
  }
865
1192
  }
@@ -887,13 +1214,13 @@ const inputCommand = define({
887
1214
  });
888
1215
  const rawContent = extractRawContent(ctx.rest, ctx.positionals);
889
1216
  if (rawContent === void 0) {
890
- logger$5.error("Content is required for input command");
891
- logger$5.error("Usage: editprompt input \"your content\"");
892
- logger$5.error(" or: editprompt input -- \"your content\"");
1217
+ logger$6.error("Content is required for input command");
1218
+ logger$6.error("Usage: editprompt input \"your content\"");
1219
+ logger$6.error(" or: editprompt input -- \"your content\"");
893
1220
  process.exit(1);
894
1221
  }
895
1222
  if (ctx.values["send-key"] && !ctx.values["auto-send"]) {
896
- logger$5.error("--send-key requires --auto-send to be enabled");
1223
+ logger$6.error("--send-key requires --auto-send to be enabled");
897
1224
  process.exit(1);
898
1225
  }
899
1226
  const config = readSendConfig();
@@ -987,8 +1314,9 @@ async function openEditorAndGetContent(editorOption, envVars, sendConfig) {
987
1314
 
988
1315
  //#endregion
989
1316
  //#region src/modes/openEditor.ts
990
- const logger$4 = getLogger(["editprompt", "open"]);
1317
+ const logger$5 = getLogger(["editprompt", "open"]);
991
1318
  async function runOpenEditorMode(options) {
1319
+ let herdrEditorPaneId;
992
1320
  if (options.targetPanes.length > 0 && options.mux === "tmux") try {
993
1321
  await markAsEditorPane$1(await getCurrentPaneId$1(), options.targetPanes);
994
1322
  } catch {}
@@ -996,16 +1324,21 @@ async function runOpenEditorMode(options) {
996
1324
  const currentPaneId = await getCurrentPaneId();
997
1325
  await markAsEditorPane(currentPaneId, options.targetPanes);
998
1326
  } catch {}
1327
+ else if (options.targetPanes.length > 0 && options.mux === "herdr") try {
1328
+ const currentPaneId = await getCurrentPaneId$2();
1329
+ herdrEditorPaneId = currentPaneId;
1330
+ await markAsEditorPane$2(currentPaneId, options.targetPanes);
1331
+ } catch {}
999
1332
  try {
1000
1333
  const sendConfig = {
1001
1334
  mux: options.mux,
1002
1335
  alwaysCopy: options.alwaysCopy,
1003
1336
  sendKeyDelay: Number.parseInt(process.env.EDITPROMPT_SEND_KEY_DELAY || "", 10) || 1e3
1004
1337
  };
1005
- logger$4.info("Opening editor...");
1338
+ logger$5.info("Opening editor...");
1006
1339
  const content = await openEditorAndGetContent(options.editor, options.env, sendConfig);
1007
1340
  if (!content) {
1008
- logger$4.info("No content entered. Exiting.");
1341
+ logger$5.info("No content entered. Exiting.");
1009
1342
  return;
1010
1343
  }
1011
1344
  try {
@@ -1014,12 +1347,12 @@ async function runOpenEditorMode(options) {
1014
1347
  console.log(content);
1015
1348
  if (options.alwaysCopy && !result.allFailed) {
1016
1349
  await copyToClipboard(content);
1017
- logger$4.info("Also copied to clipboard.");
1350
+ logger$5.info("Also copied to clipboard.");
1018
1351
  }
1019
1352
  if (options.targetPanes.length > 0 && result.successCount > 0) await focusFirstSuccessPane(options.mux, options.targetPanes, result.failedPanes);
1020
1353
  if (!result.allSuccess) process.exit(1);
1021
1354
  } catch (error) {
1022
- logger$4.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1355
+ logger$5.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1023
1356
  process.exit(1);
1024
1357
  }
1025
1358
  } finally {
@@ -1029,6 +1362,9 @@ async function runOpenEditorMode(options) {
1029
1362
  else if (options.targetPanes.length > 0 && options.mux === "wezterm") try {
1030
1363
  for (const targetPane of options.targetPanes) await clearEditorPaneId(targetPane);
1031
1364
  } catch {}
1365
+ else if (options.targetPanes.length > 0 && options.mux === "herdr") try {
1366
+ for (const targetPane of options.targetPanes) await clearEditorPaneId$2(targetPane, herdrEditorPaneId);
1367
+ } catch {}
1032
1368
  }
1033
1369
  }
1034
1370
  const openCommand = define({
@@ -1042,12 +1378,7 @@ const openCommand = define({
1042
1378
  "log-file": ARG_LOG_FILE,
1043
1379
  quiet: ARG_QUIET,
1044
1380
  verbose: ARG_VERBOSE,
1045
- env: {
1046
- short: "E",
1047
- description: "Environment variables to set (e.g., KEY=VALUE)",
1048
- type: "string",
1049
- multiple: true
1050
- }
1381
+ env: ARG_ENV
1051
1382
  },
1052
1383
  async run(ctx) {
1053
1384
  setupLogger({
@@ -1067,10 +1398,10 @@ const openCommand = define({
1067
1398
 
1068
1399
  //#endregion
1069
1400
  //#region src/modes/register.ts
1070
- const logger$3 = getLogger(["editprompt", "register"]);
1401
+ const logger$4 = getLogger(["editprompt", "register"]);
1071
1402
  async function runRegisterMode(options) {
1072
1403
  if (options.targetPanes.length === 0) {
1073
- logger$3.error("--target-pane is required for register command");
1404
+ logger$4.error("--target-pane is required for register command");
1074
1405
  process.exit(1);
1075
1406
  }
1076
1407
  let editorPaneId;
@@ -1078,29 +1409,37 @@ async function runRegisterMode(options) {
1078
1409
  else if (options.mux === "tmux") {
1079
1410
  editorPaneId = await getCurrentPaneId$1();
1080
1411
  if (!await isEditorPane(editorPaneId)) {
1081
- logger$3.error("Current pane is not an editor pane. Please run this command from an editor pane or specify --editor-pane.");
1412
+ logger$4.error("Current pane is not an editor pane. Please run this command from an editor pane or specify --editor-pane.");
1082
1413
  process.exit(1);
1083
1414
  }
1084
1415
  } else if (options.mux === "wezterm") {
1085
1416
  editorPaneId = await getCurrentPaneId();
1086
1417
  if (!isEditorPaneFromConf(editorPaneId)) {
1087
- logger$3.error("Current pane is not an editor pane. Please run this command from an editor pane or specify --editor-pane.");
1418
+ logger$4.error("Current pane is not an editor pane. Please run this command from an editor pane or specify --editor-pane.");
1419
+ process.exit(1);
1420
+ }
1421
+ } else if (options.mux === "herdr") {
1422
+ editorPaneId = await getCurrentPaneId$2();
1423
+ if (!isEditorPaneFromConf$1(editorPaneId)) {
1424
+ logger$4.error("Current pane is not an editor pane. Please run this command from an editor pane or specify --editor-pane.");
1088
1425
  process.exit(1);
1089
1426
  }
1090
1427
  } else {
1091
- logger$3.error("Unsupported multiplexer");
1428
+ logger$4.error("Unsupported multiplexer");
1092
1429
  process.exit(1);
1093
1430
  }
1094
1431
  try {
1095
1432
  let existingPanes = [];
1096
1433
  if (options.mux === "tmux") existingPanes = await getTargetPaneIds$1(editorPaneId);
1097
1434
  else if (options.mux === "wezterm") existingPanes = await getTargetPaneIds(editorPaneId);
1435
+ else if (options.mux === "herdr") existingPanes = await getTargetPaneIds$2(editorPaneId);
1098
1436
  const mergedTargetPanes = [...new Set([...existingPanes, ...options.targetPanes])];
1099
1437
  if (options.mux === "tmux") await markAsEditorPane$1(editorPaneId, mergedTargetPanes);
1100
1438
  else if (options.mux === "wezterm") await markAsEditorPane(editorPaneId, mergedTargetPanes);
1101
- logger$3.info(`Editor pane ${editorPaneId} registered with target panes: ${mergedTargetPanes.join(", ")}`);
1439
+ else if (options.mux === "herdr") await markAsEditorPane$2(editorPaneId, mergedTargetPanes);
1440
+ logger$4.info(`Editor pane ${editorPaneId} registered with target panes: ${mergedTargetPanes.join(", ")}`);
1102
1441
  } catch (error) {
1103
- logger$3.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1442
+ logger$4.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1104
1443
  process.exit(1);
1105
1444
  }
1106
1445
  }
@@ -1135,15 +1474,21 @@ const registerCommand = define({
1135
1474
 
1136
1475
  //#endregion
1137
1476
  //#region src/modes/resume.ts
1138
- const logger$2 = getLogger(["editprompt", "resume"]);
1477
+ const logger$3 = getLogger(["editprompt", "resume"]);
1139
1478
  async function runResumeMode(targetPane, mux) {
1479
+ if (mux === "herdr") try {
1480
+ process.exit(await resumeEditorPane(targetPane) ? 0 : 1);
1481
+ } catch (error) {
1482
+ logger$3.debug("Herdr resume failed: {error}", { error });
1483
+ process.exit(1);
1484
+ }
1140
1485
  if (mux === "wezterm") {
1141
1486
  const currentPaneId = await getCurrentPaneId();
1142
1487
  if (isEditorPaneFromConf(currentPaneId)) {
1143
- logger$2.debug("Current pane is an editor pane");
1488
+ logger$3.debug("Current pane is an editor pane");
1144
1489
  const originalTargetPaneIds = await getTargetPaneIds(currentPaneId);
1145
1490
  if (originalTargetPaneIds.length === 0) {
1146
- logger$2.debug("No target pane IDs found for editor pane");
1491
+ logger$3.debug("No target pane IDs found for editor pane");
1147
1492
  process.exit(1);
1148
1493
  }
1149
1494
  let focused = false;
@@ -1153,20 +1498,20 @@ async function runResumeMode(targetPane, mux) {
1153
1498
  break;
1154
1499
  }
1155
1500
  if (!focused) {
1156
- logger$2.debug("All target panes do not exist");
1501
+ logger$3.debug("All target panes do not exist");
1157
1502
  process.exit(1);
1158
1503
  }
1159
1504
  process.exit(0);
1160
1505
  }
1161
- logger$2.debug("Current pane is not an editor pane");
1506
+ logger$3.debug("Current pane is not an editor pane");
1162
1507
  const editorPaneId = await getEditorPaneId(targetPane);
1163
- logger$2.debug("wezterm editorPaneId: {editorPaneId}", { editorPaneId });
1508
+ logger$3.debug("wezterm editorPaneId: {editorPaneId}", { editorPaneId });
1164
1509
  if (editorPaneId === "") {
1165
- logger$2.debug("Editor pane ID not found");
1510
+ logger$3.debug("Editor pane ID not found");
1166
1511
  process.exit(1);
1167
1512
  }
1168
1513
  if (!await checkPaneExists(editorPaneId)) {
1169
- logger$2.debug("Editor pane does not exist");
1514
+ logger$3.debug("Editor pane does not exist");
1170
1515
  await clearEditorPaneId(targetPane);
1171
1516
  process.exit(1);
1172
1517
  }
@@ -1174,7 +1519,7 @@ async function runResumeMode(targetPane, mux) {
1174
1519
  await focusPane(editorPaneId);
1175
1520
  process.exit(0);
1176
1521
  } catch (error) {
1177
- logger$2.debug("Can't focus editorPaneId: {editorPaneId}, error: {error}", {
1522
+ logger$3.debug("Can't focus editorPaneId: {editorPaneId}, error: {error}", {
1178
1523
  editorPaneId,
1179
1524
  error
1180
1525
  });
@@ -1225,10 +1570,10 @@ const resumeCommand = define({
1225
1570
 
1226
1571
  //#endregion
1227
1572
  //#region src/modes/press.ts
1228
- const logger$1 = getLogger(["editprompt", "press"]);
1573
+ const logger$2 = getLogger(["editprompt", "press"]);
1229
1574
  async function runPressMode(key, delay = 0) {
1230
1575
  if (!key) {
1231
- logger$1.error("Key is required");
1576
+ logger$2.error("Key is required");
1232
1577
  process.exit(1);
1233
1578
  }
1234
1579
  const config = readSendConfig();
@@ -1237,32 +1582,37 @@ async function runPressMode(key, delay = 0) {
1237
1582
  if (config.mux === "tmux") {
1238
1583
  currentPaneId = await getCurrentPaneId$1();
1239
1584
  isEditor = await isEditorPane(currentPaneId);
1240
- } else {
1585
+ } else if (config.mux === "wezterm") {
1241
1586
  currentPaneId = await getCurrentPaneId();
1242
1587
  isEditor = isEditorPaneFromConf(currentPaneId);
1588
+ } else {
1589
+ currentPaneId = await getCurrentPaneId$2();
1590
+ isEditor = isEditorPaneFromConf$1(currentPaneId);
1243
1591
  }
1244
1592
  if (!isEditor) {
1245
- logger$1.error("Current pane is not an editor pane");
1593
+ logger$2.error("Current pane is not an editor pane");
1246
1594
  process.exit(1);
1247
1595
  }
1248
1596
  let targetPanes;
1249
1597
  if (config.mux === "tmux") targetPanes = await getTargetPaneIds$1(currentPaneId);
1250
- else targetPanes = await getTargetPaneIds(currentPaneId);
1598
+ else if (config.mux === "wezterm") targetPanes = await getTargetPaneIds(currentPaneId);
1599
+ else targetPanes = await getTargetPaneIds$2(currentPaneId);
1251
1600
  if (targetPanes.length === 0) {
1252
- logger$1.error("No target panes registered for this editor pane");
1601
+ logger$2.error("No target panes registered for this editor pane");
1253
1602
  process.exit(1);
1254
1603
  }
1255
1604
  let successCount = 0;
1256
1605
  for (const targetPane of targetPanes) try {
1257
1606
  if (config.mux === "wezterm") await sendKeyToWeztermPane(targetPane, key, delay);
1607
+ else if (config.mux === "herdr") await sendKeyToHerdrPane(targetPane, key, delay);
1258
1608
  else await sendKeyToTmuxPane(targetPane, key, delay);
1259
1609
  successCount++;
1260
1610
  } catch (error) {
1261
- logger$1.warn(`Failed to send key to pane ${targetPane}: ${error instanceof Error ? error.message : "Unknown error"}`);
1611
+ logger$2.warn(`Failed to send key to pane ${targetPane}: ${error instanceof Error ? error.message : "Unknown error"}`);
1262
1612
  }
1263
- if (successCount > 0) logger$1.info("Key sent successfully!");
1613
+ if (successCount > 0) logger$2.info("Key sent successfully!");
1264
1614
  else {
1265
- logger$1.error("All target panes failed to receive key");
1615
+ logger$2.error("All target panes failed to receive key");
1266
1616
  process.exit(1);
1267
1617
  }
1268
1618
  }
@@ -1286,8 +1636,8 @@ const pressCommand = define({
1286
1636
  });
1287
1637
  const key = extractRawContent(ctx.rest, ctx.positionals);
1288
1638
  if (key === void 0 || key === "") {
1289
- logger$1.error("Key is required for press command");
1290
- logger$1.error("Usage: editprompt press -- \"Tab\"");
1639
+ logger$2.error("Key is required for press command");
1640
+ logger$2.error("Usage: editprompt press -- \"Tab\"");
1291
1641
  process.exit(1);
1292
1642
  }
1293
1643
  await runPressMode(key, ctx.values.delay ? Number(ctx.values.delay) : 0);
@@ -1296,9 +1646,9 @@ const pressCommand = define({
1296
1646
 
1297
1647
  //#endregion
1298
1648
  //#region src/modes/stash.ts
1299
- const logger = getLogger(["editprompt", "stash"]);
1649
+ const logger$1 = getLogger(["editprompt", "stash"]);
1300
1650
  function getStashKey(mux, targetPaneId) {
1301
- return `${mux}.targetPane.pane_${targetPaneId}.stash`;
1651
+ return `${mux === "herdr" ? getStorageNamespace() : mux}.targetPane.pane_${targetPaneId}.stash`;
1302
1652
  }
1303
1653
  async function pushStash(mux, targetPaneId, content) {
1304
1654
  const key = (/* @__PURE__ */ new Date()).toISOString();
@@ -1345,19 +1695,23 @@ async function getTargetPaneForStash() {
1345
1695
  if (config.mux === "tmux") {
1346
1696
  currentPaneId = await getCurrentPaneId$1();
1347
1697
  isEditor = await isEditorPane(currentPaneId);
1348
- } else {
1698
+ } else if (config.mux === "wezterm") {
1349
1699
  currentPaneId = await getCurrentPaneId();
1350
1700
  isEditor = isEditorPaneFromConf(currentPaneId);
1701
+ } else {
1702
+ currentPaneId = await getCurrentPaneId$2();
1703
+ isEditor = isEditorPaneFromConf$1(currentPaneId);
1351
1704
  }
1352
1705
  if (!isEditor) {
1353
- logger.error("Current pane is not an editor pane");
1706
+ logger$1.error("Current pane is not an editor pane");
1354
1707
  process.exit(1);
1355
1708
  }
1356
1709
  let targetPanes;
1357
1710
  if (config.mux === "tmux") targetPanes = await getTargetPaneIds$1(currentPaneId);
1358
- else targetPanes = await getTargetPaneIds(currentPaneId);
1711
+ else if (config.mux === "wezterm") targetPanes = await getTargetPaneIds(currentPaneId);
1712
+ else targetPanes = await getTargetPaneIds$2(currentPaneId);
1359
1713
  if (targetPanes.length === 0) {
1360
- logger.error("No target panes registered for this editor pane");
1714
+ logger$1.error("No target panes registered for this editor pane");
1361
1715
  process.exit(1);
1362
1716
  }
1363
1717
  return {
@@ -1368,13 +1722,13 @@ async function getTargetPaneForStash() {
1368
1722
  async function runPush(rest, positionals) {
1369
1723
  const rawContent = extractRawContent(rest, positionals);
1370
1724
  if (rawContent === void 0 || rawContent.trim() === "") {
1371
- logger.error("Content is required for stash push");
1372
- logger.error("Usage: editprompt stash push -- \"your content\"");
1725
+ logger$1.error("Content is required for stash push");
1726
+ logger$1.error("Usage: editprompt stash push -- \"your content\"");
1373
1727
  process.exit(1);
1374
1728
  }
1375
1729
  const { mux, targetPaneId } = await getTargetPaneForStash();
1376
1730
  const key = await pushStash(mux, targetPaneId, rawContent);
1377
- logger.info("Stashed with key: {key}", { key });
1731
+ logger$1.info("Stashed with key: {key}", { key });
1378
1732
  }
1379
1733
  async function runList() {
1380
1734
  const { mux, targetPaneId } = await getTargetPaneForStash();
@@ -1385,8 +1739,8 @@ async function runApply(key) {
1385
1739
  const { mux, targetPaneId } = await getTargetPaneForStash();
1386
1740
  const content = getStashContent(mux, targetPaneId, key);
1387
1741
  if (content === "") {
1388
- if (key) logger.error("No stash entry found with key: {key}", { key });
1389
- else logger.error("No stash entries found");
1742
+ if (key) logger$1.error("No stash entry found with key: {key}", { key });
1743
+ else logger$1.error("No stash entries found");
1390
1744
  process.exit(1);
1391
1745
  }
1392
1746
  process.stdout.write(content);
@@ -1394,18 +1748,18 @@ async function runApply(key) {
1394
1748
  async function runDrop(key) {
1395
1749
  const { mux, targetPaneId } = await getTargetPaneForStash();
1396
1750
  if (!dropStash(mux, targetPaneId, key)) {
1397
- if (key) logger.error("No stash entry found with key: {key}", { key });
1398
- else logger.error("No stash entries found");
1751
+ if (key) logger$1.error("No stash entry found with key: {key}", { key });
1752
+ else logger$1.error("No stash entries found");
1399
1753
  process.exit(1);
1400
1754
  }
1401
- logger.info("Stash entry dropped");
1755
+ logger$1.info("Stash entry dropped");
1402
1756
  }
1403
1757
  async function runPop(key) {
1404
1758
  const { mux, targetPaneId } = await getTargetPaneForStash();
1405
1759
  const content = getStashContent(mux, targetPaneId, key);
1406
1760
  if (content === "") {
1407
- if (key) logger.error("No stash entry found with key: {key}", { key });
1408
- else logger.error("No stash entries found");
1761
+ if (key) logger$1.error("No stash entry found with key: {key}", { key });
1762
+ else logger$1.error("No stash entries found");
1409
1763
  process.exit(1);
1410
1764
  }
1411
1765
  process.stdout.write(content);
@@ -1469,14 +1823,107 @@ const stashCommand = define({
1469
1823
  await runPop(parseKeyOption(subArgs));
1470
1824
  break;
1471
1825
  default:
1472
- logger.error("Unknown subcommand '{subcommand}'", { subcommand });
1473
- logger.error("");
1826
+ logger$1.error("Unknown subcommand '{subcommand}'", { subcommand });
1827
+ logger$1.error("");
1474
1828
  showHelp();
1475
1829
  process.exit(1);
1476
1830
  }
1477
1831
  }
1478
1832
  });
1479
1833
 
1834
+ //#endregion
1835
+ //#region src/modes/toggle.ts
1836
+ const logger = getLogger(["editprompt", "toggle"]);
1837
+ const DEFAULT_EDITOR_PANE_ROWS = 12;
1838
+ function buildOpenEditorArgv(options) {
1839
+ const invocation = options.invocation ?? {
1840
+ execPath: process.execPath,
1841
+ scriptPath: process.argv[1]
1842
+ };
1843
+ if (!invocation.scriptPath) throw new Error("Cannot determine the editprompt entrypoint");
1844
+ const argv = [
1845
+ invocation.execPath,
1846
+ invocation.scriptPath,
1847
+ "open",
1848
+ "--mux",
1849
+ options.mux,
1850
+ "--target-pane",
1851
+ options.targetPane
1852
+ ];
1853
+ if (options.editor) argv.push("--editor", options.editor);
1854
+ if (options.alwaysCopy) argv.push("--always-copy");
1855
+ for (const env of options.env ?? []) argv.push("--env", env);
1856
+ if (options.logFile) argv.push("--log-file", options.logFile);
1857
+ if (options.quiet) argv.push("--quiet");
1858
+ if (options.verbose) argv.push("--verbose");
1859
+ return argv;
1860
+ }
1861
+ async function runToggleMode(options, herdrApi = herdr_exports) {
1862
+ if (options.mux !== "herdr") throw new Error("toggle currently supports only the Herdr multiplexer");
1863
+ if (!Number.isInteger(options.paneRows) || options.paneRows <= 0) throw new Error("--pane-rows must be a positive integer");
1864
+ if (await herdrApi.resumeEditorPane(options.targetPane)) return;
1865
+ const launchEnv = {};
1866
+ if (process.env.NODE_NO_WARNINGS !== void 0) launchEnv.NODE_NO_WARNINGS = process.env.NODE_NO_WARNINGS;
1867
+ const editorPaneId = await herdrApi.splitEditorPane(options.targetPane, options.paneRows, process.env.HERDR_ACTIVE_PANE_CWD, launchEnv);
1868
+ try {
1869
+ await herdrApi.runCommandInPane(editorPaneId, buildOpenEditorArgv(options));
1870
+ } catch (error) {
1871
+ try {
1872
+ await herdrApi.closePane(editorPaneId);
1873
+ } catch (closeError) {
1874
+ logger.warn("Failed to close editor pane after launch failure: {error}", { error: closeError });
1875
+ }
1876
+ throw error;
1877
+ }
1878
+ }
1879
+ const toggleCommand = define({
1880
+ name: "toggle",
1881
+ description: "Open or focus an editor pane",
1882
+ args: {
1883
+ mux: ARG_MUX,
1884
+ "target-pane": ARG_TARGET_PANE_SINGLE,
1885
+ "pane-rows": {
1886
+ description: "Desired rows for the Herdr editor pane",
1887
+ type: "number",
1888
+ default: DEFAULT_EDITOR_PANE_ROWS
1889
+ },
1890
+ editor: ARG_EDITOR,
1891
+ "always-copy": ARG_ALWAYS_COPY,
1892
+ env: ARG_ENV,
1893
+ "log-file": ARG_LOG_FILE,
1894
+ quiet: ARG_QUIET,
1895
+ verbose: ARG_VERBOSE
1896
+ },
1897
+ async run(ctx) {
1898
+ const logFile = ctx.values["log-file"];
1899
+ const quiet = Boolean(ctx.values.quiet);
1900
+ const verbose = Boolean(ctx.values.verbose);
1901
+ setupLogger({
1902
+ quiet,
1903
+ verbose,
1904
+ logFile
1905
+ });
1906
+ const targetPane = validateTargetPane(ctx.values["target-pane"], "toggle");
1907
+ const mux = validateMux(ctx.values.mux);
1908
+ try {
1909
+ await runToggleMode({
1910
+ mux,
1911
+ targetPane,
1912
+ paneRows: Number(ctx.values["pane-rows"]),
1913
+ alwaysCopy: Boolean(ctx.values["always-copy"]),
1914
+ editor: ctx.values.editor,
1915
+ env: ctx.values.env,
1916
+ logFile,
1917
+ quiet,
1918
+ verbose
1919
+ });
1920
+ } catch (error) {
1921
+ logger.error(error instanceof Error ? error.message : String(error));
1922
+ process.exit(1);
1923
+ }
1924
+ }
1925
+ });
1926
+
1480
1927
  //#endregion
1481
1928
  //#region src/index.ts
1482
1929
  await cli(process.argv.slice(2), {
@@ -1505,6 +1952,7 @@ await cli(process.argv.slice(2), {
1505
1952
  open: openCommand,
1506
1953
  register: registerCommand,
1507
1954
  resume: resumeCommand,
1955
+ toggle: toggleCommand,
1508
1956
  input: inputCommand,
1509
1957
  press: pressCommand,
1510
1958
  collect: collectCommand,