@mtayfur/opencode-prompt-enhancer 0.0.3 → 0.0.5
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 +9 -6
- package/plugins/prompt-enhancer.tsx +162 -24
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mtayfur/opencode-prompt-enhancer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
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
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
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"
|
|
@@ -65,6 +65,17 @@ type Api = Parameters<TuiPlugin>[0]
|
|
|
65
65
|
type PluginState = {
|
|
66
66
|
enhancing: boolean
|
|
67
67
|
promptRef?: TuiPromptRef
|
|
68
|
+
promptTarget?: PromptTarget
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type PromptTarget =
|
|
72
|
+
| { name: "home", workspaceID?: string }
|
|
73
|
+
| { name: "session", sessionID: string }
|
|
74
|
+
|
|
75
|
+
type PromptHandle = {
|
|
76
|
+
target: PromptTarget
|
|
77
|
+
directory: string
|
|
78
|
+
ref?: TuiPromptRef
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
function parseModelString(value: string | undefined): ModelRef | undefined {
|
|
@@ -134,40 +145,117 @@ function nextPromptInfo(prompt: TuiPromptInfo, input: string): TuiPromptInfo {
|
|
|
134
145
|
}
|
|
135
146
|
}
|
|
136
147
|
|
|
137
|
-
function
|
|
138
|
-
|
|
148
|
+
function samePromptTarget(left: PromptTarget | undefined, right: PromptTarget | undefined): boolean {
|
|
149
|
+
if (!left || !right || left.name !== right.name) return false
|
|
150
|
+
if (left.name === "home" && right.name === "home") {
|
|
151
|
+
return left.workspaceID === right.workspaceID
|
|
152
|
+
}
|
|
153
|
+
if (left.name === "session" && right.name === "session") {
|
|
154
|
+
return left.sessionID === right.sessionID
|
|
155
|
+
}
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function resolvePromptTarget(route: TuiRouteCurrent, workspaceID?: string): PromptTarget | undefined {
|
|
160
|
+
if (route.name === "home") return { name: "home", workspaceID }
|
|
161
|
+
if (isSessionRoute(route)) {
|
|
162
|
+
return { name: "session", sessionID: route.params.sessionID }
|
|
163
|
+
}
|
|
164
|
+
return undefined
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function isPromptTargetActive(route: TuiRouteCurrent, target: PromptTarget): boolean {
|
|
168
|
+
if (target.name === "home") return route.name === "home"
|
|
169
|
+
return isSessionRoute(route) && route.params.sessionID === target.sessionID
|
|
139
170
|
}
|
|
140
171
|
|
|
141
|
-
function
|
|
142
|
-
state.
|
|
172
|
+
function currentPromptTarget(api: Api, state: PluginState): PromptTarget | undefined {
|
|
173
|
+
return state.promptTarget ?? resolvePromptTarget(api.route.current)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isPromptHandleActive(api: Api, state: PluginState, handle: PromptHandle): boolean {
|
|
177
|
+
if (!isPromptTargetActive(api.route.current, handle.target)) return false
|
|
178
|
+
if (api.state.path.directory !== handle.directory) return false
|
|
179
|
+
|
|
180
|
+
if (handle.ref) {
|
|
181
|
+
return state.promptRef === handle.ref && samePromptTarget(state.promptTarget, handle.target)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return true
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function bindPromptRef(
|
|
188
|
+
state: PluginState,
|
|
189
|
+
target: PromptTarget,
|
|
190
|
+
forwarded: ((ref: TuiPromptRef | undefined) => void) | undefined,
|
|
191
|
+
ref: TuiPromptRef | undefined,
|
|
192
|
+
): void {
|
|
193
|
+
if (ref) {
|
|
194
|
+
state.promptRef = ref
|
|
195
|
+
state.promptTarget = target
|
|
196
|
+
} else if (samePromptTarget(state.promptTarget, target)) {
|
|
197
|
+
state.promptRef = undefined
|
|
198
|
+
state.promptTarget = undefined
|
|
199
|
+
}
|
|
200
|
+
|
|
143
201
|
forwarded?.(ref)
|
|
144
202
|
}
|
|
145
203
|
|
|
146
|
-
async function
|
|
147
|
-
|
|
204
|
+
async function clearPrompt(api: Api, state: PluginState, handle: PromptHandle, signal: AbortSignal, template?: TuiPromptInfo): Promise<boolean> {
|
|
205
|
+
if (!isPromptHandleActive(api, state, handle)) return false
|
|
206
|
+
|
|
207
|
+
const promptRef = handle.ref
|
|
208
|
+
if (promptRef) {
|
|
209
|
+
promptRef.set(nextPromptInfo(template ?? promptRef.current, ""))
|
|
210
|
+
return true
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
await api.client.tui.clearPrompt({ directory: handle.directory }, { signal, throwOnError: true } as const)
|
|
214
|
+
return true
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function writePrompt(
|
|
218
|
+
api: Api,
|
|
219
|
+
state: PluginState,
|
|
220
|
+
handle: PromptHandle,
|
|
221
|
+
input: string,
|
|
222
|
+
signal: AbortSignal,
|
|
223
|
+
template?: TuiPromptInfo,
|
|
224
|
+
): Promise<boolean> {
|
|
225
|
+
if (!isPromptHandleActive(api, state, handle)) return false
|
|
226
|
+
|
|
227
|
+
const promptRef = handle.ref
|
|
148
228
|
if (promptRef) {
|
|
149
229
|
promptRef.set(nextPromptInfo(template ?? promptRef.current, input))
|
|
150
230
|
promptRef.focus()
|
|
151
|
-
return
|
|
231
|
+
return true
|
|
152
232
|
}
|
|
153
233
|
|
|
154
|
-
const directory = api.state.path.directory
|
|
155
234
|
const requestOptions = { signal, throwOnError: true } as const
|
|
156
|
-
await api.client.tui.clearPrompt({ directory }, requestOptions)
|
|
235
|
+
await api.client.tui.clearPrompt({ directory: handle.directory }, requestOptions)
|
|
157
236
|
if (input) {
|
|
158
|
-
await api.client.tui.appendPrompt({ directory, text: input }, requestOptions)
|
|
237
|
+
await api.client.tui.appendPrompt({ directory: handle.directory, text: input }, requestOptions)
|
|
159
238
|
}
|
|
239
|
+
return true
|
|
160
240
|
}
|
|
161
241
|
|
|
162
|
-
async function restorePrompt(
|
|
163
|
-
|
|
242
|
+
async function restorePrompt(
|
|
243
|
+
api: Api,
|
|
244
|
+
state: PluginState,
|
|
245
|
+
handle: PromptHandle,
|
|
246
|
+
prompt: TuiPromptInfo,
|
|
247
|
+
signal: AbortSignal,
|
|
248
|
+
): Promise<boolean> {
|
|
249
|
+
if (!isPromptHandleActive(api, state, handle)) return false
|
|
250
|
+
|
|
251
|
+
const promptRef = handle.ref
|
|
164
252
|
if (promptRef) {
|
|
165
253
|
promptRef.set(clonePromptInfo(prompt))
|
|
166
254
|
promptRef.focus()
|
|
167
|
-
return
|
|
255
|
+
return true
|
|
168
256
|
}
|
|
169
257
|
|
|
170
|
-
|
|
258
|
+
return writePrompt(api, state, handle, prompt.input, signal, prompt)
|
|
171
259
|
}
|
|
172
260
|
|
|
173
261
|
function gatherContext(api: Api): string {
|
|
@@ -297,7 +385,19 @@ function openEnhanceDialog(
|
|
|
297
385
|
|
|
298
386
|
if (signal.aborted) return
|
|
299
387
|
|
|
300
|
-
const
|
|
388
|
+
const target = currentPromptTarget(api, state)
|
|
389
|
+
if (!target) {
|
|
390
|
+
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Prompt enhancement is only available from a prompt view." })
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const handle: PromptHandle = {
|
|
395
|
+
target,
|
|
396
|
+
directory: api.state.path.directory,
|
|
397
|
+
ref: state.promptRef,
|
|
398
|
+
}
|
|
399
|
+
const originalPrompt = handle.ref ? clonePromptInfo(handle.ref.current) : undefined
|
|
400
|
+
const initialValue = originalPrompt?.input ?? ""
|
|
301
401
|
|
|
302
402
|
api.ui.dialog.replace(() => (
|
|
303
403
|
<api.ui.DialogPrompt
|
|
@@ -313,11 +413,13 @@ function openEnhanceDialog(
|
|
|
313
413
|
return
|
|
314
414
|
}
|
|
315
415
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
416
|
+
if (!isPromptHandleActive(api, state, handle)) {
|
|
417
|
+
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Prompt changed while the dialog was open." })
|
|
418
|
+
api.ui.dialog.clear()
|
|
419
|
+
return
|
|
320
420
|
}
|
|
421
|
+
|
|
422
|
+
state.enhancing = true
|
|
321
423
|
api.ui.dialog.clear()
|
|
322
424
|
api.ui.toast({
|
|
323
425
|
variant: "info",
|
|
@@ -328,10 +430,29 @@ function openEnhanceDialog(
|
|
|
328
430
|
|
|
329
431
|
void (async () => {
|
|
330
432
|
try {
|
|
433
|
+
const cleared = await clearPrompt(api, state, handle, signal, originalPrompt)
|
|
434
|
+
if (!cleared) {
|
|
435
|
+
api.ui.toast({
|
|
436
|
+
variant: "warning",
|
|
437
|
+
title: TOAST_TITLE,
|
|
438
|
+
message: "Prompt changed before enhancement started.",
|
|
439
|
+
})
|
|
440
|
+
return
|
|
441
|
+
}
|
|
442
|
+
|
|
331
443
|
const enhanced = await enhanceWithModel(api, options, input, signal)
|
|
332
444
|
if (signal.aborted) return
|
|
333
445
|
|
|
334
|
-
await writePrompt(api, state, enhanced, signal, originalPrompt)
|
|
446
|
+
const wrote = await writePrompt(api, state, handle, enhanced, signal, originalPrompt)
|
|
447
|
+
if (!wrote) {
|
|
448
|
+
api.ui.toast({
|
|
449
|
+
variant: "warning",
|
|
450
|
+
title: TOAST_TITLE,
|
|
451
|
+
message: "Enhanced prompt is ready, but the original prompt is no longer active.",
|
|
452
|
+
})
|
|
453
|
+
return
|
|
454
|
+
}
|
|
455
|
+
|
|
335
456
|
api.ui.toast({
|
|
336
457
|
variant: "success",
|
|
337
458
|
title: "Prompt enhanced",
|
|
@@ -341,11 +462,26 @@ function openEnhanceDialog(
|
|
|
341
462
|
} catch (error) {
|
|
342
463
|
if (signal.aborted) return
|
|
343
464
|
|
|
465
|
+
let restored = true
|
|
344
466
|
if (originalPrompt) {
|
|
345
|
-
|
|
467
|
+
try {
|
|
468
|
+
restored = await restorePrompt(api, state, handle, originalPrompt, signal)
|
|
469
|
+
} catch {
|
|
470
|
+
// Best-effort restore; do not suppress the error toast.
|
|
471
|
+
restored = false
|
|
472
|
+
}
|
|
473
|
+
} else {
|
|
474
|
+
try {
|
|
475
|
+
restored = await writePrompt(api, state, handle, input, signal)
|
|
476
|
+
} catch {
|
|
477
|
+
restored = false
|
|
478
|
+
}
|
|
346
479
|
}
|
|
347
480
|
|
|
348
|
-
const
|
|
481
|
+
const baseMessage = error instanceof Error ? error.message : "Model enhancement failed."
|
|
482
|
+
const message = restored
|
|
483
|
+
? baseMessage
|
|
484
|
+
: `${baseMessage} Original prompt could not be restored because the active prompt changed.`
|
|
349
485
|
api.ui.toast({ variant: "error", title: TOAST_TITLE, message })
|
|
350
486
|
} finally {
|
|
351
487
|
state.enhancing = false
|
|
@@ -364,7 +500,7 @@ const tui: TuiPlugin = async (api, options) => {
|
|
|
364
500
|
home_prompt(_ctx, props) {
|
|
365
501
|
return (
|
|
366
502
|
<api.ui.Prompt
|
|
367
|
-
ref={(ref) => bindPromptRef(state, props.ref, ref)}
|
|
503
|
+
ref={(ref) => bindPromptRef(state, { name: "home", workspaceID: props.workspace_id }, props.ref, ref)}
|
|
368
504
|
workspaceID={props.workspace_id}
|
|
369
505
|
right={<api.ui.Slot name="home_prompt_right" workspace_id={props.workspace_id} />}
|
|
370
506
|
placeholders={HOME_PROMPT_PLACEHOLDERS}
|
|
@@ -374,7 +510,7 @@ const tui: TuiPlugin = async (api, options) => {
|
|
|
374
510
|
session_prompt(_ctx, props) {
|
|
375
511
|
return (
|
|
376
512
|
<api.ui.Prompt
|
|
377
|
-
ref={(ref) => bindPromptRef(state, props.ref, ref)}
|
|
513
|
+
ref={(ref) => bindPromptRef(state, { name: "session", sessionID: props.session_id }, props.ref, ref)}
|
|
378
514
|
sessionID={props.session_id}
|
|
379
515
|
visible={props.visible}
|
|
380
516
|
disabled={props.disabled}
|
|
@@ -409,6 +545,8 @@ const tui: TuiPlugin = async (api, options) => {
|
|
|
409
545
|
api.lifecycle.onDispose(() => {
|
|
410
546
|
unregister()
|
|
411
547
|
state.enhancing = false
|
|
548
|
+
state.promptRef = undefined
|
|
549
|
+
state.promptTarget = undefined
|
|
412
550
|
})
|
|
413
551
|
}
|
|
414
552
|
|