editprompt 0.0.6 → 0.2.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 +78 -28
  2. package/dist/index.js +76 -21
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
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.
4
4
 
5
+ https://github.com/user-attachments/assets/01bcda7c-7771-4b33-bf5c-629812d45cc4
6
+
5
7
  ## Features
6
8
 
7
9
  - 🖊️ **Editor Integration**: Use your preferred text editor to write prompts
@@ -36,6 +38,14 @@ editprompt -e nvim
36
38
  editprompt --process gemini
37
39
  editprompt -p gemini
38
40
 
41
+ # Send content to a specific tmux pane
42
+ editprompt --target-pane %45
43
+ editprompt -t %45
44
+
45
+ # Set environment variables for the editor
46
+ editprompt --env THEME=dark
47
+ editprompt -E THEME=dark -E LANG=ja_JP.UTF-8
48
+
39
49
  # Show help
40
50
  editprompt --help
41
51
 
@@ -43,15 +53,39 @@ editprompt --help
43
53
  editprompt --version
44
54
  ```
45
55
 
46
- It is useful to configure tmux as follows.
56
+ ### Tmux Integration
57
+
58
+ editprompt offers two modes for tmux integration:
59
+
60
+ #### Recommended: Direct Pane Targeting
61
+ 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.
47
62
 
63
+ **Split window version:**
64
+ ```tmux
65
+ bind -n M-q run-shell 'tmux split-window -v -l 20 \
66
+ -c "#{pane_current_path}" \
67
+ "editprompt --editor nvim --target-pane #{pane_id}"'
68
+ ```
69
+
70
+ **Popup version:**
71
+ ```tmux
72
+ bind -n M-q run-shell 'tmux display-popup -E \
73
+ -d "#{pane_current_path}" \
74
+ -w 80% -h 65% \
75
+ "editprompt --editor nvim --target-pane #{pane_id}"'
76
+ ```
77
+
78
+ #### Alternative: Process Auto-detection
79
+ Let editprompt automatically detect and select target processes:
80
+
81
+ **Split window version:**
48
82
  ```tmux
49
83
  bind -n M-q split-window -v -l 10 \
50
84
  -c '#{pane_current_path}' \
51
85
  'editprompt --editor nvim'
52
86
  ```
53
87
 
54
- If you prefer popup, you can configure it as follows.
88
+ **Popup version:**
55
89
  ```tmux
56
90
  bind -n M-q display-popup -E \
57
91
  -d '#{pane_current_path}' \
@@ -62,25 +96,13 @@ bind -n M-q display-popup -E \
62
96
 
63
97
  1. **Opens your editor** with a temporary markdown file
64
98
  2. **Write your prompt** and save/exit the editor
65
- 3. **Detects target processes** running on your system (default: claude)
66
- 4. **Sends the prompt** using the best available method:
67
- - 🎯 **Tmux sessions**: Direct input via `tmux send-keys`
68
- - 📋 **Clipboard**: Copies content as final fallback
69
-
70
- ### Process Selection
71
-
72
- When multiple processes are detected, you'll see an interactive selection menu:
99
+ 3. **Sends the prompt** using one of two modes:
100
+ - 🎯 **Direct pane mode** (`--target-pane`): Sends directly to specified tmux pane
101
+ - 🔍 **Process detection mode**: Finds target processes and sends via tmux or clipboard
102
+ 4. **Fallback strategy** ensures delivery:
103
+ - Tmux integration (preferred)
104
+ - Clipboard copy (fallback)
73
105
 
74
- ```
75
- ? Select a process:
76
- 1. PID: 12345 | Tmux: main:0.1 | Directory: /home/user/project1
77
- 2. PID: 67890 | Directory: /home/user/project2
78
- ```
79
-
80
- The display shows:
81
- - **PID**: Process ID
82
- - **Tmux**: Session, window, and pane (if running in tmux)
83
- - **Directory**: Working directory of the process
84
106
 
85
107
  ## Configuration
86
108
 
@@ -96,11 +118,37 @@ editprompt respects the following editor priority:
96
118
 
97
119
  - `EDITOR`: Your preferred text editor
98
120
 
99
- ## Requirements
121
+ ### Editor Integration with EDITPROMPT
122
+
123
+ 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.
124
+
125
+ #### Example: Neovim Configuration
126
+
127
+ ```lua
128
+ -- In your Neovim config (e.g., init.lua)
129
+ if vim.env.EDITPROMPT then
130
+ vim.opt.wrap = true
131
+ -- Load a specific colorscheme
132
+ vim.cmd('colorscheme blue')
133
+ end
134
+ ```
135
+
136
+ #### Setting Custom Environment Variables
100
137
 
