editprompt 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +86 -83
  2. package/dist/index.js +54 -159
  3. package/package.json +2 -6
package/README.md CHANGED
@@ -1,19 +1,28 @@
1
- # editprompt
1
+ # 📝 editprompt
2
2
 
3
- A CLI tool that lets you write prompts for CLI tools using your favorite text editor. Originally designed for [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview), but works with any CLI process.
3
+ A CLI tool that lets you write prompts for CLI tools using your favorite text editor. Works seamlessly with Claude Code, Codex CLI, Gemini CLI, and any other CLI process.
4
4
 
5
5
  https://github.com/user-attachments/assets/01bcda7c-7771-4b33-bf5c-629812d45cc4
6
6
 
7
- ## Features
8
7
 
9
- - 🖊️ **Editor Integration**: Use your preferred text editor to write prompts
10
- - 🔍 **Process Detection**: Automatically detects running CLI processes (configurable)
11
- - 🖥️ **Tmux Support**: Send prompts directly to tmux sessions
8
+ ## 🏆 Why editprompt?
9
+
10
+ - **🎯 Your Editor, Your Way**: Write prompts in your favorite editor with full syntax highlighting, plugins, and customizations
11
+ - **🚫 No Accidental Sends**: Never accidentally hit Enter and send an incomplete prompt again
12
+ - 🔄 **Reusable Prompts**: Save and iterate on prompts with `--always-copy`
13
+ - 📝 **Multi-line Commands**: Complex SQL queries, JSON payloads
14
+
15
+
16
+ ## ✨ Features
17
+
18
+ - 🖊️ **Editor Integration**: Use your preferred text editor to write prompts
19
+ - 🖥️ **Multiplexer Support**: Send prompts directly to tmux or WezTerm sessions
20
+ - 🖥️ **Universal Terminal Support**: Works with any terminal via clipboard - no multiplexer required
12
21
  - 📋 **Clipboard Fallback**: Automatically copies to clipboard if sending fails
13
22
  - 📋 **Always Copy Option**: Copy to clipboard even after successful tmux delivery (`--always-copy`)
14
- - ⚡ **Smart Fallbacks**: Multiple fallback strategies ensure your prompt gets delivered
15
23
 
16
- ## Installation
24
+
25
+ ## 📦 Installation
17
26
 
18
27
  ```bash
19
28
  # Install globally via npm
@@ -23,52 +32,33 @@ npm install -g editprompt
23
32
  npx editprompt
24
33
  ```
25
34
 
26
- ## Usage
27
-
28
- ### Basic Usage
29
-
30
- ```bash
31
- # Use with your default editor (from $EDITOR)
32
- editprompt
33
-
34
- # Specify a different editor
35
- editprompt --editor nvim
36
- editprompt -e nvim
35
+ ## 🚀 Usage
37
36
 
38
- # Target a different process (default: claude)
39
- editprompt --process gemini
40
- editprompt -p gemini
37
+ 1. Run `editprompt` to open a temporary Markdown file in your editor
38
+ 2. Write your prompt and save the file
39
+ 3. Your prompt is automatically sent to the target pane or copied to clipboard if no pane is found
41
40
 
42
- # Send content to a specific tmux pane
43
- editprompt --target-pane %45
44
- editprompt -t %45
41
+ editprompt works with **any terminal** - no special setup required!
45
42
 
46
- # Set environment variables for the editor
47
- editprompt --env THEME=dark
48
- editprompt -E THEME=dark -E LANG=ja_JP.UTF-8
49
-
50
- # Always copy to clipboard after sending to tmux pane
51
- editprompt --always-copy
52
-
53
- # Show help
54
- editprompt --help
55
-
56
- # Show version
57
- editprompt --version
43
+ ```sh
44
+ # Just run it - content will be copied to clipboard
45
+ editprompt
46
+ # Then paste (Ctrl+V / Cmd+V) into any CLI tool:
47
+ # - Claude Code
48
+ # - Codex
49
+ # - Any REPL or interactive prompt
58
50
  ```
59
51
 
60
- ### Tmux Integration
52
+ Optional integrations (Tmux/Wezterm) provide seamless auto-send.
61
53
 
62
- editprompt offers two modes for tmux integration:
63
54
 
64
- #### Recommended: Direct Pane Targeting
65
- Use `--target-pane #{pane_id}` to automatically send content back to the pane where you triggered the command. This is useful when using Claude Code, etc. in multiple panes.
55
+ ### 🖥️ Tmux Integration
66
56
 
