opencode-brl-cost 0.1.1 → 0.1.3
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 +26 -10
- package/dist/index.js +142 -0
- package/dist/index.js.map +7 -0
- package/package.json +13 -11
- package/index.tsx +0 -48
package/README.md
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
# opencode-brl-cost
|
|
2
2
|
|
|
3
|
-
OpenCode **TUI plugin** that pins
|
|
3
|
+
OpenCode **TUI plugin** that pins cost tracking to the bottom bar, in Brazilian Reais (R$).
|
|
4
4
|
|
|
5
5
|
## How it works
|
|
6
6
|
|
|
7
7
|
- Renders a fixed status bar via the `app_bottom` TUI slot.
|
|
8
|
-
-
|
|
9
|
-
-
|
|
8
|
+
- Bottom-left shows **aggregated spend** since the start of today, this week (Monday), and this month.
|
|
9
|
+
- Bottom-right shows **the active session's total cost** (`session.cost`, USD).
|
|
10
|
+
- Costs come from each session's cumulative cost (`session.cost`, USD), attributed to the day it was **created**, converted with a **hardcoded rate of R$ 5,00 / US$ 1,00**.
|
|
11
|
+
- Sessions are enumerated via the SDK (`session.list`) on load and kept fresh through `session.*` events.
|
|
12
|
+
- Falls back to `R$ 0,00` when there's no data.
|
|
10
13
|
- Currency formatting uses `Intl.NumberFormat` with `pt-BR` (e.g. `R$ 1,23`).
|
|
11
14
|
|
|
12
15
|
## Install
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
Add the plugin to `tui.json`:
|
|
15
18
|
|
|
16
19
|
```json
|
|
17
20
|
{
|
|
@@ -19,26 +22,39 @@ TUI plugins are loaded from a file plugin or an npm package. Add the plugin to `
|
|
|
19
22
|
}
|
|
20
23
|
```
|
|
21
24
|
|
|
22
|
-
Then restart OpenCode
|
|
25
|
+
Then **restart OpenCode**. The bottom bar appears at the bottom.
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
> Pin a version for stability: `"opencode-brl-cost@0.1.2"`.
|
|
28
|
+
|
|
29
|
+
## Why this shape
|
|
30
|
+
|
|
31
|
+
Per the `qwert:opencode-plugin` skill, OpenCode plugins must ship a **built `.js`** via npm with an
|
|
32
|
+
`exports["./tui"]` entrypoint. The runtime does **not** transpile `.tsx`/`.ts` from `node_modules` — it
|
|
33
|
+
fails silently. So this package publishes `dist/index.js` (esbuild output), never raw `.tsx`.
|
|
26
34
|
|
|
27
35
|
## Local development
|
|
28
36
|
|
|
29
37
|
```bash
|
|
30
38
|
npm install
|
|
39
|
+
npm run build # → dist/index.js
|
|
31
40
|
npm run typecheck
|
|
32
41
|
```
|
|
33
42
|
|
|
34
|
-
To test
|
|
43
|
+
To test a local checkout without publishing, point `tui.json` at the built file:
|
|
35
44
|
|
|
36
45
|
```json
|
|
37
46
|
{
|
|
38
|
-
"plugin": ["file:///absolute/path/to/opencode-brl-cost/index.
|
|
47
|
+
"plugin": ["file:///absolute/path/to/opencode-brl-cost/dist/index.js"]
|
|
39
48
|
}
|
|
40
49
|
```
|
|
41
50
|
|
|
51
|
+
## Publish
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm version patch
|
|
55
|
+
npm publish
|
|
56
|
+
```
|
|
57
|
+
|
|
42
58
|
## Roadmap
|
|
43
59
|
|
|
44
60
|
- Configurable USD→BRL rate (instead of the hardcoded 5)
|
|
@@ -47,4 +63,4 @@ To test locally, point `tui.json` at the source file:
|
|
|
47
63
|
|
|
48
64
|
## License
|
|
49
65
|
|
|
50
|
-
|
|
66
|
+
AGPL-3.0 · Copyright (C) 2025 oporpino <dev@porpi.no>
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// src/index.tsx
|
|
2
|
+
import { createSignal } from "solid-js";
|
|
3
|
+
import { jsx, jsxs } from "@opentui/solid/jsx-runtime";
|
|
4
|
+
var BRL_PER_USD = 5;
|
|
5
|
+
var formatBRL = (usd) => {
|
|
6
|
+
const rounded = Math.round(usd * BRL_PER_USD * 100) / 100;
|
|
7
|
+
return new Intl.NumberFormat("pt-BR", {
|
|
8
|
+
style: "currency",
|
|
9
|
+
currency: "BRL"
|
|
10
|
+
}).format(rounded);
|
|
11
|
+
};
|
|
12
|
+
var startOfDay = (date) => new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();
|
|
13
|
+
var startOfWeek = (date) => {
|
|
14
|
+
const current = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
15
|
+
current.setDate(current.getDate() - (current.getDay() + 6) % 7);
|
|
16
|
+
return current.getTime();
|
|
17
|
+
};
|
|
18
|
+
var startOfMonth = (date) => new Date(date.getFullYear(), date.getMonth(), 1).getTime();
|
|
19
|
+
var tui = async (api) => {
|
|
20
|
+
const [dayCost, setDayCost] = createSignal("R$ 0,00");
|
|
21
|
+
const [weekCost, setWeekCost] = createSignal("R$ 0,00");
|
|
22
|
+
const [monthCost, setMonthCost] = createSignal("R$ 0,00");
|
|
23
|
+
const [sessionCost, setSessionCost] = createSignal("R$ 0,00");
|
|
24
|
+
const costs = /* @__PURE__ */ new Map();
|
|
25
|
+
const assess = (info) => {
|
|
26
|
+
if (!info || typeof info.cost !== "number" || typeof info.time?.created !== "number") return;
|
|
27
|
+
costs.set(info.id, { cost: info.cost, created: info.time.created });
|
|
28
|
+
};
|
|
29
|
+
const currentSessionID = () => {
|
|
30
|
+
const route = api.route.current;
|
|
31
|
+
return route.name === "session" && typeof route.params?.sessionID === "string" ? route.params.sessionID : void 0;
|
|
32
|
+
};
|
|
33
|
+
const fetchSessionCost = async (sessionID) => {
|
|
34
|
+
try {
|
|
35
|
+
const result = await api.client.session.get({ sessionID }, { throwOnError: true });
|
|
36
|
+
const info = result.data;
|
|
37
|
+
if (!info) return;
|
|
38
|
+
assess(info);
|
|
39
|
+
setSessionCost(formatBRL(typeof info.cost === "number" ? info.cost : 0));
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const refresh = () => {
|
|
44
|
+
const now = /* @__PURE__ */ new Date();
|
|
45
|
+
const dayStart = startOfDay(now);
|
|
46
|
+
const weekStart = startOfWeek(now);
|
|
47
|
+
const monthStart = startOfMonth(now);
|
|
48
|
+
let day = 0;
|
|
49
|
+
let week = 0;
|
|
50
|
+
let month = 0;
|
|
51
|
+
for (const entry of costs.values()) {
|
|
52
|
+
if (entry.created >= dayStart) day += entry.cost;
|
|
53
|
+
if (entry.created >= weekStart) week += entry.cost;
|
|
54
|
+
if (entry.created >= monthStart) month += entry.cost;
|
|
55
|
+
}
|
|
56
|
+
setDayCost(formatBRL(day));
|
|
57
|
+
setWeekCost(formatBRL(week));
|
|
58
|
+
setMonthCost(formatBRL(month));
|
|
59
|
+
const sessionID = currentSessionID();
|
|
60
|
+
const sessionCost2 = sessionID ? costs.get(sessionID)?.cost : void 0;
|
|
61
|
+
setSessionCost(formatBRL(typeof sessionCost2 === "number" ? sessionCost2 : 0));
|
|
62
|
+
if (sessionID) void fetchSessionCost(sessionID);
|
|
63
|
+
};
|
|
64
|
+
const seed = async () => {
|
|
65
|
+
try {
|
|
66
|
+
const result = await api.client.session.list({ limit: 1e3 });
|
|
67
|
+
if (Array.isArray(result?.data)) result.data.forEach((s) => assess(s));
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
refresh();
|
|
71
|
+
};
|
|
72
|
+
refresh();
|
|
73
|
+
void seed();
|
|
74
|
+
api.event.on("session.created", (event) => {
|
|
75
|
+
assess(event.properties.info);
|
|
76
|
+
refresh();
|
|
77
|
+
});
|
|
78
|
+
api.event.on("session.updated", (event) => {
|
|
79
|
+
assess(event.properties.info);
|
|
80
|
+
if (event.properties.sessionID === currentSessionID()) refresh();
|
|
81
|
+
});
|
|
82
|
+
api.event.on("session.deleted", (event) => {
|
|
83
|
+
costs.delete(event.properties.sessionID);
|
|
84
|
+
refresh();
|
|
85
|
+
});
|
|
86
|
+
api.event.on("session.idle", (event) => {
|
|
87
|
+
if (event.properties.sessionID === currentSessionID()) refresh();
|
|
88
|
+
});
|
|
89
|
+
api.event.on("tui.session.select", (event) => {
|
|
90
|
+
if (event.properties.sessionID === currentSessionID()) refresh();
|
|
91
|
+
});
|
|
92
|
+
api.slots.register({
|
|
93
|
+
slots: {
|
|
94
|
+
app_bottom(ctx) {
|
|
95
|
+
if (!currentSessionID()) return void 0;
|
|
96
|
+
const theme = ctx.theme.current;
|
|
97
|
+
const separator = " | ";
|
|
98
|
+
return /* @__PURE__ */ jsxs(
|
|
99
|
+
"box",
|
|
100
|
+
{
|
|
101
|
+
flexDirection: "row",
|
|
102
|
+
justifyContent: "space-between",
|
|
103
|
+
flexGrow: 1,
|
|
104
|
+
paddingLeft: 2,
|
|
105
|
+
paddingRight: 2,
|
|
106
|
+
border: true,
|
|
107
|
+
borderColor: theme.border,
|
|
108
|
+
children: [
|
|
109
|
+
/* @__PURE__ */ jsxs("box", { flexDirection: "row", children: [
|
|
110
|
+
/* @__PURE__ */ jsx("text", { fg: theme.textMuted, children: "dia: " }),
|
|
111
|
+
/* @__PURE__ */ jsx("text", { fg: theme.accent, children: dayCost() }),
|
|
112
|
+
/* @__PURE__ */ jsxs("text", { fg: theme.textMuted, children: [
|
|
113
|
+
separator,
|
|
114
|
+
"semana: "
|
|
115
|
+
] }),
|
|
116
|
+
/* @__PURE__ */ jsx("text", { fg: theme.success, children: weekCost() }),
|
|
117
|
+
/* @__PURE__ */ jsxs("text", { fg: theme.textMuted, children: [
|
|
118
|
+
separator,
|
|
119
|
+
"m\xEAs: "
|
|
120
|
+
] }),
|
|
121
|
+
/* @__PURE__ */ jsx("text", { fg: theme.warning, children: monthCost() })
|
|
122
|
+
] }),
|
|
123
|
+
/* @__PURE__ */ jsxs("box", { flexDirection: "row", children: [
|
|
124
|
+
/* @__PURE__ */ jsx("text", { fg: theme.textMuted, children: "\u25C6 session: " }),
|
|
125
|
+
/* @__PURE__ */ jsx("text", { fg: theme.text, children: sessionCost() })
|
|
126
|
+
] })
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
var plugin = {
|
|
135
|
+
id: "opencode-brl-cost",
|
|
136
|
+
tui
|
|
137
|
+
};
|
|
138
|
+
var index_default = plugin;
|
|
139
|
+
export {
|
|
140
|
+
index_default as default
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.tsx"],
|
|
4
|
+
"sourcesContent": ["import { createSignal } from \"solid-js\"\nimport type { TuiPlugin, TuiPluginModule } from \"@opencode-ai/plugin/tui\"\n\nconst BRL_PER_USD = 5\n\nconst formatBRL = (usd: number) => {\n const rounded = Math.round(usd * BRL_PER_USD * 100) / 100\n return new Intl.NumberFormat(\"pt-BR\", {\n style: \"currency\",\n currency: \"BRL\",\n }).format(rounded)\n}\n\nconst startOfDay = (date: Date): number =>\n new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime()\n\nconst startOfWeek = (date: Date): number => {\n const current = new Date(date.getFullYear(), date.getMonth(), date.getDate())\n current.setDate(current.getDate() - ((current.getDay() + 6) % 7))\n return current.getTime()\n}\n\nconst startOfMonth = (date: Date): number =>\n new Date(date.getFullYear(), date.getMonth(), 1).getTime()\n\nconst tui: TuiPlugin = async (api) => {\n const [dayCost, setDayCost] = createSignal<string>(\"R$ 0,00\")\n const [weekCost, setWeekCost] = createSignal<string>(\"R$ 0,00\")\n const [monthCost, setMonthCost] = createSignal<string>(\"R$ 0,00\")\n const [sessionCost, setSessionCost] = createSignal<string>(\"R$ 0,00\")\n\n const costs = new Map<string, { cost: number; created: number }>()\n\n const assess = (info: { id: string; cost?: number; time?: { created?: number } } | undefined) => {\n if (!info || typeof info.cost !== \"number\" || typeof info.time?.created !== \"number\") return\n costs.set(info.id, { cost: info.cost, created: info.time.created })\n }\n\n const currentSessionID = (): string | undefined => {\n const route = api.route.current\n return route.name === \"session\" && typeof route.params?.sessionID === \"string\"\n ? route.params.sessionID\n : undefined\n }\n\n const fetchSessionCost = async (sessionID: string) => {\n try {\n const result = await api.client.session.get({ sessionID }, { throwOnError: true })\n const info = result.data\n if (!info) return\n assess(info)\n setSessionCost(formatBRL(typeof info.cost === \"number\" ? info.cost : 0))\n } catch {\n // ignore\n }\n }\n\n const refresh = () => {\n const now = new Date()\n const dayStart = startOfDay(now)\n const weekStart = startOfWeek(now)\n const monthStart = startOfMonth(now)\n let day = 0\n let week = 0\n let month = 0\n\n for (const entry of costs.values()) {\n if (entry.created >= dayStart) day += entry.cost\n if (entry.created >= weekStart) week += entry.cost\n if (entry.created >= monthStart) month += entry.cost\n }\n setDayCost(formatBRL(day))\n setWeekCost(formatBRL(week))\n setMonthCost(formatBRL(month))\n\n const sessionID = currentSessionID()\n const sessionCost = sessionID ? costs.get(sessionID)?.cost : undefined\n setSessionCost(formatBRL(typeof sessionCost === \"number\" ? sessionCost : 0))\n if (sessionID) void fetchSessionCost(sessionID)\n }\n\n const seed = async () => {\n try {\n const result = await api.client.session.list({ limit: 1000 })\n if (Array.isArray(result?.data)) result.data.forEach((s) => assess(s))\n } catch {\n // ignore\n }\n refresh()\n }\n\n refresh()\n void seed()\n\n api.event.on(\"session.created\", (event) => {\n assess(event.properties.info)\n refresh()\n })\n api.event.on(\"session.updated\", (event) => {\n assess(event.properties.info)\n if (event.properties.sessionID === currentSessionID()) refresh()\n })\n api.event.on(\"session.deleted\", (event) => {\n costs.delete(event.properties.sessionID)\n refresh()\n })\n api.event.on(\"session.idle\", (event) => {\n if (event.properties.sessionID === currentSessionID()) refresh()\n })\n api.event.on(\"tui.session.select\", (event) => {\n if (event.properties.sessionID === currentSessionID()) refresh()\n })\n\n api.slots.register({\n slots: {\n app_bottom(ctx) {\n if (!currentSessionID()) return undefined\n const theme = ctx.theme.current\n const separator = \" | \"\n return (\n <box\n flexDirection=\"row\"\n justifyContent=\"space-between\"\n flexGrow={1}\n paddingLeft={2}\n paddingRight={2}\n border\n borderColor={theme.border}\n >\n <box flexDirection=\"row\">\n <text fg={theme.textMuted}>dia: </text>\n <text fg={theme.accent}>{dayCost()}</text>\n <text fg={theme.textMuted}>{separator}semana: </text>\n <text fg={theme.success}>{weekCost()}</text>\n <text fg={theme.textMuted}>{separator}m\u00EAs: </text>\n <text fg={theme.warning}>{monthCost()}</text>\n </box>\n <box flexDirection=\"row\">\n <text fg={theme.textMuted}>\u25C6 session: </text>\n <text fg={theme.text}>{sessionCost()}</text>\n </box>\n </box>\n )\n },\n },\n })\n}\n\nconst plugin: TuiPluginModule = {\n id: \"opencode-brl-cost\",\n tui,\n}\n\nexport default plugin"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["sessionCost"]
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-brl-cost",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "OpenCode TUI plugin that shows the session cost in Brazilian Reais (R$)",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./index.
|
|
7
|
-
"files": [
|
|
8
|
-
"index.tsx"
|
|
9
|
-
],
|
|
6
|
+
"main": "./dist/index.js",
|
|
10
7
|
"exports": {
|
|
11
|
-
".": "./index.
|
|
12
|
-
"./tui": "./index.
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./tui": "./dist/index.js"
|
|
13
10
|
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
14
|
"scripts": {
|
|
15
|
+
"build": "node scripts/build.mjs",
|
|
16
|
+
"prepublishOnly": "npm run build",
|
|
15
17
|
"typecheck": "tsc --noEmit"
|
|
16
18
|
},
|
|
17
19
|
"keywords": [
|
|
@@ -22,12 +24,12 @@
|
|
|
22
24
|
"brl",
|
|
23
25
|
"currency"
|
|
24
26
|
],
|
|
25
|
-
"license": "
|
|
27
|
+
"license": "AGPL-3.0",
|
|
28
|
+
"author": "oporpino <dev@porpi.no>",
|
|
26
29
|
"devDependencies": {
|
|
27
30
|
"@opencode-ai/plugin": "1.18.29",
|
|
28
31
|
"@opentui/solid": "0.4.5",
|
|
29
|
-
"
|
|
30
|
-
"solid-js": "^1.8.0",
|
|
32
|
+
"esbuild": "^0.28.0",
|
|
31
33
|
"typescript": "5.8.2"
|
|
32
34
|
}
|
|
33
|
-
}
|
|
35
|
+
}
|
package/index.tsx
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui"
|
|
2
|
-
import type { Session } from "@opencode-ai/sdk/v2"
|
|
3
|
-
|
|
4
|
-
const BRL_PER_USD = 5
|
|
5
|
-
|
|
6
|
-
const formatBRL = (usd: number) => {
|
|
7
|
-
const rounded = Math.round(usd * BRL_PER_USD * 100) / 100
|
|
8
|
-
return new Intl.NumberFormat("pt-BR", {
|
|
9
|
-
style: "currency",
|
|
10
|
-
currency: "BRL",
|
|
11
|
-
}).format(rounded)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const tui: TuiPlugin = async (api) => {
|
|
15
|
-
const currentSessionID = (): string | undefined => {
|
|
16
|
-
const route = api.route.current
|
|
17
|
-
return route.name === "session" && typeof route.params?.sessionID === "string"
|
|
18
|
-
? route.params.sessionID
|
|
19
|
-
: undefined
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const currentCostBRL = (): string => {
|
|
23
|
-
const sessionID = currentSessionID()
|
|
24
|
-
if (!sessionID) return formatBRL(0)
|
|
25
|
-
const session = api.state.session.get(sessionID) as Session | undefined
|
|
26
|
-
return formatBRL(typeof session?.cost === "number" ? session.cost : 0)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
api.slots.register({
|
|
30
|
-
slots: {
|
|
31
|
-
app_bottom(ctx) {
|
|
32
|
-
const theme = ctx.theme.current
|
|
33
|
-
return (
|
|
34
|
-
<box flexDirection="row" justifyContent="flex-end" paddingRight={2}>
|
|
35
|
-
<text fg={theme.textMuted}>{currentCostBRL()}</text>
|
|
36
|
-
</box>
|
|
37
|
-
)
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
})
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const plugin: TuiPluginModule = {
|
|
44
|
-
id: "opencode-brl-cost",
|
|
45
|
-
tui,
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export default plugin
|