@objctp/opencode-better-prompt 0.8.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/LICENSE +21 -0
- package/README.md +161 -0
- package/agents/prompt-correction.md +121 -0
- package/agents/prompt-enhancement.md +91 -0
- package/agents/prompt-summarisation.md +68 -0
- package/agents/prompt-translation.md +54 -0
- package/config/better-prompt.local.md.example +88 -0
- package/opencode.json +6 -0
- package/package.json +48 -0
- package/plugins/better-prompt/agents.ts +238 -0
- package/plugins/better-prompt/catalog.ts +271 -0
- package/plugins/better-prompt/config.ts +104 -0
- package/plugins/better-prompt/format.ts +26 -0
- package/plugins/better-prompt/pipeline.ts +190 -0
- package/plugins/better-prompt/state.ts +21 -0
- package/plugins/better-prompt/tui/format.ts +42 -0
- package/plugins/better-prompt/tui/routes.tsx +310 -0
- package/plugins/better-prompt/tui/select-view.tsx +58 -0
- package/plugins/better-prompt/tui/sidebar-panel.tsx +220 -0
- package/plugins/better-prompt/types.ts +137 -0
- package/plugins/better-prompt-tui.tsx +129 -0
- package/plugins/better-prompt.ts +298 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { createEffect, createSignal, onCleanup } from "solid-js";
|
|
7
|
+
import { CONFIG_DEFAULTS, CONFIG_PATH, parseConfig } from "../config";
|
|
8
|
+
import type { Config } from "../config";
|
|
9
|
+
import { formatDuration, formatTokens } from "../format";
|
|
10
|
+
import type { PipelineState, StageState } from "../types";
|
|
11
|
+
|
|
12
|
+
const POLL_MS = 500;
|
|
13
|
+
|
|
14
|
+
const STATE_PATH = join(homedir(), ".local", "state", "opencode", "better-prompt", "state.json");
|
|
15
|
+
|
|
16
|
+
const STAGE_DEFS = [
|
|
17
|
+
{ key: "correction" as const, label: "correction", enabled: (c: Config) => c.correction },
|
|
18
|
+
{ key: "translation" as const, label: "translation", enabled: (c: Config) => c.translation },
|
|
19
|
+
{ key: "context" as const, label: "context", enabled: (c: Config) => c.enhancement },
|
|
20
|
+
{ key: "enhancement" as const, label: "enhancement", enabled: (c: Config) => c.enhancement },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export function SidebarPanel(props: { theme: Record<string, unknown> }) {
|
|
24
|
+
const [state, setState] = createSignal<PipelineState | null>(null);
|
|
25
|
+
const [config, setConfig] = createSignal<Config>({ ...CONFIG_DEFAULTS });
|
|
26
|
+
|
|
27
|
+
const accent = () => (props.theme.accent as string) || "#A78BFA";
|
|
28
|
+
const muted = () => (props.theme.textMuted as string) || "#888888";
|
|
29
|
+
const success = () => (props.theme.success as string) || "#22C55E";
|
|
30
|
+
const warning = () => (props.theme.warning as string) || "#F59E0B";
|
|
31
|
+
const info = () => (props.theme.info as string) || "#06B6D4";
|
|
32
|
+
|
|
33
|
+
createEffect(() => {
|
|
34
|
+
function load() {
|
|
35
|
+
try {
|
|
36
|
+
setConfig(parseConfig(CONFIG_PATH));
|
|
37
|
+
} catch {
|
|
38
|
+
// config read may fail if file doesn't exist yet
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
if (!existsSync(STATE_PATH)) {
|
|
42
|
+
setState(null);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const raw = readFileSync(STATE_PATH, "utf8").trim();
|
|
46
|
+
if (!raw) {
|
|
47
|
+
setState(null);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
setState(JSON.parse(raw) as PipelineState);
|
|
51
|
+
} catch {
|
|
52
|
+
// stale read is fine
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
load();
|
|
56
|
+
const timer = setInterval(load, POLL_MS);
|
|
57
|
+
onCleanup(() => clearInterval(timer));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const isVisible = () => state() !== null;
|
|
61
|
+
|
|
62
|
+
function stageSymbol(status: StageState["status"]): { char: string; color: string } {
|
|
63
|
+
switch (status) {
|
|
64
|
+
case "active":
|
|
65
|
+
return { char: "\u25C6", color: info() };
|
|
66
|
+
case "complete":
|
|
67
|
+
return { char: "\u25C7", color: success() };
|
|
68
|
+
case "skipped":
|
|
69
|
+
return { char: "\u25CB", color: muted() };
|
|
70
|
+
case "error":
|
|
71
|
+
return { char: "\u25B2", color: warning() };
|
|
72
|
+
default:
|
|
73
|
+
return { char: "\u25CB", color: muted() };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function stageVerb(label: string): string {
|
|
78
|
+
const map: Record<string, string> = {
|
|
79
|
+
correction: "correcting",
|
|
80
|
+
translation: "translating",
|
|
81
|
+
context: "summarising",
|
|
82
|
+
enhancement: "enhancing",
|
|
83
|
+
};
|
|
84
|
+
return map[label] || `${label}...`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function stageDetail(def: (typeof STAGE_DEFS)[number], ss: StageState, s: PipelineState): string {
|
|
88
|
+
if (ss.status === "active") {
|
|
89
|
+
return stageVerb(def.label);
|
|
90
|
+
}
|
|
91
|
+
if (ss.status === "complete") {
|
|
92
|
+
const parts: string[] = ["done"];
|
|
93
|
+
const dur = formatDuration(ss.durationMs);
|
|
94
|
+
if (dur) parts.push(dur);
|
|
95
|
+
if (def.key === "correction" && s.mistakes > 0) {
|
|
96
|
+
parts.push(`${s.mistakes} mistake${s.mistakes > 1 ? "s" : ""}`);
|
|
97
|
+
}
|
|
98
|
+
return parts.join(" \u00B7 ");
|
|
99
|
+
}
|
|
100
|
+
if (ss.status === "skipped") {
|
|
101
|
+
const parts: string[] = ["skipped"];
|
|
102
|
+
if (def.key === "translation" && s.language === "en") parts.push("(en)");
|
|
103
|
+
return parts.join(" ");
|
|
104
|
+
}
|
|
105
|
+
if (ss.status === "error") {
|
|
106
|
+
return ss.error || "error";
|
|
107
|
+
}
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<box width="100%" flexDirection="column" marginBottom={isVisible() ? 1 : 0}>
|
|
113
|
+
{isVisible() && (
|
|
114
|
+
<text fg={accent()}>
|
|
115
|
+
<b>Better Prompt</b>
|
|
116
|
+
</text>
|
|
117
|
+
)}
|
|
118
|
+
|
|
119
|
+
{isVisible() && (
|
|
120
|
+
<box width="100%" flexDirection="column" paddingLeft={1}>
|
|
121
|
+
{(() => {
|
|
122
|
+
const s = state();
|
|
123
|
+
if (!s) return [];
|
|
124
|
+
const c = config();
|
|
125
|
+
const verbose = c.verbose;
|
|
126
|
+
const visibleStages = STAGE_DEFS.filter((def) => def.enabled(c));
|
|
127
|
+
const elements: unknown[] = [];
|
|
128
|
+
|
|
129
|
+
for (let i = 0; i < visibleStages.length; i++) {
|
|
130
|
+
const def = visibleStages[i];
|
|
131
|
+
const ss = s.stages[def.key];
|
|
132
|
+
const sym = stageSymbol(ss.status);
|
|
133
|
+
const isLast = i === visibleStages.length - 1;
|
|
134
|
+
const connector = isLast ? " " : "\u2502 ";
|
|
135
|
+
|
|
136
|
+
elements.push(
|
|
137
|
+
<box flexDirection="row">
|
|
138
|
+
<text fg={sym.color}>{sym.char}</text>
|
|
139
|
+
<text fg={accent()}> {def.label}</text>
|
|
140
|
+
</box>,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const detail = stageDetail(def, ss, s);
|
|
144
|
+
const showLangBadge =
|
|
145
|
+
def.key === "correction" && s.language && ss.status !== "pending";
|
|
146
|
+
|
|
147
|
+
elements.push(
|
|
148
|
+
<box flexDirection="row">
|
|
149
|
+
<text fg={muted()}>{connector}</text>
|
|
150
|
+
{showLangBadge ? (
|
|
151
|
+
<>
|
|
152
|
+
<text bg="#374151" fg="#E5E7EB">
|
|
153
|
+
{" "}
|
|
154
|
+
{s.language}{" "}
|
|
155
|
+
</text>
|
|
156
|
+
<text fg={ss.status === "error" ? warning() : muted()}> {detail}</text>
|
|
157
|
+
</>
|
|
158
|
+
) : (
|
|
159
|
+
<text fg={ss.status === "error" ? warning() : muted()}> {detail}</text>
|
|
160
|
+
)}
|
|
161
|
+
</box>,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
if (
|
|
165
|
+
verbose &&
|
|
166
|
+
def.key === "correction" &&
|
|
167
|
+
ss.status === "complete" &&
|
|
168
|
+
s.preview?.mistakeDetails &&
|
|
169
|
+
s.preview.mistakeDetails.length > 0
|
|
170
|
+
) {
|
|
171
|
+
for (const m of s.preview.mistakeDetails.slice(0, 5)) {
|
|
172
|
+
elements.push(
|
|
173
|
+
<box flexDirection="row">
|
|
174
|
+
<text fg={muted()}>{connector}</text>
|
|
175
|
+
<text fg={muted()}>
|
|
176
|
+
{" "}
|
|
177
|
+
{m.type}: "{m.original}" → "{m.correction}"
|
|
178
|
+
</text>
|
|
179
|
+
</box>,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!isLast) {
|
|
185
|
+
elements.push(<text fg={muted()}>│</text>);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (s.inputTokens > 0 || s.outputTokens > 0) {
|
|
190
|
+
const costParts: string[] = [];
|
|
191
|
+
if (s.cost > 0) costParts.push(`$${s.cost.toFixed(4)}`);
|
|
192
|
+
costParts.push(
|
|
193
|
+
`${formatTokens(s.inputTokens)}\u2192${formatTokens(s.outputTokens)}t`,
|
|
194
|
+
);
|
|
195
|
+
elements.push(
|
|
196
|
+
<box flexDirection="row" marginTop={0}>
|
|
197
|
+
<text fg={muted()}> </text>
|
|
198
|
+
{s.cost > 0 ? (
|
|
199
|
+
<text fg={muted()}> {costParts.join(" \u00B7 ")}</text>
|
|
200
|
+
) : (
|
|
201
|
+
<text fg={muted()}> {costParts[0]}</text>
|
|
202
|
+
)}
|
|
203
|
+
{s.sessionInputTokens > 0 ? (
|
|
204
|
+
<text fg={muted()}>
|
|
205
|
+
{" "}
|
|
206
|
+
(sess: {formatTokens(s.sessionInputTokens)}→
|
|
207
|
+
{formatTokens(s.sessionOutputTokens)}t)
|
|
208
|
+
</text>
|
|
209
|
+
) : null}
|
|
210
|
+
</box>,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return elements;
|
|
215
|
+
})()}
|
|
216
|
+
</box>
|
|
217
|
+
)}
|
|
218
|
+
</box>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
|
|
3
|
+
// :::: Model / catalog types :::: ///////////////////////////
|
|
4
|
+
|
|
5
|
+
export type ModelRef = { providerID: string; modelID: string };
|
|
6
|
+
|
|
7
|
+
export interface ModelEntry {
|
|
8
|
+
id: string;
|
|
9
|
+
providerID: string;
|
|
10
|
+
modelID: string;
|
|
11
|
+
tier: "fast" | "capable" | "powerful";
|
|
12
|
+
context: number;
|
|
13
|
+
cost: number;
|
|
14
|
+
toolCall: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CatalogModel {
|
|
18
|
+
id: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
tool_call?: boolean;
|
|
21
|
+
limit?: { context?: number; output?: number };
|
|
22
|
+
cost?: { input?: number; output?: number };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CatalogProvider {
|
|
26
|
+
id: string;
|
|
27
|
+
name?: string;
|
|
28
|
+
env?: string[];
|
|
29
|
+
models?: Record<string, CatalogModel>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type Catalog = Record<string, CatalogProvider>;
|
|
33
|
+
|
|
34
|
+
export interface CatalogCandidate {
|
|
35
|
+
id: string;
|
|
36
|
+
context: number;
|
|
37
|
+
cost: number;
|
|
38
|
+
toolCall: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface OpenCodeConfig {
|
|
42
|
+
disabled_providers?: string[];
|
|
43
|
+
enabled_providers?: string[];
|
|
44
|
+
provider?: Record<string, { models?: Record<string, unknown> }>;
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// :::: Pipeline types :::: //////////////////////////////////
|
|
49
|
+
|
|
50
|
+
export interface Usage {
|
|
51
|
+
cost: number;
|
|
52
|
+
inputTokens: number;
|
|
53
|
+
outputTokens: number;
|
|
54
|
+
cacheWriteTokens: number;
|
|
55
|
+
cacheReadTokens: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface PipelineResult {
|
|
59
|
+
result: string;
|
|
60
|
+
corrected: string | null;
|
|
61
|
+
detectedLanguage: string | null;
|
|
62
|
+
mistakes: Array<{ type: string; original: string; correction: string }>;
|
|
63
|
+
contextSummary: string;
|
|
64
|
+
usage: Usage;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type StageStatus = "starting" | "complete" | "skipped" | "error";
|
|
68
|
+
export type StageNotifier = (stage: string, status: StageStatus, detail?: string) => void;
|
|
69
|
+
|
|
70
|
+
export interface SessionContext {
|
|
71
|
+
summary: string;
|
|
72
|
+
lastMessageID: string;
|
|
73
|
+
messageCount: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface AuditEntry {
|
|
77
|
+
date: string;
|
|
78
|
+
prompt: string;
|
|
79
|
+
language: string | null;
|
|
80
|
+
corrected: string | null;
|
|
81
|
+
enhanced: string | null;
|
|
82
|
+
"mistake-nature": string[];
|
|
83
|
+
mistakes: Array<{ type: string; original: string; correction: string }>;
|
|
84
|
+
models: {
|
|
85
|
+
correction: string | null;
|
|
86
|
+
translation: string | null;
|
|
87
|
+
enhancement: string | null;
|
|
88
|
+
context: string | null;
|
|
89
|
+
};
|
|
90
|
+
usage: Usage;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// :::: State types :::: /////////////////////////////////////
|
|
94
|
+
|
|
95
|
+
export interface StageState {
|
|
96
|
+
status: "pending" | "active" | "complete" | "skipped" | "error";
|
|
97
|
+
durationMs: number | null;
|
|
98
|
+
error?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface PipelineState {
|
|
102
|
+
timestamp: string;
|
|
103
|
+
status: "running" | "modified" | "no_changes" | "error";
|
|
104
|
+
language: string | null;
|
|
105
|
+
mistakes: number;
|
|
106
|
+
stages: {
|
|
107
|
+
correction: StageState;
|
|
108
|
+
translation: StageState;
|
|
109
|
+
context: StageState;
|
|
110
|
+
enhancement: StageState;
|
|
111
|
+
};
|
|
112
|
+
cost: number;
|
|
113
|
+
inputTokens: number;
|
|
114
|
+
outputTokens: number;
|
|
115
|
+
cacheWriteTokens: number;
|
|
116
|
+
cacheReadTokens: number;
|
|
117
|
+
sessionCost: number;
|
|
118
|
+
sessionInputTokens: number;
|
|
119
|
+
sessionOutputTokens: number;
|
|
120
|
+
preview?: {
|
|
121
|
+
original: string;
|
|
122
|
+
processed: string;
|
|
123
|
+
mistakeDetails: Array<{ type: string; original: string; correction: string }>;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// :::: Plugin dependency injection :::: /////////////////////
|
|
128
|
+
|
|
129
|
+
export type ToastVariant = "info" | "success" | "error";
|
|
130
|
+
|
|
131
|
+
export interface PipelineDeps {
|
|
132
|
+
client: PluginInput["client"];
|
|
133
|
+
logError: (message: string, error?: unknown) => Promise<void>;
|
|
134
|
+
logWarn: (message: string, detail?: string) => Promise<void>;
|
|
135
|
+
toast: (message: string, variant?: ToastVariant, duration?: number) => Promise<void>;
|
|
136
|
+
sessionContexts: Map<string, SessionContext>;
|
|
137
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/** @jsxImportSource @opentui/solid */
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui";
|
|
6
|
+
import { SidebarPanel } from "./better-prompt/tui/sidebar-panel";
|
|
7
|
+
import { AuditRoute, ConfigRoute, ToggleRoute } from "./better-prompt/tui/routes";
|
|
8
|
+
|
|
9
|
+
// :::: TUI Plugin :::: //////////////////////////////////////
|
|
10
|
+
|
|
11
|
+
const tui: TuiPlugin = async (api) => {
|
|
12
|
+
function getAuditPath(): string {
|
|
13
|
+
return join(api.state.path.directory, ".opencode", "better-prompt", "audit.json");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Track previous route so we can navigate back
|
|
17
|
+
let prevRoute: { name: string; params?: Record<string, unknown> } = {
|
|
18
|
+
name: "home",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function goBack() {
|
|
22
|
+
api.route.navigate(prevRoute.name, prevRoute.params);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function snapshotCurrentRoute() {
|
|
26
|
+
return {
|
|
27
|
+
name: api.route.current.name,
|
|
28
|
+
params: (api.route.current as Record<string, unknown>).params as
|
|
29
|
+
| Record<string, unknown>
|
|
30
|
+
| undefined,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// :::: Command handlers (orchestration) :::: /////////////
|
|
35
|
+
|
|
36
|
+
function handleToggle() {
|
|
37
|
+
prevRoute = snapshotCurrentRoute();
|
|
38
|
+
api.route.navigate("better-prompt:toggle");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function handleConfig() {
|
|
42
|
+
prevRoute = snapshotCurrentRoute();
|
|
43
|
+
api.route.navigate("better-prompt:config");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function handleAudit() {
|
|
47
|
+
const auditPath = getAuditPath();
|
|
48
|
+
|
|
49
|
+
if (!existsSync(auditPath)) {
|
|
50
|
+
api.ui.toast({
|
|
51
|
+
variant: "info",
|
|
52
|
+
message: "No audit data available. Enable with /better-prompt:toggle audit on",
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const allLines = readFileSync(auditPath, "utf8")
|
|
58
|
+
.trim()
|
|
59
|
+
.split("\n")
|
|
60
|
+
.filter((l: string) => l.trim());
|
|
61
|
+
|
|
62
|
+
if (allLines.length === 0) {
|
|
63
|
+
api.ui.toast({ variant: "info", message: "Audit trail is empty." });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
prevRoute = snapshotCurrentRoute();
|
|
68
|
+
api.route.navigate("better-prompt:audit");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// :::: Registration :::: /////////////////////////////////
|
|
72
|
+
|
|
73
|
+
api.slots.register({
|
|
74
|
+
order: 150,
|
|
75
|
+
slots: {
|
|
76
|
+
sidebar_content(ctx) {
|
|
77
|
+
return (
|
|
78
|
+
<SidebarPanel
|
|
79
|
+
theme={(ctx.theme as unknown as { current: Record<string, unknown> }).current ?? {}}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
api.route.register([
|
|
87
|
+
{ name: "better-prompt:toggle", render: () => <ToggleRoute api={api} goBack={goBack} /> },
|
|
88
|
+
{ name: "better-prompt:config", render: () => <ConfigRoute api={api} goBack={goBack} /> },
|
|
89
|
+
{ name: "better-prompt:audit", render: () => <AuditRoute api={api} goBack={goBack} /> },
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
api.keymap.registerLayer({
|
|
93
|
+
commands: [
|
|
94
|
+
{
|
|
95
|
+
name: "better-prompt.toggle",
|
|
96
|
+
title: "BP: Toggle Stage",
|
|
97
|
+
category: "Better Prompt",
|
|
98
|
+
namespace: "palette",
|
|
99
|
+
slashName: "better-prompt:toggle",
|
|
100
|
+
run: handleToggle,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "better-prompt.config",
|
|
104
|
+
title: "BP: Show Config",
|
|
105
|
+
category: "Better Prompt",
|
|
106
|
+
namespace: "palette",
|
|
107
|
+
slashName: "better-prompt:config",
|
|
108
|
+
run: handleConfig,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "better-prompt.audit",
|
|
112
|
+
title: "BP: Audit Trail",
|
|
113
|
+
category: "Better Prompt",
|
|
114
|
+
namespace: "palette",
|
|
115
|
+
slashName: "better-prompt:audit",
|
|
116
|
+
run: handleAudit,
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// :::: Module export :::: ///////////////////////////////////
|
|
123
|
+
|
|
124
|
+
const plugin: TuiPluginModule & { id: string } = {
|
|
125
|
+
id: "@objctp/opencode-better-prompt",
|
|
126
|
+
tui,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export default plugin;
|