editprompt 1.4.1 → 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 +688 -201
  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,12 +90,323 @@ function setupLogger(options = {}) {
72
90
 
73
91
  //#endregion
74
92
  //#region package.json
75
- var version = "1.4.1";
93
+ var version = "1.6.0";
94
+
95
+ //#endregion
96
+ //#region src/config/constants.ts
97
+ const TEMP_FILE_PREFIX = ".editprompt-";
98
+ const TEMP_FILE_EXTENSION = ".md";
99
+ const DEFAULT_EDITOR = "vim";
100
+ const TMUX_IMSG_LIMIT_BYTES = 16 * 1024;
101
+ const TMUX_SEND_CHUNK_BYTES = TMUX_IMSG_LIMIT_BYTES / 2;
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;
105
+
106
+ //#endregion
107
+ //#region src/utils/contentChunker.ts
108
+ /**
109
+ * Split content into chunks not exceeding maxBytes (UTF-8 byte count),
110
+ * never breaking a Unicode code point across chunks.
111
+ *
112
+ * Content shorter than maxBytes is returned as a single chunk, so short
113
+ * input keeps the previous behavior of being sent in one shot.
114
+ */
115
+ function splitByByteSize(content, maxBytes) {
116
+ if (content === "") return [];
117
+ const chunks = [];
118
+ let current = "";
119
+ let currentBytes = 0;
120
+ for (const char of content) {
121
+ const charBytes = Buffer.byteLength(char, "utf8");
122
+ if (current !== "" && currentBytes + charBytes > maxBytes) {
123
+ chunks.push(current);
124
+ current = "";
125
+ currentBytes = 0;
126
+ }
127
+ current += char;
128
+ currentBytes += charBytes;
129
+ }
130
+ if (current !== "") chunks.push(current);
131
+ return chunks;
132
+ }
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
+ }
76
405
 
77
406
  //#endregion
78
407
  //#region src/modules/tmux.ts
79
408
  const execAsync$1 = promisify(exec);
80
- const logger$11 = getLogger(["editprompt", "tmux"]);
409
+ const logger$12 = getLogger(["editprompt", "tmux"]);
81
410
  async function getCurrentPaneId$1() {
82
411
  const envPaneId = process.env.TMUX_PANE?.trim();
83
412
  if (envPaneId) return envPaneId;
@@ -157,18 +486,17 @@ async function sendKeyToTmuxPane(paneId, key, delay = 1e3) {
157
486
  }
158
487
  async function inputToTmuxPane(paneId, content) {
159
488
  await execAsync$1(`tmux if-shell -t '${paneId}' '[ "#{pane_in_mode}" = "1" ]' "copy-mode -q -t '${paneId}'"`);
160
- await execAsync$1(`tmux send-keys -t '${paneId}' -- '${content.replace(/'/g, "'\\''")}'`);
161
- logger$11.debug("Content sent to tmux pane: {paneId}", { paneId });
489
+ const chunks = splitByByteSize(content, TMUX_SEND_CHUNK_BYTES);
490
+ for (const chunk of chunks) await execAsync$1(`tmux send-keys -t '${paneId}' -- '${chunk.replace(/'/g, "'\\''")}'`);
491
+ logger$12.debug("Content sent to tmux pane: {paneId} ({chunks} chunk(s))", {
492
+ paneId,
493
+ chunks: chunks.length
494
+ });
162
495
  }
163
496
 
164
- //#endregion
165
- //#region src/modules/conf.ts
166
- const projectName = process.env.NODE_ENV === "test" ? "editprompt-test" : "editprompt";
167
- const conf = new Conf({ projectName });
168
-
169
497
  //#endregion
170
498
  //#region src/modules/wezterm.ts
171
- const logger$10 = getLogger(["editprompt", "wezterm"]);
499
+ const logger$11 = getLogger(["editprompt", "wezterm"]);
172
500
  const execAsync = promisify(exec);
173
501
  async function getCurrentPaneId() {
174
502
  try {
@@ -176,26 +504,26 @@ async function getCurrentPaneId() {
176
504
  const activePane = JSON.parse(stdout).find((pane) => pane.is_active === true);
177
505
  return String(activePane?.pane_id);
178
506
  } catch (error) {
179
- logger$10.debug("getCurrentPaneId failed: {error}", { error });
507
+ logger$11.debug("getCurrentPaneId failed: {error}", { error });
180
508
  return "";
181
509
  }
182
510
  }
183
511
  async function checkPaneExists(paneId) {
184
512
  try {
185
513
  const { stdout } = await execAsync("wezterm cli list --format json");
186
- logger$10.debug("wezterm cli list output: {stdout}", { stdout });
514
+ logger$11.debug("wezterm cli list output: {stdout}", { stdout });
187
515
  return JSON.parse(stdout).some((pane) => String(pane.pane_id) === paneId);
188
516
  } catch (error) {
189
- logger$10.debug("checkPaneExists failed: {error}", { error });
517
+ logger$11.debug("checkPaneExists failed: {error}", { error });
190
518
  return false;
191
519
  }
192
520
  }
193
521
  async function saveEditorPaneId(targetPaneId, editorPaneId) {
194
- 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 });
195
523
  try {
196
524
  conf.set(`wezterm.targetPane.pane_${targetPaneId}`, { editorPaneId });
197
525
  } catch (error) {
198
- logger$10.debug("saveEditorPaneId failed: {error}", { error });
526
+ logger$11.debug("saveEditorPaneId failed: {error}", { error });
199
527
  }
200
528
  }
201
529
  async function getEditorPaneId(targetPaneId) {
@@ -204,7 +532,7 @@ async function getEditorPaneId(targetPaneId) {
204
532
  if (typeof data === "object" && data !== null && "editorPaneId" in data) return String(data.editorPaneId);
205
533
  return "";
206
534
  } catch (error) {
207
- logger$10.debug("getEditorPaneId failed: {error}", { error });
535
+ logger$11.debug("getEditorPaneId failed: {error}", { error });
208
536
  return "";
209
537
  }
210
538
  }
