editprompt 1.8.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +21 -4
  2. package/dist/index.js +1057 -978
  3. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,96 +1,411 @@
1
1
  #!/usr/bin/env node
2
2
  import { configureSync, getAnsiColorFormatter, getLogger, getTextFormatter, parseLogLevel, withFilter } from "@logtape/logtape";
3
3
  import { cli, define } from "gunshi";
4
- import { appendFileSync } from "node:fs";
4
+ import { appendFileSync, existsSync } from "node:fs";
5
+ import { homedir, tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+ import { exec, spawn } from "node:child_process";
8
+ import Conf from "conf";
5
9
  import { createHash, randomBytes } from "node:crypto";
6
10
  import { createConnection } from "node:net";
7
- import Conf from "conf";
8
- import { exec, spawn } from "node:child_process";
9
11
  import { promisify } from "node:util";
10
- import { join } from "node:path";
11
12
  import clipboardy from "clipboardy";
12
13
  import { mkdir, readFile, writeFile } from "node:fs/promises";
13
- import { tmpdir } from "node:os";
14
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
- };
15
+ //#region src/modules/conf.ts
16
+ const projectName = process.env.NODE_ENV === "test" ? "editprompt-test" : "editprompt";
17
+ const conf = new Conf({ projectName });
30
18
 
31
19
  //#endregion
