@mtayfur/opencode-prompt-enhancer 0.0.2 → 0.0.4

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/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "@mtayfur/opencode-prompt-enhancer",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "OpenCode plugin that rewrites rough drafts into stronger prompts.",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "type": "module",
9
- "main": "./index.ts",
10
9
  "exports": {
11
10
  ".": "./index.ts",
12
11
  "./tui": "./plugins/prompt-enhancer.tsx"
@@ -22,10 +21,14 @@
22
21
  "index.ts",
23
22
  "plugins"
24
23
  ],
25
- "dependencies": {
26
- "@opencode-ai/plugin": "1.14.20",
27
- "@opentui/solid": "0.2.1",
28
- "solid-js": "1.9.12"
24
+ "engines": {
25
+ "opencode": ">=1.3.14"
26
+ },
27
+ "peerDependencies": {
28
+ "@opencode-ai/plugin": "*",
29
+ "@opencode-ai/sdk": "*",
30
+ "@opentui/solid": "*",
31
+ "solid-js": "*"
29
32
  },
30
33
  "devDependencies": {
31
34
  "typescript": "5.8.2"
@@ -1,7 +1,16 @@
1
1
  /** @jsxImportSource @opentui/solid */
2
2
  import type { PluginOptions } from "@opencode-ai/plugin"
3
3
  import type { Message, Part, TextPart } from "@opencode-ai/sdk/v2"
4
- import type { TuiPlugin, TuiPluginModule, TuiRouteCurrent, TuiSidebarFileItem, TuiSidebarTodoItem } from "@opencode-ai/plugin/tui"
4
+ import type {
5
+ TuiPlugin,
6
+ TuiPluginModule,
7
+ TuiPromptInfo,
8
+ TuiPromptRef,
9
+ TuiRouteCurrent,
10
+ TuiSidebarFileItem,
11
+ TuiSidebarTodoItem,
12
+ TuiSlotPlugin,
13
+ } from "@opencode-ai/plugin/tui"
5
14
 
6
15
  const MAX_RECENT_MESSAGES = 6
7
16
  const MAX_CHANGED_FILES = 30
@@ -10,6 +19,10 @@ const MAX_TODOS = 10
10
19
  const DIALOG_TITLE = "Enhance Prompt"
11
20
  const TOAST_TITLE = "Prompt enhancer"
12
21
  const TEMP_SESSION_TITLE = "Prompt Enhancer"
22
+ const HOME_PROMPT_PLACEHOLDERS = {
23
+ normal: ["Fix a TODO in the codebase", "What is the tech stack of this project?", "Fix broken tests"],
24
+ shell: ["ls -la", "git status", "pwd"],
25
+ }
13
26
 
14
27
  const ENHANCER_SYSTEM_PROMPT = `You rewrite rough user drafts into strong prompts for OpenCode, an AI coding assistant.
15
28
 
@@ -51,6 +64,7 @@ type ModelRef = {
51
64
  type Api = Parameters<TuiPlugin>[0]
52
65
  type PluginState = {
53
66
  enhancing: boolean
67
+ promptRef?: TuiPromptRef
54
68
  }
55
69
 
56
70
  function parseModelString(value: string | undefined): ModelRef | undefined {
@@ -104,6 +118,58 @@ function resolveEnhancerModel(api: Api, options: PluginOptions | undefined): Mod
104
118
  return parseModelString(api.state.config.small_model || api.state.config.model)
105
119
  }
106
120
 
121
+ function clonePromptInfo(prompt: TuiPromptInfo): TuiPromptInfo {
122
+ return {
123
+ input: prompt.input,
124
+ mode: prompt.mode,
125
+ parts: prompt.parts.map((part) => ({ ...part })),
126
+ }
127
+ }
128
+
129
+ function nextPromptInfo(prompt: TuiPromptInfo, input: string): TuiPromptInfo {
130
+ return {
131
+ input,
132
+ mode: prompt.mode,
133
+ parts: prompt.parts.filter((part) => part.type !== "text").map((part) => ({ ...part })),
134
+ }
135
+ }
136
+
137
+ function currentPromptText(state: PluginState): string {
138
+ return state.promptRef?.current.input ?? ""
139
+ }
140
+
141
+ function bindPromptRef(state: PluginState, forwarded: ((ref: TuiPromptRef | undefined) => void) | undefined, ref: TuiPromptRef | undefined): void {
142
+ state.promptRef = ref
143
+ forwarded?.(ref)
144
+ }
145
+
146
+ async function writePrompt(api: Api, state: PluginState, input: string, signal: AbortSignal, template?: TuiPromptInfo): Promise<void> {
147
+ const promptRef = state.promptRef
148
+ if (promptRef) {
149
+ promptRef.set(nextPromptInfo(template ?? promptRef.current, input))
150
+ promptRef.focus()
151
+ return
152
+ }
153
+
154
+ const directory = api.state.path.directory
155
+ const requestOptions = { signal, throwOnError: true } as const
156
+ await api.client.tui.clearPrompt({ directory }, requestOptions)
157
+ if (input) {
158
+ await api.client.tui.appendPrompt({ directory, text: input }, requestOptions)
159
+ }
160
+ }
161
+
162
+ async function restorePrompt(api: Api, state: PluginState, prompt: TuiPromptInfo, signal: AbortSignal): Promise<void> {
163
+ const promptRef = state.promptRef
164
+ if (promptRef) {
165
+ promptRef.set(clonePromptInfo(prompt))
166
+ promptRef.focus()
167
+ return
168
+ }
169
+
170
+ await writePrompt(api, state, prompt.input, signal, prompt)
171
+ }
172
+
107
173
  function gatherContext(api: Api): string {
108
174
  const sections: string[] = []
109
175
 
@@ -231,10 +297,13 @@ function openEnhanceDialog(
231
297
 
232
298
  if (signal.aborted) return
233
299
 
300
+ const initialValue = currentPromptText(state)
301
+
234
302
  api.ui.dialog.replace(() => (
235
303
  <api.ui.DialogPrompt
236
304
  title={DIALOG_TITLE}
237
305
  placeholder="Describe what you want to do..."
306
+ value={initialValue}
238
307
  onCancel={() => api.ui.dialog.clear()}
239
308
  onConfirm={(value) => {
240
309
  const input = value.trim()
@@ -244,7 +313,11 @@ function openEnhanceDialog(
244
313
  return
245
314
  }
246
315
 
316
+ const originalPrompt = state.promptRef ? clonePromptInfo(state.promptRef.current) : undefined
247
317
  state.enhancing = true
318
+ if (originalPrompt) {
319
+ state.promptRef?.set(nextPromptInfo(originalPrompt, ""))
320
+ }
248
321
  api.ui.dialog.clear()
249
322
  api.ui.toast({
250
323
  variant: "info",
@@ -258,10 +331,7 @@ function openEnhanceDialog(
258
331
  const enhanced = await enhanceWithModel(api, options, input, signal)
259
332
  if (signal.aborted) return
260
333
 
261
- const directory = api.state.path.directory
262
- const requestOptions = { signal, throwOnError: true } as const
263
- await api.client.tui.clearPrompt({ directory }, requestOptions)
264
- await api.client.tui.appendPrompt({ directory, text: enhanced }, requestOptions)
334
+ await writePrompt(api, state, enhanced, signal, originalPrompt)
265
335
  api.ui.toast({
266
336
  variant: "success",
267
337
  title: "Prompt enhanced",
@@ -271,6 +341,10 @@ function openEnhanceDialog(
271
341
  } catch (error) {
272
342
  if (signal.aborted) return
273
343
 
344
+ if (originalPrompt) {
345
+ await restorePrompt(api, state, originalPrompt, signal)
346
+ }
347
+
274
348
  const message = error instanceof Error ? error.message : "Model enhancement failed."
275
349
  api.ui.toast({ variant: "error", title: TOAST_TITLE, message })
276
350
  } finally {
@@ -285,11 +359,40 @@ function openEnhanceDialog(
285
359
  const tui: TuiPlugin = async (api, options) => {
286
360
  const state: PluginState = { enhancing: false }
287
361
 
362
+ const promptSlots: TuiSlotPlugin = {
363
+ slots: {
364
+ home_prompt(_ctx, props) {
365
+ return (
366
+ <api.ui.Prompt
367
+ ref={(ref) => bindPromptRef(state, props.ref, ref)}
368
+ workspaceID={props.workspace_id}
369
+ right={<api.ui.Slot name="home_prompt_right" workspace_id={props.workspace_id} />}
370
+ placeholders={HOME_PROMPT_PLACEHOLDERS}
371
+ />
372
+ )
373
+ },
374
+ session_prompt(_ctx, props) {
375
+ return (
376
+ <api.ui.Prompt
377
+ ref={(ref) => bindPromptRef(state, props.ref, ref)}
378
+ sessionID={props.session_id}
379
+ visible={props.visible}
380
+ disabled={props.disabled}
381
+ onSubmit={props.on_submit}
382
+ right={<api.ui.Slot name="session_prompt_right" session_id={props.session_id} />}
383
+ />
384
+ )
385
+ },
386
+ },
387
+ }
388
+
389
+ api.slots.register(promptSlots)
390
+
288
391
  const unregister = api.command.register(() => [
289
392
  {
290
393
  title: DIALOG_TITLE,
291
394
  value: "prompt-enhancer.enhance",
292
- description: "Rewrite a draft prompt with project context (Ctrl+E)",
395
+ description: "Enhance prompt with project context (Ctrl+E)",
293
396
  category: "Prompt",
294
397
  keybind: "ctrl+e",
295
398
  suggested: true,
@@ -315,4 +418,3 @@ const plugin: TuiPluginModule & { id: string } = {
315
418
  }
316
419
 
317
420
  export default plugin
318
-