@@ -214,7 +542,7 @@ async function clearEditorPaneId(targetPaneId) {
214
542
  conf.delete(`wezterm.targetPane.pane_${targetPaneId}`);
215
543
  if (editorPaneId) conf.delete(`wezterm.editorPane.pane_${editorPaneId}`);
216
544
  } catch (error) {
217
- logger$10.debug("clearEditorPaneId failed: {error}", { error });
545
+ logger$11.debug("clearEditorPaneId failed: {error}", { error });
218
546
  }
219
547
  }
220
548
  async function focusPane(paneId) {
@@ -226,7 +554,7 @@ async function markAsEditorPane(editorPaneId, targetPaneIds) {
226
554
  conf.set(`wezterm.editorPane.pane_${editorPaneId}`, { targetPaneIds: uniqueTargetPaneIds });
227
555
  for (const targetPaneId of uniqueTargetPaneIds) await saveEditorPaneId(targetPaneId, editorPaneId);
228
556
  } catch (error) {
229
- logger$10.debug("markAsEditorPane failed: {error}", { error });
557
+ logger$11.debug("markAsEditorPane failed: {error}", { error });
230
558
  }
231
559
  }
232
560
  async function getTargetPaneIds(editorPaneId) {
@@ -238,7 +566,7 @@ async function getTargetPaneIds(editorPaneId) {
238
566
  }
239
567
  return [];
240
568
  } catch (error) {
241
- logger$10.debug("getTargetPaneIds failed: {error}", { error });
569
+ logger$11.debug("getTargetPaneIds failed: {error}", { error });
242
570
  return [];
243
571
  }
244
572
  }
@@ -246,7 +574,7 @@ function isEditorPaneFromConf(paneId) {
246
574
  try {
247
575
  return conf.has(`wezterm.editorPane.pane_${paneId}`);
248
576
  } catch (error) {
249
- logger$10.debug("isEditorPaneFromConf failed: {error}", { error });
577
+ logger$11.debug("isEditorPaneFromConf failed: {error}", { error });
250
578
  return false;
251
579
  }
252
580
  }
@@ -264,7 +592,7 @@ async function appendToQuoteText(paneId, content) {
264
592
  } else newData = { quote_text: content };
265
593
  conf.set(`wezterm.targetPane.pane_${paneId}`, newData);
266
594
  } catch (error) {
267
- logger$10.debug("appendToQuoteText failed: {error}", { error });
595
+ logger$11.debug("appendToQuoteText failed: {error}", { error });
268
596
  }
269
597
  }
270
598
  async function getQuoteText(paneId) {
@@ -273,7 +601,7 @@ async function getQuoteText(paneId) {
273
601
  if (typeof data === "object" && data !== null && "quote_text" in data) return String(data.quote_text);
274
602
  return "";
275
603
  } catch (error) {
276
- logger$10.debug("getQuoteText failed: {error}", { error });
604
+ logger$11.debug("getQuoteText failed: {error}", { error });
277
605
  return "";
278
606
  }
279
607
  }
@@ -282,7 +610,7 @@ async function clearQuoteText(paneId) {
282
610
  const key = `wezterm.targetPane.pane_${paneId}.quote_text`;
283
611
  if (conf.has(key)) conf.delete(key);
284
612
  } catch (error) {
285
- logger$10.debug("clearQuoteText failed: {error}", { error });
613
+ logger$11.debug("clearQuoteText failed: {error}", { error });
286
614
  }
287
615
  }
288
616
  async function sendKeyToWeztermPane(paneId, key, delay = 1e3) {
@@ -290,8 +618,12 @@ async function sendKeyToWeztermPane(paneId, key, delay = 1e3) {
290
618
  await execAsync(`wezterm cli send-text --no-paste --pane-id '${paneId}' $'${key}'`);
291
619
  }
292
620
  async function inputToWeztermPane(paneId, content) {
293
- await execAsync(`wezterm cli send-text --no-paste --pane-id '${paneId}' -- '${content.replace(/'/g, "'\\''")}'`);
294
- logger$10.debug("Content sent to wezterm pane: {paneId}", { paneId });
621
+ const chunks = splitByByteSize(content, WEZTERM_SEND_CHUNK_BYTES);
622
+ for (const chunk of chunks) await execAsync(`wezterm cli send-text --no-paste --pane-id '${paneId}' -- '${chunk.replace(/'/g, "'\\''")}'`);
623
+ logger$11.debug("Content sent to wezterm pane: {paneId} ({chunks} chunk(s))", {
624
+ paneId,
625
+ chunks: chunks.length
626
+ });
295
627
  }
296
628
 
297
629
  //#endregion
@@ -432,87 +764,36 @@ function processQuoteText(text, options) {
432
764
  }
433
765
 
434
766
  //#endregion
435
- //#region src/modes/common.ts
436
- const logger$9 = getLogger(["editprompt", "delivery"]);
767
+ //#region src/utils/mux.ts
768
+ const SUPPORTED_MUXES = [
769
+ "tmux",
770
+ "wezterm",
771
+ "herdr"
772
+ ];
437
773
  function isMuxType(value) {
438
- return value === "tmux" || value === "wezterm";
774
+ return value === "tmux" || value === "wezterm" || value === "herdr";
439
775
  }
440
- const SUPPORTED_MUXES = ["tmux", "wezterm"];
441
- async function copyToClipboard(content) {
442
- await clipboardy.write(content);
776
+ function hasValue(value) {
777
+ return typeof value === "string" && value.trim() !== "";
443
778
  }
444
- async function inputContentToPane(content, mux, targetPaneId) {
445
- if (mux === "wezterm") await inputToWeztermPane(targetPaneId, content);
446
- else await inputToTmuxPane(targetPaneId, content);
447
- }
448
- async function focusFirstSuccessPane(mux, targetPanes, failedPanes) {
449
- const firstSuccessPane = targetPanes.find((p) => !failedPanes.includes(p));
450
- if (firstSuccessPane) if (mux === "tmux") await focusPane$1(firstSuccessPane);
451
- 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));
452
781
  }