32
- //#region src/modules/logger.ts
33
- function resolveLogFilePath(options) {
34
- if (options.logFile) return options.logFile;
35
- const envPath = process.env.EDITPROMPT_LOG_FILE;
36
- if (envPath) return envPath;
37
- }
38
- function resolveFileLogLevel(options) {
39
- if (options.verbose) return "debug";
40
- const envLevel = process.env.EDITPROMPT_LOG_LEVEL;
41
- if (envLevel) return parseLogLevel(envLevel);
42
- return "info";
20
+ //#region src/backends/stores.ts
21
+ const VAR_EDITOR_PANE = "editor_pane";
22
+ const VAR_IS_EDITOR = "is_editor";
23
+ const VAR_TARGET_PANES = "target_panes";
24
+ const VAR_QUOTE = "quote";
25
+ /**
26
+ * State kept in variables on the panes themselves (tmux pane options, niwaterm
27
+ * tab variables). Variable names are `<prefix>editor_pane`, `<prefix>is_editor`,
28
+ * `<prefix>target_panes` and `<prefix>quote`.
29
+ *
30
+ * The variables disappear with their pane, so forgetting an editor pane is a
31
+ * no-op here: only the link on the target pane is cleared.
32
+ */
33
+ function paneVariableStore(io, prefix) {
34
+ const name = (key) => `${prefix}${key}`;
35
+ const read = async (paneId, key) => (await io.get(paneId, name(key))).trim();
36
+ return {
37
+ getEditorPaneId: (targetPaneId) => read(targetPaneId, VAR_EDITOR_PANE),
38
+ setEditorPaneId: (targetPaneId, editorPaneId) => io.set(targetPaneId, name(VAR_EDITOR_PANE), editorPaneId),
39
+ removeEditorPaneId: (targetPaneId) => io.set(targetPaneId, name(VAR_EDITOR_PANE), ""),
40
+ isEditorPane: async (paneId) => await read(paneId, VAR_IS_EDITOR) === "1",
41
+ getTargetPaneIds: async (editorPaneId) => {
42
+ const value = await read(editorPaneId, VAR_TARGET_PANES);
43
+ if (value === "") return [];
44
+ return value.split(",").map((id) => id.trim());
45
+ },
46
+ setTargetPaneIds: async (editorPaneId, targetPaneIds) => {
47
+ await io.set(editorPaneId, name(VAR_IS_EDITOR), "1");
48
+ await io.set(editorPaneId, name(VAR_TARGET_PANES), targetPaneIds.join(","));
49
+ },
50
+ removeEditorPane: async () => {},
51
+ keepsEditorPaneMarks: true,
52
+ getQuote: (targetPaneId) => io.get(targetPaneId, name(VAR_QUOTE)),
53
+ setQuote: (targetPaneId, text) => io.set(targetPaneId, name(VAR_QUOTE), text),
54
+ removeQuote: (targetPaneId) => io.set(targetPaneId, name(VAR_QUOTE), "")
55
+ };
43
56
  }
44
- function resolveLogLevel(options) {
45
- if (options.quiet) return null;
46
- if (options.verbose) return "debug";
47
- const envLevel = process.env.EDITPROMPT_LOG_LEVEL;
48
- if (envLevel) return parseLogLevel(envLevel);
49
- return "info";
57
+ function readRecord(key) {
58
+ const data = conf.get(key);
59
+ return typeof data === "object" && data !== null && !Array.isArray(data) ? data : void 0;
50
60
  }
51
- function setupLogger(options = {}) {
52
- const stderrLevel = resolveLogLevel(options);
53
- const stderrFormatter = getAnsiColorFormatter({
54
- timestamp: "time-timezone",
55
- level: "ABBR"
61
+ function setField(key, field, value) {
62
+ conf.set(key, {
63
+ ...readRecord(key),
64
+ [field]: value
56
65
  });
57
- const rawStderrSink = (record) => {
58
- process.stderr.write(stderrFormatter(record));
66
+ }
67
+ function removeField(key, field) {
68
+ const data = readRecord(key);
69
+ if (!data || !(field in data)) return;
70
+ const { [field]: _, ...remaining } = data;
71
+ if (Object.keys(remaining).length === 0) conf.delete(key);
72
+ else conf.set(key, remaining);
73
+ }
74
+ /**
75
+ * State kept in editprompt's Conf storage, for backends whose panes cannot hold
76
+ * variables. The layout is:
77
+ *
78
+ * - `<namespace>.targetPane.pane_<id>`: `{ editorPaneId?, quote_text? }`
79
+ * - `<namespace>.editorPane.pane_<id>`: `{ targetPaneIds }`
80
+ *
81
+ * The namespace is resolved on every access so that it can depend on the
82
+ * environment (Herdr scopes it to the session socket).
83
+ */
84
+ function confStore(namespace, options = {}) {
85
+ const targetKey = (paneId) => `${namespace()}.targetPane.pane_${paneId}`;
86
+ const editorKey = (paneId) => `${namespace()}.editorPane.pane_${paneId}`;
87
+ const store = {
88
+ getEditorPaneId: async (targetPaneId) => {
89
+ const data = readRecord(targetKey(targetPaneId));
90
+ return data && "editorPaneId" in data ? String(data.editorPaneId) : "";
91
+ },
92
+ setEditorPaneId: async (targetPaneId, editorPaneId) => {
93
+ setField(targetKey(targetPaneId), "editorPaneId", editorPaneId);
94
+ },
95
+ removeEditorPaneId: async (targetPaneId) => {
96
+ removeField(targetKey(targetPaneId), "editorPaneId");
97
+ },
98
+ isEditorPane: async (paneId) => conf.has(editorKey(paneId)),
99
+ getTargetPaneIds: async (editorPaneId) => {
100
+ const targetPaneIds = readRecord(editorKey(editorPaneId))?.targetPaneIds;
101
+ return Array.isArray(targetPaneIds) ? targetPaneIds.map((id) => String(id)) : [];
102
+ },
103
+ setTargetPaneIds: async (editorPaneId, targetPaneIds) => {
104
+ conf.set(editorKey(editorPaneId), { targetPaneIds });
105
+ },
106
+ removeEditorPane: async (editorPaneId) => {
107
+ conf.delete(editorKey(editorPaneId));
108
+ },
109
+ getQuote: async (targetPaneId) => {
110
+ const data = readRecord(targetKey(targetPaneId));
111
+ return data && "quote_text" in data ? String(data.quote_text) : "";
112
+ },
113
+ setQuote: async (targetPaneId, text) => {
114
+ setField(targetKey(targetPaneId), "quote_text", text);
115
+ },
116
+ removeQuote: async (targetPaneId) => {
117
+ removeField(targetKey(targetPaneId), "quote_text");
118
+ }
59
119
  };
60
- const sinks = { stderr: stderrLevel === null ? () => {} : withFilter(rawStderrSink, stderrLevel) };
61
- const loggerSinks = ["stderr"];
62
- const logFilePath = resolveLogFilePath(options);
63
- const fileLogLevel = resolveFileLogLevel(options);
64
- if (logFilePath) try {
65
- appendFileSync(logFilePath, "");
66
- const fileFormatter = getTextFormatter({
67
- timestamp: "date-time-timezone",
68
- level: "ABBR"
69
- });
70
- const rawFileSink = (record) => {
71
- appendFileSync(logFilePath, fileFormatter(record));
120
+ if (!options.wholeTargetRecord) return store;
121
+ const logger = getLogger([...options.logCategory ?? ["editprompt", "state"]]);
122
+ return swallowErrors(logger, {
123
+ ...store,
124
+ setEditorPaneId: async (targetPaneId, editorPaneId) => {
125
+ logger.debug(`Saving editor pane ID to conf key: ${namespace()}.targetPane.pane_{targetPaneId}`, { targetPaneId });
126
+ conf.set(targetKey(targetPaneId), { editorPaneId });
127
+ },
128
+ removeEditorPaneId: async (targetPaneId) => {
129
+ conf.delete(targetKey(targetPaneId));
130
+ },
131
+ removeQuote: async (targetPaneId) => {
132
+ const key = `${targetKey(targetPaneId)}.quote_text`;
133
+ if (conf.has(key)) conf.delete(key);
134
+ }
135
+ });
136
+ }
137
+ const FAILURE_RESULTS = {
138
+ getEditorPaneId: "",
139
+ setEditorPaneId: void 0,
140
+ removeEditorPaneId: void 0,
141
+ isEditorPane: false,
142
+ getTargetPaneIds: [],
143
+ setTargetPaneIds: void 0,
144
+ removeEditorPane: void 0,
145
+ getQuote: "",
146
+ setQuote: void 0,
147
+ removeQuote: void 0
148
+ };
149
+ const LEGACY_METHOD_NAMES = {
150
+ getEditorPaneId: "getEditorPaneId",
151
+ setEditorPaneId: "saveEditorPaneId",
152
+ removeEditorPaneId: "clearEditorPaneId",
153
+ isEditorPane: "isEditorPaneFromConf",
154
+ getTargetPaneIds: "getTargetPaneIds",
155
+ setTargetPaneIds: "markAsEditorPane",
156
+ removeEditorPane: "clearEditorPaneId",
157
+ getQuote: "getQuoteText",
158
+ setQuote: "appendToQuoteText",
159
+ removeQuote: "clearQuoteText"
160
+ };
161
+ function swallowErrors(logger, store) {
162
+ const wrapped = {};
163
+ for (const method of Object.keys(FAILURE_RESULTS)) {
164
+ const call = store[method];
165
+ wrapped[method] = async (...args) => {
166
+ try {
167
+ return await call(...args);
168
+ } catch (error) {
169
+ logger.debug(`${LEGACY_METHOD_NAMES[method]} failed: {error}`, { error });
170
+ return FAILURE_RESULTS[method];
171
+ }
72
172
  };
73
- sinks.file = withFilter(rawFileSink, fileLogLevel);
74
- loggerSinks.push("file");
75
- } catch {
76
- process.stderr.write(`Warning: Cannot write to log file '${logFilePath}', continuing without file logging.\n`);
77
173
  }
78
- configureSync({
79
- sinks,
80
- loggers: [{
81
- category: ["editprompt"],
82
- sinks: loggerSinks,
83
- lowestLevel: "trace"
84
- }, {
85
- category: ["logtape", "meta"],
86
- lowestLevel: null
87
- }]
88
- });
174
+ return wrapped;
89
175
  }
90
176
 
91
177
  //#endregion
92
- //#region package.json
93
- var version = "1.8.1";
178
+ //#region src/backends/types.ts
179
+ function canSplitPane(backend) {
180
+ return typeof backend.splitPane === "function" && typeof backend.runInPane === "function" && typeof backend.closePane === "function";
181
+ }
182
+ /** Identity helper that gives backend definitions full type checking. */
183
+ function defineBackend(backend) {
184
+ return backend;
185
+ }
186
+
187
+ //#endregion
188
+ //#region src/backends/command.ts
189
+ const logger$16 = getLogger(["editprompt", "command-backend"]);
190
+ const COMMAND_VARIABLES = {
191
+ currentPane: [],
192
+ paneExists: ["EDITPROMPT_PANE"],
193
+ focus: ["EDITPROMPT_PANE"],
194
+ sendText: ["EDITPROMPT_PANE"],
195
+ sendKey: ["EDITPROMPT_PANE", "EDITPROMPT_KEY"],
196
+ splitPane: [
197
+ "EDITPROMPT_PANE",
198
+ "EDITPROMPT_ROWS",
199
+ "EDITPROMPT_CWD"
200
+ ],
201
+ runInPane: ["EDITPROMPT_PANE", "EDITPROMPT_COMMAND"],
202
+ closePane: ["EDITPROMPT_PANE"]
203
+ };
204
+ const COMMAND_VARIABLE_NAMES = new Set(Object.values(COMMAND_VARIABLES).flat());
205
+ const REQUIRED_COMMANDS = [
206
+ "paneExists",
207
+ "focus",
208
+ "sendText",
209
+ "sendKey"
210
+ ];
211
+ const OPTIONAL_COMMANDS = [
212
+ "splitPane",
213
+ "runInPane",
214
+ "closePane"
215
+ ];
216
+ const KNOWN_FIELDS = new Set([
217
+ "detect",
218
+ "currentPane",
219
+ ...REQUIRED_COMMANDS,
220
+ ...OPTIONAL_COMMANDS,
221
+ "maxChunkBytes",
222
+ "submitKey",
223
+ "quoteSeparator",
224
+ "collectInput"
225
+ ]);
226
+ const DEFAULT_MAX_CHUNK_BYTES = 8192;
227
+ const SHELL = "/bin/sh";
228
+ const VARIABLE_PATTERN = /\$\{?(EDITPROMPT_[A-Z_]+)/g;
229
+ var CommandBackendConfigError = class extends Error {
230
+ name = "CommandBackendConfigError";
231
+ };
232
+ function isRecord(value) {
233
+ return typeof value === "object" && value !== null && !Array.isArray(value);
234
+ }
235
+ function parseScript(value, path, field, errors) {
236
+ if (typeof value !== "string" || value.trim() === "") {
237
+ errors.push(`${path} must be a non-empty string`);
238
+ return;
239
+ }
240
+ const allowed = COMMAND_VARIABLES[field];
241
+ const reported = /* @__PURE__ */ new Set();
242
+ for (const [, name] of value.matchAll(VARIABLE_PATTERN)) {
243
+ const variable = name;
244
+ if (COMMAND_VARIABLE_NAMES.has(variable) && !allowed.includes(variable)) {
245
+ if (!reported.has(variable)) {
246
+ reported.add(variable);
247
+ const hint = allowed.length > 0 ? ` (available: ${allowed.map((n) => `$${n}`).join(", ")})` : "";
248
+ errors.push(`${path} uses $${variable}, which is not set for this command${hint}`);
249
+ }
250
+ }
251
+ }
252
+ return {
253
+ path,
254
+ source: value
255
+ };
256
+ }
257
+ function readEnvRef(value) {
258
+ if (isRecord(value) && Object.keys(value).length === 1 && typeof value.env === "string") return value.env;
259
+ }
260
+ function shellQuote(value) {
261
+ return `'${value.replace(/'/g, "'\\''")}'`;
262
+ }
263
+ function runScript(script, values, input) {
264
+ return new Promise((resolve, reject) => {
265
+ const child = spawn(SHELL, ["-c", script.source], {
266
+ env: {
267
+ ...process.env,
268
+ ...values
269
+ },
270
+ stdio: [
271
+ input === void 0 ? "ignore" : "pipe",
272
+ "pipe",
273
+ "pipe"
274
+ ]
275
+ });
276
+ let stdout = "";
277
+ let stderr = "";
278
+ child.stdout?.setEncoding("utf8");
279
+ child.stdout?.on("data", (chunk) => {
280
+ stdout += chunk;
281
+ });
282
+ child.stderr?.setEncoding("utf8");
283
+ child.stderr?.on("data", (chunk) => {
284
+ stderr += chunk;
285
+ });
286
+ child.on("error", (error) => {
287
+ reject(/* @__PURE__ */ new Error(`Failed to run ${script.path}: ${error.message}`));
288
+ });
289
+ child.on("close", (code) => {
290
+ resolve({
291
+ code,
292
+ stdout,
293
+ stderr
294
+ });
295
+ });
296
+ if (input !== void 0) {
297
+ child.stdin?.on("error", (error) => {
298
+ reject(/* @__PURE__ */ new Error(`Failed to write to ${script.path}: ${error.message}`));
299
+ });
300
+ child.stdin?.end(input);
301
+ }
302
+ });
303
+ }
304
+ async function runChecked(script, values, input) {
305
+ const result = await runScript(script, values, input);
306
+ if (result.code !== 0) throw new Error(`${script.path} exited with ${result.code}: ${result.stderr.trim()}`);
307
+ return result.stdout;
308
+ }
309
+ /**
310
+ * Validates a backend declared in the configuration file and builds it.
311
+ * Throws CommandBackendConfigError listing every problem found.
312
+ */
313
+ function createCommandBackend(name, spec) {
314
+ const path = `backends.${name}`;
315
+ const errors = [];
316
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(name)) errors.push(`${path}: the name may only contain letters, digits, "-" and "_"`);
317
+ if (!isRecord(spec)) throw new CommandBackendConfigError(`${path} must be an object`);
318
+ for (const key of Object.keys(spec)) if (!KNOWN_FIELDS.has(key)) errors.push(`${path}.${key} is not a known option`);
319
+ let detectEnv;
320
+ if (spec.detect !== void 0) {
321
+ detectEnv = readEnvRef(spec.detect);
322
+ if (detectEnv === void 0) errors.push(`${path}.detect must be { env = "VARIABLE_NAME" }`);
323
+ }
324
+ const currentPaneEnv = readEnvRef(spec.currentPane);
325
+ let currentPaneScript;
326
+ if (spec.currentPane === void 0) errors.push(`${path}.currentPane is required`);
327
+ else if (currentPaneEnv === void 0) currentPaneScript = parseScript(spec.currentPane, `${path}.currentPane`, "currentPane", errors);
328
+ const commands = {};
329
+ for (const field of REQUIRED_COMMANDS) if (spec[field] === void 0) errors.push(`${path}.${field} is required`);
330
+ else commands[field] = parseScript(spec[field], `${path}.${field}`, field, errors);
331
+ const optionalCount = OPTIONAL_COMMANDS.filter((field) => spec[field] !== void 0).length;
332
+ if (optionalCount > 0 && optionalCount < OPTIONAL_COMMANDS.length) errors.push(`${path}: splitPane, runInPane and closePane must be set together`);
333
+ for (const field of OPTIONAL_COMMANDS) if (spec[field] !== void 0) commands[field] = parseScript(spec[field], `${path}.${field}`, field, errors);
334
+ const maxChunkBytes = spec.maxChunkBytes ?? DEFAULT_MAX_CHUNK_BYTES;
335
+ if (typeof maxChunkBytes !== "number" || !Number.isInteger(maxChunkBytes) || maxChunkBytes < 4) errors.push(`${path}.maxChunkBytes must be an integer of at least 4`);
336
+ const submitKey = spec.submitKey ?? "Enter";
337
+ if (typeof submitKey !== "string" || submitKey === "") errors.push(`${path}.submitKey must be a non-empty string`);
338
+ const quoteSeparator = spec.quoteSeparator ?? "\n\n";
339
+ if (typeof quoteSeparator !== "string") errors.push(`${path}.quoteSeparator must be a string`);
340
+ const collectInput = spec.collectInput ?? "argument";
341
+ if (collectInput !== "stdin" && collectInput !== "argument") errors.push(`${path}.collectInput must be "stdin" or "argument"`);
342
+ if (errors.length > 0) throw new CommandBackendConfigError(errors.join("\n"));
343
+ const command = (field) => commands[field];
344
+ const namespace = () => `state.${name}`;
345
+ const backend = defineBackend({
346
+ name,
347
+ detect: detectEnv ? (env) => (env[detectEnv]?.trim() ?? "") !== "" : void 0,
348
+ getCurrentPaneId: async () => {
349
+ if (currentPaneEnv !== void 0) {
350
+ const paneId = process.env[currentPaneEnv]?.trim();
351
+ if (!paneId) throw new Error(`${currentPaneEnv} is not set`);
352
+ return paneId;
353
+ }
354
+ return (await runChecked(currentPaneScript, {})).trim();
355
+ },
356
+ paneExists: async (paneId) => {
357
+ try {
358
+ return (await runScript(command("paneExists"), { EDITPROMPT_PANE: paneId })).code === 0;
359
+ } catch (error) {
360
+ logger$16.debug("paneExists failed: {error}", { error });
361
+ return false;
362
+ }
363
+ },
364
+ focusPane: async (paneId) => {
365
+ await runChecked(command("focus"), { EDITPROMPT_PANE: paneId });
366
+ },
367
+ sendText: async (paneId, chunk) => {
368
+ await runChecked(command("sendText"), { EDITPROMPT_PANE: paneId }, chunk);
369
+ },
370
+ sendKey: async (paneId, key) => {
371
+ await runChecked(command("sendKey"), {
372
+ EDITPROMPT_PANE: paneId,
373
+ EDITPROMPT_KEY: key
374
+ });
375
+ },
376
+ maxChunkBytes,
377
+ defaultSubmitKey: submitKey,
378
+ quoteSeparator,
379
+ collectInput,
380
+ storageNamespace: namespace,
381
+ resumeAcceptsEditorPane: true,
382
+ keepNewerEditorLink: true,
383
+ quietResumeErrors: "all",
384
+ state: confStore(namespace)
385
+ });
386
+ if (commands.splitPane && commands.runInPane && commands.closePane) {
387
+ const { splitPane, runInPane, closePane } = commands;
388
+ backend.splitPane = async (targetPaneId, options) => {
389
+ const paneId = (await runChecked(splitPane, {
390
+ EDITPROMPT_PANE: targetPaneId,
391
+ EDITPROMPT_ROWS: String(options.rows),
392
+ EDITPROMPT_CWD: options.cwd ?? process.cwd()
393
+ })).trim();
394
+ if (paneId === "") throw new Error(`${path}.splitPane did not print the new pane ID`);
395
+ return paneId;
396
+ };
397
+ backend.runInPane = async (paneId, argv) => {
398
+ await runChecked(runInPane, {
399
+ EDITPROMPT_PANE: paneId,
400
+ EDITPROMPT_COMMAND: argv.map(shellQuote).join(" ")
401
+ });
402
+ };
403
+ backend.closePane = async (paneId) => {
404
+ await runChecked(closePane, { EDITPROMPT_PANE: paneId });
405
+ };
406
+ }
407
+ return backend;
408
+ }
94
409
 
95
410
  //#endregion
96
411
  //#region src/config/constants.ts
@@ -106,63 +421,8 @@ const NIWATERM_SEND_CHUNK_BYTES = WEZTERM_SEND_CHUNK_BYTES;
106
421
  const REX_CONTROL_LIMIT_BYTES = 64 * 1024;
107
422
  const REX_SEND_CHUNK_BYTES = REX_CONTROL_LIMIT_BYTES / 8;
108
423
 
109
- //#endregion
110
- //#region src/utils/contentChunker.ts
111
- /**
112
- * Split content into chunks not exceeding maxBytes (UTF-8 byte count),
113
- * never breaking a Unicode code point across chunks.
114
- *
115
- * Content shorter than maxBytes is returned as a single chunk, so short
116
- * input keeps the previous behavior of being sent in one shot.
117
- */
118
- function splitByByteSize(content, maxBytes) {
119
- if (content === "") return [];
120
- const chunks = [];
121
- let current = "";
122
- let currentBytes = 0;
123
- for (const char of content) {
124
- const charBytes = Buffer.byteLength(char, "utf8");
125
- if (current !== "" && currentBytes + charBytes > maxBytes) {
126
- chunks.push(current);
127
- current = "";
128
- currentBytes = 0;
129
- }
130
- current += char;
131
- currentBytes += charBytes;
132
- }
133
- if (current !== "") chunks.push(current);
134
- return chunks;
135
- }
136
-
137
- //#endregion
138
- //#region src/modules/conf.ts
139
- const projectName = process.env.NODE_ENV === "test" ? "editprompt-test" : "editprompt";
140
- const conf = new Conf({ projectName });
141
-
142
424
  //#endregion
143
425
  //#region src/modules/herdr.ts
144
- var herdr_exports = /* @__PURE__ */ __exportAll({
145
- appendToQuoteText: () => appendToQuoteText$2,
146
- calculateEditorSplitRatio: () => calculateEditorSplitRatio,
147
- checkPaneExists: () => checkPaneExists$4,
148
- clearEditorPaneId: () => clearEditorPaneId$4,
149
- clearQuoteText: () => clearQuoteText$2,
150
- closePane: () => closePane,
151
- focusPane: () => focusPane$4,
152
- getCurrentPaneId: () => getCurrentPaneId$4,
153
- getEditorPaneId: () => getEditorPaneId$4,
154
- getQuoteText: () => getQuoteText$2,
155
- getStorageNamespace: () => getStorageNamespace,
156
- getTargetPaneIds: () => getTargetPaneIds$4,
157
- inputToHerdrPane: () => inputToHerdrPane,
158
- isEditorPaneFromConf: () => isEditorPaneFromConf$2,
159
- markAsEditorPane: () => markAsEditorPane$4,
160
- resumeEditorPane: () => resumeEditorPane$1,
161
- runCommandInPane: () => runCommandInPane,
162
- saveEditorPaneId: () => saveEditorPaneId$4,
163
- sendKeyToHerdrPane: () => sendKeyToHerdrPane,
164
- splitEditorPane: () => splitEditorPane
165
- });
166
426
  const logger$15 = getLogger(["editprompt", "herdr"]);
167
427
  const HERDR_REQUEST_TIMEOUT_MS = 6e3;
168
428
  let requestSequence = 0;
@@ -244,12 +504,6 @@ async function sendRequest(method, params) {
244
504
  function getStorageNamespace() {
245
505
  return `herdr.session_${createHash("sha256").update(getSocketPath()).digest("hex").slice(0, 16)}`;
246
506
  }
247
- function targetPaneKey$1(paneId) {
248
- return `${getStorageNamespace()}.targetPane.pane_${paneId}`;
249
- }
250
- function editorPaneKey$1(paneId) {
251
- return `${getStorageNamespace()}.editorPane.pane_${paneId}`;
252
- }
253
507
  async function getCurrentPaneId$4() {
254
508
  const paneId = process.env.HERDR_PANE_ID?.trim() || process.env.HERDR_ACTIVE_PANE_ID?.trim();
255
509
  if (!paneId) throw new Error("HERDR_PANE_ID or HERDR_ACTIVE_PANE_ID is not set");
@@ -301,119 +555,69 @@ async function runCommandInPane(paneId, argv) {
301
555
  async function closePane(paneId) {
302
556
  await sendRequest("pane.close", { pane_id: paneId });
303
557
  }
304
- async function inputToHerdrPane(paneId, content) {
305
- const chunks = splitByByteSize(content, HERDR_SEND_CHUNK_BYTES);
306
- for (const chunk of chunks) await sendRequest("pane.send_text", {
558
+ async function sendTextChunk$4(paneId, chunk) {
559
+ await sendRequest("pane.send_text", {
307
560
  pane_id: paneId,
308
561
  text: chunk
309
562
  });
310
- logger$15.debug("Content sent to Herdr pane: {paneId} ({chunkCount} chunks)", {
311
- paneId,
312
- chunkCount: chunks.length
313
- });
314
563
  }
315
- async function sendKeyToHerdrPane(paneId, key, delay = 1e3) {
316
- await new Promise((resolve) => setTimeout(resolve, delay));
564
+ async function sendKey$4(paneId, key) {
317
565
  await sendRequest("pane.send_keys", {
318
566
  pane_id: paneId,
319
567
  keys: [key]
320
568
  });
321
569
  }
322
- async function saveEditorPaneId$4(targetPaneId, editorPaneId) {
323
- const data = conf.get(targetPaneKey$1(targetPaneId));
324
- const existing = typeof data === "object" && data !== null ? data : {};
325
- conf.set(targetPaneKey$1(targetPaneId), {
326
- ...existing,
327
- editorPaneId
570
+ function logContentSent$4(paneId, chunkCount) {
571
+ logger$15.debug("Content sent to Herdr pane: {paneId} ({chunkCount} chunks)", {
572
+ paneId,
573
+ chunkCount
328
574
  });
329
575
  }
330
- async function getEditorPaneId$4(targetPaneId) {
331
- const data = conf.get(targetPaneKey$1(targetPaneId));
332
- if (typeof data === "object" && data !== null && "editorPaneId" in data) return String(data.editorPaneId);
333
- return "";
334
- }
335
- async function clearEditorPaneId$4(targetPaneId, expectedEditorPaneId) {
336
- const editorPaneId = await getEditorPaneId$4(targetPaneId);
337
- if (expectedEditorPaneId && editorPaneId !== expectedEditorPaneId) {
338
- conf.delete(editorPaneKey$1(expectedEditorPaneId));
339
- return;
340
- }
341
- const data = conf.get(targetPaneKey$1(targetPaneId));
342
- if (typeof data === "object" && data !== null && "editorPaneId" in data) {
343
- const { editorPaneId: _, ...remaining } = data;
344
- if (Object.keys(remaining).length === 0) conf.delete(targetPaneKey$1(targetPaneId));
345
- else conf.set(targetPaneKey$1(targetPaneId), remaining);
346
- }
347
- if (editorPaneId) conf.delete(editorPaneKey$1(editorPaneId));
348
- }
349
- async function markAsEditorPane$4(editorPaneId, targetPaneIds) {
350
- const uniqueTargetPaneIds = [...new Set(targetPaneIds)];
351
- conf.set(editorPaneKey$1(editorPaneId), { targetPaneIds: uniqueTargetPaneIds });
352
- for (const targetPaneId of uniqueTargetPaneIds) await saveEditorPaneId$4(targetPaneId, editorPaneId);
353
- }
354
- async function getTargetPaneIds$4(editorPaneId) {
355
- const data = conf.get(editorPaneKey$1(editorPaneId));
356
- if (typeof data === "object" && data !== null && "targetPaneIds" in data) {
357
- const targetPaneIds = data.targetPaneIds;
358
- if (Array.isArray(targetPaneIds)) return targetPaneIds.map((id) => String(id));
359
- }
360
- return [];
361
- }
362
- function isEditorPaneFromConf$2(paneId) {
363
- return conf.has(editorPaneKey$1(paneId));
364
- }
365
- async function resumeEditorPane$1(targetPaneId) {
366
- const currentPaneId = await getCurrentPaneId$4();
367
- const editorPaneId = isEditorPaneFromConf$2(currentPaneId) ? currentPaneId : isEditorPaneFromConf$2(targetPaneId) ? targetPaneId : "";
368
- if (editorPaneId) {
369
- const targetPaneIds = await getTargetPaneIds$4(editorPaneId);
370
- for (const paneId of targetPaneIds) if (await checkPaneExists$4(paneId)) {
371
- await focusPane$4(paneId);
372
- return true;
373
- }
374
- return false;
375
- }
376
- const registeredEditorPaneId = await getEditorPaneId$4(targetPaneId);
377
- if (registeredEditorPaneId === "") return false;
378
- if (!await checkPaneExists$4(registeredEditorPaneId)) {
379
- await clearEditorPaneId$4(targetPaneId);
380
- return false;
381
- }
382
- await focusPane$4(registeredEditorPaneId);
383
- return true;
576
+
577
+ //#endregion
578
+ //#region src/backends/env.ts
579
+ function hasValue(value) {
580
+ return typeof value === "string" && value.trim() !== "";
384
581
  }
385
- async function appendToQuoteText$2(paneId, content) {
386
- const data = conf.get(targetPaneKey$1(paneId));
387
- const existing = typeof data === "object" && data !== null ? data : {};
388
- const existingQuoteText = "quote_text" in existing ? String(existing.quote_text) : "";
389
- const quoteText = existingQuoteText.trim() !== "" ? `${existingQuoteText}\n\n${content}` : content;
390
- conf.set(targetPaneKey$1(paneId), {
391
- ...existing,
392
- quote_text: quoteText
393
- });
582
+ function isHerdrEnvironment(env = process.env) {
583
+ return hasValue(env.HERDR_SOCKET_PATH) && (hasValue(env.HERDR_PANE_ID) || hasValue(env.HERDR_ACTIVE_PANE_ID));
394
584
  }
395
- async function getQuoteText$2(paneId) {
396
- const data = conf.get(targetPaneKey$1(paneId));
397
- if (typeof data === "object" && data !== null && "quote_text" in data) return String(data.quote_text);
398
- return "";
585
+ function isNiwatermEnvironment(env = process.env) {
586
+ return hasValue(env.NIWATERM_TAB_ID);
399
587
  }
400
- async function clearQuoteText$2(paneId) {
401
- const data = conf.get(targetPaneKey$1(paneId));
402
- if (typeof data === "object" && data !== null && "quote_text" in data) {
403
- const { quote_text: _, ...remaining } = data;
404
- if (Object.keys(remaining).length === 0) conf.delete(targetPaneKey$1(paneId));
405
- else conf.set(targetPaneKey$1(paneId), remaining);
406
- }
588
+ function isRexEnvironment(env = process.env) {
589
+ return hasValue(env.REX_BLOCK);
407
590
  }
408
591
 
592
+ //#endregion
593
+ //#region src/backends/herdr.ts
594
+ const herdrBackend = defineBackend({
595
+ name: "herdr",
596
+ detect: (env) => isHerdrEnvironment(env),
597
+ getCurrentPaneId: () => getCurrentPaneId$4(),
598
+ paneExists: (paneId) => checkPaneExists$4(paneId),
599
+ focusPane: (paneId) => focusPane$4(paneId),
600
+ sendText: (paneId, chunk) => sendTextChunk$4(paneId, chunk),
601
+ sendKey: (paneId, key) => sendKey$4(paneId, key),
602
+ logContentSent: (paneId, chunkCount) => logContentSent$4(paneId, chunkCount),
603
+ maxChunkBytes: HERDR_SEND_CHUNK_BYTES,
604
+ defaultSubmitKey: "enter",
605
+ quoteSeparator: "\n\n",
606
+ collectInput: "argument",
607
+ quietResumeErrors: "all",
608
+ resumeAcceptsEditorPane: true,
609
+ keepNewerEditorLink: true,
610
+ storageNamespace: () => getStorageNamespace(),
611
+ state: confStore(() => getStorageNamespace()),
612
+ splitPane: (targetPaneId, { rows, cwd, env }) => splitEditorPane(targetPaneId, rows, cwd ?? process.env.HERDR_ACTIVE_PANE_CWD, env),
613
+ runInPane: (paneId, argv) => runCommandInPane(paneId, argv),
614
+ closePane: (paneId) => closePane(paneId)
615
+ });
616
+
409
617
  //#endregion
410
618
  //#region src/modules/niwaterm.ts
411
619
  const execAsync$2 = promisify(exec);
412
620
  const logger$14 = getLogger(["editprompt", "niwaterm"]);
413
- const VAR_EDITOR_PANE = "editprompt_editor_pane";
414
- const VAR_IS_EDITOR = "editprompt_is_editor";
415
- const VAR_TARGET_PANES = "editprompt_target_panes";
416
- const VAR_QUOTE = "editprompt_quote";
417
621
  function quoteShellWord(value) {
418
622
  return `'${value.replace(/'/g, "'\\''")}'`;
419
623
  }
@@ -446,52 +650,43 @@ async function checkPaneExists$3(paneId) {
446
650
  async function focusPane$3(paneId) {
447
651
  await execAsync$2(`niwa tab focus -t ${quoteShellWord(paneId)}`);
448
652
  }
449
- async function saveEditorPaneId$3(targetPaneId, editorPaneId) {
450
- await setTabVar(targetPaneId, VAR_EDITOR_PANE, editorPaneId);
451
- }
452
- async function clearEditorPaneId$3(targetPaneId) {
453
- await setTabVar(targetPaneId, VAR_EDITOR_PANE, "");
454
- }
455
- async function getEditorPaneId$3(targetPaneId) {
456
- return getTabVar(targetPaneId, VAR_EDITOR_PANE);
457
- }
458
- async function markAsEditorPane$3(editorPaneId, targetPaneIds) {
459
- const uniqueTargetPaneIds = [...new Set(targetPaneIds)];
460
- await setTabVar(editorPaneId, VAR_IS_EDITOR, "1");
461
- await setTabVar(editorPaneId, VAR_TARGET_PANES, uniqueTargetPaneIds.join(","));
462
- for (const targetPaneId of uniqueTargetPaneIds) await saveEditorPaneId$3(targetPaneId, editorPaneId);
463
- }
464
- async function getTargetPaneIds$3(editorPaneId) {
465
- const value = await getTabVar(editorPaneId, VAR_TARGET_PANES);
466
- if (value === "") return [];
467
- return value.split(",").map((id) => id.trim());
468
- }
469
- async function isEditorPane$1(paneId) {
470
- return await getTabVar(paneId, VAR_IS_EDITOR) === "1";
471
- }
472
- async function getQuoteVariableContent$1(paneId) {
473
- return getTabVar(paneId, VAR_QUOTE);
474
- }
475
- async function appendToQuoteVariable$1(paneId, content) {
476
- const existingContent = await getQuoteVariableContent$1(paneId);
477
- await setTabVar(paneId, VAR_QUOTE, existingContent.trim() !== "" ? `${existingContent}\n${content}` : content);
478
- }
479
- async function clearQuoteVariable$1(paneId) {
480
- await setTabVar(paneId, VAR_QUOTE, "");
481
- }
482
- async function sendKeyToNiwatermPane(paneId, key, delay = 1e3) {
483
- await new Promise((resolve) => setTimeout(resolve, delay));
653
+ async function sendKey$3(paneId, key) {
484
654
  await execAsync$2(`niwa tab send-keys -t ${quoteShellWord(paneId)} ${quoteShellWord(key)}`);
485
655
  }
486
- async function inputToNiwatermPane(paneId, content) {
487
- const chunks = splitByByteSize(content, NIWATERM_SEND_CHUNK_BYTES);
488
- for (const chunk of chunks) await execAsync$2(`niwa tab send-keys -t ${quoteShellWord(paneId)} ${quoteShellWord(chunk)}`);
656
+ async function sendTextChunk$3(paneId, chunk) {
657
+ await execAsync$2(`niwa tab send-keys -t ${quoteShellWord(paneId)} -- ${quoteShellWord(chunk)}`);
658
+ }
659
+ function logContentSent$3(paneId, chunkCount) {
489
660
  logger$14.debug("Content sent to niwaterm pane: {paneId} ({chunks} chunk(s))", {
490
661
  paneId,
491
- chunks: chunks.length
662
+ chunks: chunkCount
492
663
  });
493
664
  }
494
665
 
666
+ //#endregion
667
+ //#region src/backends/niwaterm.ts
668
+ const niwatermBackend = defineBackend({
669
+ name: "niwaterm",
670
+ detect: (env) => isNiwatermEnvironment(env),
671
+ getCurrentPaneId: () => getCurrentPaneId$3(),
672
+ paneExists: (paneId) => checkPaneExists$3(paneId),
673
+ focusPane: (paneId) => focusPane$3(paneId),
674
+ sendText: (paneId, chunk) => sendTextChunk$3(paneId, chunk),
675
+ sendKey: (paneId, key) => sendKey$3(paneId, key),
676
+ logContentSent: (paneId, chunkCount) => logContentSent$3(paneId, chunkCount),
677
+ maxChunkBytes: NIWATERM_SEND_CHUNK_BYTES,
678
+ defaultSubmitKey: "C-m",
679
+ quoteSeparator: "\n",
680
+ collectInput: "argument",
681
+ quietResumeErrors: "editorFocus",
682
+ traceResume: true,
683
+ storageNamespace: () => "niwaterm",
684
+ state: paneVariableStore({
685
+ get: (paneId, name) => getTabVar(paneId, name),
686
+ set: (paneId, name, value) => setTabVar(paneId, name, value)
687
+ }, "editprompt_")
688
+ });
689
+
495
690
  //#endregion
496
691
  //#region src/modules/rex.ts
497
692
  const logger$13 = getLogger(["editprompt", "rex"]);
@@ -501,12 +696,6 @@ function rexCommand() {
501
696
  const binDir = process.env.REX_BIN_DIR?.trim();
502
697
  return binDir ? join(binDir, REX_COMMAND) : REX_COMMAND;
503
698
  }
504
- function targetPaneKey(paneId) {
505
- return `rex.targetPane.pane_${paneId}`;
506
- }
507
- function editorPaneKey(paneId) {
508
- return `rex.editorPane.pane_${paneId}`;
509
- }
510
699
  function rexArgs(args) {
511
700
  return [...REX_GLOBAL_ARGS, ...args];
512
701
  }
@@ -577,102 +766,14 @@ async function focusPane$2(paneId) {
577
766
  paneId
578
767
  ]);
579
768
  }
580
- async function saveEditorPaneId$2(targetPaneId, editorPaneId) {
581
- const data = conf.get(targetPaneKey(targetPaneId));
582
- const existing = typeof data === "object" && data !== null ? data : {};
583
- conf.set(targetPaneKey(targetPaneId), {
584
- ...existing,
585
- editorPaneId
586
- });
587
- }
588
- async function getEditorPaneId$2(targetPaneId) {
589
- const data = conf.get(targetPaneKey(targetPaneId));
590
- if (typeof data === "object" && data !== null && "editorPaneId" in data) return String(data.editorPaneId);
591
- return "";
592
- }
593
- async function clearEditorPaneId$2(targetPaneId) {
594
- const editorPaneId = await getEditorPaneId$2(targetPaneId);
595
- const data = conf.get(targetPaneKey(targetPaneId));
596
- if (typeof data === "object" && data !== null && "editorPaneId" in data) {
597
- const { editorPaneId: _, ...remaining } = data;
598
- if (Object.keys(remaining).length === 0) conf.delete(targetPaneKey(targetPaneId));
599
- else conf.set(targetPaneKey(targetPaneId), remaining);
600
- }
601
- if (editorPaneId) conf.delete(editorPaneKey(editorPaneId));
602
- }
603
- async function markAsEditorPane$2(editorPaneId, targetPaneIds) {
604
- const uniqueTargetPaneIds = [...new Set(targetPaneIds)];
605
- conf.set(editorPaneKey(editorPaneId), { targetPaneIds: uniqueTargetPaneIds });
606
- for (const targetPaneId of uniqueTargetPaneIds) await saveEditorPaneId$2(targetPaneId, editorPaneId);
607
- }
608
- async function getTargetPaneIds$2(editorPaneId) {
609
- const data = conf.get(editorPaneKey(editorPaneId));
610
- if (typeof data === "object" && data !== null && "targetPaneIds" in data) {
611
- const targetPaneIds = data.targetPaneIds;
612
- if (Array.isArray(targetPaneIds)) return targetPaneIds.map((id) => String(id));
613
- }
614
- return [];
615
- }
616
- function isEditorPaneFromConf$1(paneId) {
617
- return conf.has(editorPaneKey(paneId));
618
- }
619
- async function resumeEditorPane(targetPaneId) {
620
- const currentPaneId = await getCurrentPaneId$2();
621
- const editorPaneId = isEditorPaneFromConf$1(currentPaneId) ? currentPaneId : isEditorPaneFromConf$1(targetPaneId) ? targetPaneId : "";
622
- if (editorPaneId) {
623
- const targetPaneIds = await getTargetPaneIds$2(editorPaneId);
624
- for (const paneId of targetPaneIds) if (await checkPaneExists$2(paneId)) {
625
- await focusPane$2(paneId);
626
- return true;
627
- }
628
- return false;
629
- }
630
- const registeredEditorPaneId = await getEditorPaneId$2(targetPaneId);
631
- if (registeredEditorPaneId === "") return false;
632
- if (!await checkPaneExists$2(registeredEditorPaneId)) {
633
- await clearEditorPaneId$2(targetPaneId);
634
- return false;
635
- }
636
- await focusPane$2(registeredEditorPaneId);
637
- return true;
638
- }
639
- async function appendToQuoteText$1(paneId, content) {
640
- const data = conf.get(targetPaneKey(paneId));
641
- const existing = typeof data === "object" && data !== null ? data : {};
642
- const existingQuoteText = "quote_text" in existing ? String(existing.quote_text) : "";
643
- const quoteText = existingQuoteText.trim() !== "" ? `${existingQuoteText}\n\n${content}` : content;
644
- conf.set(targetPaneKey(paneId), {
645
- ...existing,
646
- quote_text: quoteText
647
- });
648
- }
649
- async function getQuoteText$1(paneId) {
650
- const data = conf.get(targetPaneKey(paneId));
651
- if (typeof data === "object" && data !== null && "quote_text" in data) return String(data.quote_text);
652
- return "";
653
- }
654
- async function clearQuoteText$1(paneId) {
655
- const data = conf.get(targetPaneKey(paneId));
656
- if (typeof data === "object" && data !== null && "quote_text" in data) {
657
- const { quote_text: _, ...remaining } = data;
658
- if (Object.keys(remaining).length === 0) conf.delete(targetPaneKey(paneId));
659
- else conf.set(targetPaneKey(paneId), remaining);
660
- }
661
- }
662
- async function inputToRexPane(paneId, content) {
663
- const chunks = splitByByteSize(content, REX_SEND_CHUNK_BYTES);
664
- for (const chunk of chunks) await runRex([
769
+ async function sendTextChunk$2(paneId, chunk) {
770
+ await runRex([
665
771
  "send",
666
772
  "-b",
667
773
  paneId
668
774
  ], chunk);
669
- logger$13.debug("Content sent to Rex pane: {paneId} ({chunks} chunk(s))", {
670
- paneId,
671
- chunks: chunks.length
672
- });
673
775
  }
674
- async function sendKeyToRexPane(paneId, key, delay = 1e3) {
675
- await new Promise((resolve) => setTimeout(resolve, delay));
776
+ async function sendKey$2(paneId, key) {
676
777
  await runRex([
677
778
  "send-key",
678
779
  "-b",
@@ -680,6 +781,33 @@ async function sendKeyToRexPane(paneId, key, delay = 1e3) {
680
781
  key
681
782
  ]);
682
783
  }
784
+ function logContentSent$2(paneId, chunkCount) {
785
+ logger$13.debug("Content sent to Rex pane: {paneId} ({chunks} chunk(s))", {
786
+ paneId,
787
+ chunks: chunkCount
788
+ });
789
+ }
790
+
791
+ //#endregion
792
+ //#region src/backends/rex.ts
793
+ const rexBackend = defineBackend({
794
+ name: "rex",
795
+ detect: (env) => isRexEnvironment(env),
796
+ getCurrentPaneId: () => getCurrentPaneId$2(),
797
+ paneExists: (paneId) => checkPaneExists$2(paneId),
798
+ focusPane: (paneId) => focusPane$2(paneId),
799
+ sendText: (paneId, chunk) => sendTextChunk$2(paneId, chunk),
800
+ sendKey: (paneId, key) => sendKey$2(paneId, key),
801
+ logContentSent: (paneId, chunkCount) => logContentSent$2(paneId, chunkCount),
802
+ maxChunkBytes: REX_SEND_CHUNK_BYTES,
803
+ defaultSubmitKey: "enter",
804
+ quoteSeparator: "\n\n",
805
+ collectInput: "argument",
806
+ quietResumeErrors: "all",
807
+ resumeAcceptsEditorPane: true,
808
+ storageNamespace: () => "rex",
809
+ state: confStore(() => "rex")
810
+ });
683
811
 
684
812
  //#endregion
685
813
  //#region src/modules/tmux.ts
@@ -691,20 +819,6 @@ async function getCurrentPaneId$1() {
691
819
  const { stdout } = await execAsync$1("tmux display-message -p \"#{pane_id}\"");
692
820
  return stdout.trim();
693
821
  }
694
- async function saveEditorPaneId$1(targetPaneId, editorPaneId) {
695
- await execAsync$1(`tmux set-option -pt '${targetPaneId}' @editprompt_editor_pane '${editorPaneId}'`);
696
- }
697
- async function clearEditorPaneId$1(targetPaneId) {
698
- await execAsync$1(`tmux set-option -pt '${targetPaneId}' @editprompt_editor_pane ""`);
699
- }
700
- async function getEditorPaneId$1(targetPaneId) {
701
- try {
702
- const { stdout } = await execAsync$1(`tmux show -pt '${targetPaneId}' -v @editprompt_editor_pane`);
703
- return stdout.trim();
704
- } catch {
705
- return "";
706
- }
707
- }
708
822
  async function checkPaneExists$1(paneId) {
709
823
  try {
710
824
  const { stdout } = await execAsync$1("tmux list-panes -a -F \"#{pane_id}\"");
@@ -716,62 +830,55 @@ async function checkPaneExists$1(paneId) {
716
830
  async function focusPane$1(paneId) {
717
831
  await execAsync$1(`tmux select-pane -t '${paneId}'`);
718
832
  }
719
- async function markAsEditorPane$1(editorPaneId, targetPaneIds) {
720
- await execAsync$1(`tmux set-option -pt '${editorPaneId}' @editprompt_is_editor 1`);
721
- const uniqueTargetPaneIds = [...new Set(targetPaneIds)];
722
- await execAsync$1(`tmux set-option -pt '${editorPaneId}' @editprompt_target_panes '${uniqueTargetPaneIds.join(",")}'`);
723
- for (const targetPaneId of uniqueTargetPaneIds) await saveEditorPaneId$1(targetPaneId, editorPaneId);
724
- }
725
- async function getTargetPaneIds$1(editorPaneId) {
833
+ async function getPaneOption(paneId, name) {
726
834
  try {
727
- const { stdout } = await execAsync$1(`tmux show -pt '${editorPaneId}' -v @editprompt_target_panes`);
728
- const value = stdout.trim();
729
- if (value === "") return [];
730
- return value.split(",").map((id) => id.trim());
731
- } catch {
732
- return [];
733
- }
734
- }
735
- async function isEditorPane(paneId) {
736
- try {
737
- const { stdout } = await execAsync$1(`tmux show -pt '${paneId}' -v @editprompt_is_editor`);
738
- return stdout.trim() === "1";
739
- } catch {
740
- return false;
741
- }
742
- }
743
- async function getQuoteVariableContent(paneId) {
744
- try {
745
- const { stdout } = await execAsync$1(`tmux show -pt '${paneId}' -v @editprompt_quote`);
835
+ const { stdout } = await execAsync$1(`tmux show -pt '${paneId}' -v ${name}`);
746
836
  return stdout;
747
837
  } catch {
748
838
  return "";
749
839
  }
750
840
  }
751
- async function appendToQuoteVariable(paneId, content) {
752
- let newContent = "";
753
- const existingContent = await getQuoteVariableContent(paneId);
754
- if (existingContent.trim() !== "") newContent = `${existingContent}\n${content}`;
755
- else newContent = content;
756
- await execAsync$1(`tmux set-option -pt '${paneId}' @editprompt_quote '${newContent.replace(/'/g, "'\\''")}' `);
757
- }
758
- async function clearQuoteVariable(targetPaneId) {
759
- await execAsync$1(`tmux set-option -pt '${targetPaneId}' @editprompt_quote ""`);
841
+ async function setPaneOption(paneId, name, value) {
842
+ await execAsync$1(`tmux set-option -pt '${paneId}' ${name} '${value.replace(/'/g, "'\\''")}'`);
760
843
  }
761
- async function sendKeyToTmuxPane(paneId, key, delay = 1e3) {
762
- await new Promise((resolve) => setTimeout(resolve, delay));
844
+ async function sendKey$1(paneId, key) {
763
845
  await execAsync$1(`tmux send-keys -t '${paneId}' '${key}'`);
764
846
  }
765
- async function inputToTmuxPane(paneId, content) {
847
+ async function exitCopyMode(paneId) {
766
848
  await execAsync$1(`tmux if-shell -t '${paneId}' '[ "#{pane_in_mode}" = "1" ]' "copy-mode -q -t '${paneId}'"`);
767
- const chunks = splitByByteSize(content, TMUX_SEND_CHUNK_BYTES);
768
- for (const chunk of chunks) await execAsync$1(`tmux send-keys -t '${paneId}' -- '${chunk.replace(/'/g, "'\\''")}'`);
849
+ }
850
+ async function sendTextChunk$1(paneId, chunk) {
851
+ await execAsync$1(`tmux send-keys -t '${paneId}' -- '${chunk.replace(/'/g, "'\\''")}'`);
852
+ }
853
+ function logContentSent$1(paneId, chunkCount) {
769
854
  logger$12.debug("Content sent to tmux pane: {paneId} ({chunks} chunk(s))", {
770
855
  paneId,
771
- chunks: chunks.length
856
+ chunks: chunkCount
772
857
  });
773
858
  }
774
859
 
860
+ //#endregion
861
+ //#region src/backends/tmux.ts
862
+ const tmuxBackend = defineBackend({
863
+ name: "tmux",
864
+ getCurrentPaneId: () => getCurrentPaneId$1(),
865
+ paneExists: (paneId) => checkPaneExists$1(paneId),
866
+ focusPane: (paneId) => focusPane$1(paneId),
867
+ beforeSendText: (paneId) => exitCopyMode(paneId),
868
+ sendText: (paneId, chunk) => sendTextChunk$1(paneId, chunk),
869
+ sendKey: (paneId, key) => sendKey$1(paneId, key),
870
+ logContentSent: (paneId, chunkCount) => logContentSent$1(paneId, chunkCount),
871
+ maxChunkBytes: TMUX_SEND_CHUNK_BYTES,
872
+ defaultSubmitKey: "C-m",
873
+ quoteSeparator: "\n",
874
+ collectInput: "stdin",
875
+ storageNamespace: () => "tmux",
876
+ state: paneVariableStore({
877
+ get: (paneId, name) => getPaneOption(paneId, name),
878
+ set: (paneId, name, value) => setPaneOption(paneId, name, value)
879
+ }, "@editprompt_")
880
+ });
881
+
775
882
  //#endregion
776
883
  //#region src/modules/wezterm.ts
777
884
  const logger$11 = getLogger(["editprompt", "wezterm"]);
@@ -796,114 +903,270 @@ async function checkPaneExists(paneId) {
796
903
  return false;
797
904
  }
798
905
  }
799
- async function saveEditorPaneId(targetPaneId, editorPaneId) {
800
- logger$11.debug("Saving editor pane ID to conf key: wezterm.targetPane.pane_{targetPaneId}", { targetPaneId });
801
- try {
802
- conf.set(`wezterm.targetPane.pane_${targetPaneId}`, { editorPaneId });
803
- } catch (error) {
804
- logger$11.debug("saveEditorPaneId failed: {error}", { error });
805
- }
906
+ async function focusPane(paneId) {
907
+ await execAsync(`wezterm cli activate-pane --pane-id '${paneId}'`);
806
908
  }
807
- async function getEditorPaneId(targetPaneId) {
808
- try {
809
- const data = conf.get(`wezterm.targetPane.pane_${targetPaneId}`);
810
- if (typeof data === "object" && data !== null && "editorPaneId" in data) return String(data.editorPaneId);
811
- return "";
812
- } catch (error) {
813
- logger$11.debug("getEditorPaneId failed: {error}", { error });
814
- return "";
815
- }
909
+ async function sendKey(paneId, key) {
910
+ await execAsync(`wezterm cli send-text --no-paste --pane-id '${paneId}' $'${key}'`);
816
911
  }
817
- async function clearEditorPaneId(targetPaneId) {
818
- try {
819
- const editorPaneId = await getEditorPaneId(targetPaneId);
820
- conf.delete(`wezterm.targetPane.pane_${targetPaneId}`);
821
- if (editorPaneId) conf.delete(`wezterm.editorPane.pane_${editorPaneId}`);
822
- } catch (error) {
823
- logger$11.debug("clearEditorPaneId failed: {error}", { error });
824
- }
912
+ async function sendTextChunk(paneId, chunk) {
913
+ await execAsync(`wezterm cli send-text --no-paste --pane-id '${paneId}' -- '${chunk.replace(/'/g, "'\\''")}'`);
825
914
  }
826
- async function focusPane(paneId) {
827
- await execAsync(`wezterm cli activate-pane --pane-id '${paneId}'`);
915
+ function logContentSent(paneId, chunkCount) {
916
+ logger$11.debug("Content sent to wezterm pane: {paneId} ({chunks} chunk(s))", {
917
+ paneId,
918
+ chunks: chunkCount
919
+ });
828
920
  }
829
- async function markAsEditorPane(editorPaneId, targetPaneIds) {
830
- try {
831
- const uniqueTargetPaneIds = [...new Set(targetPaneIds)];
832
- conf.set(`wezterm.editorPane.pane_${editorPaneId}`, { targetPaneIds: uniqueTargetPaneIds });
833
- for (const targetPaneId of uniqueTargetPaneIds) await saveEditorPaneId(targetPaneId, editorPaneId);
834
- } catch (error) {
835
- logger$11.debug("markAsEditorPane failed: {error}", { error });
921
+
922
+ //#endregion
923
+ //#region src/backends/wezterm.ts
924
+ const weztermBackend = defineBackend({
925
+ name: "wezterm",
926
+ getCurrentPaneId: () => getCurrentPaneId(),
927
+ paneExists: (paneId) => checkPaneExists(paneId),
928
+ focusPane: (paneId) => focusPane(paneId),
929
+ sendText: (paneId, chunk) => sendTextChunk(paneId, chunk),
930
+ sendKey: (paneId, key) => sendKey(paneId, key),
931
+ logContentSent: (paneId, chunkCount) => logContentSent(paneId, chunkCount),
932
+ maxChunkBytes: WEZTERM_SEND_CHUNK_BYTES,
933
+ defaultSubmitKey: "\\r",
934
+ quoteSeparator: "\n\n",
935
+ collectInput: "argument",
936
+ quietResumeErrors: "editorFocus",
937
+ traceResume: true,
938
+ storageNamespace: () => "wezterm",
939
+ state: confStore(() => "wezterm", {
940
+ wholeTargetRecord: true,
941
+ logCategory: ["editprompt", "wezterm"]
942
+ })
943
+ });
944
+
945
+ //#endregion
946
+ //#region src/backends/registry.ts
947
+ /** Used when nothing is configured and no backend detects its environment. */
948
+ const FALLBACK_BACKEND_NAME = "tmux";
949
+ const BUILTIN_BACKENDS = [
950
+ tmuxBackend,
951
+ weztermBackend,
952
+ herdrBackend,
953
+ niwatermBackend,
954
+ rexBackend
955
+ ];
956
+ const backends = new Map(BUILTIN_BACKENDS.map((b) => [b.name, b]));
957
+ function isBuiltinBackendName(name) {
958
+ return BUILTIN_BACKENDS.some((backend) => backend.name === name);
959
+ }
960
+ function registerBackend(backend) {
961
+ if (backends.has(backend.name)) throw new Error(`A backend named '${backend.name}' is already registered`);
962
+ backends.set(backend.name, backend);
963
+ }
964
+ function hasBackend(name) {
965
+ return typeof name === "string" && backends.has(name);
966
+ }
967
+ function listBackendNames() {
968
+ return [...backends.keys()];
969
+ }
970
+ function getBackend(name) {
971
+ const backend = backends.get(name);
972
+ if (!backend) throw new Error(`Invalid multiplexer type '${name}'. Supported values: ${listBackendNames().join(", ")}`);
973
+ return backend;
974
+ }
975
+ function detectBackendName(env = process.env) {
976
+ for (const backend of backends.values()) if (backend.detect?.(env)) return backend.name;
977
+ return FALLBACK_BACKEND_NAME;
978
+ }
979
+ /**
980
+ * Resolves the backend name from, in order: the explicit `--mux` value,
981
+ * `EDITPROMPT_MUX`, and auto-detection. Throws for an unknown name.
982
+ */
983
+ function resolveBackendName(explicit, env = process.env) {
984
+ const value = explicit || env.EDITPROMPT_MUX || detectBackendName(env);
985
+ if (!hasBackend(value)) {
986
+ const displayValue = typeof value === "string" ? value : `<${typeof value}>`;
987
+ throw new Error(`Invalid multiplexer type '${displayValue}'. Supported values: ${listBackendNames().join(", ")}`);
836
988
  }
989
+ return value;
837
990
  }
838
- async function getTargetPaneIds(editorPaneId) {
839
- try {
840
- const data = conf.get(`wezterm.editorPane.pane_${editorPaneId}`);
841
- if (typeof data === "object" && data !== null && "targetPaneIds" in data) {
842
- const targetPaneIds = data.targetPaneIds;
843
- if (Array.isArray(targetPaneIds)) return targetPaneIds.map((id) => String(id));
991
+
992
+ //#endregion
993
+ //#region src/config/userConfig.ts
994
+ const RC_FILE = ".editpromptrc";
995
+ /**
996
+ * The directory holding the configuration file: `EDITPROMPT_CONFIG_DIR`, or
997
+ * `editprompt` under `XDG_CONFIG_HOME` (default `~/.config`).
998
+ */
999
+ function getConfigDir(env = process.env) {
1000
+ const explicit = env.EDITPROMPT_CONFIG_DIR?.trim();
1001
+ if (explicit) return explicit;
1002
+ return join(env.XDG_CONFIG_HOME?.trim() || join(homedir(), ".config"), "editprompt");
1003
+ }
1004
+ /**
1005
+ * Loads the configuration with c12, so every format c12 supports works:
1006
+ * `config.{json,jsonc,json5,yaml,yml,toml,js,ts,mjs,cjs,mts,cts}` in the
1007
+ * config directory, and the `.editpromptrc` RC file there, in
1008
+ * `$XDG_CONFIG_HOME` or in the home directory.
1009
+ *
1010
+ * Configuration is read from the user's config directory only, never from the
1011
+ * current working directory: a repository must not be able to change how
1012
+ * editprompt talks to the terminal, or run code through a config file.
1013
+ */
1014
+ async function loadUserConfig(configDir = getConfigDir()) {
1015
+ const { loadConfig } = await import("c12");
1016
+ const { config } = await loadConfig({
1017
+ name: "editprompt",
1018
+ cwd: configDir,
1019
+ configFile: "config",
1020
+ rcFile: RC_FILE,
1021
+ globalRc: true,
1022
+ dotenv: false,
1023
+ packageJson: false
1024
+ });
1025
+ return config ?? {};
1026
+ }
1027
+ /**
1028
+ * Registers the backends declared in the configuration. Returns one message
1029
+ * per backend that could not be registered, so that a broken entry does not
1030
+ * take the built-in backends down with it.
1031
+ */
1032
+ function registerConfiguredBackends(config) {
1033
+ const errors = [];
1034
+ const { backends } = config;
1035
+ if (backends === void 0) return errors;
1036
+ if (typeof backends !== "object" || backends === null || Array.isArray(backends)) return ["backends must be an object keyed by backend name"];
1037
+ for (const [name, spec] of Object.entries(backends)) {
1038
+ if (isBuiltinBackendName(name)) {
1039
+ errors.push(`backends.${name}: '${name}' is a built-in backend and cannot be redefined`);
1040
+ continue;
1041
+ }
1042
+ try {
1043
+ registerBackend(createCommandBackend(name, spec));
1044
+ } catch (error) {
1045
+ errors.push(error instanceof Error ? error.message : String(error));
844
1046
  }
845
- return [];
846
- } catch (error) {
847
- logger$11.debug("getTargetPaneIds failed: {error}", { error });
848
- return [];
849
1047
  }
1048
+ return errors;
850
1049
  }
851
- function isEditorPaneFromConf(paneId) {
852
- try {
853
- return conf.has(`wezterm.editorPane.pane_${paneId}`);
854
- } catch (error) {
855
- logger$11.debug("isEditorPaneFromConf failed: {error}", { error });
856
- return false;
1050
+ /**
1051
+ * Returns the `--mux` value given on the command line, ignoring anything after
1052
+ * `--` (content passed to input, collect or stash).
1053
+ */
1054
+ function findMuxArgument(argv) {
1055
+ for (let i = 0; i < argv.length; i++) {
1056
+ const arg = argv[i];
1057
+ if (arg === "--") return;
1058
+ if (arg === "--mux" || arg === "-m") return argv[i + 1];
1059
+ if (arg.startsWith("--mux=")) return arg.slice(6);
857
1060
  }
858
1061
  }
859
- async function appendToQuoteText(paneId, content) {
1062
+ /**
1063
+ * Decides whether this invocation can use a configured backend at all, so that
1064
+ * the common case skips loading c12. Configured backends cannot replace
1065
+ * built-in ones and are detected after them, so they only matter when an
1066
+ * explicit `--mux`/EDITPROMPT_MUX names a non-built-in backend, or when
1067
+ * auto-detection would otherwise fall back to tmux.
1068
+ */
1069
+ function mayUseConfiguredBackend(argv, env = process.env) {
1070
+ const explicit = findMuxArgument(argv) || env.EDITPROMPT_MUX;
1071
+ if (explicit) return !isBuiltinBackendName(explicit);
1072
+ return detectBackendName(env) === FALLBACK_BACKEND_NAME;
1073
+ }
1074
+ /** Returns true when one of the files c12 would read exists. */
1075
+ function hasConfigFiles(configDir = getConfigDir(), env = process.env) {
1076
+ return existsSync(configDir) || existsSync(join(env.XDG_CONFIG_HOME?.trim() || homedir(), RC_FILE));
1077
+ }
1078
+ /** Loads the configuration and registers its backends, reporting problems on stderr. */
1079
+ async function setupUserConfig(configDir = getConfigDir()) {
1080
+ if (!hasConfigFiles(configDir)) return;
1081
+ let config;
860
1082
  try {
861
- const data = conf.get(`wezterm.targetPane.pane_${paneId}`);
862
- let newData;
863
- if (typeof data === "object" && data !== null) {
864
- const existingQuoteText = "quote_text" in data ? String(data.quote_text) : "";
865
- const newQuoteText = existingQuoteText.trim() !== "" ? `${existingQuoteText}\n\n${content}` : content;
866
- newData = {
867
- ...data,
868
- quote_text: newQuoteText
869
- };
870
- } else newData = { quote_text: content };
871
- conf.set(`wezterm.targetPane.pane_${paneId}`, newData);
1083
+ config = await loadUserConfig(configDir);
872
1084
  } catch (error) {
873
- logger$11.debug("appendToQuoteText failed: {error}", { error });
1085
+ console.error(`editprompt: failed to load the configuration: ${error instanceof Error ? error.message : String(error)}`);
1086
+ return;
874
1087
  }
1088
+ for (const message of registerConfiguredBackends(config)) console.error(`editprompt: invalid configuration: ${message}`);
875
1089
  }
876
- async function getQuoteText(paneId) {
877
- try {
878
- const data = conf.get(`wezterm.targetPane.pane_${paneId}`);
879
- if (typeof data === "object" && data !== null && "quote_text" in data) return String(data.quote_text);
880
- return "";
881
- } catch (error) {
882
- logger$11.debug("getQuoteText failed: {error}", { error });
883
- return "";
884
- }
1090
+
1091
+ //#endregion
1092
+ //#region src/modules/logger.ts
1093
+ function resolveLogFilePath(options) {
1094
+ if (options.logFile) return options.logFile;
1095
+ const envPath = process.env.EDITPROMPT_LOG_FILE;
1096
+ if (envPath) return envPath;
885
1097
  }
886
- async function clearQuoteText(paneId) {
887
- try {
888
- const key = `wezterm.targetPane.pane_${paneId}.quote_text`;
889
- if (conf.has(key)) conf.delete(key);
890
- } catch (error) {
891
- logger$11.debug("clearQuoteText failed: {error}", { error });
892
- }
1098
+ function resolveFileLogLevel(options) {
1099
+ if (options.verbose) return "debug";
1100
+ const envLevel = process.env.EDITPROMPT_LOG_LEVEL;
1101
+ if (envLevel) return parseLogLevel(envLevel);
1102
+ return "info";
893
1103
  }
894
- async function sendKeyToWeztermPane(paneId, key, delay = 1e3) {
895
- await new Promise((resolve) => setTimeout(resolve, delay));
896
- await execAsync(`wezterm cli send-text --no-paste --pane-id '${paneId}' $'${key}'`);
1104
+ function resolveLogLevel(options) {
1105
+ if (options.quiet) return null;
1106
+ if (options.verbose) return "debug";
1107
+ const envLevel = process.env.EDITPROMPT_LOG_LEVEL;
1108
+ if (envLevel) return parseLogLevel(envLevel);
1109
+ return "info";
897
1110
  }
898
- async function inputToWeztermPane(paneId, content) {
899
- const chunks = splitByByteSize(content, WEZTERM_SEND_CHUNK_BYTES);
900
- for (const chunk of chunks) await execAsync(`wezterm cli send-text --no-paste --pane-id '${paneId}' -- '${chunk.replace(/'/g, "'\\''")}'`);
901
- logger$11.debug("Content sent to wezterm pane: {paneId} ({chunks} chunk(s))", {
902
- paneId,
903
- chunks: chunks.length
1111
+ function setupLogger(options = {}) {
1112
+ const stderrLevel = resolveLogLevel(options);
1113
+ const stderrFormatter = getAnsiColorFormatter({
1114
+ timestamp: "time-timezone",
1115
+ level: "ABBR"
1116
+ });
1117
+ const rawStderrSink = (record) => {
1118
+ process.stderr.write(stderrFormatter(record));
1119
+ };
1120
+ const sinks = { stderr: stderrLevel === null ? () => {} : withFilter(rawStderrSink, stderrLevel) };
1121
+ const loggerSinks = ["stderr"];
1122
+ const logFilePath = resolveLogFilePath(options);
1123
+ const fileLogLevel = resolveFileLogLevel(options);
1124
+ if (logFilePath) try {
1125
+ appendFileSync(logFilePath, "");
1126
+ const fileFormatter = getTextFormatter({
1127
+ timestamp: "date-time-timezone",
1128
+ level: "ABBR"
1129
+ });
1130
+ const rawFileSink = (record) => {
1131
+ appendFileSync(logFilePath, fileFormatter(record));
1132
+ };
1133
+ sinks.file = withFilter(rawFileSink, fileLogLevel);
1134
+ loggerSinks.push("file");
1135
+ } catch {
1136
+ process.stderr.write(`Warning: Cannot write to log file '${logFilePath}', continuing without file logging.\n`);
1137
+ }
1138
+ configureSync({
1139
+ sinks,
1140
+ loggers: [{
1141
+ category: ["editprompt"],
1142
+ sinks: loggerSinks,
1143
+ lowestLevel: "trace"
1144
+ }, {
1145
+ category: ["logtape", "meta"],
1146
+ lowestLevel: null
1147
+ }]
904
1148
  });
905
1149
  }
906
1150
 
1151
+ //#endregion
1152
+ //#region package.json
1153
+ var version = "2.0.0";
1154
+
1155
+ //#endregion
1156
+ //#region src/core/quote.ts
1157
+ /** Appends a quote to the target pane's quote buffer. */
1158
+ async function appendQuote(backend, targetPaneId, content) {
1159
+ const existing = await backend.state.getQuote(targetPaneId);
1160
+ const quote = existing.trim() !== "" ? `${existing}${backend.quoteSeparator}${content}` : content;
1161
+ await backend.state.setQuote(targetPaneId, quote);
1162
+ }
1163
+ /** Returns the target pane's quote buffer and clears it. */
1164
+ async function takeQuote(backend, targetPaneId) {
1165
+ const quote = await backend.state.getQuote(targetPaneId);
1166
+ await backend.state.removeQuote(targetPaneId);
1167
+ return quote;
1168
+ }
1169
+
907
1170
  //#endregion
908
1171
  //#region src/utils/argumentParser.ts
909
1172
  /**
@@ -1037,41 +1300,14 @@ function processQuoteText(text, options) {
1037
1300
  let processedLines;
1038
1301
  if (hasNoLeadingWhitespaceInLaterLines) processedLines = mergeIndentedContinuations(lines, lines.map((line) => line.trimStart().trimEnd()));
1039
1302
  else processedLines = removeWhitespaceAndMergeLines(lines);
1040
- if (!withQuote) return processedLines.join("\n");
1041
- return `${processedLines.map((line) => `> ${line}`).join("\n")}\n\n`;
1042
- }
1043
-
1044
- //#endregion
1045
- //#region src/utils/mux.ts
1046
- const SUPPORTED_MUXES = [
1047
- "tmux",
1048
- "wezterm",
1049
- "herdr",
1050
- "niwaterm",
1051
- "rex"
1052
- ];
1053
- function isMuxType(value) {
1054
- return value === "tmux" || value === "wezterm" || value === "herdr" || value === "niwaterm" || value === "rex";
1055
- }
1056
- function hasValue(value) {
1057
- return typeof value === "string" && value.trim() !== "";
1058
- }
1059
- function isHerdrEnvironment(env = process.env) {
1060
- return hasValue(env.HERDR_SOCKET_PATH) && (hasValue(env.HERDR_PANE_ID) || hasValue(env.HERDR_ACTIVE_PANE_ID));
1061
- }
1062
- function isNiwatermEnvironment(env = process.env) {
1063
- return hasValue(env.NIWATERM_TAB_ID);
1064
- }
1065
- function isRexEnvironment(env = process.env) {
1066
- return hasValue(env.REX_BLOCK);
1303
+ if (!withQuote) return processedLines.join("\n");
1304
+ return `${processedLines.map((line) => `> ${line}`).join("\n")}\n\n`;
1067
1305
  }
1306
+
1307
+ //#endregion
1308
+ //#region src/utils/mux.ts
1068
1309
  function resolveMux(explicitMux, env = process.env) {
1069
- const muxValue = explicitMux || env.EDITPROMPT_MUX || (isHerdrEnvironment(env) ? "herdr" : isNiwatermEnvironment(env) ? "niwaterm" : isRexEnvironment(env) ? "rex" : "tmux");
1070
- if (!isMuxType(muxValue)) {
1071
- const displayValue = typeof muxValue === "string" ? muxValue : `<${typeof muxValue}>`;
1072
- throw new Error(`Invalid multiplexer type '${displayValue}'. Supported values: ${SUPPORTED_MUXES.join(", ")}`);
1073
- }
1074
- return muxValue;
1310
+ return resolveBackendName(explicitMux, env);
1075
1311
  }
1076
1312
 
1077
1313
  //#endregion
@@ -1189,11 +1425,7 @@ async function runCollectMode(mux, targetPaneId, rawContent, outputs = ["buffer"
1189
1425
  if (rawContent !== void 0) selection = rawContent;
1190
1426
  else selection = await readStdin();
1191
1427
  const processedText = processQuoteText(selection, { withQuote });
1192
- for (const output of outputs) if (output === "buffer") if (mux === "tmux") await appendToQuoteVariable(targetPaneId, processedText);
1193
- else if (mux === "wezterm") await appendToQuoteText(targetPaneId, processedText);
1194
- else if (mux === "niwaterm") await appendToQuoteVariable$1(targetPaneId, processedText);
1195
- else if (mux === "rex") await appendToQuoteText$1(targetPaneId, processedText);
1196
- else await appendToQuoteText$2(targetPaneId, processedText);
1428
+ for (const output of outputs) if (output === "buffer") await appendQuote(getBackend(mux), targetPaneId, processedText);
1197
1429
  else if (output === "stdout") process.stdout.write(processedText);
1198
1430
  } catch (error) {
1199
1431
  logger$9.error(`${error instanceof Error ? error.message : "Unknown error"}`);
@@ -1223,7 +1455,7 @@ const collectCommand = define({
1223
1455
  const outputs = normalizeCollectOutputs(ctx.values.output);
1224
1456
  const withQuote = !ctx.values["no-quote"];
1225
1457
  let rawContent;
1226
- if (mux === "wezterm" || mux === "herdr" || mux === "niwaterm" || mux === "rex") {
1458
+ if (getBackend(mux).collectInput === "argument") {
1227
1459
  rawContent = extractRawContent(ctx.rest, ctx.positionals);
1228
1460
  if (rawContent === void 0) {
1229
1461
  logger$9.error(`Text content is required for collect mode with ${mux}. Use: editprompt collect --mux ${mux} --target-pane <id> -- "<text>"`);
@@ -1249,120 +1481,124 @@ function readSendConfig() {
1249
1481
  }
1250
1482
 
1251
1483
  //#endregion
1252
- //#region src/modes/dump.ts
1253
- const logger$8 = getLogger(["editprompt", "dump"]);
1254
- async function runDumpMode() {
1255
- try {
1256
- const config = readSendConfig();
1257
- let currentPaneId;
1258
- let isEditor;
1259
- if (config.mux === "tmux") {
1260
- currentPaneId = await getCurrentPaneId$1();
1261
- isEditor = await isEditorPane(currentPaneId);
1262
- } else if (config.mux === "wezterm") {
1263
- currentPaneId = await getCurrentPaneId();
1264
- isEditor = isEditorPaneFromConf(currentPaneId);
1265
- } else if (config.mux === "niwaterm") {
1266
- currentPaneId = await getCurrentPaneId$3();
1267
- isEditor = await isEditorPane$1(currentPaneId);
1268
- } else if (config.mux === "rex") {
1269
- currentPaneId = await getCurrentPaneId$2();
1270
- isEditor = isEditorPaneFromConf$1(currentPaneId);
1271
- } else {
1272
- currentPaneId = await getCurrentPaneId$4();
1273
- isEditor = isEditorPaneFromConf$2(currentPaneId);
1274
- }
1275
- if (!isEditor) {
1276
- logger$8.error("Current pane is not an editor pane");
1277
- process.exit(1);
1278
- }
1279
- let targetPanes;
1280
- if (config.mux === "tmux") targetPanes = await getTargetPaneIds$1(currentPaneId);
1281
- else if (config.mux === "wezterm") targetPanes = await getTargetPaneIds(currentPaneId);
1282
- else if (config.mux === "niwaterm") targetPanes = await getTargetPaneIds$3(currentPaneId);
1283
- else if (config.mux === "rex") targetPanes = await getTargetPaneIds$2(currentPaneId);
1284
- else targetPanes = await getTargetPaneIds$4(currentPaneId);
1285
- if (targetPanes.length === 0) {
1286
- logger$8.error("No target panes registered for this editor pane");
1287
- process.exit(1);
1288
- }
1289
- const quoteContents = [];
1290
- for (const targetPane of targetPanes) {
1291
- let content;
1292
- if (config.mux === "tmux") {
1293
- content = await getQuoteVariableContent(targetPane);
1294
- await clearQuoteVariable(targetPane);
1295
- } else if (config.mux === "wezterm") {
1296
- content = await getQuoteText(targetPane);
1297
- await clearQuoteText(targetPane);
1298
- } else if (config.mux === "niwaterm") {
1299
- content = await getQuoteVariableContent$1(targetPane);
1300
- await clearQuoteVariable$1(targetPane);
1301
- } else if (config.mux === "rex") {
1302
- content = await getQuoteText$1(targetPane);
1303
- await clearQuoteText$1(targetPane);
1304
- } else {
1305
- content = await getQuoteText$2(targetPane);
1306
- await clearQuoteText$2(targetPane);
1307
- }
1308
- if (content.trim() !== "") quoteContents.push(content);
1309
- }
1310
- const combinedContent = quoteContents.join("\n");
1311
- process.stdout.write(combinedContent.replace(/\n{3,}$/, "\n\n"));
1312
- process.exit(0);
1313
- } catch (error) {
1314
- logger$8.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1315
- process.exit(1);
1316
- }
1484
+ //#region src/core/link.ts
1485
+ /**
1486
+ * Marks a pane as an editor pane serving the target panes, and records the
1487
+ * editor pane on each target pane so that resume can find it.
1488
+ */
1489
+ async function markAsEditorPane(backend, editorPaneId, targetPaneIds) {
1490
+ const uniqueTargetPaneIds = [...new Set(targetPaneIds)];
1491
+ await backend.state.setTargetPaneIds(editorPaneId, uniqueTargetPaneIds);
1492
+ for (const targetPaneId of uniqueTargetPaneIds) await backend.state.setEditorPaneId(targetPaneId, editorPaneId);
1317
1493
  }
1318
- const dumpCommand = define({
1319
- name: "dump",
1320
- description: "Output and clear collected quoted text from environment variables",
1321
- args: {
1322
- "log-file": ARG_LOG_FILE,
1323
- quiet: ARG_QUIET,
1324
- verbose: ARG_VERBOSE
1325
- },
1326
- async run(ctx) {
1327
- setupLogger({
1328
- quiet: Boolean(ctx.values.quiet),
1329
- verbose: Boolean(ctx.values.verbose),
1330
- logFile: ctx.values["log-file"]
1331
- });
1332
- await runDumpMode();
1494
+ function isEditorPane(backend, paneId) {
1495
+ return backend.state.isEditorPane(paneId);
1496
+ }
1497
+ function getTargetPaneIds(backend, editorPaneId) {
1498
+ return backend.state.getTargetPaneIds(editorPaneId);
1499
+ }
1500
+ /**
1501
+ * Clears the editor pane registered on a target pane.
1502
+ *
1503
+ * When `expectedEditorPaneId` is given and the target pane has meanwhile been
1504
+ * linked to a newer editor pane, the newer link is kept and only the expected
1505
+ * editor pane is forgotten. This lets an editor that exits late leave a newer
1506
+ * editor's registration alone.
1507
+ */
1508
+ async function clearEditorLink(backend, targetPaneId, expectedEditorPaneId) {
1509
+ if (!expectedEditorPaneId && backend.state.keepsEditorPaneMarks) {
1510
+ await backend.state.removeEditorPaneId(targetPaneId);
1511
+ return;
1333
1512
  }
1334
- });
1513
+ const editorPaneId = await backend.state.getEditorPaneId(targetPaneId);
1514
+ if (expectedEditorPaneId && editorPaneId !== expectedEditorPaneId) {
1515
+ await backend.state.removeEditorPane(expectedEditorPaneId);
1516
+ return;
1517
+ }
1518
+ await backend.state.removeEditorPaneId(targetPaneId);
1519
+ if (editorPaneId) await backend.state.removeEditorPane(editorPaneId);
1520
+ }
1521
+ var EditorContextError = class extends Error {
1522
+ name = "EditorContextError";
1523
+ };
1524
+ /**
1525
+ * Resolves the editor pane editprompt runs in and the target panes registered
1526
+ * on it. Throws EditorContextError when the current pane is not an editor pane
1527
+ * or has no target panes.
1528
+ */
1529
+ async function resolveEditorContext(backend) {
1530
+ const editorPaneId = await backend.getCurrentPaneId();
1531
+ if (!await backend.state.isEditorPane(editorPaneId)) throw new EditorContextError("Current pane is not an editor pane");
1532
+ const targetPaneIds = await backend.state.getTargetPaneIds(editorPaneId);
1533
+ if (targetPaneIds.length === 0) throw new EditorContextError("No target panes registered for this editor pane");
1534
+ return {
1535
+ editorPaneId,
1536
+ targetPaneIds
1537
+ };
1538
+ }
1335
1539
 
1336
1540
  //#endregion
1337
- //#region src/utils/contentProcessor.ts
1338
- function processContent(content) {
1339
- let processed = content.replace(/\n$/, "");
1340
- if (/@[^\n]*$/.test(processed)) processed += " ";
1341
- return processed;
1541
+ //#region src/utils/contentChunker.ts
1542
+ /**
1543
+ * Split content into chunks not exceeding maxBytes (UTF-8 byte count),
1544
+ * never breaking a Unicode code point across chunks.
1545
+ *
1546
+ * Content shorter than maxBytes is returned as a single chunk, so short
1547
+ * input keeps the previous behavior of being sent in one shot.
1548
+ */
1549
+ function splitByByteSize(content, maxBytes) {
1550
+ if (content === "") return [];
1551
+ const chunks = [];
1552
+ let current = "";
1553
+ let currentBytes = 0;
1554
+ for (const char of content) {
1555
+ const charBytes = Buffer.byteLength(char, "utf8");
1556
+ if (current !== "" && currentBytes + charBytes > maxBytes) {
1557
+ chunks.push(current);
1558
+ current = "";
1559
+ currentBytes = 0;
1560
+ }
1561
+ current += char;
1562
+ currentBytes += charBytes;
1563
+ }
1564
+ if (current !== "") chunks.push(current);
1565
+ return chunks;
1342
1566
  }
1343
1567
 
1344
1568
  //#endregion
1345
- //#region src/modes/common.ts
1346
- const logger$7 = getLogger(["editprompt", "delivery"]);
1569
+ //#region src/core/delivery.ts
1570
+ const logger$8 = getLogger(["editprompt", "delivery"]);
1347
1571
  async function copyToClipboard(content) {
1348
1572
  await clipboardy.write(content);
1349
1573
  }
1350
- async function inputContentToPane(content, mux, targetPaneId) {
1351
- if (mux === "wezterm") await inputToWeztermPane(targetPaneId, content);
1352
- else if (mux === "herdr") await inputToHerdrPane(targetPaneId, content);
1353
- else if (mux === "niwaterm") await inputToNiwatermPane(targetPaneId, content);
1354
- else if (mux === "rex") await inputToRexPane(targetPaneId, content);
1355
- else await inputToTmuxPane(targetPaneId, content);
1574
+ /**
1575
+ * Types content into a pane without changing focus. Long content is split
1576
+ * into chunks the backend accepts and sent in order.
1577
+ */
1578
+ async function sendContentToPane(backend, paneId, content) {
1579
+ await backend.beforeSendText?.(paneId);
1580
+ const chunks = splitByByteSize(content, backend.maxChunkBytes);
1581
+ for (const chunk of chunks) await backend.sendText(paneId, chunk);
1582
+ if (backend.logContentSent) backend.logContentSent(paneId, chunks.length);
1583
+ else logger$8.debug("Content sent to {backend} pane: {paneId} ({chunks} chunk(s))", {
1584
+ backend: backend.name,
1585
+ paneId,
1586
+ chunks: chunks.length
1587
+ });
1588
+ }
1589
+ /**
1590
+ * Sends a key after a delay. The delay keeps some CLIs (e.g., codex) from
1591
+ * treating the key as part of a paste.
1592
+ */
1593
+ async function sendKeyToPane(backend, paneId, key, delay = 1e3) {
1594
+ await new Promise((resolve) => setTimeout(resolve, delay));
1595
+ await backend.sendKey(paneId, key);
1356
1596
  }
1357
- async function focusFirstSuccessPane(mux, targetPanes, failedPanes) {
1597
+ async function focusFirstSuccessPane(backend, targetPanes, failedPanes) {
1358
1598
  const firstSuccessPane = targetPanes.find((p) => !failedPanes.includes(p));
1359
- if (firstSuccessPane) if (mux === "tmux") await focusPane$1(firstSuccessPane);
1360
- else if (mux === "wezterm") await focusPane(firstSuccessPane);
1361
- else if (mux === "niwaterm") await focusPane$3(firstSuccessPane);
1362
- else if (mux === "rex") await focusPane$2(firstSuccessPane);
1363
- else await focusPane$4(firstSuccessPane);
1599
+ if (firstSuccessPane) await backend.focusPane(firstSuccessPane);
1364
1600
  }
1365
- async function handleContentDelivery(content, mux, targetPanes) {
1601
+ async function handleContentDelivery(content, backend, targetPanes) {
1366
1602
  if (!content) return {
1367
1603
  successCount: 0,
1368
1604
  totalCount: 0,
@@ -1373,9 +1609,9 @@ async function handleContentDelivery(content, mux, targetPanes) {
1373
1609
  if (targetPanes.length === 0) {
1374
1610
  try {
1375
1611
  await copyToClipboard(content);
1376
- logger$7.info("Content copied to clipboard.");
1612
+ logger$8.info("Content copied to clipboard.");
1377
1613
  } catch (error) {
1378
- logger$7.warn(`Failed to copy to clipboard: ${error instanceof Error ? error.message : "Unknown error"}`);
1614
+ logger$8.warn(`Failed to copy to clipboard: ${error instanceof Error ? error.message : "Unknown error"}`);
1379
1615
  }
1380
1616
  return {
1381
1617
  successCount: 0,
@@ -1387,13 +1623,13 @@ async function handleContentDelivery(content, mux, targetPanes) {
1387
1623
  }
1388
1624
  const results = [];
1389
1625
  for (const targetPane of targetPanes) try {
1390
- await inputContentToPane(content, mux, targetPane);
1626
+ await sendContentToPane(backend, targetPane, content);
1391
1627
  results.push({
1392
1628
  pane: targetPane,
1393
1629
  success: true
1394
1630
  });
1395
1631
  } catch (error) {
1396
- logger$7.warn(`Failed to send to pane ${targetPane}: ${error instanceof Error ? error.message : "Unknown error"}`);
1632
+ logger$8.warn(`Failed to send to pane ${targetPane}: ${error instanceof Error ? error.message : "Unknown error"}`);
1397
1633
  results.push({
1398
1634
  pane: targetPane,
1399
1635
  success: false
@@ -1403,13 +1639,13 @@ async function handleContentDelivery(content, mux, targetPanes) {
1403
1639
  const failedPanes = results.filter((r) => !r.success).map((r) => r.pane);
1404
1640
  const allSuccess = successCount === targetPanes.length;
1405
1641
  const allFailed = successCount === 0;
1406
- if (allSuccess) logger$7.info("Content sent successfully to all panes!");
1642
+ if (allSuccess) logger$8.info("Content sent successfully to all panes!");
1407
1643
  else if (allFailed) {
1408
- logger$7.error("All target panes failed to receive content.");
1409
- logger$7.info("Falling back to clipboard...");
1644
+ logger$8.error("All target panes failed to receive content.");
1645
+ logger$8.info("Falling back to clipboard...");
1410
1646
  await copyToClipboard(content);
1411
- logger$7.info("Content copied to clipboard.");
1412
- } else logger$7.warn(`Content sent to ${successCount}/${targetPanes.length} panes. Failed panes: ${failedPanes.join(", ")}`);
1647
+ logger$8.info("Content copied to clipboard.");
1648
+ } else logger$8.warn(`Content sent to ${successCount}/${targetPanes.length} panes. Failed panes: ${failedPanes.join(", ")}`);
1413
1649
  return {
1414
1650
  successCount,
1415
1651
  totalCount: targetPanes.length,
@@ -1419,6 +1655,71 @@ async function handleContentDelivery(content, mux, targetPanes) {
1419
1655
  };
1420
1656
  }
1421
1657
 
1658
+ //#endregion
1659
+ //#region src/modes/common.ts
1660
+ /**
1661
+ * Resolves the editor pane and its target panes for commands that run inside
1662
+ * an editor pane, exiting with an error (logged to the command's logger) when
1663
+ * there are none.
1664
+ */
1665
+ async function requireEditorContext(backend, logger) {
1666
+ try {
1667
+ return await resolveEditorContext(backend);
1668
+ } catch (error) {
1669
+ if (error instanceof EditorContextError) {
1670
+ logger.error(error.message);
1671
+ process.exit(1);
1672
+ }
1673
+ throw error;
1674
+ }
1675
+ }
1676
+
1677
+ //#endregion
1678
+ //#region src/modes/dump.ts
1679
+ const logger$7 = getLogger(["editprompt", "dump"]);
1680
+ async function runDumpMode() {
1681
+ try {
1682
+ const backend = getBackend(readSendConfig().mux);
1683
+ const { targetPaneIds } = await requireEditorContext(backend, logger$7);
1684
+ const quoteContents = [];
1685
+ for (const targetPane of targetPaneIds) {
1686
+ const content = await takeQuote(backend, targetPane);
1687
+ if (content.trim() !== "") quoteContents.push(content);
1688
+ }
1689
+ const combinedContent = quoteContents.join("\n");
1690
+ process.stdout.write(combinedContent.replace(/\n{3,}$/, "\n\n"));
1691
+ process.exit(0);
1692
+ } catch (error) {
1693
+ logger$7.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1694
+ process.exit(1);
1695
+ }
1696
+ }
1697
+ const dumpCommand = define({
1698
+ name: "dump",
1699
+ description: "Output and clear collected quoted text from environment variables",
1700
+ args: {
1701
+ "log-file": ARG_LOG_FILE,
1702
+ quiet: ARG_QUIET,
1703
+ verbose: ARG_VERBOSE
1704
+ },
1705
+ async run(ctx) {
1706
+ setupLogger({
1707
+ quiet: Boolean(ctx.values.quiet),
1708
+ verbose: Boolean(ctx.values.verbose),
1709
+ logFile: ctx.values["log-file"]
1710
+ });
1711
+ await runDumpMode();
1712
+ }
1713
+ });
1714
+
1715
+ //#endregion
1716
+ //#region src/utils/contentProcessor.ts
1717
+ function processContent(content) {
1718
+ let processed = content.replace(/\n$/, "");
1719
+ if (/@[^\n]*$/.test(processed)) processed += " ";
1720
+ return processed;
1721
+ }
1722
+
1422
1723
  //#endregion
1423
1724
  //#region src/modes/input.ts
1424
1725
  const logger$6 = getLogger(["editprompt", "input"]);
@@ -1429,59 +1730,15 @@ async function runInputMode(rawContent, autoSend, sendKey, sendKeyDelay) {
1429
1730
  return;
1430
1731
  }
1431
1732
  const config = readSendConfig();
1432
- let currentPaneId;
1433
- let isEditor;
1434
- if (config.mux === "tmux") {
1435
- currentPaneId = await getCurrentPaneId$1();
1436
- isEditor = await isEditorPane(currentPaneId);
1437
- } else if (config.mux === "wezterm") {
1438
- currentPaneId = await getCurrentPaneId();
1439
- isEditor = isEditorPaneFromConf(currentPaneId);
1440
- } else if (config.mux === "niwaterm") {
1441
- currentPaneId = await getCurrentPaneId$3();
1442
- isEditor = await isEditorPane$1(currentPaneId);
1443
- } else if (config.mux === "rex") {
1444
- currentPaneId = await getCurrentPaneId$2();
1445
- isEditor = isEditorPaneFromConf$1(currentPaneId);
1446
- } else {
1447
- currentPaneId = await getCurrentPaneId$4();
1448
- isEditor = isEditorPaneFromConf$2(currentPaneId);
1449
- }
1450
- if (!isEditor) {
1451
- logger$6.error("Current pane is not an editor pane");
1452
- process.exit(1);
1453
- }
1454
- let targetPanes;
1455
- if (config.mux === "tmux") targetPanes = await getTargetPaneIds$1(currentPaneId);
1456
- else if (config.mux === "wezterm") targetPanes = await getTargetPaneIds(currentPaneId);
1457
- else if (config.mux === "niwaterm") targetPanes = await getTargetPaneIds$3(currentPaneId);
1458
- else if (config.mux === "rex") targetPanes = await getTargetPaneIds$2(currentPaneId);
1459
- else targetPanes = await getTargetPaneIds$4(currentPaneId);
1460
- if (targetPanes.length === 0) {
1461
- logger$6.error("No target panes registered for this editor pane");
1462
- process.exit(1);
1463
- }
1733
+ const backend = getBackend(config.mux);
1734
+ const { targetPaneIds: targetPanes } = await requireEditorContext(backend, logger$6);
1464
1735
  if (autoSend) {
1465
- const key = sendKey || (config.mux === "wezterm" ? "\\r" : config.mux === "herdr" || config.mux === "rex" ? "enter" : "C-m");
1736
+ const key = sendKey || backend.defaultSubmitKey;
1466
1737
  const delay = /\.(png|webp|avif|jpe?g|gif)\b/i.test(content) ? sendKeyDelay ?? 1e3 : 200;
1467
1738
  let successCount = 0;
1468
1739
  for (const targetPane of targetPanes) try {
1469
- if (config.mux === "wezterm") {
1470
- await inputToWeztermPane(targetPane, content);
1471
- await sendKeyToWeztermPane(targetPane, key, delay);
1472
- } else if (config.mux === "herdr") {
1473
- await inputToHerdrPane(targetPane, content);
1474
- await sendKeyToHerdrPane(targetPane, key, delay);
1475
- } else if (config.mux === "niwaterm") {
1476
- await inputToNiwatermPane(targetPane, content);
1477
- await sendKeyToNiwatermPane(targetPane, key, delay);
1478
- } else if (config.mux === "rex") {
1479
- await inputToRexPane(targetPane, content);
1480
- await sendKeyToRexPane(targetPane, key, delay);
1481
- } else {
1482
- await inputToTmuxPane(targetPane, content);
1483
- await sendKeyToTmuxPane(targetPane, key, delay);
1484
- }
1740
+ await sendContentToPane(backend, targetPane, content);
1741
+ await sendKeyToPane(backend, targetPane, key, delay);
1485
1742
  successCount++;
1486
1743
  } catch (error) {
1487
1744
  logger$6.error(`Failed to send to pane ${targetPane}: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -1498,12 +1755,12 @@ async function runInputMode(rawContent, autoSend, sendKey, sendKeyDelay) {
1498
1755
  return;
1499
1756
  }
1500
1757
  try {
1501
- const result = await handleContentDelivery(content, config.mux, targetPanes);
1758
+ const result = await handleContentDelivery(content, backend, targetPanes);
1502
1759
  if (config.alwaysCopy && !result.allFailed) {
1503
1760
  await copyToClipboard(content);
1504
1761
  logger$6.info("Also copied to clipboard.");
1505
1762
  }
1506
- if (result.successCount > 0) await focusFirstSuccessPane(config.mux, targetPanes, result.failedPanes);
1763
+ if (result.successCount > 0) await focusFirstSuccessPane(backend, targetPanes, result.failedPanes);
1507
1764
  if (result.allFailed) process.exit(1);
1508
1765
  } catch (error) {
1509
1766
  logger$6.error(`${error instanceof Error ? error.message : "Unknown error"}`);
@@ -1636,26 +1893,11 @@ async function openEditorAndGetContent(editorOption, envVars, sendConfig) {
1636
1893
  //#region src/modes/openEditor.ts
1637
1894
  const logger$5 = getLogger(["editprompt", "open"]);
1638
1895
  async function runOpenEditorMode(options) {
1639
- let herdrEditorPaneId;
1640
- if (options.targetPanes.length > 0 && options.mux === "tmux") try {
1641
- await markAsEditorPane$1(await getCurrentPaneId$1(), options.targetPanes);
1642
- } catch {}
1643
- else if (options.targetPanes.length > 0 && options.mux === "wezterm") try {
1644
- const currentPaneId = await getCurrentPaneId();
1645
- await markAsEditorPane(currentPaneId, options.targetPanes);
1646
- } catch {}
1647
- else if (options.targetPanes.length > 0 && options.mux === "herdr") try {
1648
- const currentPaneId = await getCurrentPaneId$4();
1649
- herdrEditorPaneId = currentPaneId;
1650
- await markAsEditorPane$4(currentPaneId, options.targetPanes);
1651
- } catch {}
1652
- else if (options.targetPanes.length > 0 && options.mux === "niwaterm") try {
1653
- const currentPaneId = await getCurrentPaneId$3();
1654
- await markAsEditorPane$3(currentPaneId, options.targetPanes);
1655
- } catch {}
1656
- else if (options.targetPanes.length > 0 && options.mux === "rex") try {
1657
- const currentPaneId = await getCurrentPaneId$2();
1658
- await markAsEditorPane$2(currentPaneId, options.targetPanes);
1896
+ const backend = getBackend(options.mux);
1897
+ let editorPaneId;
1898
+ if (options.targetPanes.length > 0) try {
1899
+ editorPaneId = await backend.getCurrentPaneId();
1900
+ await markAsEditorPane(backend, editorPaneId, options.targetPanes);
1659
1901
  } catch {}
1660
1902
  try {
1661
1903
  const sendConfig = {
@@ -1670,34 +1912,23 @@ async function runOpenEditorMode(options) {
1670
1912
  return;
1671
1913
  }
1672
1914
  try {
1673
- const result = await handleContentDelivery(content, options.mux, options.targetPanes);
1915
+ const result = await handleContentDelivery(content, backend, options.targetPanes);
1674
1916
  console.log("---");
1675
1917
  console.log(content);
1676
1918
  if (options.alwaysCopy && !result.allFailed) {
1677
1919
  await copyToClipboard(content);
1678
1920
  logger$5.info("Also copied to clipboard.");
1679
1921
  }
1680
- if (options.targetPanes.length > 0 && result.successCount > 0) await focusFirstSuccessPane(options.mux, options.targetPanes, result.failedPanes);
1922
+ if (options.targetPanes.length > 0 && result.successCount > 0) await focusFirstSuccessPane(backend, options.targetPanes, result.failedPanes);
1681
1923
  if (!result.allSuccess) process.exit(1);
1682
1924
  } catch (error) {
1683
1925
  logger$5.error(`${error instanceof Error ? error.message : "Unknown error"}`);
1684
1926
  process.exit(1);
1685
1927
  }
1686
1928
  } finally {
1687
- if (options.targetPanes.length > 0 && options.mux === "tmux") try {
1688
- for (const targetPane of options.targetPanes) await clearEditorPaneId$1(targetPane);
1689
- } catch {}
1690
- else if (options.targetPanes.length > 0 && options.mux === "wezterm") try {
1691
- for (const targetPane of options.targetPanes) await clearEditorPaneId(targetPane);
1692
- } catch {}
1693
- else if (options.targetPanes.length > 0 && options.mux === "herdr") try {
1694
- for (const targetPane of options.targetPanes) await clearEditorPaneId$4(targetPane, herdrEditorPaneId);
1695
- } catch {}
1696
- else if (options.targetPanes.length > 0 && options.mux === "niwaterm") try {
1697
- for (const targetPane of options.targetPanes) await clearEditorPaneId$3(targetPane);
1698
- } catch {}
1699
- else if (options.targetPanes.length > 0 && options.mux === "rex") try {
1700
- for (const targetPane of options.targetPanes) await clearEditorPaneId$2(targetPane);
1929
+ if (options.targetPanes.length > 0) try {
1930
+ const expectedEditorPaneId = backend.keepNewerEditorLink ? editorPaneId : void 0;
1931
+ for (const targetPane of options.targetPanes) await clearEditorLink(backend, targetPane, expectedEditorPaneId);
1701
1932
  } catch {}
1702
1933
  }
1703
1934
  }
@@ -1738,55 +1969,20 @@ async function runRegisterMode(options) {
1738
1969
  logger$4.error("--target-pane is required for register command");
1739
1970
  process.exit(1);
1740
1971
  }
1972
+ const backend = getBackend(options.mux);
1741
1973
  let editorPaneId;
1742
1974
  if (options.editorPane) editorPaneId = options.editorPane;
1743
- else if (options.mux === "tmux") {
1744
- editorPaneId = await getCurrentPaneId$1();
1745
- if (!await isEditorPane(editorPaneId)) {
1746
- logger$4.error("Current pane is not an editor pane. Please run this command from an editor pane or specify --editor-pane.");
1747
- process.exit(1);
1748
- }
1749
- } else if (options.mux === "wezterm") {
1750
- editorPaneId = await getCurrentPaneId();
1751
- if (!isEditorPaneFromConf(editorPaneId)) {
1752
- logger$4.error("Current pane is not an editor pane. Please run this command from an editor pane or specify --editor-pane.");
1753
- process.exit(1);
1754
- }
1755
- } else if (options.mux === "herdr") {
1756
- editorPaneId = await getCurrentPaneId$4();
1757
- if (!isEditorPaneFromConf$2(editorPaneId)) {
1758
- logger$4.error("Current pane is not an editor pane. Please run this command from an editor pane or specify --editor-pane.");
1759
- process.exit(1);
1760
- }
1761
- } else if (options.mux === "niwaterm") {
1762
- editorPaneId = await getCurrentPaneId$3();
1763
- if (!await isEditorPane$1(editorPaneId)) {
1764
- logger$4.error("Current pane is not an editor pane. Please run this command from an editor pane or specify --editor-pane.");
1765
- process.exit(1);
1766
- }
1767
- } else if (options.mux === "rex") {
1768
- editorPaneId = await getCurrentPaneId$2();
1769
- if (!isEditorPaneFromConf$1(editorPaneId)) {
1975
+ else {
1976
+ editorPaneId = await backend.getCurrentPaneId();
1977
+ if (!await isEditorPane(backend, editorPaneId)) {
1770
1978
  logger$4.error("Current pane is not an editor pane. Please run this command from an editor pane or specify --editor-pane.");
1771
1979
  process.exit(1);
1772
1980
  }
1773
- } else {
1774
- logger$4.error("Unsupported multiplexer");
1775
- process.exit(1);
1776
1981
  }
1777
1982
  try {
1778
- let existingPanes = [];
1779
- if (options.mux === "tmux") existingPanes = await getTargetPaneIds$1(editorPaneId);
1780
- else if (options.mux === "wezterm") existingPanes = await getTargetPaneIds(editorPaneId);
1781
- else if (options.mux === "herdr") existingPanes = await getTargetPaneIds$4(editorPaneId);
1782
- else if (options.mux === "niwaterm") existingPanes = await getTargetPaneIds$3(editorPaneId);
1783
- else if (options.mux === "rex") existingPanes = await getTargetPaneIds$2(editorPaneId);
1983
+ const existingPanes = await getTargetPaneIds(backend, editorPaneId);
1784
1984
  const mergedTargetPanes = [...new Set([...existingPanes, ...options.targetPanes])];
1785
- if (options.mux === "tmux") await markAsEditorPane$1(editorPaneId, mergedTargetPanes);
1786
- else if (options.mux === "wezterm") await markAsEditorPane(editorPaneId, mergedTargetPanes);
1787
- else if (options.mux === "herdr") await markAsEditorPane$4(editorPaneId, mergedTargetPanes);
1788
- else if (options.mux === "niwaterm") await markAsEditorPane$3(editorPaneId, mergedTargetPanes);
1789
- else if (options.mux === "rex") await markAsEditorPane$2(editorPaneId, mergedTargetPanes);
1985
+ await markAsEditorPane(backend, editorPaneId, mergedTargetPanes);
1790
1986
  logger$4.info(`Editor pane ${editorPaneId} registered with target panes: ${mergedTargetPanes.join(", ")}`);
1791
1987
  } catch (error) {
1792
1988
  logger$4.error(`${error instanceof Error ? error.message : "Unknown error"}`);
@@ -1823,130 +2019,74 @@ const registerCommand = define({
1823
2019
  });
1824
2020
 
1825
2021
  //#endregion
1826
- //#region src/modes/resume.ts
1827
- const logger$3 = getLogger(["editprompt", "resume"]);
1828
- async function runResumeMode(targetPane, mux) {
1829
- if (mux === "herdr") try {
1830
- process.exit(await resumeEditorPane$1(targetPane) ? 0 : 1);
1831
- } catch (error) {
1832
- logger$3.debug("Herdr resume failed: {error}", { error });
1833
- process.exit(1);
1834
- }
1835
- if (mux === "rex") try {
1836
- process.exit(await resumeEditorPane(targetPane) ? 0 : 1);
1837
- } catch (error) {
1838
- logger$3.debug("Rex resume failed: {error}", { error });
1839
- process.exit(1);
2022
+ //#region src/core/resume.ts
2023
+ /** Raised when focusing the editor pane from its target pane fails. */
2024
+ var EditorFocusError = class extends Error {
2025
+ name = "EditorFocusError";
2026
+ constructor(editorPaneId, cause) {
2027
+ super(`Can't focus editor pane ${editorPaneId}: ${cause instanceof Error ? cause.message : String(cause)}`);
2028
+ this.editorPaneId = editorPaneId;
2029
+ this.cause = cause;
1840
2030
  }
1841
- if (mux === "niwaterm") {
1842
- const currentPaneId = await getCurrentPaneId$3();
1843
- if (await isEditorPane$1(currentPaneId)) {
1844
- logger$3.debug("Current pane is an editor pane");
1845
- const originalTargetPaneIds = await getTargetPaneIds$3(currentPaneId);
1846
- if (originalTargetPaneIds.length === 0) {
1847
- logger$3.debug("No target pane IDs found for editor pane");
1848
- process.exit(1);
1849
- }
1850
- let focused = false;
1851
- for (const paneId of originalTargetPaneIds) if (await checkPaneExists$3(paneId)) {
1852
- await focusPane$3(paneId);
1853
- focused = true;
1854
- break;
1855
- }
1856
- if (!focused) {
1857
- logger$3.debug("All target panes do not exist");
1858
- process.exit(1);
1859
- }
1860
- process.exit(0);
1861
- }
1862
- logger$3.debug("Current pane is not an editor pane");
1863
- const editorPaneId = await getEditorPaneId$3(targetPane);
1864
- logger$3.debug("niwaterm editorPaneId: {editorPaneId}", { editorPaneId });
1865
- if (editorPaneId === "") {
1866
- logger$3.debug("Editor pane ID not found");
1867
- process.exit(1);
1868
- }
1869
- if (!await checkPaneExists$3(editorPaneId)) {
1870
- logger$3.debug("Editor pane does not exist");
1871
- await clearEditorPaneId$3(targetPane);
1872
- process.exit(1);
2031
+ };
2032
+ async function switchEditorFocus(backend, targetPaneId, trace = () => {}) {
2033
+ const currentPaneId = await backend.getCurrentPaneId();
2034
+ const editorPaneId = await backend.state.isEditorPane(currentPaneId) ? currentPaneId : backend.resumeAcceptsEditorPane && await backend.state.isEditorPane(targetPaneId) ? targetPaneId : "";
2035
+ if (editorPaneId) {
2036
+ trace("Current pane is an editor pane");
2037
+ const targetPaneIds = await backend.state.getTargetPaneIds(editorPaneId);
2038
+ if (targetPaneIds.length === 0) {
2039
+ trace("No target pane IDs found for editor pane");
2040
+ return false;
1873
2041
  }
1874
- try {
1875
- await focusPane$3(editorPaneId);
1876
- process.exit(0);
1877
- } catch (error) {
1878
- logger$3.debug("Can't focus editorPaneId: {editorPaneId}, error: {error}", {
1879
- editorPaneId,
1880
- error
1881
- });
1882
- process.exit(1);
2042
+ for (const paneId of targetPaneIds) if (await backend.paneExists(paneId)) {
2043
+ await backend.focusPane(paneId);
2044
+ return true;
1883
2045
  }
2046
+ trace("All target panes do not exist");
2047
+ return false;
1884
2048
  }
1885
- if (mux === "wezterm") {
1886
- const currentPaneId = await getCurrentPaneId();
1887
- if (isEditorPaneFromConf(currentPaneId)) {
1888
- logger$3.debug("Current pane is an editor pane");
1889
- const originalTargetPaneIds = await getTargetPaneIds(currentPaneId);
1890
- if (originalTargetPaneIds.length === 0) {
1891
- logger$3.debug("No target pane IDs found for editor pane");
1892
- process.exit(1);
1893
- }
1894
- let focused = false;
1895
- for (const paneId of originalTargetPaneIds) if (await checkPaneExists(paneId)) {
1896
- await focusPane(paneId);
1897
- focused = true;
1898
- break;
1899
- }
1900
- if (!focused) {
1901
- logger$3.debug("All target panes do not exist");
1902
- process.exit(1);
1903
- }
1904
- process.exit(0);
1905
- }
1906
- logger$3.debug("Current pane is not an editor pane");
1907
- const editorPaneId = await getEditorPaneId(targetPane);
1908
- logger$3.debug("wezterm editorPaneId: {editorPaneId}", { editorPaneId });
1909
- if (editorPaneId === "") {
1910
- logger$3.debug("Editor pane ID not found");
1911
- process.exit(1);
1912
- }
1913
- if (!await checkPaneExists(editorPaneId)) {
1914
- logger$3.debug("Editor pane does not exist");
1915
- await clearEditorPaneId(targetPane);
1916
- process.exit(1);
1917
- }
1918
- try {
1919
- await focusPane(editorPaneId);
1920
- process.exit(0);
1921
- } catch (error) {
1922
- logger$3.debug("Can't focus editorPaneId: {editorPaneId}, error: {error}", {
1923
- editorPaneId,
1924
- error
1925
- });
1926
- process.exit(1);
1927
- }
2049
+ trace("Current pane is not an editor pane");
2050
+ const registeredEditorPaneId = await backend.state.getEditorPaneId(targetPaneId);
2051
+ trace(`${backend.name} editorPaneId: {editorPaneId}`, { editorPaneId: registeredEditorPaneId });
2052
+ if (registeredEditorPaneId === "") {
2053
+ trace("Editor pane ID not found");
2054
+ return false;
1928
2055
  }
1929
- const currentPaneId = await getCurrentPaneId$1();
1930
- if (await isEditorPane(currentPaneId)) {
1931
- const originalTargetPaneIds = await getTargetPaneIds$1(currentPaneId);
1932
- if (originalTargetPaneIds.length === 0) process.exit(1);
1933
- let focused = false;
1934
- for (const paneId of originalTargetPaneIds) if (await checkPaneExists$1(paneId)) {
1935
- await focusPane$1(paneId);
1936
- focused = true;
1937
- break;
1938
- }
1939
- if (!focused) process.exit(1);
1940
- process.exit(0);
2056
+ if (!await backend.paneExists(registeredEditorPaneId)) {
2057
+ trace("Editor pane does not exist");
2058
+ await clearEditorLink(backend, targetPaneId);
2059
+ return false;
1941
2060
  }
1942
- const editorPaneId = await getEditorPaneId$1(targetPane);
1943
- if (editorPaneId === "") process.exit(1);
1944
- if (!await checkPaneExists$1(editorPaneId)) {
1945
- await clearEditorPaneId$1(targetPane);
2061
+ try {
2062
+ await backend.focusPane(registeredEditorPaneId);
2063
+ } catch (error) {
2064
+ throw new EditorFocusError(registeredEditorPaneId, error);
2065
+ }
2066
+ return true;
2067
+ }
2068
+
2069
+ //#endregion
2070
+ //#region src/modes/resume.ts
2071
+ const logger$3 = getLogger(["editprompt", "resume"]);
2072
+ async function runResumeMode(targetPane, mux) {
2073
+ const backend = getBackend(mux);
2074
+ let switched;
2075
+ try {
2076
+ switched = await switchEditorFocus(backend, targetPane, backend.traceResume ? (message, properties) => logger$3.debug(message, properties) : void 0);
2077
+ } catch (error) {
2078
+ if (!(backend.quietResumeErrors === "all" || backend.quietResumeErrors === "editorFocus" && error instanceof EditorFocusError)) throw error instanceof EditorFocusError ? error.cause : error;
2079
+ if (error instanceof EditorFocusError) logger$3.debug("Can't focus editorPaneId: {editorPaneId}, error: {error}", {
2080
+ editorPaneId: error.editorPaneId,
2081
+ error: error.cause
2082
+ });
2083
+ else {
2084
+ const label = backend.name.charAt(0).toUpperCase() + backend.name.slice(1);
2085
+ logger$3.debug(`${label} resume failed: {error}`, { error });
2086
+ }
1946
2087
  process.exit(1);
1947
2088
  }
1948
- await focusPane$1(editorPaneId);
1949
- process.exit(0);
2089
+ process.exit(switched ? 0 : 1);
1950
2090
  }
1951
2091
  const resumeCommand = define({
1952
2092
  name: "resume",
@@ -1976,46 +2116,11 @@ async function runPressMode(key, delay = 0) {
1976
2116
  logger$2.error("Key is required");
1977
2117
  process.exit(1);
1978
2118
  }
1979
- const config = readSendConfig();
1980
- let currentPaneId;
1981
- let isEditor;
1982
- if (config.mux === "tmux") {
1983
- currentPaneId = await getCurrentPaneId$1();
1984
- isEditor = await isEditorPane(currentPaneId);
1985
- } else if (config.mux === "wezterm") {
1986
- currentPaneId = await getCurrentPaneId();
1987
- isEditor = isEditorPaneFromConf(currentPaneId);
1988
- } else if (config.mux === "niwaterm") {
1989
- currentPaneId = await getCurrentPaneId$3();
1990
- isEditor = await isEditorPane$1(currentPaneId);
1991
- } else if (config.mux === "rex") {
1992
- currentPaneId = await getCurrentPaneId$2();
1993
- isEditor = isEditorPaneFromConf$1(currentPaneId);
1994
- } else {
1995
- currentPaneId = await getCurrentPaneId$4();
1996
- isEditor = isEditorPaneFromConf$2(currentPaneId);
1997
- }
1998
- if (!isEditor) {
1999
- logger$2.error("Current pane is not an editor pane");
2000
- process.exit(1);
2001
- }
2002
- let targetPanes;
2003
- if (config.mux === "tmux") targetPanes = await getTargetPaneIds$1(currentPaneId);
2004
- else if (config.mux === "wezterm") targetPanes = await getTargetPaneIds(currentPaneId);
2005
- else if (config.mux === "niwaterm") targetPanes = await getTargetPaneIds$3(currentPaneId);
2006
- else if (config.mux === "rex") targetPanes = await getTargetPaneIds$2(currentPaneId);
2007
- else targetPanes = await getTargetPaneIds$4(currentPaneId);
2008
- if (targetPanes.length === 0) {
2009
- logger$2.error("No target panes registered for this editor pane");
2010
- process.exit(1);
2011
- }
2119
+ const backend = getBackend(readSendConfig().mux);
2120
+ const { targetPaneIds } = await requireEditorContext(backend, logger$2);
2012
2121
  let successCount = 0;
2013
- for (const targetPane of targetPanes) try {
2014
- if (config.mux === "wezterm") await sendKeyToWeztermPane(targetPane, key, delay);
2015
- else if (config.mux === "herdr") await sendKeyToHerdrPane(targetPane, key, delay);
2016
- else if (config.mux === "niwaterm") await sendKeyToNiwatermPane(targetPane, key, delay);
2017
- else if (config.mux === "rex") await sendKeyToRexPane(targetPane, key, delay);
2018
- else await sendKeyToTmuxPane(targetPane, key, delay);
2122
+ for (const targetPane of targetPaneIds) try {
2123
+ await sendKeyToPane(backend, targetPane, key, delay);
2019
2124
  successCount++;
2020
2125
  } catch (error) {
2021
2126
  logger$2.warn(`Failed to send key to pane ${targetPane}: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -2058,7 +2163,7 @@ const pressCommand = define({
2058
2163
  //#region src/modes/stash.ts
2059
2164
  const logger$1 = getLogger(["editprompt", "stash"]);
2060
2165
  function getStashKey(mux, targetPaneId) {
2061
- return `${mux === "herdr" ? getStorageNamespace() : mux}.targetPane.pane_${targetPaneId}.stash`;
2166
+ return `${getBackend(mux).storageNamespace()}.targetPane.pane_${targetPaneId}.stash`;
2062
2167
  }
2063
2168
  async function pushStash(mux, targetPaneId, content) {
2064
2169
  const key = (/* @__PURE__ */ new Date()).toISOString();
@@ -2100,41 +2205,10 @@ function dropStash(mux, targetPaneId, key) {
2100
2205
  }
2101
2206
  async function getTargetPaneForStash() {
2102
2207
  const config = readSendConfig();
2103
- let currentPaneId;
2104
- let isEditor;
2105
- if (config.mux === "tmux") {
2106
- currentPaneId = await getCurrentPaneId$1();
2107
- isEditor = await isEditorPane(currentPaneId);
2108
- } else if (config.mux === "wezterm") {
2109
- currentPaneId = await getCurrentPaneId();
2110
- isEditor = isEditorPaneFromConf(currentPaneId);
2111
- } else if (config.mux === "niwaterm") {
2112
- currentPaneId = await getCurrentPaneId$3();
2113
- isEditor = await isEditorPane$1(currentPaneId);
2114
- } else if (config.mux === "rex") {
2115
- currentPaneId = await getCurrentPaneId$2();
2116
- isEditor = isEditorPaneFromConf$1(currentPaneId);
2117
- } else {
2118
- currentPaneId = await getCurrentPaneId$4();
2119
- isEditor = isEditorPaneFromConf$2(currentPaneId);
2120
- }
2121
- if (!isEditor) {
2122
- logger$1.error("Current pane is not an editor pane");
2123
- process.exit(1);
2124
- }
2125
- let targetPanes;
2126
- if (config.mux === "tmux") targetPanes = await getTargetPaneIds$1(currentPaneId);
2127
- else if (config.mux === "wezterm") targetPanes = await getTargetPaneIds(currentPaneId);
2128
- else if (config.mux === "niwaterm") targetPanes = await getTargetPaneIds$3(currentPaneId);
2129
- else if (config.mux === "rex") targetPanes = await getTargetPaneIds$2(currentPaneId);
2130
- else targetPanes = await getTargetPaneIds$4(currentPaneId);
2131
- if (targetPanes.length === 0) {
2132
- logger$1.error("No target panes registered for this editor pane");
2133
- process.exit(1);
2134
- }
2208
+ const { targetPaneIds } = await requireEditorContext(getBackend(config.mux), logger$1);
2135
2209
  return {
2136
2210
  mux: config.mux,
2137
- targetPaneId: targetPanes[0]
2211
+ targetPaneId: targetPaneIds[0]
2138
2212
  };
2139
2213
  }
2140
2214
  async function runPush(rest, positionals) {
@@ -2276,18 +2350,21 @@ function buildOpenEditorArgv(options) {
2276
2350
  if (options.verbose) argv.push("--verbose");
2277
2351
  return argv;
2278
2352
  }
2279
- async function runToggleMode(options, herdrApi = herdr_exports) {
2280
- if (options.mux !== "herdr") throw new Error("toggle currently supports only the Herdr multiplexer");
2353
+ async function runToggleMode(options, backend = getBackend(options.mux)) {
2354
+ if (!canSplitPane(backend)) throw new Error(isBuiltinBackendName(backend.name) ? "toggle currently supports only the Herdr multiplexer" : `toggle is not supported by the '${backend.name}' multiplexer because it cannot split panes`);
2281
2355
  if (!Number.isInteger(options.paneRows) || options.paneRows <= 0) throw new Error("--pane-rows must be a positive integer");
2282
- if (await herdrApi.resumeEditorPane(options.targetPane)) return;
2356
+ if (await switchEditorFocus(backend, options.targetPane)) return;
2283
2357
  const launchEnv = {};
2284
2358
  if (process.env.NODE_NO_WARNINGS !== void 0) launchEnv.NODE_NO_WARNINGS = process.env.NODE_NO_WARNINGS;
2285
- const editorPaneId = await herdrApi.splitEditorPane(options.targetPane, options.paneRows, process.env.HERDR_ACTIVE_PANE_CWD, launchEnv);
2359
+ const editorPaneId = await backend.splitPane(options.targetPane, {
2360
+ rows: options.paneRows,
2361
+ env: launchEnv
2362
+ });
2286
2363
  try {
2287
- await herdrApi.runCommandInPane(editorPaneId, buildOpenEditorArgv(options));
2364
+ await backend.runInPane(editorPaneId, buildOpenEditorArgv(options));
2288
2365
  } catch (error) {
2289
2366
  try {
2290
- await herdrApi.closePane(editorPaneId);
2367
+ await backend.closePane(editorPaneId);
2291
2368
  } catch (closeError) {
2292
2369
  logger.warn("Failed to close editor pane after launch failure: {error}", { error: closeError });
2293
2370
  }
@@ -2344,7 +2421,9 @@ const toggleCommand = define({
2344
2421
 
2345
2422
  //#endregion
2346
2423
  //#region src/index.ts
2347
- await cli(process.argv.slice(2), {
2424
+ const argv = process.argv.slice(2);
2425
+ if (mayUseConfiguredBackend(argv)) await setupUserConfig();
2426
+ await cli(argv, {
2348
2427
  name: "editprompt",
2349
2428
  description: "A CLI tool that lets you write prompts for CLI tools using your favorite text editor",
2350
2429
  args: {},