101
- - Node.js 18+ or Bun
102
- - Target CLI process (default: `claude` command)
103
- - Optional: tmux (for direct session integration)
138
+ You can also pass custom environment variables to your editor:
139
+
140
+ ```bash
141
+ # Single environment variable
142
+ editprompt --env THEME=dark
143
+
144
+ # Multiple environment variables
145
+ editprompt --env THEME=dark --env FOO=fooooo
146
+
147
+ # Useful for editor-specific configurations
148
+ editprompt --env NVIM_CONFIG=minimal
149
+ ```
150
+
151
+ ---
104
152
 
105
153
  ## Development
106
154
 
@@ -141,12 +189,14 @@ src/
141
189
 
142
190
  ### Tmux Integration
143
191
 
144
- When the target process is running in a tmux session, editprompt uses `tmux send-keys` to send input directly to the appropriate pane. This provides seamless integration without disrupting your existing session.
192
+ editprompt supports two tmux integration modes:
193
+
194
+ - **Direct pane targeting** (`--target-pane`): Bypasses process detection and sends content directly to specified pane ID
195
+ - **Process-based targeting**: Detects target processes and links them to tmux panes for delivery
145
196
 
146
197
  ### Fallback Strategy
147
198
 
148
- editprompt implements fallback strategy:
199
+ editprompt implements a robust fallback strategy:
149
200
 
150
201
  1. **Tmux Integration**: Direct input to tmux panes (when available)
151
- <!-- 2. **New Process**: Launch new Claude instance with piped input -->
152
- 2. **Clipboard**: Copy content to clipboard with error notification
202
+ 2. **Clipboard**: Copy content to clipboard with user notification
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import find from "find-process";
10
10
  import inquirer from "inquirer";
11
11
 
12
12
  //#region package.json
13
- var version = "0.0.6";
13
+ var version = "0.2.0";
14
14
 
15
15
  //#endregion
16
16
  //#region src/config/constants.ts
@@ -19,6 +19,25 @@ const TEMP_FILE_EXTENSION = ".md";
19
19
  const DEFAULT_EDITOR = "vim";
20
20
  const DEFAULT_PROCESS_NAME = "claude";
21
21
 
22
+ //#endregion
23
+ //#region src/utils/envParser.ts
24
+ /**
25
+ * 環境変数文字列をパースしてオブジェクトに変換
26
+ * @param envStrings - ["KEY=VALUE", "FOO=bar"] 形式の配列
27
+ * @returns 環境変数のキーバリューオブジェクト
28
+ */
29
+ function parseEnvVars(envStrings) {
30
+ if (!envStrings || envStrings.length === 0) return {};
31
+ const result = {};
32
+ for (const envString of envStrings) {
33
+ const [key, ...valueParts] = envString.split("=");
34
+ if (!key || valueParts.length === 0) throw new Error(`Invalid environment variable format: ${envString}`);
35
+ const value = valueParts.join("=");
36
+ result[key] = value;
37
+ }
38
+ return result;
39
+ }
40
+
22
41
  //#endregion
23
42
  //#region src/utils/tempFile.ts