453
- async function handleContentDelivery(content, mux, targetPanes) {
454
- if (!content) return {
455
- successCount: 0,
456
- totalCount: 0,
457
- allSuccess: true,
458
- allFailed: false,
459
- failedPanes: []
460
- };
461
- if (targetPanes.length === 0) {
462
- try {
463
- await copyToClipboard(content);
464
- logger$9.info("Content copied to clipboard.");
465
- } catch (error) {
466
- logger$9.warn(`Failed to copy to clipboard: ${error instanceof Error ? error.message : "Unknown error"}`);
467
- }
468
- return {
469
- successCount: 0,
470
- totalCount: 0,
471
- allSuccess: true,
472
- allFailed: false,
473
- failedPanes: []
474
- };
475
- }
476
- const results = [];
477
- for (const targetPane of targetPanes) try {
478
- await inputContentToPane(content, mux, targetPane);
479
- results.push({
480
- pane: targetPane,
481
- success: true
482
- });
483
- } catch (error) {
484
- logger$9.warn(`Failed to send to pane ${targetPane}: ${error instanceof Error ? error.message : "Unknown error"}`);
485
- results.push({
486
- pane: targetPane,
487
- success: false
488
- });
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(", ")}`);
489
787
  }
490
- const successCount = results.filter((r) => r.success).length;
491
- const failedPanes = results.filter((r) => !r.success).map((r) => r.pane);
492
- const allSuccess = successCount === targetPanes.length;
493
- const allFailed = successCount === 0;
494
- if (allSuccess) logger$9.info("Content sent successfully to all panes!");
495
- else if (allFailed) {
496
- logger$9.error("All target panes failed to receive content.");
497
- logger$9.info("Falling back to clipboard...");
498
- await copyToClipboard(content);
499
- logger$9.info("Content copied to clipboard.");
500
- } else logger$9.warn(`Content sent to ${successCount}/${targetPanes.length} panes. Failed panes: ${failedPanes.join(", ")}`);
501
- return {
502
- successCount,
503
- totalCount: targetPanes.length,
504
- allSuccess,
505
- allFailed,
506
- failedPanes
507
- };
788
+ return muxValue;
508
789
  }
509
790
 
510
791
  //#endregion
511
792
  //#region src/modes/args.ts
512
- const logger$8 = getLogger(["editprompt"]);
793
+ const logger$10 = getLogger(["editprompt"]);
513
794
  const ARG_MUX = {
514
795
  short: "m",
515
- description: "Multiplexer type (tmux or wezterm, default: tmux)",
796
+ description: "Multiplexer type (tmux, wezterm, or herdr; auto-detects Herdr, otherwise tmux)",
516
797
  type: "string"
517
798
  };
518
799
  const ARG_TARGET_PANE_SINGLE = {
@@ -558,17 +839,23 @@ const ARG_VERBOSE = {
558
839
  description: "Enable debug-level log output",
559
840
  type: "boolean"
560
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
+ };
561
848
  function validateMux(value) {
562
- const muxValue = value || "tmux";
563
- if (!isMuxType(muxValue)) {
564
- 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));
565
853
  process.exit(1);
566
854
  }
567
- return muxValue;
568
855
  }
569
856
  function validateTargetPane(value, commandName) {
570
857
  if (!value || typeof value !== "string") {
571
- logger$8.error(`--target-pane is required for ${commandName} command`);
858
+ logger$10.error(`--target-pane is required for ${commandName} command`);
572
859
  process.exit(1);
573
860
  }
574
861
  return value;
@@ -581,7 +868,7 @@ function normalizeTargetPanes(value) {
581
868
 
582
869
  //#endregion
583
870
  //#region src/modes/collect.ts
584
- const logger$7 = getLogger(["editprompt", "collect"]);
871
+ const logger$9 = getLogger(["editprompt", "collect"]);
585
872
  const SUPPORTED_OUTPUTS = ["buffer", "stdout"];
586
873
  async function readStdin() {
587
874
  return new Promise((resolve, reject) => {
@@ -605,7 +892,7 @@ function normalizeCollectOutputs(value) {
605
892
  const uniqueOutputs = [...new Set(outputs)];
606
893
  const invalid = uniqueOutputs.filter((v) => !SUPPORTED_OUTPUTS.includes(v));
607
894
  if (invalid.length > 0) {
608
- 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(", ")}`);
609
896
  process.exit(1);
610
897
  }
611
898
  return uniqueOutputs;
@@ -616,12 +903,12 @@ async function runCollectMode(mux, targetPaneId, rawContent, outputs = ["buffer"
616
903
  if (rawContent !== void 0) selection = rawContent;
617
904
  else selection = await readStdin();
618
905
  const processedText = processQuoteText(selection, { withQuote });
619
- for (const output of outputs) if (output === "buffer") {
620
- if (mux === "tmux") await appendToQuoteVariable(targetPaneId, processedText);
621
- else if (mux === "wezterm") await appendToQuoteText(targetPaneId, processedText);
622
- } 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);
623
910
  } catch (error) {
624
- logger$7.error(`${error instanceof Error ? error.message : "Unknown error"}`);
911
+ logger$9.error(`${error instanceof Error ? error.message : "Unknown error"}`);
625
912
  process.exit(1);
626
913
  }
