editprompt 0.4.4 → 0.5.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 +60 -27
- package/dist/index.js +123 -38
- package/package.json +62 -62
package/README.md
CHANGED
|
@@ -34,22 +34,17 @@ npx editprompt
|
|
|
34
34
|
|
|
35
35
|
## 🚀 Usage
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
- Run `editprompt` to open a temporary Markdown file in your editor
|
|
38
|
+
- Write your prompt comfortably with full editor features
|
|
39
|
+
- Save and close - it automatically:
|
|
40
|
+
- Sends to tmux/wezterm panes if detected
|
|
41
|
+
- Falls back to clipboard otherwise (works with **any terminal**)
|
|
42
42
|
|
|
43
43
|
```sh
|
|
44
|
-
# Just run it - content will be copied to clipboard
|
|
45
44
|
editprompt
|
|
46
|
-
# Then paste (Ctrl+V / Cmd+V) into any CLI tool:
|
|
47
|
-
# - Claude Code
|
|
48
|
-
# - Codex
|
|
49
|
-
# - Any REPL or interactive prompt
|
|
50
45
|
```
|
|
51
46
|
|
|
52
|
-
|
|
47
|
+
**Advanced usage:** You can also send content **without closing the editor** for faster iteration. See `Send Without Closing Editor` for details.
|
|
53
48
|
|
|
54
49
|
|
|
55
50
|
### 🖥️ Tmux Integration
|
|
@@ -117,6 +112,59 @@ editprompt --help
|
|
|
117
112
|
```
|
|
118
113
|
|
|
119
114
|
|
|
115
|
+
## 📤 Send Without Closing Editor
|
|
116
|
+
|
|
117
|
+
While editprompt is running, you can send content to the target pane or clipboard without closing the editor. This allows you to iterate quickly on your prompts.
|
|
118
|
+
|
|
119
|
+
### Basic Usage
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Run this command from within your editor session
|
|
123
|
+
editprompt -- "your content here"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This sends the content to the target pane (or clipboard) while keeping your editor open, so you can continue editing and send multiple times.
|
|
127
|
+
|
|
128
|
+
### Neovim Integration Example
|
|
129
|
+
|
|
130
|
+
You can set up a convenient keybinding to send your buffer content:
|
|
131
|
+
|
|
132
|
+
```lua
|
|
133
|
+
-- Send buffer content while keeping the editor open
|
|
134
|
+
if vim.env.EDITPROMPT then
|
|
135
|
+
vim.keymap.set("n", "<Space>x", function()
|
|
136
|
+
vim.cmd("silent write")
|
|
137
|
+
-- Get buffer content
|
|
138
|
+
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
|
139
|
+
local content = table.concat(lines, "\n")
|
|
140
|
+
|
|
141
|
+
-- Execute editprompt command
|
|
142
|
+
vim.system(
|
|
143
|
+
{ "editprompt", "--", content },
|
|
144
|
+
{ text = true },
|
|
145
|
+
function(obj)
|
|
146
|
+
vim.schedule(function()
|
|
147
|
+
if obj.code == 0 then
|
|
148
|
+
-- Clear buffer on success
|
|
149
|
+
vim.api.nvim_buf_set_lines(0, 0, -1, false, {})
|
|
150
|
+
else
|
|
151
|
+
-- Show error notification
|
|
152
|
+
vim.notify("editprompt failed: " .. (obj.stderr or "unknown error"), vim.log.levels.ERROR)
|
|
153
|
+
end
|
|
154
|
+
end)
|
|
155
|
+
end
|
|
156
|
+
)
|
|
157
|
+
end, { silent = true, desc = "Send buffer content to editprompt" })
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
With this configuration:
|
|
162
|
+
1. Open editprompt using the tmux/wezterm keybinding mentioned above
|
|
163
|
+
2. Write your prompt in the editor
|
|
164
|
+
3. Press `<Space>x` to send the content to the target pane
|
|
165
|
+
4. The buffer is automatically cleared on success
|
|
166
|
+
5. Continue editing to send more content
|
|
167
|
+
|
|
120
168
|
|
|
121
169
|
## ⚙️ Configuration
|
|
122
170
|
|
|
@@ -126,7 +174,7 @@ editprompt respects the following editor priority:
|
|
|
126
174
|
|
|
127
175
|
1. `--editor/-e` command line option
|
|
128
176
|
2. `$EDITOR` environment variable
|
|
129
|
-
3. Default: `
|
|
177
|
+
3. Default: `vim`
|
|
130
178
|
|
|
131
179
|
### 🌍 Environment Variables
|
|
132
180
|
|
|
@@ -184,21 +232,6 @@ bun test
|
|
|
184
232
|
bun run dev
|
|
185
233
|
```
|
|
186
234
|
|
|
187
|
-
### 📁 Project Structure
|
|
188
|
-
|
|
189
|
-
```
|
|
190
|
-
src/
|
|
191
|
-
├── config/
|
|
192
|
-
│ └── constants.ts # Configuration constants
|
|
193
|
-
├── modules/
|
|
194
|
-
│ ├── editor.ts # Editor launching and file handling
|
|
195
|
-
│ ├── process.ts # Process detection and communication
|
|
196
|
-
│ └── selector.ts # Interactive process selection
|
|
197
|
-
├── utils/
|
|
198
|
-
│ └── tempFile.ts # Temporary file management
|
|
199
|
-
└── index.ts # CLI entry point
|
|
200
|
-
```
|
|
201
|
-
|
|
202
235
|
## 🔍 Technical Details
|
|
203
236
|
|
|
204
237
|
### 🔄 Fallback Strategy
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { promisify } from "node:util";
|
|
|
8
8
|
import clipboardy from "clipboardy";
|
|
9
9
|
|
|
10
10
|
//#region package.json
|
|
11
|
-
var version = "0.
|
|
11
|
+
var version = "0.5.0";
|
|
12
12
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/config/constants.ts
|
|
@@ -16,12 +16,20 @@ const TEMP_FILE_PREFIX = ".editprompt-";
|
|
|
16
16
|
const TEMP_FILE_EXTENSION = ".md";
|
|
17
17
|
const DEFAULT_EDITOR = "vim";
|
|
18
18
|
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/utils/contentProcessor.ts
|
|
21
|
+
function processContent(content) {
|
|
22
|
+
let processed = content.replace(/\n$/, "");
|
|
23
|
+
if (/@[^\n]*$/.test(processed)) processed += " ";
|
|
24
|
+
return processed;
|
|
25
|
+
}
|
|
26
|
+
|
|
19
27
|
//#endregion
|
|
20
28
|
//#region src/utils/envParser.ts
|
|
21
29
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @param envStrings - ["KEY=VALUE", "FOO=bar"]
|
|
24
|
-
* @returns
|
|
30
|
+
* Parses environment variable strings into an object.
|
|
31
|
+
* @param envStrings - An array of strings in the format ["KEY=VALUE", "FOO=bar"].
|
|
32
|
+
* @returns An object of environment variable key-value pairs.
|
|
25
33
|
*/
|
|
26
34
|
function parseEnvVars(envStrings) {
|
|
27
35
|
if (!envStrings || envStrings.length === 0) return {};
|
|
@@ -61,11 +69,18 @@ async function createTempFile() {
|
|
|
61
69
|
function getEditor(editorOption) {
|
|
62
70
|
return editorOption || process.env.EDITOR || DEFAULT_EDITOR;
|
|
63
71
|
}
|
|
64
|
-
async function launchEditor(editor, filePath, envVars) {
|
|
72
|
+
async function launchEditor(editor, filePath, envVars, sendConfig) {
|
|
65
73
|
return new Promise((resolve, reject) => {
|
|
74
|
+
const configEnv = {};
|
|
75
|
+
if (sendConfig) {
|
|
76
|
+
if (sendConfig.targetPane) configEnv.EDITPROMPT_TARGET_PANE = sendConfig.targetPane;
|
|
77
|
+
configEnv.EDITPROMPT_MUX = sendConfig.mux;
|
|
78
|
+
configEnv.EDITPROMPT_ALWAYS_COPY = sendConfig.alwaysCopy ? "1" : "0";
|
|
79
|
+
}
|
|
66
80
|
const processEnv = {
|
|
67
81
|
...process.env,
|
|
68
82
|
EDITPROMPT: "1",
|
|
83
|
+
...configEnv,
|
|
69
84
|
...envVars
|
|
70
85
|
};
|
|
71
86
|
const editorProcess = spawn(editor, [filePath], {
|
|
@@ -84,20 +99,18 @@ async function launchEditor(editor, filePath, envVars) {
|
|
|
84
99
|
}
|
|
85
100
|
async function readFileContent(filePath) {
|
|
86
101
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (/@[^\n]*$/.test(content)) content += " ";
|
|
90
|
-
return content;
|
|
102
|
+
const content = await readFile(filePath, "utf-8");
|
|
103
|
+
return processContent(content);
|
|
91
104
|
} catch (error) {
|
|
92
105
|
throw new Error(`Failed to read file: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
93
106
|
}
|
|
94
107
|
}
|
|
95
|
-
async function openEditorAndGetContent(editorOption, envVars) {
|
|
108
|
+
async function openEditorAndGetContent(editorOption, envVars, sendConfig) {
|
|
96
109
|
const tempFilePath = await createTempFile();
|
|
97
110
|
const editor = getEditor(editorOption);
|
|
98
111
|
const parsedEnvVars = parseEnvVars(envVars);
|
|
99
112
|
try {
|
|
100
|
-
await launchEditor(editor, tempFilePath, parsedEnvVars);
|
|
113
|
+
await launchEditor(editor, tempFilePath, parsedEnvVars, sendConfig);
|
|
101
114
|
const content = await readFileContent(tempFilePath);
|
|
102
115
|
return content;
|
|
103
116
|
} catch (error) {
|
|
@@ -121,11 +134,12 @@ async function sendToTmuxPane(paneId, content) {
|
|
|
121
134
|
async function sendToWeztermPane(paneId, content) {
|
|
122
135
|
await execAsync(`wezterm cli send-text --no-paste --pane-id '${paneId}' -- '${content.replace(/'/g, "'\\''")}'`);
|
|
123
136
|
console.log(`Content sent to wezterm pane: ${paneId}`);
|
|
137
|
+
await execAsync(`wezterm cli activate-pane --pane-id '${paneId}'`);
|
|
124
138
|
}
|
|
125
139
|
async function copyToClipboard(content) {
|
|
126
140
|
await clipboardy.write(content);
|
|
127
141
|
}
|
|
128
|
-
async function sendContentToPane(
|
|
142
|
+
async function sendContentToPane(content, mux, targetPaneId, alwaysCopy) {
|
|
129
143
|
try {
|
|
130
144
|
if (mux === "wezterm") await sendToWeztermPane(targetPaneId, content);
|
|
131
145
|
else await sendToTmuxPane(targetPaneId, content);
|
|
@@ -139,6 +153,91 @@ async function sendContentToPane(targetPaneId, content, mux = "tmux", alwaysCopy
|
|
|
139
153
|
}
|
|
140
154
|
}
|
|
141
155
|
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/modes/common.ts
|
|
158
|
+
function outputContent(content) {
|
|
159
|
+
console.log("---");
|
|
160
|
+
console.log(content);
|
|
161
|
+
}
|
|
162
|
+
async function handleContentDelivery(content, mux, targetPane, alwaysCopy) {
|
|
163
|
+
if (!content) return;
|
|
164
|
+
if (targetPane) try {
|
|
165
|
+
await sendContentToPane(content, mux, targetPane, alwaysCopy);
|
|
166
|
+
console.log("Content sent successfully!");
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.log(`Failed to send to pane: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
169
|
+
console.log("Falling back to clipboard...");
|
|
170
|
+
await copyToClipboard(content);
|
|
171
|
+
console.log("Content copied to clipboard.");
|
|
172
|
+
}
|
|
173
|
+
else try {
|
|
174
|
+
await copyToClipboard(content);
|
|
175
|
+
console.log("Content copied to clipboard.");
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.log(`Failed to copy to clipboard: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
178
|
+
}
|
|
179
|
+
outputContent(content);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/modes/openEditor.ts
|
|
184
|
+
async function runOpenEditorMode(options) {
|
|
185
|
+
const sendConfig = {
|
|
186
|
+
targetPane: options.targetPane,
|
|
187
|
+
mux: options.mux,
|
|
188
|
+
alwaysCopy: options.alwaysCopy
|
|
189
|
+
};
|
|
190
|
+
console.log("Opening editor...");
|
|
191
|
+
const content = await openEditorAndGetContent(options.editor, options.env, sendConfig);
|
|
192
|
+
if (!content) {
|
|
193
|
+
console.log("No content entered. Exiting.");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
await handleContentDelivery(content, options.mux, options.targetPane, options.alwaysCopy);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/utils/sendConfig.ts
|
|
206
|
+
const VALID_MUX_TYPES = ["tmux", "wezterm"];
|
|
207
|
+
function readSendConfig() {
|
|
208
|
+
const targetPane = process.env.EDITPROMPT_TARGET_PANE;
|
|
209
|
+
const muxValue = process.env.EDITPROMPT_MUX || "tmux";
|
|
210
|
+
if (!VALID_MUX_TYPES.includes(muxValue)) throw new Error(`Invalid EDITPROMPT_MUX value: ${muxValue}. Must be one of: ${VALID_MUX_TYPES.join(", ")}`);
|
|
211
|
+
const mux = muxValue;
|
|
212
|
+
const alwaysCopy = process.env.EDITPROMPT_ALWAYS_COPY === "1";
|
|
213
|
+
return {
|
|
214
|
+
targetPane,
|
|
215
|
+
mux,
|
|
216
|
+
alwaysCopy
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/modes/sendOnly.ts
|
|
222
|
+
async function runSendOnlyMode(rawContent) {
|
|
223
|
+
const content = processContent(rawContent);
|
|
224
|
+
if (!content) {
|
|
225
|
+
console.log("No content to send. Exiting.");
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const config = readSendConfig();
|
|
229
|
+
if (!config.targetPane) {
|
|
230
|
+
console.error("Error: EDITPROMPT_TARGET_PANE environment variable is required in send-only mode");
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
await handleContentDelivery(content, config.mux, config.targetPane, config.alwaysCopy);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
console.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
142
241
|
//#endregion
|
|
143
242
|
//#region src/index.ts
|
|
144
243
|
const argv = process.argv.slice(2);
|
|
@@ -179,6 +278,11 @@ await cli(argv, {
|
|
|
179
278
|
},
|
|
180
279
|
async run(ctx) {
|
|
181
280
|
try {
|
|
281
|
+
const rawContent = ctx.positionals[0];
|
|
282
|
+
if (rawContent !== void 0) {
|
|
283
|
+
await runSendOnlyMode(rawContent);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
182
286
|
const muxValue = ctx.values.mux || "tmux";
|
|
183
287
|
if (!isMuxType(muxValue)) {
|
|
184
288
|
console.error(`Error: Invalid mux type '${muxValue}'. Supported values: tmux, wezterm`);
|
|
@@ -193,32 +297,13 @@ await cli(argv, {
|
|
|
193
297
|
console.warn("Warning: --process option is deprecated and will be removed in future versions.");
|
|
194
298
|
console.warn("Use --target-pane to specify the target pane directly.");
|
|
195
299
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const alwaysCopy = ctx.values["always-copy"];
|
|
204
|
-
if (targetPane) try {
|
|
205
|
-
console.log("Sending content to specified pane...");
|
|
206
|
-
await sendContentToPane(targetPane, content, mux, alwaysCopy);
|
|
207
|
-
console.log("Content sent successfully!");
|
|
208
|
-
} catch (error) {
|
|
209
|
-
console.log(`Failed to send to pane: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
210
|
-
console.log("Falling back to clipboard...");
|
|
211
|
-
await copyToClipboard(content);
|
|
212
|
-
console.log("Content copied to clipboard.");
|
|
213
|
-
}
|
|
214
|
-
else try {
|
|
215
|
-
await copyToClipboard(content);
|
|
216
|
-
console.log("Content copied to clipboard.");
|
|
217
|
-
} catch (error) {
|
|
218
|
-
console.log(`Failed to copy to clipboard: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
219
|
-
}
|
|
220
|
-
console.log("---");
|
|
221
|
-
console.log(content);
|
|
300
|
+
await runOpenEditorMode({
|
|
301
|
+
mux,
|
|
302
|
+
targetPane: ctx.values["target-pane"],
|
|
303
|
+
alwaysCopy: ctx.values["always-copy"] || false,
|
|
304
|
+
editor: ctx.values.editor,
|
|
305
|
+
env: ctx.values.env
|
|
306
|
+
});
|
|
222
307
|
} catch (error) {
|
|
223
308
|
console.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
224
309
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
2
|
+
"name": "editprompt",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"author": "eetann",
|
|
5
|
+
"description": "A CLI tool that lets you write prompts for CLI tools using your favorite text editor",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/eetann/editprompt.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/eetann/editprompt",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/eetann/editprompt/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"cli",
|
|
17
|
+
"editor",
|
|
18
|
+
"prompt",
|
|
19
|
+
"tmux",
|
|
20
|
+
"clipboard",
|
|
21
|
+
"command-line",
|
|
22
|
+
"text-editor"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"files": ["dist"],
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"module": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": "./dist/index.js",
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"bin": {
|
|
37
|
+
"editprompt": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsdown",
|
|
41
|
+
"dev": "tsdown --watch",
|
|
42
|
+
"lint": "biome check",
|
|
43
|
+
"formant": "biome format --write",
|
|
44
|
+
"typecheck": "tsgo --noEmit",
|
|
45
|
+
"release": "bun run lint && bun run typecheck && bun run test && bun run build && bumpp",
|
|
46
|
+
"test": "bun test",
|
|
47
|
+
"test:watch": "bun test --watch"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@biomejs/biome": "1.9.4",
|
|
51
|
+
"@types/bun": "^1.2.16",
|
|
52
|
+
"@types/node": "^24.0.1",
|
|
53
|
+
"@typescript/native-preview": "^7.0.0-dev.20250712.1",
|
|
54
|
+
"bumpp": "^10.2.0",
|
|
55
|
+
"tsdown": "latest"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"clipboardy": "^4.0.0",
|
|
59
|
+
"gunshi": "^0.26.3"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"typescript": "^5"
|
|
63
|
+
}
|
|
64
64
|
}
|