67
57
  **Split window version:**
68
58
  ```tmux
69
59
  bind -n M-q run-shell 'tmux split-window -v -l 20 \
70
- -c "#{pane_current_path}" \
71
- "editprompt --editor nvim --target-pane #{pane_id}"'
60
+ -c "#{pane_current_path}" \
61
+ "editprompt --editor nvim --always-copy --target-pane #{pane_id}"'
72
62
  ```
73
63
 
74
64
  **Popup version:**
@@ -76,41 +66,61 @@ bind -n M-q run-shell 'tmux split-window -v -l 20 \
76
66
  bind -n M-q run-shell 'tmux display-popup -E \
77
67
  -d "#{pane_current_path}" \
78
68
  -w 80% -h 65% \
79
- "editprompt --editor nvim --target-pane #{pane_id}"'
69
+ "editprompt --editor nvim --always-copy --target-pane #{pane_id}"'
80
70
  ```
81
71
 
82
- #### Alternative: Process Auto-detection
83
- Let editprompt automatically detect and select target processes:
84
72
 
85
- **Split window version:**
86
- ```tmux
87
- bind -n M-q split-window -v -l 10 \
88
- -c '#{pane_current_path}' \
89
- 'editprompt --editor nvim'
73
+ ### 🖼️ WezTerm Integration
74
+ ```lua
75
+ {
76
+ key = "q",
77
+ mods = "OPT",
78
+ action = wezterm.action_callback(function(window, pane)
79
+ local target_pane_id = tostring(pane:pane_id())
80
+ window:perform_action(
81
+ act.SplitPane({
82
+ direction = "Down",
83
+ size = { Cells = 10 },
84
+ }),
85
+ pane
86
+ )
87
+ wezterm.time.call_after(1, function()
88
+ window:perform_action(
89
+ act.SendString(
90
+ string.format(
91
+ "editprompt --editor nvim --always-copy --mux wezterm --target-pane %s\n",
92
+ target_pane_id
93
+ )
94
+ ),
95
+ window:active_pane()
96
+ )
97
+ end)
98
+ end),
99
+ },
90
100
  ```
91
101
 
92
- **Popup version:**
93
- ```tmux
94
- bind -n M-q display-popup -E \
95
- -d '#{pane_current_path}' \
96
- 'editprompt --editor nvim'
97
- ```
102
+ ### 💡 Basic Usage
103
+
104
+ ```bash
105
+ # Use with your default editor (from $EDITOR)
106
+ editprompt
107
+
108
+ # Specify a different editor
109
+ editprompt --editor nvim
110
+ editprompt -e nvim
98
111
 
99
- ### How it Works
112
+ # Always copy to clipboard
113
+ editprompt --always-copy
114
+
115
+ # Show help
116
+ editprompt --help
117
+ ```
100
118
 
101
- 1. **Opens your editor** with a temporary markdown file
102
- 2. **Write your prompt** and save/exit the editor
103
- 3. **Sends the prompt** using one of two modes:
104
- - 🎯 **Direct pane mode** (`--target-pane`): Sends directly to specified tmux pane
105
- - 🔍 **Process detection mode**: Finds target processes and sends via tmux or clipboard
106
- 4. **Fallback strategy** ensures delivery:
107
- - Tmux integration (preferred)
108
- - Clipboard copy (fallback)
109
119
 
110
120
 
111
- ## Configuration
121
+ ## ⚙️ Configuration
112
122
 
113
- ### Editor Selection
123
+ ### 📝 Editor Selection
114
124
 
115
125
  editprompt respects the following editor priority:
116
126
 
@@ -118,15 +128,15 @@ editprompt respects the following editor priority:
118
128
  2. `$EDITOR` environment variable
