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.
- package/README.md +21 -4
- package/dist/index.js +1057 -978
- 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
|
|
16
|
-
|
|
17
|
-
|
|
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/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
level: "ABBR"
|
|
61
|
+
function setField(key, field, value) {
|
|
62
|
+
conf.set(key, {
|
|
63
|
+
...readRecord(key),
|
|
64
|
+
[field]: value
|
|
56
65
|
});
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
|
93
|
-
|
|
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
|
|
305
|
-
|
|
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
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
386
|
-
|
|
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
|
-
|
|
396
|
-
|
|
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
|
-
|
|
401
|
-
|
|
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
|
|
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
|
|
487
|
-
|
|
488
|
-
|
|
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:
|
|
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
|
|
581
|
-
|
|
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
|
|
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
|
|
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 '${
|
|
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
|
|
752
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
768
|
-
|
|
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:
|
|
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
|
|
800
|
-
|
|
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
|
|
808
|
-
|
|
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
|
|
818
|
-
|
|
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
|
-
|
|
827
|
-
|
|
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
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
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
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
-
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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
|
-
|
|
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")
|
|
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 === "
|
|
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/
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
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
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
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/
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
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/
|
|
1346
|
-
const logger$
|
|
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
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
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(
|
|
1597
|
+
async function focusFirstSuccessPane(backend, targetPanes, failedPanes) {
|
|
1358
1598
|
const firstSuccessPane = targetPanes.find((p) => !failedPanes.includes(p));
|
|
1359
|
-
if (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,
|
|
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$
|
|
1612
|
+
logger$8.info("Content copied to clipboard.");
|
|
1377
1613
|
} catch (error) {
|
|
1378
|
-
logger$
|
|
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
|
|
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$
|
|
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$
|
|
1642
|
+
if (allSuccess) logger$8.info("Content sent successfully to all panes!");
|
|
1407
1643
|
else if (allFailed) {
|
|
1408
|
-
logger$
|
|
1409
|
-
logger$
|
|
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$
|
|
1412
|
-
} else logger$
|
|
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
|
-
|
|
1433
|
-
|
|
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 ||
|
|
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
|
-
|
|
1470
|
-
|
|
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,
|
|
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(
|
|
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
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
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,
|
|
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(
|
|
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
|
|
1688
|
-
|
|
1689
|
-
|
|
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
|
|
1744
|
-
editorPaneId = await getCurrentPaneId
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
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
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
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
|
-
|
|
1875
|
-
await focusPane
|
|
1876
|
-
|
|
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
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
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
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
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
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
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
|
-
|
|
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
|
|
1980
|
-
|
|
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
|
|
2014
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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,
|
|
2280
|
-
if (
|
|
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
|
|
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
|
|
2359
|
+
const editorPaneId = await backend.splitPane(options.targetPane, {
|
|
2360
|
+
rows: options.paneRows,
|
|
2361
|
+
env: launchEnv
|
|
2362
|
+
});
|
|
2286
2363
|
try {
|
|
2287
|
-
await
|
|
2364
|
+
await backend.runInPane(editorPaneId, buildOpenEditorArgv(options));
|
|
2288
2365
|
} catch (error) {
|
|
2289
2366
|
try {
|
|
2290
|
-
await
|
|
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
|
-
|
|
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: {},
|