627
914
  }
@@ -648,10 +935,10 @@ const collectCommand = define({
648
935
  const outputs = normalizeCollectOutputs(ctx.values.output);
649
936
  const withQuote = !ctx.values["no-quote"];
650
937
  let rawContent;
651
- if (mux === "wezterm") {
938
+ if (mux === "wezterm" || mux === "herdr") {
652
939
  rawContent = extractRawContent(ctx.rest, ctx.positionals);
653
940
  if (rawContent === void 0) {
654
- 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>"`);
655
942
  process.exit(1);
656
943
  }
657
944
  }
@@ -661,11 +948,8 @@ const collectCommand = define({
661
948
 
662
949
  //#endregion
663
950
  //#region src/utils/sendConfig.ts
664
- const VALID_MUX_TYPES = ["tmux", "wezterm"];
665
951
  function readSendConfig() {
666
- const muxValue = process.env.EDITPROMPT_MUX || "tmux";
667
- if (!VALID_MUX_TYPES.includes(muxValue)) throw new Error(`Invalid EDITPROMPT_MUX value: ${muxValue}. Must be one of: ${VALID_MUX_TYPES.join(", ")}`);
668
- const mux = muxValue;
952
+ const mux = resolveMux();
669
953
  const alwaysCopy = process.env.EDITPROMPT_ALWAYS_COPY === "1";
670
954
  const delayValue = process.env.EDITPROMPT_SEND_KEY_DELAY;
671
955
  const parsedDelay = delayValue ? Number.parseInt(delayValue, 10) : NaN;
@@ -678,7 +962,7 @@ function readSendConfig() {
678
962
 
679
963
  //#endregion
680
964
  //#region src/modes/dump.ts
681
- const logger$6 = getLogger(["editprompt", "dump"]);
965
+ const logger$8 = getLogger(["editprompt", "dump"]);
682
966
  async function runDumpMode() {
683
967
  try {
684
968
  const config = readSendConfig();
@@ -687,19 +971,23 @@ async function runDumpMode() {
687
971
  if (config.mux === "tmux") {
688
972
  currentPaneId = await getCurrentPaneId$1();
689
973
  isEditor = await isEditorPane(currentPaneId);
690
- } else {
974
+ } else if (config.mux === "wezterm") {
691
975
  currentPaneId = await getCurrentPaneId();
692
976
  isEditor = isEditorPaneFromConf(currentPaneId);
977
+ } else {
978
+ currentPaneId = await getCurrentPaneId$2();
979
+ isEditor = isEditorPaneFromConf$1(currentPaneId);
693
980
  }
694
981
  if (!isEditor) {
695
- logger$6.error("Current pane is not an editor pane");
982
+ logger$8.error("Current pane is not an editor pane");
696
983
  process.exit(1);
697
984
  }
698
985
  let targetPanes;
699
986
  if (config.mux === "tmux") targetPanes = await getTargetPaneIds$1(currentPaneId);
700
- else targetPanes = await getTargetPaneIds(currentPaneId);
987
+ else if (config.mux === "wezterm") targetPanes = await getTargetPaneIds(currentPaneId);
988
+ else targetPanes = await getTargetPaneIds$2(currentPaneId);
701
989
  if (targetPanes.length === 0) {
702
- logger$6.error("No target panes registered for this editor pane");
990
+ logger$8.error("No target panes registered for this editor pane");
703
991
  process.exit(1);
704
992
  }
705
993
  const quoteContents = [];
@@ -708,9 +996,12 @@ async function runDumpMode() {
708
996
  if (config.mux === "tmux") {
709
997
  content = await getQuoteVariableContent(targetPane);
710
998
  await clearQuoteVariable(targetPane);
711
- } else {
999
+ } else if (config.mux === "wezterm") {
712
1000
  content = await getQuoteText(targetPane);
713
1001
  await clearQuoteText(targetPane);
1002
+ } else {
1003
+ content = await getQuoteText$1(targetPane);
1004
+ await clearQuoteText$1(targetPane);
714
1005
  }
715
1006
  if (content.trim() !== "") quoteContents.push(content);
716
1007
  }
@@ -718,7 +1009,7 @@ async function runDumpMode() {
718
1009
  process.stdout.write(combinedContent.replace(/\n{3,}$/, "\n\n"));
719
1010
  process.exit(0);
720
1011
  } catch (error) {
721
- logger$6.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1012
+ logger$8.error(`${error instanceof Error ? error.message : "Unknown error"}`);
722
1013
  process.exit(1);
723
1014
  }
724
1015
  }
@@ -748,13 +1039,87 @@ function processContent(content) {
748
1039
  return processed;
749
1040
  }
750
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
+
751
1116
  //#endregion
752
1117
  //#region src/modes/input.ts
753
- const logger$5 = getLogger(["editprompt", "input"]);
1118
+ const logger$6 = getLogger(["editprompt", "input"]);
754
1119
  async function runInputMode(rawContent, autoSend, sendKey, sendKeyDelay) {
755
1120
  const content = processContent(rawContent);
756
1121
  if (!content) {
757
- logger$5.info("No content to send. Exiting.");
1122
+ logger$6.info("No content to send. Exiting.");
758
1123
  return;
759
1124
  }
760
1125
  const config = readSendConfig();
@@ -763,44 +1128,51 @@ async function runInputMode(rawContent, autoSend, sendKey, sendKeyDelay) {
763
1128
  if (config.mux === "tmux") {
764
1129
  currentPaneId = await getCurrentPaneId$1();
765
1130
  isEditor = await isEditorPane(currentPaneId);
766
- } else {
1131
+ } else if (config.mux === "wezterm") {
767
1132
  currentPaneId = await getCurrentPaneId();
768
1133
  isEditor = isEditorPaneFromConf(currentPaneId);
1134
+ } else {
1135
+ currentPaneId = await getCurrentPaneId$2();
1136
+ isEditor = isEditorPaneFromConf$1(currentPaneId);
769
1137
  }
770
1138
  if (!isEditor) {
771
- logger$5.error("Current pane is not an editor pane");
1139
+ logger$6.error("Current pane is not an editor pane");
772
1140
  process.exit(1);
773
1141
  }
774
1142
  let targetPanes;
775
1143
  if (config.mux === "tmux") targetPanes = await getTargetPaneIds$1(currentPaneId);
776
- else targetPanes = await getTargetPaneIds(currentPaneId);
1144
+ else if (config.mux === "wezterm") targetPanes = await getTargetPaneIds(currentPaneId);
1145
+ else targetPanes = await getTargetPaneIds$2(currentPaneId);
777
1146
  if (targetPanes.length === 0) {
778
- logger$5.error("No target panes registered for this editor pane");
1147
+ logger$6.error("No target panes registered for this editor pane");
779
1148
  process.exit(1);
780
1149
  }
781
1150
  if (autoSend) {
782
- const key = sendKey || (config.mux === "wezterm" ? "\\r" : "C-m");
1151
+ const key = sendKey || (config.mux === "wezterm" ? "\\r" : config.mux === "herdr" ? "enter" : "C-m");
783
1152
  const delay = /\.(png|webp|avif|jpe?g|gif)\b/i.test(content) ? sendKeyDelay ?? 1e3 : 200;
784
1153
  let successCount = 0;
785
1154
  for (const targetPane of targetPanes) try {
786
1155
  if (config.mux === "wezterm") {
787
1156
  await inputToWeztermPane(targetPane, content);
788
1157
  await sendKeyToWeztermPane(targetPane, key, delay);
1158
+ } else if (config.mux === "herdr") {
1159
+ await inputToHerdrPane(targetPane, content);
1160
+ await sendKeyToHerdrPane(targetPane, key, delay);
789
1161
  } else {
790
1162
  await inputToTmuxPane(targetPane, content);
791
1163
  await sendKeyToTmuxPane(targetPane, key, delay);
792
1164
  }
793
1165
  successCount++;
794
1166
  } catch (error) {
795
- 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"}`);
796
1168
  }
797
1169
  if (config.alwaysCopy) {
798
1170
  await copyToClipboard(content);
799
- logger$5.info("Also copied to clipboard.");
1171
+ logger$6.info("Also copied to clipboard.");
800
1172
  }
801
- if (successCount > 0) logger$5.info("Content sent and submitted successfully!");
1173
+ if (successCount > 0) logger$6.info("Content sent and submitted successfully!");
802
1174
  else {
803
- logger$5.error("All target panes failed to receive content");
1175
+ logger$6.error("All target panes failed to receive content");
804
1176
  process.exit(1);
805
1177
  }
806
1178
  return;
@@ -809,12 +1181,12 @@ async function runInputMode(rawContent, autoSend, sendKey, sendKeyDelay) {
809
1181
  const result = await handleContentDelivery(content, config.mux, targetPanes);
810
1182
  if (config.alwaysCopy && !result.allFailed) {
811
1183
  await copyToClipboard(content);
812
- logger$5.info("Also copied to clipboard.");
1184
+ logger$6.info("Also copied to clipboard.");
813
1185
  }
814
1186
  if (result.successCount > 0) await focusFirstSuccessPane(config.mux, targetPanes, result.failedPanes);
815
1187
  if (result.allFailed) process.exit(1);
816
1188
  } catch (error) {
817
- logger$5.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1189
+ logger$6.error(`${error instanceof Error ? error.message : "Unknown error"}`);
818
1190
  process.exit(1);
819
1191
  }
820
1192
  }
@@ -842,13 +1214,13 @@ const inputCommand = define({
842
1214
  });
843
1215
  const rawContent = extractRawContent(ctx.rest, ctx.positionals);
844
1216
  if (rawContent === void 0) {
845
- logger$5.error("Content is required for input command");
846
- logger$5.error("Usage: editprompt input \"your content\"");
847
- 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\"");
848
1220
  process.exit(1);
849
1221
  }
850
1222
  if (ctx.values["send-key"] && !ctx.values["auto-send"]) {
851
- logger$5.error("--send-key requires --auto-send to be enabled");
1223
+ logger$6.error("--send-key requires --auto-send to be enabled");
852
1224
  process.exit(1);
853
1225
  }
854
1226
  const config = readSendConfig();
@@ -856,12 +1228,6 @@ const inputCommand = define({
856
1228
  }
857
1229
  });
858
1230
 
859
- //#endregion
860
- //#region src/config/constants.ts
861
- const TEMP_FILE_PREFIX = ".editprompt-";
862
- const TEMP_FILE_EXTENSION = ".md";
863
- const DEFAULT_EDITOR = "vim";
864
-
865
1231
  //#endregion
866
1232
  //#region src/utils/envParser.ts
867
1233
  /**
@@ -948,8 +1314,9 @@ async function openEditorAndGetContent(editorOption, envVars, sendConfig) {
948
1314
 
949
1315
  //#endregion
950
1316
  //#region src/modes/openEditor.ts
951
- const logger$4 = getLogger(["editprompt", "open"]);
1317
+ const logger$5 = getLogger(["editprompt", "open"]);
952
1318
  async function runOpenEditorMode(options) {
1319
+ let herdrEditorPaneId;
953
1320
  if (options.targetPanes.length > 0 && options.mux === "tmux") try {
954
1321
  await markAsEditorPane$1(await getCurrentPaneId$1(), options.targetPanes);
955
1322
  } catch {}
@@ -957,16 +1324,21 @@ async function runOpenEditorMode(options) {
957
1324
  const currentPaneId = await getCurrentPaneId();
958
1325
  await markAsEditorPane(currentPaneId, options.targetPanes);
959
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 {}
960
1332
  try {
961
1333
  const sendConfig = {
962
1334
  mux: options.mux,
963
1335
  alwaysCopy: options.alwaysCopy,
964
1336
  sendKeyDelay: Number.parseInt(process.env.EDITPROMPT_SEND_KEY_DELAY || "", 10) || 1e3
965
1337
  };
966
- logger$4.info("Opening editor...");
1338
+ logger$5.info("Opening editor...");
967
1339
  const content = await openEditorAndGetContent(options.editor, options.env, sendConfig);
968
1340
  if (!content) {
969
- logger$4.info("No content entered. Exiting.");
1341
+ logger$5.info("No content entered. Exiting.");
970
1342
  return;
971
1343
  }
972
1344
  try {
@@ -975,12 +1347,12 @@ async function runOpenEditorMode(options) {
975
1347
  console.log(content);
976
1348
  if (options.alwaysCopy && !result.allFailed) {
977
1349
  await copyToClipboard(content);
978
- logger$4.info("Also copied to clipboard.");
1350
+ logger$5.info("Also copied to clipboard.");
979
1351
  }
980
1352
  if (options.targetPanes.length > 0 && result.successCount > 0) await focusFirstSuccessPane(options.mux, options.targetPanes, result.failedPanes);
981
1353
  if (!result.allSuccess) process.exit(1);
982
1354
  } catch (error) {
983
- logger$4.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1355
+ logger$5.error(`${error instanceof Error ? error.message : "Unknown error"}`);
984
1356
  process.exit(1);
985
1357
  }
986
1358
  } finally {
@@ -990,6 +1362,9 @@ async function runOpenEditorMode(options) {
990
1362
  else if (options.targetPanes.length > 0 && options.mux === "wezterm") try {
991
1363
  for (const targetPane of options.targetPanes) await clearEditorPaneId(targetPane);
992
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 {}
993
1368
  }
994
1369
  }
995
1370
  const openCommand = define({
@@ -1003,12 +1378,7 @@ const openCommand = define({
1003
1378
  "log-file": ARG_LOG_FILE,
1004
1379
  quiet: ARG_QUIET,
1005
1380
  verbose: ARG_VERBOSE,
1006
- env: {
1007
- short: "E",
1008
- description: "Environment variables to set (e.g., KEY=VALUE)",
1009
- type: "string",
1010
- multiple: true
1011
- }
1381
+ env: ARG_ENV
1012
1382
  },
1013
1383
  async run(ctx) {
1014
1384
  setupLogger({
@@ -1028,10 +1398,10 @@ const openCommand = define({
1028
1398
 
1029
1399
  //#endregion
1030
1400
  //#region src/modes/register.ts
1031
- const logger$3 = getLogger(["editprompt", "register"]);
1401
+ const logger$4 = getLogger(["editprompt", "register"]);
1032
1402
  async function runRegisterMode(options) {
1033
1403
  if (options.targetPanes.length === 0) {
1034
- logger$3.error("--target-pane is required for register command");
1404
+ logger$4.error("--target-pane is required for register command");
1035
1405
  process.exit(1);
1036
1406
  }
1037
1407
  let editorPaneId;
@@ -1039,29 +1409,37 @@ async function runRegisterMode(options) {
1039
1409
  else if (options.mux === "tmux") {
1040
1410
  editorPaneId = await getCurrentPaneId$1();
1041
1411
  if (!await isEditorPane(editorPaneId)) {
1042
- 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.");
1043
1413
  process.exit(1);
1044
1414
  }
1045
1415
  } else if (options.mux === "wezterm") {
1046
1416
  editorPaneId = await getCurrentPaneId();
1047
1417
  if (!isEditorPaneFromConf(editorPaneId)) {
1048
- 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.");
1049
1425
  process.exit(1);
1050
1426
  }
1051
1427
  } else {
1052
- logger$3.error("Unsupported multiplexer");
1428
+ logger$4.error("Unsupported multiplexer");
1053
1429
  process.exit(1);
1054
1430
  }
1055
1431
  try {
1056
1432
  let existingPanes = [];
1057
1433
  if (options.mux === "tmux") existingPanes = await getTargetPaneIds$1(editorPaneId);
1058
1434
  else if (options.mux === "wezterm") existingPanes = await getTargetPaneIds(editorPaneId);
1435
+ else if (options.mux === "herdr") existingPanes = await getTargetPaneIds$2(editorPaneId);
1059
1436
  const mergedTargetPanes = [...new Set([...existingPanes, ...options.targetPanes])];
1060
1437
  if (options.mux === "tmux") await markAsEditorPane$1(editorPaneId, mergedTargetPanes);
1061
1438
  else if (options.mux === "wezterm") await markAsEditorPane(editorPaneId, mergedTargetPanes);
1062
- 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(", ")}`);
1063
1441
  } catch (error) {
1064
- logger$3.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1442
+ logger$4.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1065
1443
  process.exit(1);
1066
1444
  }
1067
1445
  }
@@ -1096,15 +1474,21 @@ const registerCommand = define({
1096
1474
 
1097
1475
  //#endregion
1098
1476
  //#region src/modes/resume.ts
1099
- const logger$2 = getLogger(["editprompt", "resume"]);
1477
+ const logger$3 = getLogger(["editprompt", "resume"]);
1100
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
+ }
1101
1485
  if (mux === "wezterm") {
1102
1486
  const currentPaneId = await getCurrentPaneId();
1103
1487
  if (isEditorPaneFromConf(currentPaneId)) {
1104
- logger$2.debug("Current pane is an editor pane");
1488
+ logger$3.debug("Current pane is an editor pane");
1105
1489
  const originalTargetPaneIds = await getTargetPaneIds(currentPaneId);
1106
1490
  if (originalTargetPaneIds.length === 0) {
1107
- logger$2.debug("No target pane IDs found for editor pane");
1491
+ logger$3.debug("No target pane IDs found for editor pane");
1108
1492
  process.exit(1);
1109
1493
  }
1110
1494
  let focused = false;
@@ -1114,20 +1498,20 @@ async function runResumeMode(targetPane, mux) {
1114
1498
  break;
1115
1499
  }
1116
1500
  if (!focused) {
1117
- logger$2.debug("All target panes do not exist");
1501
+ logger$3.debug("All target panes do not exist");
1118
1502
  process.exit(1);
1119
1503
  }
1120
1504
  process.exit(0);
1121
1505
  }
1122
- logger$2.debug("Current pane is not an editor pane");
1506
+ logger$3.debug("Current pane is not an editor pane");
1123
1507
  const editorPaneId = await getEditorPaneId(targetPane);
1124
- logger$2.debug("wezterm editorPaneId: {editorPaneId}", { editorPaneId });
1508
+ logger$3.debug("wezterm editorPaneId: {editorPaneId}", { editorPaneId });
1125
1509
  if (editorPaneId === "") {
1126
- logger$2.debug("Editor pane ID not found");
1510
+ logger$3.debug("Editor pane ID not found");
1127
1511
  process.exit(1);
1128
1512
  }
1129
1513
  if (!await checkPaneExists(editorPaneId)) {
1130
- logger$2.debug("Editor pane does not exist");
1514
+ logger$3.debug("Editor pane does not exist");
1131
1515
  await clearEditorPaneId(targetPane);
1132
1516
  process.exit(1);
1133
1517
  }
@@ -1135,7 +1519,7 @@ async function runResumeMode(targetPane, mux) {
1135
1519
  await focusPane(editorPaneId);
1136
1520
  process.exit(0);
1137
1521
  } catch (error) {
1138
- logger$2.debug("Can't focus editorPaneId: {editorPaneId}, error: {error}", {
1522
+ logger$3.debug("Can't focus editorPaneId: {editorPaneId}, error: {error}", {
1139
1523
  editorPaneId,
1140
1524
  error
1141
1525
  });
@@ -1186,10 +1570,10 @@ const resumeCommand = define({
1186
1570
 
1187
1571
  //#endregion
1188
1572
  //#region src/modes/press.ts
1189
- const logger$1 = getLogger(["editprompt", "press"]);
1573
+ const logger$2 = getLogger(["editprompt", "press"]);
1190
1574
  async function runPressMode(key, delay = 0) {
1191
1575
  if (!key) {
1192
- logger$1.error("Key is required");
1576
+ logger$2.error("Key is required");
1193
1577
  process.exit(1);
1194
1578
  }
1195
1579
  const config = readSendConfig();
@@ -1198,32 +1582,37 @@ async function runPressMode(key, delay = 0) {
1198
1582
  if (config.mux === "tmux") {
1199
1583
  currentPaneId = await getCurrentPaneId$1();
1200
1584
  isEditor = await isEditorPane(currentPaneId);
1201
- } else {
1585
+ } else if (config.mux === "wezterm") {
1202
1586
  currentPaneId = await getCurrentPaneId();
1203
1587
  isEditor = isEditorPaneFromConf(currentPaneId);
1588
+ } else {
1589
+ currentPaneId = await getCurrentPaneId$2();
1590
+ isEditor = isEditorPaneFromConf$1(currentPaneId);
1204
1591
  }
1205
1592
  if (!isEditor) {
1206
- logger$1.error("Current pane is not an editor pane");
1593
+ logger$2.error("Current pane is not an editor pane");
1207
1594
  process.exit(1);
1208
1595
  }
1209
1596
  let targetPanes;
1210
1597
  if (config.mux === "tmux") targetPanes = await getTargetPaneIds$1(currentPaneId);
1211
- else targetPanes = await getTargetPaneIds(currentPaneId);
1598
+ else if (config.mux === "wezterm") targetPanes = await getTargetPaneIds(currentPaneId);
1599
+ else targetPanes = await getTargetPaneIds$2(currentPaneId);
1212
1600
  if (targetPanes.length === 0) {
1213
- logger$1.error("No target panes registered for this editor pane");
1601
+ logger$2.error("No target panes registered for this editor pane");
1214
1602
  process.exit(1);
1215
1603
  }
1216
1604
  let successCount = 0;
1217
1605
  for (const targetPane of targetPanes) try {
1218
1606
  if (config.mux === "wezterm") await sendKeyToWeztermPane(targetPane, key, delay);
1607
+ else if (config.mux === "herdr") await sendKeyToHerdrPane(targetPane, key, delay);
1219
1608
  else await sendKeyToTmuxPane(targetPane, key, delay);
1220
1609
  successCount++;
1221
1610
  } catch (error) {
1222
- 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"}`);
1223
1612
  }
1224
- if (successCount > 0) logger$1.info("Key sent successfully!");
1613
+ if (successCount > 0) logger$2.info("Key sent successfully!");
1225
1614
  else {
1226
- logger$1.error("All target panes failed to receive key");
1615
+ logger$2.error("All target panes failed to receive key");
1227
1616
  process.exit(1);
1228
1617
  }
1229
1618
  }
@@ -1247,8 +1636,8 @@ const pressCommand = define({
1247
1636
  });
1248
1637
  const key = extractRawContent(ctx.rest, ctx.positionals);
1249
1638
  if (key === void 0 || key === "") {
1250
- logger$1.error("Key is required for press command");
1251
- 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\"");
1252
1641
  process.exit(1);
1253
1642
  }
1254
1643
  await runPressMode(key, ctx.values.delay ? Number(ctx.values.delay) : 0);
@@ -1257,9 +1646,9 @@ const pressCommand = define({
1257
1646
 
1258
1647
  //#endregion
1259
1648
  //#region src/modes/stash.ts
1260
- const logger = getLogger(["editprompt", "stash"]);
1649
+ const logger$1 = getLogger(["editprompt", "stash"]);
1261
1650
  function getStashKey(mux, targetPaneId) {
1262
- return `${mux}.targetPane.pane_${targetPaneId}.stash`;
1651
+ return `${mux === "herdr" ? getStorageNamespace() : mux}.targetPane.pane_${targetPaneId}.stash`;
1263
1652
  }
1264
1653
  async function pushStash(mux, targetPaneId, content) {
1265
1654
  const key = (/* @__PURE__ */ new Date()).toISOString();
@@ -1306,19 +1695,23 @@ async function getTargetPaneForStash() {
1306
1695
  if (config.mux === "tmux") {
1307
1696
  currentPaneId = await getCurrentPaneId$1();
1308
1697
  isEditor = await isEditorPane(currentPaneId);
1309
- } else {
1698
+ } else if (config.mux === "wezterm") {
1310
1699
  currentPaneId = await getCurrentPaneId();
1311
1700
  isEditor = isEditorPaneFromConf(currentPaneId);
1701
+ } else {
1702
+ currentPaneId = await getCurrentPaneId$2();
1703
+ isEditor = isEditorPaneFromConf$1(currentPaneId);
1312
1704
  }
1313
1705
  if (!isEditor) {
1314
- logger.error("Current pane is not an editor pane");
1706
+ logger$1.error("Current pane is not an editor pane");
1315
1707
  process.exit(1);
1316
1708
  }
1317
1709
  let targetPanes;
1318
1710
  if (config.mux === "tmux") targetPanes = await getTargetPaneIds$1(currentPaneId);
1319
- else targetPanes = await getTargetPaneIds(currentPaneId);
1711
+ else if (config.mux === "wezterm") targetPanes = await getTargetPaneIds(currentPaneId);
1712
+ else targetPanes = await getTargetPaneIds$2(currentPaneId);
1320
1713
  if (targetPanes.length === 0) {
1321
- logger.error("No target panes registered for this editor pane");
1714
+ logger$1.error("No target panes registered for this editor pane");
1322
1715
  process.exit(1);
1323
1716
  }
1324
1717
  return {
@@ -1329,13 +1722,13 @@ async function getTargetPaneForStash() {
1329
1722
  async function runPush(rest, positionals) {
1330
1723
  const rawContent = extractRawContent(rest, positionals);
1331
1724
  if (rawContent === void 0 || rawContent.trim() === "") {
1332
- logger.error("Content is required for stash push");
1333
- 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\"");
1334
1727
  process.exit(1);
1335
1728
  }
1336
1729
  const { mux, targetPaneId } = await getTargetPaneForStash();
1337
1730
  const key = await pushStash(mux, targetPaneId, rawContent);
1338
- logger.info("Stashed with key: {key}", { key });
1731
+ logger$1.info("Stashed with key: {key}", { key });
1339
1732
  }
1340
1733
  async function runList() {
1341
1734
  const { mux, targetPaneId } = await getTargetPaneForStash();
@@ -1346,8 +1739,8 @@ async function runApply(key) {
1346
1739
  const { mux, targetPaneId } = await getTargetPaneForStash();
1347
1740
  const content = getStashContent(mux, targetPaneId, key);
1348
1741
  if (content === "") {
1349
- if (key) logger.error("No stash entry found with key: {key}", { key });
1350
- 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");
1351
1744
  process.exit(1);
1352
1745
  }
1353
1746
  process.stdout.write(content);
@@ -1355,18 +1748,18 @@ async function runApply(key) {
1355
1748
  async function runDrop(key) {
1356
1749
  const { mux, targetPaneId } = await getTargetPaneForStash();
1357
1750
  if (!dropStash(mux, targetPaneId, key)) {
1358
- if (key) logger.error("No stash entry found with key: {key}", { key });
1359
- 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");
1360
1753
  process.exit(1);
1361
1754
  }
1362
- logger.info("Stash entry dropped");
1755
+ logger$1.info("Stash entry dropped");
1363
1756
  }
1364
1757
  async function runPop(key) {
1365
1758
  const { mux, targetPaneId } = await getTargetPaneForStash();
1366
1759
  const content = getStashContent(mux, targetPaneId, key);
1367
1760
  if (content === "") {
1368
- if (key) logger.error("No stash entry found with key: {key}", { key });
1369
- 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");
1370
1763
  process.exit(1);
1371
1764
  }
1372
1765
  process.stdout.write(content);
@@ -1430,14 +1823,107 @@ const stashCommand = define({
1430
1823
  await runPop(parseKeyOption(subArgs));
1431
1824
  break;
1432
1825
  default:
1433
- logger.error("Unknown subcommand '{subcommand}'", { subcommand });
1434
- logger.error("");
1826
+ logger$1.error("Unknown subcommand '{subcommand}'", { subcommand });
1827
+ logger$1.error("");
1435
1828
  showHelp();
1436
1829
  process.exit(1);
1437
1830
  }
1438
1831
  }
1439
1832
  });
1440
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
+
1441
1927
  //#endregion
1442
1928
  //#region src/index.ts
1443
1929
  await cli(process.argv.slice(2), {
@@ -1466,6 +1952,7 @@ await cli(process.argv.slice(2), {
1466
1952
  open: openCommand,
1467
1953
  register: registerCommand,
1468
1954
  resume: resumeCommand,
1955
+ toggle: toggleCommand,
1469
1956
  input: inputCommand,
1470
1957
  press: pressCommand,
1471
1958
  collect: collectCommand,