119
129
  3. Default: `nvim`
120
130
 
121
- ### Environment Variables
131
+ ### 🌍 Environment Variables
122
132
 
123
133
  - `EDITOR`: Your preferred text editor
124
134
 
125
- ### Editor Integration with EDITPROMPT
135
+ ### 🔧 Editor Integration with EDITPROMPT
126
136
 
127
137
  editprompt automatically sets `EDITPROMPT=1` when launching your editor. This allows you to detect when your editor is launched by editprompt and enable specific configurations or plugins.
128
138
 
129
- #### Example: Neovim Configuration
139
+ #### 🔍 Example: Neovim Configuration
130
140
 
131
141
  ```lua
132
142
  -- In your Neovim config (e.g., init.lua)
@@ -137,7 +147,7 @@ if vim.env.EDITPROMPT then
137
147
  end
138
148
  ```
139
149
 
140
- #### Setting Custom Environment Variables
150
+ #### 🛠️ Setting Custom Environment Variables
141
151
 
142
152
  You can also pass custom environment variables to your editor:
143
153
 
@@ -154,7 +164,7 @@ editprompt --env NVIM_CONFIG=minimal
154
164
 
155
165
  ---
156
166
 
157
- ## Development
167
+ ## 🔧 Development
158
168
 
159
169
  ```bash
160
170
  # Clone the repository
@@ -174,7 +184,7 @@ bun test
174
184
  bun run dev
175
185
  ```
176
186
 
177
- ### Project Structure
187
+ ### 📁 Project Structure
178
188
 
179
189
  ```
180
190
  src/
@@ -189,16 +199,9 @@ src/
189
199
  └── index.ts # CLI entry point
190
200
  ```
191
201
 
192
- ## Technical Details
193
-
194
- ### Tmux Integration
195
-
196
- editprompt supports two tmux integration modes:
197
-
198
- - **Direct pane targeting** (`--target-pane`): Bypasses process detection and sends content directly to specified pane ID
199
- - **Process-based targeting**: Detects target processes and links them to tmux panes for delivery
202
+ ## 🔍 Technical Details
200
203
 
201
- ### Fallback Strategy
204
+ ### 🔄 Fallback Strategy
202
205
 
203
206
  editprompt implements a robust fallback strategy:
204
207
 
package/dist/index.js CHANGED
@@ -1,23 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import { cli } from "gunshi";
3
3
  import { exec, spawn } from "node:child_process";
4
- import { mkdtemp, readFile, readlink, writeFile } from "node:fs/promises";
4
+ import { mkdtemp, readFile, writeFile } from "node:fs/promises";
5
5
  import { tmpdir } from "node:os";
6
6
  import { join } from "node:path";
7
7
  import { promisify } from "node:util";
8
8
  import clipboardy from "clipboardy";
9
- import find from "find-process";
10
- import inquirer from "inquirer";
11
9
 
12
10
  //#region package.json
13
- var version = "0.2.2";
11
+ var version = "0.3.0";
14
12
 
15
13
  //#endregion
16
14
  //#region src/config/constants.ts
17
15
  const TEMP_FILE_PREFIX = ".editprompt-";
18
16
  const TEMP_FILE_EXTENSION = ".md";
19
17
  const DEFAULT_EDITOR = "vim";
20
- const DEFAULT_PROCESS_NAME = "claude";
21
18
 
22
19
  //#endregion
23
20
  //#region src/utils/envParser.ts
