opencode-usage-stats 0.2.0 → 0.3.1
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/README.md +17 -2
- package/dist/tui.d.ts +22 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +206 -0
- package/dist/tui.js.map +7 -0
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -17,7 +17,22 @@ OpenCode will install the npm package automatically on startup.
|
|
|
17
17
|
|
|
18
18
|
## Use
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
The plugin provides both a TUI sidebar panel and a slash command.
|
|
21
|
+
|
|
22
|
+
### TUI sidebar
|
|
23
|
+
|
|
24
|
+
When OpenCode starts, a small **Usage** panel appears in the right sidebar showing the current session's live stats:
|
|
25
|
+
|
|
26
|
+
- Messages
|
|
27
|
+
- Cost
|
|
28
|
+
- Tokens
|
|
29
|
+
- Top model
|
|
30
|
+
|
|
31
|
+
It updates automatically as new assistant messages arrive.
|
|
32
|
+
|
|
33
|
+
### Slash command
|
|
34
|
+
|
|
35
|
+
Run the slash command in any session:
|
|
21
36
|
|
|
22
37
|
```
|
|
23
38
|
/usage
|
|
@@ -29,7 +44,7 @@ Or ask the agent:
|
|
|
29
44
|
Show my OpenCode usage
|
|
30
45
|
```
|
|
31
46
|
|
|
32
|
-
|
|
47
|
+
This returns a markdown summary of all sessions, messages, tokens, cost, model/tool breakdown.
|
|
33
48
|
|
|
34
49
|
## Configuration
|
|
35
50
|
|
package/dist/tui.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { TuiPluginModule } from "@opencode-ai/plugin/tui";
|
|
2
|
+
import type { Message } from "@opencode-ai/sdk/v2";
|
|
3
|
+
export interface CurrentSessionStats {
|
|
4
|
+
messages: number;
|
|
5
|
+
cost: number;
|
|
6
|
+
inputTokens: number;
|
|
7
|
+
outputTokens: number;
|
|
8
|
+
reasoningTokens: number;
|
|
9
|
+
cacheRead: number;
|
|
10
|
+
cacheWrite: number;
|
|
11
|
+
models: Array<{
|
|
12
|
+
key: string;
|
|
13
|
+
messages: number;
|
|
14
|
+
cost: number;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export declare function aggregateCurrentSessionMessages(messages: ReadonlyArray<Message>): CurrentSessionStats;
|
|
18
|
+
declare const plugin: TuiPluginModule & {
|
|
19
|
+
id: string;
|
|
20
|
+
};
|
|
21
|
+
export default plugin;
|
|
22
|
+
//# sourceMappingURL=tui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAC5E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC/D;AAmCD,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,GAC/B,mBAAmB,CA0CrB;AAgED,QAAA,MAAM,MAAM,EAAE,eAAe,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAmG3C,CAAA;eAEc,MAAM"}
|
package/dist/tui.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// src/tui.ts
|
|
2
|
+
import { createElement, insert, setProp } from "@opentui/solid";
|
|
3
|
+
import { createSignal } from "solid-js";
|
|
4
|
+
function element(tag, props, children = []) {
|
|
5
|
+
const node = createElement(tag);
|
|
6
|
+
for (const [key, value] of Object.entries(props)) {
|
|
7
|
+
if (value !== void 0) setProp(node, key, value);
|
|
8
|
+
}
|
|
9
|
+
for (const child of children) {
|
|
10
|
+
if (child !== null && child !== void 0 && child !== false) {
|
|
11
|
+
insert(node, child);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return node;
|
|
15
|
+
}
|
|
16
|
+
function box(props, children = []) {
|
|
17
|
+
return element("box", props, children);
|
|
18
|
+
}
|
|
19
|
+
function text(props, children = []) {
|
|
20
|
+
return element("text", props, children);
|
|
21
|
+
}
|
|
22
|
+
function formatNumber(num) {
|
|
23
|
+
if (num >= 1e6) return `${(num / 1e6).toFixed(1)}M`;
|
|
24
|
+
if (num >= 1e3) return `${(num / 1e3).toFixed(1)}K`;
|
|
25
|
+
return num.toLocaleString();
|
|
26
|
+
}
|
|
27
|
+
function aggregateCurrentSessionMessages(messages) {
|
|
28
|
+
const stats = {
|
|
29
|
+
messages: 0,
|
|
30
|
+
cost: 0,
|
|
31
|
+
inputTokens: 0,
|
|
32
|
+
outputTokens: 0,
|
|
33
|
+
reasoningTokens: 0,
|
|
34
|
+
cacheRead: 0,
|
|
35
|
+
cacheWrite: 0,
|
|
36
|
+
models: []
|
|
37
|
+
};
|
|
38
|
+
const modelMap = {};
|
|
39
|
+
for (const message of messages) {
|
|
40
|
+
if (message.role !== "assistant") continue;
|
|
41
|
+
stats.messages += 1;
|
|
42
|
+
stats.cost += message.cost ?? 0;
|
|
43
|
+
const tokens = message.tokens;
|
|
44
|
+
if (tokens) {
|
|
45
|
+
stats.inputTokens += tokens.input ?? 0;
|
|
46
|
+
stats.outputTokens += tokens.output ?? 0;
|
|
47
|
+
stats.reasoningTokens += tokens.reasoning ?? 0;
|
|
48
|
+
stats.cacheRead += tokens.cache?.read ?? 0;
|
|
49
|
+
stats.cacheWrite += tokens.cache?.write ?? 0;
|
|
50
|
+
}
|
|
51
|
+
const key = `${message.providerID}/${message.modelID}`;
|
|
52
|
+
if (!modelMap[key]) {
|
|
53
|
+
modelMap[key] = { messages: 0, cost: 0 };
|
|
54
|
+
}
|
|
55
|
+
modelMap[key].messages += 1;
|
|
56
|
+
modelMap[key].cost += message.cost ?? 0;
|
|
57
|
+
}
|
|
58
|
+
stats.models = Object.entries(modelMap).sort(([, a], [, b]) => b.messages - a.messages).map(([key, usage]) => ({ key, ...usage }));
|
|
59
|
+
return stats;
|
|
60
|
+
}
|
|
61
|
+
function renderStatsPanel(stats, theme, error) {
|
|
62
|
+
const totalTokens = stats.inputTokens + stats.outputTokens + stats.reasoningTokens + stats.cacheRead + stats.cacheWrite;
|
|
63
|
+
const lines = [
|
|
64
|
+
{ label: "Messages", value: stats.messages.toLocaleString() },
|
|
65
|
+
{ label: "Cost", value: `$${stats.cost.toFixed(4)}` },
|
|
66
|
+
{ label: "Tokens", value: formatNumber(totalTokens) }
|
|
67
|
+
];
|
|
68
|
+
if (stats.models.length > 0) {
|
|
69
|
+
lines.push({
|
|
70
|
+
label: "Model",
|
|
71
|
+
value: stats.models[0].key.split("/").pop() ?? stats.models[0].key
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const children = [text({ fg: theme.text, bold: true }, ["Usage"])];
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
children.push(
|
|
77
|
+
box({ width: "100%", flexDirection: "row" }, [
|
|
78
|
+
text({ fg: theme.textMuted }, [`${line.label}:`]),
|
|
79
|
+
text({ fg: line.accent ? theme.accent : theme.text }, [line.value])
|
|
80
|
+
])
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (error) {
|
|
84
|
+
children.push(text({ fg: theme.accent, wrap: "truncate" }, [error]));
|
|
85
|
+
}
|
|
86
|
+
return box(
|
|
87
|
+
{
|
|
88
|
+
width: "100%",
|
|
89
|
+
flexDirection: "column",
|
|
90
|
+
paddingTop: 1,
|
|
91
|
+
paddingBottom: 1,
|
|
92
|
+
paddingLeft: 1,
|
|
93
|
+
paddingRight: 1
|
|
94
|
+
},
|
|
95
|
+
children
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
function log(api, level, message, extra) {
|
|
99
|
+
try {
|
|
100
|
+
void api.client.app.log({
|
|
101
|
+
service: "opencode-usage-stats:tui",
|
|
102
|
+
level,
|
|
103
|
+
message,
|
|
104
|
+
extra
|
|
105
|
+
});
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
var plugin = {
|
|
110
|
+
id: "opencode-usage-stats:tui",
|
|
111
|
+
tui: async (api) => {
|
|
112
|
+
log(api, "info", "TUI plugin initializing");
|
|
113
|
+
const [getStats, setStats] = createSignal({
|
|
114
|
+
messages: 0,
|
|
115
|
+
cost: 0,
|
|
116
|
+
inputTokens: 0,
|
|
117
|
+
outputTokens: 0,
|
|
118
|
+
reasoningTokens: 0,
|
|
119
|
+
cacheRead: 0,
|
|
120
|
+
cacheWrite: 0,
|
|
121
|
+
models: []
|
|
122
|
+
});
|
|
123
|
+
const [getError, setError] = createSignal(void 0);
|
|
124
|
+
const [getSessionID, setSessionID] = createSignal("");
|
|
125
|
+
const refresh = () => {
|
|
126
|
+
if (!api.state.ready) {
|
|
127
|
+
setError("state not ready");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const sessionID = getSessionID();
|
|
131
|
+
if (!sessionID) return;
|
|
132
|
+
try {
|
|
133
|
+
const messages = api.state.session.messages(sessionID);
|
|
134
|
+
const stats = aggregateCurrentSessionMessages(messages);
|
|
135
|
+
setStats(stats);
|
|
136
|
+
setError(void 0);
|
|
137
|
+
log(api, "debug", "Refreshed usage stats", {
|
|
138
|
+
messages: stats.messages,
|
|
139
|
+
cost: stats.cost,
|
|
140
|
+
sessionID
|
|
141
|
+
});
|
|
142
|
+
} catch (error) {
|
|
143
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
144
|
+
setError(message);
|
|
145
|
+
log(api, "error", "Failed to refresh usage stats", { error: message, sessionID });
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const unsubscribers = [];
|
|
149
|
+
try {
|
|
150
|
+
unsubscribers.push(api.event.on("message.updated", refresh));
|
|
151
|
+
unsubscribers.push(api.event.on("message.part.updated", refresh));
|
|
152
|
+
unsubscribers.push(api.event.on("session.status", refresh));
|
|
153
|
+
log(api, "info", "Subscribed to usage events");
|
|
154
|
+
} catch (error) {
|
|
155
|
+
log(api, "error", "Failed to subscribe to events", {
|
|
156
|
+
error: error instanceof Error ? error.message : String(error)
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
api.slots.register({
|
|
161
|
+
order: 100,
|
|
162
|
+
slots: {
|
|
163
|
+
sidebar_content(_ctx, data) {
|
|
164
|
+
try {
|
|
165
|
+
if (data.session_id !== getSessionID()) {
|
|
166
|
+
setSessionID(data.session_id);
|
|
167
|
+
log(api, "debug", "Session changed", { session_id: data.session_id });
|
|
168
|
+
refresh();
|
|
169
|
+
}
|
|
170
|
+
const theme = api.theme.current;
|
|
171
|
+
const stats = getStats();
|
|
172
|
+
const error = getError();
|
|
173
|
+
return renderStatsPanel(stats, {
|
|
174
|
+
text: theme.text,
|
|
175
|
+
textMuted: theme.textMuted,
|
|
176
|
+
accent: theme.accent
|
|
177
|
+
}, error);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
180
|
+
log(api, "error", "Failed to render sidebar_content", { error: message });
|
|
181
|
+
return text({ fg: api.theme.current.error }, ["usage stats error"]);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
log(api, "info", "Registered sidebar_content slot");
|
|
187
|
+
api.renderer.requestRender();
|
|
188
|
+
} catch (error) {
|
|
189
|
+
log(api, "error", "Failed to register sidebar_content slot", {
|
|
190
|
+
error: error instanceof Error ? error.message : String(error)
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
api.lifecycle.onDispose(() => {
|
|
194
|
+
for (const unsubscribe of unsubscribers) {
|
|
195
|
+
unsubscribe();
|
|
196
|
+
}
|
|
197
|
+
log(api, "info", "TUI plugin disposed");
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
var tui_default = plugin;
|
|
202
|
+
export {
|
|
203
|
+
aggregateCurrentSessionMessages,
|
|
204
|
+
tui_default as default
|
|
205
|
+
};
|
|
206
|
+
//# sourceMappingURL=tui.js.map
|
package/dist/tui.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/tui.ts"],
|
|
4
|
+
"sourcesContent": ["import type { TuiPluginApi, TuiPluginModule } from \"@opencode-ai/plugin/tui\"\nimport type { Message } from \"@opencode-ai/sdk/v2\"\nimport type { JSX } from \"@opentui/solid\"\nimport { createElement, insert, setProp } from \"@opentui/solid\"\nimport { createSignal } from \"solid-js\"\n\nexport interface CurrentSessionStats {\n messages: number\n cost: number\n inputTokens: number\n outputTokens: number\n reasoningTokens: number\n cacheRead: number\n cacheWrite: number\n models: Array<{ key: string; messages: number; cost: number }>\n}\n\ntype Child = JSX.Element | string | number | null | undefined | false\n\nfunction element(\n tag: string,\n props: Record<string, unknown>,\n children: Child[] = [],\n): JSX.Element {\n const node = createElement(tag)\n for (const [key, value] of Object.entries(props)) {\n if (value !== undefined) setProp(node, key, value)\n }\n for (const child of children) {\n if (child !== null && child !== undefined && child !== false) {\n insert(node, child)\n }\n }\n return node as JSX.Element\n}\n\nfunction box(props: Record<string, unknown>, children: Child[] = []): JSX.Element {\n return element(\"box\", props, children)\n}\n\nfunction text(props: Record<string, unknown>, children: Child[] = []): JSX.Element {\n return element(\"text\", props, children)\n}\n\nfunction formatNumber(num: number): string {\n if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(1)}M`\n if (num >= 1_000) return `${(num / 1_000).toFixed(1)}K`\n return num.toLocaleString()\n}\n\nexport function aggregateCurrentSessionMessages(\n messages: ReadonlyArray<Message>,\n): CurrentSessionStats {\n const stats: CurrentSessionStats = {\n messages: 0,\n cost: 0,\n inputTokens: 0,\n outputTokens: 0,\n reasoningTokens: 0,\n cacheRead: 0,\n cacheWrite: 0,\n models: [],\n }\n\n const modelMap: Record<string, { messages: number; cost: number }> = {}\n\n for (const message of messages) {\n if (message.role !== \"assistant\") continue\n\n stats.messages += 1\n stats.cost += message.cost ?? 0\n\n const tokens = message.tokens\n if (tokens) {\n stats.inputTokens += tokens.input ?? 0\n stats.outputTokens += tokens.output ?? 0\n stats.reasoningTokens += tokens.reasoning ?? 0\n stats.cacheRead += tokens.cache?.read ?? 0\n stats.cacheWrite += tokens.cache?.write ?? 0\n }\n\n const key = `${message.providerID}/${message.modelID}`\n if (!modelMap[key]) {\n modelMap[key] = { messages: 0, cost: 0 }\n }\n modelMap[key].messages += 1\n modelMap[key].cost += message.cost ?? 0\n }\n\n stats.models = Object.entries(modelMap)\n .sort(([, a], [, b]) => b.messages - a.messages)\n .map(([key, usage]) => ({ key, ...usage }))\n\n return stats\n}\n\nfunction renderStatsPanel(\n stats: CurrentSessionStats,\n theme: { text: unknown; textMuted: unknown; accent: unknown },\n error?: string,\n): JSX.Element {\n const totalTokens =\n stats.inputTokens + stats.outputTokens + stats.reasoningTokens + stats.cacheRead + stats.cacheWrite\n\n const lines: Array<{ label: string; value: string; accent?: boolean }> = [\n { label: \"Messages\", value: stats.messages.toLocaleString() },\n { label: \"Cost\", value: `$${stats.cost.toFixed(4)}` },\n { label: \"Tokens\", value: formatNumber(totalTokens) },\n ]\n\n if (stats.models.length > 0) {\n lines.push({\n label: \"Model\",\n value: stats.models[0].key.split(\"/\").pop() ?? stats.models[0].key,\n })\n }\n\n const children: Child[] = [text({ fg: theme.text, bold: true }, [\"Usage\"])]\n\n for (const line of lines) {\n children.push(\n box({ width: \"100%\", flexDirection: \"row\" }, [\n text({ fg: theme.textMuted }, [`${line.label}:`]),\n text({ fg: line.accent ? theme.accent : theme.text }, [line.value]),\n ]),\n )\n }\n\n if (error) {\n children.push(text({ fg: theme.accent, wrap: \"truncate\" }, [error]))\n }\n\n return box(\n {\n width: \"100%\",\n flexDirection: \"column\",\n paddingTop: 1,\n paddingBottom: 1,\n paddingLeft: 1,\n paddingRight: 1,\n },\n children,\n )\n}\n\nfunction log(api: TuiPluginApi, level: \"debug\" | \"info\" | \"warn\" | \"error\", message: string, extra?: Record<string, unknown>) {\n try {\n void api.client.app.log({\n service: \"opencode-usage-stats:tui\",\n level,\n message,\n extra,\n })\n } catch {\n // Logging is best-effort; ignore failures.\n }\n}\n\nconst plugin: TuiPluginModule & { id: string } = {\n id: \"opencode-usage-stats:tui\",\n tui: async (api) => {\n log(api, \"info\", \"TUI plugin initializing\")\n\n const [getStats, setStats] = createSignal<CurrentSessionStats>({\n messages: 0,\n cost: 0,\n inputTokens: 0,\n outputTokens: 0,\n reasoningTokens: 0,\n cacheRead: 0,\n cacheWrite: 0,\n models: [],\n })\n\n const [getError, setError] = createSignal<string | undefined>(undefined)\n const [getSessionID, setSessionID] = createSignal<string>(\"\")\n\n const refresh = (): void => {\n if (!api.state.ready) {\n setError(\"state not ready\")\n return\n }\n const sessionID = getSessionID()\n if (!sessionID) return\n try {\n const messages = api.state.session.messages(sessionID)\n const stats = aggregateCurrentSessionMessages(messages)\n setStats(stats)\n setError(undefined)\n log(api, \"debug\", \"Refreshed usage stats\", {\n messages: stats.messages,\n cost: stats.cost,\n sessionID,\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n setError(message)\n log(api, \"error\", \"Failed to refresh usage stats\", { error: message, sessionID })\n }\n }\n\n const unsubscribers: Array<() => void> = []\n try {\n unsubscribers.push(api.event.on(\"message.updated\", refresh))\n unsubscribers.push(api.event.on(\"message.part.updated\", refresh))\n unsubscribers.push(api.event.on(\"session.status\", refresh))\n log(api, \"info\", \"Subscribed to usage events\")\n } catch (error) {\n log(api, \"error\", \"Failed to subscribe to events\", {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n try {\n api.slots.register({\n order: 100,\n slots: {\n sidebar_content(_ctx, data: { session_id: string }) {\n try {\n if (data.session_id !== getSessionID()) {\n setSessionID(data.session_id)\n log(api, \"debug\", \"Session changed\", { session_id: data.session_id })\n refresh()\n }\n\n const theme = api.theme.current\n const stats = getStats()\n const error = getError()\n\n return renderStatsPanel(stats, {\n text: theme.text,\n textMuted: theme.textMuted,\n accent: theme.accent,\n }, error)\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n log(api, \"error\", \"Failed to render sidebar_content\", { error: message })\n return text({ fg: api.theme.current.error }, [\"usage stats error\"])\n }\n },\n },\n })\n log(api, \"info\", \"Registered sidebar_content slot\")\n api.renderer.requestRender()\n } catch (error) {\n log(api, \"error\", \"Failed to register sidebar_content slot\", {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n\n api.lifecycle.onDispose(() => {\n for (const unsubscribe of unsubscribers) {\n unsubscribe()\n }\n log(api, \"info\", \"TUI plugin disposed\")\n })\n },\n}\n\nexport default plugin\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-usage-stats",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "An OpenCode plugin that surfaces local usage statistics and reserves a cloud usage API integration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./tui": {
|
|
14
|
+
"import": "./dist/tui.js"
|
|
12
15
|
}
|
|
13
16
|
},
|
|
14
17
|
"files": [
|
|
@@ -17,11 +20,13 @@
|
|
|
17
20
|
"LICENSE"
|
|
18
21
|
],
|
|
19
22
|
"oc-plugin": [
|
|
20
|
-
"server"
|
|
23
|
+
"server",
|
|
24
|
+
"tui"
|
|
21
25
|
],
|
|
22
26
|
"scripts": {
|
|
23
|
-
"build": "npm run build:
|
|
24
|
-
"build:
|
|
27
|
+
"build": "npm run build:server && npm run build:tui && npm run build:types",
|
|
28
|
+
"build:server": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:@opencode-ai/plugin --external:@opencode-ai/sdk --external:node:* --sourcemap",
|
|
29
|
+
"build:tui": "esbuild src/tui.ts --bundle --platform=node --format=esm --outfile=dist/tui.js --external:@opencode-ai/plugin --external:@opentui/solid --external:solid-js --sourcemap",
|
|
25
30
|
"build:types": "tsc --emitDeclarationOnly",
|
|
26
31
|
"test": "vitest run",
|
|
27
32
|
"prepublishOnly": "npm run build"
|
|
@@ -50,8 +55,11 @@
|
|
|
50
55
|
"devDependencies": {
|
|
51
56
|
"@opencode-ai/plugin": "^1.18.11",
|
|
52
57
|
"@opencode-ai/sdk": "^1.18.11",
|
|
58
|
+
"@opentui/keymap": "^0.4.5",
|
|
59
|
+
"@opentui/solid": "^0.4.5",
|
|
53
60
|
"@types/node": "^22.0.0",
|
|
54
61
|
"esbuild": "^0.25.0",
|
|
62
|
+
"solid-js": "^1.9.14",
|
|
55
63
|
"typescript": "^7.0.0",
|
|
56
64
|
"vitest": "^4.0.0"
|
|
57
65
|
},
|