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 CHANGED
@@ -1,17 +1,20 @@
1
1
  # opencode-brl-cost
2
2
 
3
- OpenCode **TUI plugin** that pins the current session's cost to the bottom bar, in Brazilian Reais (R$).
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
- - Shows **the active session's total cost** (`session.cost`, USD) converted with a **hardcoded rate of R$ 5,00 / US$ 1,00**.
9
- - On the home screen (no session selected) the bar stays empty. It never shows a stale value.
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
- TUI plugins are loaded from a file plugin or an npm package. Add the plugin to `tui.json`:
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. The bottom bar appears when a session is open.
25
+ Then **restart OpenCode**. The bottom bar appears at the bottom.
23
26
 
24
- This repo is published as a `.tsx` source plugin (the format OpenCode's TUI loader transpiles natively),
25
- mirroring `@br4zz4/opencode-skill-picker`.
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 locally, point `tui.json` at the source file:
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.tsx"]
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
- MIT
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.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.tsx",
7
- "files": [
8
- "index.tsx"
9
- ],
6
+ "main": "./dist/index.js",
10
7
  "exports": {
11
- ".": "./index.tsx",
12
- "./tui": "./index.tsx"
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": "MIT",
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
- "@opencode-ai/sdk": "1.18.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