24
43
  function getFormattedDateTime() {
@@ -44,11 +63,17 @@ async function createTempFile() {
44
63
  function getEditor(editorOption) {
45
64
  return editorOption || process.env.EDITOR || DEFAULT_EDITOR;
46
65
  }
47
- async function launchEditor(editor, filePath) {
66
+ async function launchEditor(editor, filePath, envVars) {
48
67
  return new Promise((resolve, reject) => {
68
+ const processEnv = {
69
+ ...process.env,
70
+ EDITPROMPT: "1",
71
+ ...envVars
72
+ };
49
73
  const editorProcess = spawn(editor, [filePath], {
50
74
  stdio: "inherit",
51
- shell: true
75
+ shell: true,
76
+ env: processEnv
52
77
  });
53
78
  editorProcess.on("error", (error) => {
54
79
  reject(new Error(`Failed to launch editor: ${error.message}`));
@@ -67,13 +92,13 @@ async function readFileContent(filePath) {
67
92
  throw new Error(`Failed to read file: ${error instanceof Error ? error.message : "Unknown error"}`);
68
93
  }
69
94
  }
70
- async function openEditorAndGetContent(editorOption) {
95
+ async function openEditorAndGetContent(editorOption, envVars) {
71
96
  const tempFilePath = await createTempFile();
72
97
  const editor = getEditor(editorOption);
98
+ const parsedEnvVars = parseEnvVars(envVars);
73
99
  try {
74
- await launchEditor(editor, tempFilePath);
100
+ await launchEditor(editor, tempFilePath, parsedEnvVars);
75
101
  const content = await readFileContent(tempFilePath);
76
- if (!content) throw new Error("No content was entered in the editor");
77
102
  return content;
78
103
  } catch (error) {
79
104
  if (error instanceof Error) throw error;
@@ -169,11 +194,20 @@ async function sendToTmuxPane(session, window, pane, content) {
169
194
  const target = `${session}:${window}.${pane}`;
170
195
  await execAsync(`tmux send-keys -t '${target}' '${content.replace(/'/g, "'\\''")}'`);
171
196
  }
197
+ async function sendToSpecificPane(paneId, content) {
198
+ const tempContent = content.replace(/'/g, "'\\''");
199
+ await execAsync(`tmux load-buffer -b editprompt - <<< '${tempContent}'`);
200
+ await execAsync(`tmux paste-buffer -d -t '${paneId}' -b editprompt`);
201
+ }
172
202
  async function copyToClipboard(content) {
173
203
  await clipboardy.write(content);
174
204
  }
175
- async function sendContentToProcess(process$1, content) {
205
+ async function sendContentToProcess(process$1, content, targetPaneId) {
176
206
  try {
207
+ if (targetPaneId) {
208
+ await sendToSpecificPane(targetPaneId, content);
209
+ return;
210
+ }
177
211
  if (process$1.tmuxSession && process$1.tmuxWindow && process$1.tmuxPane) {
178
212
  await sendToTmuxPane(process$1.tmuxSession, process$1.tmuxWindow, process$1.tmuxPane, content);
179
213
  return;
@@ -226,30 +260,51 @@ await cli(argv, {
226
260
  short: "p",
227
261
  description: "Process name to target (default: claude)",
228
262
  type: "string"
263
+ },
264
+ "target-pane": {
265
+ short: "t",
266
+ description: "Target tmux pane ID to send content to",
267
+ type: "string"
268
+ },
269
+ env: {
270
+ short: "E",
271
+ description: "Environment variables to set (e.g., KEY=VALUE)",
272
+ type: "string",
273
+ multiple: true
229
274
  }
230
275
  },
231
276
  async run(ctx) {
232
277
  try {
233
278
  console.log("Opening editor...");
234
- const content = await openEditorAndGetContent(ctx.values.editor);
279
+ const content = await openEditorAndGetContent(ctx.values.editor, ctx.values.env);
235
280
  if (!content) {
236
281
  console.log("No content entered. Exiting.");
237
282
  return;
238
283
  }
239
- const processName = ctx.values.process || DEFAULT_PROCESS_NAME;
240
- console.log(`Searching for ${processName} processes...`);
241
- const processes = await findTargetProcesses(processName);
242
- if (processes.length === 0) console.log(`No ${processName} process found.`);
243
- else {
244
- const selectedProcess = await selectProcess(processes);
245
- if (!selectedProcess) return;
246
- const processInfo = [`PID ${selectedProcess.pid}`];
247
- if (selectedProcess.tmuxSession) processInfo.push(`Tmux: ${selectedProcess.tmuxSession}:${selectedProcess.tmuxWindow}.${selectedProcess.tmuxPane}`);
248
- if (selectedProcess.cwd) processInfo.push(`Directory: ${selectedProcess.cwd}`);
249
- console.log(`Selected process: ${processInfo.join(" | ")}`);
250
- console.log(`Sending content to ${processName} process...`);
251
- await sendContentToProcess(selectedProcess, content);
284
+ const targetPane = ctx.values["target-pane"];
285
+ if (targetPane) {
286
+ console.log("Sending content to specified pane...");
287
+ await sendContentToProcess({
288
+ pid: 0,
289
+ name: "direct-pane"
290
+ }, content, targetPane);
252
291
  console.log("Content sent successfully!");
292
+ } else {
293
+ const processName = ctx.values.process || DEFAULT_PROCESS_NAME;
294
+ console.log(`Searching for ${processName} processes...`);
295
+ const processes = await findTargetProcesses(processName);
296
+ if (processes.length === 0) console.log(`No ${processName} process found.`);
297
+ else {
298
+ const selectedProcess = await selectProcess(processes);
299
+ if (!selectedProcess) return;
300
+ const processInfo = [`PID ${selectedProcess.pid}`];
301
+ if (selectedProcess.tmuxSession) processInfo.push(`Tmux: ${selectedProcess.tmuxSession}:${selectedProcess.tmuxWindow}.${selectedProcess.tmuxPane}`);
302
+ if (selectedProcess.cwd) processInfo.push(`Directory: ${selectedProcess.cwd}`);
303
+ console.log(`Selected process: ${processInfo.join(" | ")}`);
304
+ console.log(`Sending content to ${processName} process...`);
305
+ await sendContentToProcess(selectedProcess, content);
306
+ console.log("Content sent successfully!");
307
+ }
253
308
  }
254
309
  } catch (error) {
255
310
  console.error(`Error: ${error instanceof Error ? error.message : "Unknown error"}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "editprompt",
3
- "version": "0.0.6",
3
+ "version": "0.2.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",
@@ -40,6 +40,7 @@
40
40
  "build": "tsdown",
41
41
  "dev": "tsdown --watch",
42
42
  "lint": "biome check",
43
+ "formant": "biome format --write",
43
44
  "typecheck": "tsgo --noEmit",
44
45
  "release": "bun run lint && bun run typecheck && bun run test && bun run build && bumpp",
45
46
  "test": "bun test",