@@ -109,147 +106,32 @@ async function openEditorAndGetContent(editorOption, envVars) {
109
106
  //#endregion
110
107
  //#region src/modules/process.ts
111
108
  const execAsync = promisify(exec);
112
- async function getProcessCwd(pid) {
113
- try {
114
- const cwdPath = join("/proc", pid.toString(), "cwd");
115
- const cwd = await readlink(cwdPath);
116
- return cwd;
117
- } catch (error) {
118
- return void 0;
119
- }
109
+ function isMuxType(value) {
110
+ return value === "tmux" || value === "wezterm";
120
111
  }
121
- async function findTargetProcesses(processName = DEFAULT_PROCESS_NAME) {
122
- const tmuxAvailable = await checkTmuxAvailable();
123
- if (tmuxAvailable) {
124
- const tmuxProcesses = await findTargetInTmux(processName);
125
- if (tmuxProcesses.length > 0) return Promise.all(tmuxProcesses.map(async (proc) => {
126
- const cwd = await getProcessCwd(proc.pid);
127
- return {
128
- ...proc,
129
- cwd
130
- };
131
- }));
132
- }
133
- const processes = await find("name", processName);
134
- const targetProcesses = await Promise.all(processes.map(async (proc) => {
135
- const cwd = await getProcessCwd(proc.pid);
136
- return {
137
- pid: proc.pid,
138
- name: proc.name,
139
- cmd: proc.cmd,
140
- cwd
141
- };
142
- }));
143
- return targetProcesses.filter((p) => p.name === processName);
112
+ async function sendToTmuxPane(paneId, content) {
113
+ await execAsync(`tmux send-keys -t '${paneId}' '${content.replace(/'/g, "'\\''")}'`);
114
+ console.log(`Content sent to tmux pane: ${paneId}`);
144
115
  }
145
- async function checkTmuxAvailable() {
146
- try {
147
- await execAsync("tmux list-sessions");
148
- return true;
149
- } catch {
150
- return false;
151
- }
152
- }
153
- async function getTmuxPanes() {
154
- try {
155
- const { stdout } = await execAsync("tmux list-panes -a -F '#{session_name}:#{window_index}.#{pane_index}:#{pane_pid}:#{pane_current_command}'");
156
- return stdout.trim().split("\n").map((line) => {
157
- const [session, windowPane, pid, command] = line.split(":");
158
- let win = void 0;
159
- let pane = void 0;
160
- if (windowPane) [win, pane] = windowPane.split(".");
161
- return {
162
- session,
163
- window: win,
164
- pane,
165
- pid: pid ? Number.parseInt(pid, 10) : void 0,
166
- command
167
- };
168
- });
169
- } catch {
170
- return [];
171
- }
172
- }
173
- async function findTargetInTmux(processName = DEFAULT_PROCESS_NAME) {
174
- const processes = await find("name", processName);
175
- const targetProcesses = processes.filter((p) => p.cmd.match(processName));
176
- if (targetProcesses.length === 0) return [];
177
- const tmuxPanes = await getTmuxPanes();
178
- if (tmuxPanes.length === 0) return [];
179
- const matchedProcesses = [];
180
- for (const process$1 of targetProcesses) {
181
- const matchingPane = tmuxPanes.find((pane) => pane.pid === process$1.ppid);
182
- if (matchingPane?.session && matchingPane.window && matchingPane.pane) matchedProcesses.push({
183
- pid: process$1.pid,
184
- name: process$1.name,
185
- cmd: process$1.cmd,
186
- tmuxSession: matchingPane.session,
187
- tmuxWindow: matchingPane.window,
188
- tmuxPane: matchingPane.pane
189
- });
190
- }
191
- return matchedProcesses;
192
- }
193
- async function sendToTmuxPane(session, window, pane, content) {
194
- const target = `${session}:${window}.${pane}`;
195
- await execAsync(`tmux send-keys -t '${target}' '${content.replace(/'/g, "'\\''")}'`);
196
- }
197
- async function sendToSpecificPane(paneId, content) {
198
- const tempContent = content.replace(/'/g, "'\\''");
199
- await execAsync(`printf %s '${tempContent}' | tmux load-buffer -b editprompt -`);
200
- await execAsync(`tmux paste-buffer -d -t '${paneId}' -b editprompt`);
116
+ async function sendToWeztermPane(paneId, content) {
117
+ await execAsync(`wezterm cli send-text --no-paste --pane-id '${paneId}' '${content.replace(/'/g, "'\\''")}'`);
118
+ console.log(`Content sent to wezterm pane: ${paneId}`);
201
119
  }
202
120
  async function copyToClipboard(content) {
203
121
  await clipboardy.write(content);
204
122
  }
205
- async function sendContentToProcess(process$1, content, targetPaneId, alwaysCopy) {
123
+ async function sendContentToPane(targetPaneId, content, mux = "tmux", alwaysCopy) {
206
124
  try {
207
- if (targetPaneId) {
208
- await sendToSpecificPane(targetPaneId, content);
209
- if (alwaysCopy) {
210
- await copyToClipboard(content);
211
- console.log("Copy!");
212
- }
213
- return;
214
- }
215
- if (process$1.tmuxSession && process$1.tmuxWindow && process$1.tmuxPane) {
216
- await sendToTmuxPane(process$1.tmuxSession, process$1.tmuxWindow, process$1.tmuxPane, content);
217
- if (alwaysCopy) {
218
- await copyToClipboard(content);
219
- console.log("Copy!");
220
- }
221
- return;
125
+ if (mux === "wezterm") await sendToWeztermPane(targetPaneId, content);
126
+ else await sendToTmuxPane(targetPaneId, content);
127
+ if (alwaysCopy) {
128
+ await copyToClipboard(content);
129
+ console.log("Also copied to clipboard.");
222
130
  }
223
131
  } catch (error) {
224
- console.log(`Failed to send to process. Content copied to clipboard. Error: ${error instanceof Error ? error.message : "Unknown error"}`);
132
+ console.log(`Failed to send to pane. Error: ${error instanceof Error ? error.message : "Unknown error"}`);
133
+ throw error;
225
134
  }
226
- await copyToClipboard(content);
227
- console.log("Copy!");
228
- }
229
-
230
- //#endregion
231
- //#region src/modules/selector.ts
232
- function formatProcessChoice(proc, index) {
233
- const parts = [`PID: ${proc.pid}`];
234
- if (proc.tmuxSession) parts.push(`Tmux: ${proc.tmuxSession}:${proc.tmuxWindow}.${proc.tmuxPane}`);
235
- if (proc.cwd) parts.push(`Directory: ${proc.cwd}`);
236
- return {
237
- name: `${index + 1}. ${parts.join(" | ")}`,
238
- value: proc
239
- };
240
- }
241
- async function selectProcess(processes) {
242
- if (processes.length === 0) throw new Error("No processes to select from");
243
- if (processes.length === 1) return processes[0];
244
- const choices = processes.map((proc, index) => formatProcessChoice(proc, index));
245
- const { selectedProcess } = await inquirer.prompt([{
246
- type: "list",
247
- name: "selectedProcess",
248
- message: "Select a process:",
249
- choices
250
- }]);
251
- if (!selectedProcess) throw new Error("No process was selected");
252
- return selectedProcess;
253
135
  }
254
136
 
255
137
  //#endregion
@@ -266,12 +148,17 @@ await cli(argv, {
266
148
  },
267
149
  process: {
268
150
  short: "p",
269
- description: "Process name to target (default: claude)",
151
+ description: "Process name to target (DEPRECATED - will be removed in v0.4.0)",
270
152
  type: "string"
271
153
  },
272
154
  "target-pane": {
273
155
  short: "t",
274
- description: "Target tmux pane ID to send content to",
156
+ description: "Target pane ID to send content to",
157
+ type: "string"
158
+ },
159
+ mux: {
160
+ short: "m",
161
+ description: "Multiplexer type (tmux or wezterm, default: tmux)",
275
162
  type: "string"
276
163
  },
277
164
  env: {
@@ -287,6 +174,20 @@ await cli(argv, {
287
174
  },
288
175
  async run(ctx) {
289
176
  try {
177
+ const muxValue = ctx.values.mux || "tmux";
178
+ if (!isMuxType(muxValue)) {
179
+ console.error(`Error: Invalid mux type '${muxValue}'. Supported values: tmux, wezterm`);
180
+ process.exit(1);
181
+ }
182
+ const mux = muxValue;
183
+ if (mux === "wezterm" && !ctx.values["target-pane"]) {
184
+ console.error("Error: --target-pane is required when using --mux=wezterm");
185
+ process.exit(1);
186
+ }
187
+ if (ctx.values.process) {
188
+ console.warn("Warning: --process option is deprecated and will be removed in future versions.");
189
+ console.warn("Use --target-pane to specify the target pane directly.");
190
+ }
290
191
  console.log("Opening editor...");
291
192
  const content = await openEditorAndGetContent(ctx.values.editor, ctx.values.env);
292
193
  if (!content) {
@@ -295,30 +196,24 @@ await cli(argv, {
295
196
  }
296
197
  const targetPane = ctx.values["target-pane"];
297
198
  const alwaysCopy = ctx.values["always-copy"];
298
- if (targetPane) {
199
+ if (targetPane) try {
299
200
  console.log("Sending content to specified pane...");
300
- await sendContentToProcess({
301
- pid: 0,
302
- name: "direct-pane"
303
- }, content, targetPane, alwaysCopy);
201
+ await sendContentToPane(targetPane, content, mux, alwaysCopy);
304
202
  console.log("Content sent successfully!");
305
- } else {
306
- const processName = ctx.values.process || DEFAULT_PROCESS_NAME;
307
- console.log(`Searching for ${processName} processes...`);
308
- const processes = await findTargetProcesses(processName);
309
- if (processes.length === 0) console.log(`No ${processName} process found.`);
310
- else {
311
- const selectedProcess = await selectProcess(processes);
312
- if (!selectedProcess) return;
313
- const processInfo = [`PID ${selectedProcess.pid}`];
314
- if (selectedProcess.tmuxSession) processInfo.push(`Tmux: ${selectedProcess.tmuxSession}:${selectedProcess.tmuxWindow}.${selectedProcess.tmuxPane}`);
315
- if (selectedProcess.cwd) processInfo.push(`Directory: ${selectedProcess.cwd}`);
316
- console.log(`Selected process: ${processInfo.join(" | ")}`);
317
- console.log(`Sending content to ${processName} process...`);
318
- await sendContentToProcess(selectedProcess, content, void 0, alwaysCopy);
319
- console.log("Content sent successfully!");
320
- }
203
+ } catch (error) {
204
+ console.log(`Failed to send to pane: ${error instanceof Error ? error.message : "Unknown error"}`);
205
+ console.log("Falling back to clipboard...");
206
+ await copyToClipboard(content);
207
+ console.log("Content copied to clipboard.");
208
+ }
209
+ else try {
210
+ await copyToClipboard(content);
211
+ console.log("Content copied to clipboard.");
212
+ } catch (error) {
213
+ console.log(`Failed to copy to clipboard: ${error instanceof Error ? error.message : "Unknown error"}`);
321
214
  }
215
+ console.log("---");
216
+ console.log(content);
322
217
  } catch (error) {
323
218
  console.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
324
219
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "editprompt",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "author": "eetann",
5
5
  "description": "A CLI tool that lets you write prompts for CLI tools using your favorite text editor",
6
6
  "license": "MIT",
@@ -49,8 +49,6 @@
49
49
  "devDependencies": {
50
50
  "@biomejs/biome": "1.9.4",
51
51
  "@types/bun": "^1.2.16",
52
- "@types/find-process": "^1.2.2",
53
- "@types/inquirer": "^9.0.8",
54
52
  "@types/node": "^24.0.1",
55
53
  "@typescript/native-preview": "^7.0.0-dev.20250712.1",
56
54
  "bumpp": "^10.2.0",
@@ -59,9 +57,7 @@
59
57
  "packageManager": "pnpm@10.8.1+sha512.c50088ba998c67b8ca8c99df8a5e02fd2ae2e2b29aaf238feaa9e124248d3f48f9fb6db2424949ff901cffbb5e0f0cc1ad6aedb602cd29450751d11c35023677",
60
58
  "dependencies": {
61
59
  "clipboardy": "^4.0.0",
62
- "find-process": "^1.4.10",
63
- "gunshi": "^0.26.3",
64
- "inquirer": "^12.6.3"
60
+ "gunshi": "^0.26.3"
65
61
  },
66
62
  "peerDependencies": {
67
63
  "typescript": "^5"