@clwnd/opencode 0.18.5 → 0.18.7

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/dist/index.js CHANGED
@@ -1462,6 +1462,23 @@ var clwndPlugin = async (input) => {
1462
1462
  }
1463
1463
  }
1464
1464
  },
1465
+ // Suppress OC's builtin tools that clwnd has replaced. We can't remove
1466
+ // them from OC's registry, but we can rewrite their descriptions so the
1467
+ // model never picks them. Without this, the model sees "edit", "write",
1468
+ // "glob", "grep" in the active tool list, tries to call them, and gets
1469
+ // an invalid-tool bounce because Claude CLI has --disallowedTools'd them.
1470
+ "tool.definition": async (input2, output) => {
1471
+ const replaced = {
1472
+ edit: "DISABLED \u2014 use do_code instead.",
1473
+ write: "DISABLED \u2014 use do_code for code files, do_noncode for non-code files.",
1474
+ glob: "DISABLED \u2014 use read with a glob pattern or directory path instead.",
1475
+ grep: "DISABLED \u2014 use read with the pattern parameter instead."
1476
+ };
1477
+ if (replaced[input2.toolID]) {
1478
+ output.description = replaced[input2.toolID];
1479
+ output.parameters = { type: "object", properties: {} };
1480
+ }
1481
+ },
1465
1482
  "chat.headers": async (ctx, output) => {
1466
1483
  output.headers["x-clwnd-agent"] = typeof ctx.agent === "string" ? ctx.agent : ctx.agent?.name ?? JSON.stringify(ctx.agent);
1467
1484
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clwnd/opencode",
3
- "version": "0.18.5",
3
+ "version": "0.18.7",
4
4
  "description": "clwnd for opencode",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,11 +9,12 @@
9
9
  "import": "./dist/index.js"
10
10
  },
11
11
  "./tui": {
12
- "import": "./dist/tui.js"
12
+ "import": "./tui.tsx"
13
13
  }
14
14
  },
15
15
  "files": [
16
- "dist/"
16
+ "dist/",
17
+ "tui.tsx"
17
18
  ],
18
19
  "scripts": {
19
20
  "build": "tsup",
@@ -40,7 +41,10 @@
40
41
  "@ai-sdk/provider-utils": "^4.0.21"
41
42
  },
42
43
  "peerDependencies": {
43
- "@opencode-ai/plugin": ">=1.3.5"
44
+ "@opencode-ai/plugin": ">=1.3.5",
45
+ "@opentui/core": "^0.1.97",
46
+ "@opentui/solid": "^0.1.97",
47
+ "solid-js": "^1.9.12"
44
48
  },
45
49
  "devDependencies": {
46
50
  "@opencode-ai/plugin": "^1.3.5",
package/tui.tsx ADDED
@@ -0,0 +1,123 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui"
3
+ import { createSignal, onCleanup, createMemo, Show } from "solid-js"
4
+
5
+ const VERSION = (() => {
6
+ try {
7
+ const fs = require("fs")
8
+ const path = require("path")
9
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"))
10
+ return pkg.version ?? "?"
11
+ } catch {
12
+ return "?"
13
+ }
14
+ })()
15
+
16
+ interface DaemonData {
17
+ status: "connected" | "disconnected"
18
+ procs: number
19
+ sessions: number
20
+ uptimeMin: number
21
+ readDedup: number
22
+ bashCapped: number
23
+ contextWarnings: number
24
+ }
25
+
26
+ const SOCK_PATH = (() => {
27
+ const runtime = process.env.XDG_RUNTIME_DIR
28
+ const sock = process.env.CLWND_SOCKET ?? (runtime ? `${runtime}/clwnd/clwnd.sock` : "/tmp/clwnd/clwnd.sock")
29
+ return sock + ".http"
30
+ })()
31
+
32
+ async function fetchDaemon(): Promise<DaemonData> {
33
+ try {
34
+ const [statusResp, savingsResp] = await Promise.all([
35
+ fetch("http://localhost/status", { unix: SOCK_PATH } as RequestInit),
36
+ fetch("http://localhost/savings", { unix: SOCK_PATH } as RequestInit),
37
+ ])
38
+ const status = (await statusResp.json()) as any
39
+ const savings = (await savingsResp.json()) as any
40
+ const c = savings.counters ?? {}
41
+ return {
42
+ status: "connected",
43
+ procs: (status.procs ?? []).length,
44
+ sessions: status.sessions ?? 0,
45
+ uptimeMin: Math.round((savings.uptimeMs ?? 0) / 60_000),
46
+ readDedup: c.readDedupHits ?? 0,
47
+ bashCapped: c.bashTruncated ?? 0,
48
+ contextWarnings: c.contextOverThreshold ?? 0,
49
+ }
50
+ } catch {
51
+ return {
52
+ status: "disconnected", procs: 0, sessions: 0, uptimeMin: 0,
53
+ readDedup: 0, bashCapped: 0, contextWarnings: 0,
54
+ }
55
+ }
56
+ }
57
+
58
+ function SidebarView(props: { api: any; session_id: string }) {
59
+ const theme = () => props.api.theme.current
60
+ const [data, setData] = createSignal<DaemonData>({
61
+ status: "disconnected", procs: 0, sessions: 0, uptimeMin: 0,
62
+ readDedup: 0, bashCapped: 0, contextWarnings: 0,
63
+ })
64
+
65
+ const poll = () => fetchDaemon().then(setData).catch(() => {})
66
+ poll()
67
+ const timer = setInterval(poll, 10_000)
68
+ onCleanup(() => clearInterval(timer))
69
+
70
+ const statusLine = createMemo(() => {
71
+ const d = data()
72
+ const dot = d.status === "connected" ? "●" : "○"
73
+ return `${dot} ${d.status} · ${String(d.uptimeMin)}m`
74
+ })
75
+
76
+ const procsLine = createMemo(() => {
77
+ const d = data()
78
+ return `${String(d.procs)} proc${d.procs !== 1 ? "s" : ""} · ${String(d.sessions)} session${d.sessions !== 1 ? "s" : ""}`
79
+ })
80
+
81
+ const savingsLine = createMemo(() => {
82
+ const d = data()
83
+ if (d.readDedup === 0 && d.bashCapped === 0) return ""
84
+ return `saved: ${String(d.readDedup)} dedup · ${String(d.bashCapped)} capped`
85
+ })
86
+
87
+ // green = active procs, secondary = idle (connected, nothing running), muted = disconnected
88
+ const dotColor = createMemo(() => {
89
+ const d = data()
90
+ if (d.status === "disconnected") return theme().textMuted
91
+ if (d.procs > 0) return theme().success
92
+ return theme().secondary
93
+ })
94
+
95
+ return (
96
+ <box>
97
+ <text fg={theme().textMuted}>
98
+ <span style={{ fg: dotColor() }}>{"•"}</span>
99
+ {" "}
100
+ <b>{"clwnd"}</b>
101
+ {" "}
102
+ <span>{VERSION}</span>
103
+ </text>
104
+ <text fg={theme().textMuted}>{procsLine()}</text>
105
+ <Show when={savingsLine() !== ""}>
106
+ <text fg={theme().textMuted}>{savingsLine()}</text>
107
+ </Show>
108
+ </box>
109
+ )
110
+ }
111
+
112
+ const tui: TuiPlugin = async (api) => {
113
+ api.slots.register({
114
+ order: 150,
115
+ slots: {
116
+ sidebar_content(_ctx, props) {
117
+ return <SidebarView api={api} session_id={props.session_id} />
118
+ },
119
+ },
120
+ })
121
+ }
122
+
123
+ export default { id: "@clwnd/opencode", tui } satisfies TuiPluginModule & { id: string }
package/dist/tui.js DELETED
@@ -1,92 +0,0 @@
1
- // tui.tsx
2
- import { createSignal, onCleanup, createMemo } from "solid-js";
3
- var SOCK_PATH = (() => {
4
- const runtime = process.env.XDG_RUNTIME_DIR;
5
- const sock = process.env.CLWND_SOCKET ?? (runtime ? `${runtime}/clwnd/clwnd.sock` : "/tmp/clwnd/clwnd.sock");
6
- return sock + ".http";
7
- })();
8
- async function fetchDaemon() {
9
- try {
10
- const [statusResp, savingsResp] = await Promise.all([
11
- fetch("http://localhost/status", { unix: SOCK_PATH }),
12
- fetch("http://localhost/savings", { unix: SOCK_PATH })
13
- ]);
14
- const status = await statusResp.json();
15
- const savings = await savingsResp.json();
16
- const c = savings.counters ?? {};
17
- return {
18
- status: "connected",
19
- procs: (status.procs ?? []).length,
20
- sessions: status.sessions ?? 0,
21
- uptimeMin: Math.round((savings.uptimeMs ?? 0) / 6e4),
22
- readDedup: c.readDedupHits ?? 0,
23
- bashCapped: c.bashTruncated ?? 0,
24
- contextWarnings: c.contextOverThreshold ?? 0
25
- };
26
- } catch {
27
- return {
28
- status: "disconnected",
29
- procs: 0,
30
- sessions: 0,
31
- uptimeMin: 0,
32
- readDedup: 0,
33
- bashCapped: 0,
34
- contextWarnings: 0
35
- };
36
- }
37
- }
38
- function View(props) {
39
- const theme = () => props.api.theme.current;
40
- const [data, setData] = createSignal({
41
- status: "disconnected",
42
- procs: 0,
43
- sessions: 0,
44
- uptimeMin: 0,
45
- readDedup: 0,
46
- bashCapped: 0,
47
- contextWarnings: 0
48
- });
49
- const poll = () => fetchDaemon().then(setData).catch(() => {
50
- });
51
- poll();
52
- const timer = setInterval(poll, 1e4);
53
- onCleanup(() => clearInterval(timer));
54
- const dot = createMemo(() => data().status === "connected" ? "\u25CF" : "\u25CB");
55
- const dotColor = createMemo(() => data().status === "connected" ? theme().success : theme().error);
56
- return <box>
57
- <text fg={theme().text}>
58
- <b>clwnd</b>
59
- </text>
60
- <text>
61
- <text fg={dotColor()}>{dot()}</text>
62
- <text fg={theme().textMuted}> {data().status} · {data().uptimeMin}m uptime</text>
63
- </text>
64
- <text fg={theme().textMuted}>
65
- {data().procs} proc{data().procs !== 1 ? "s" : ""} · {data().sessions} session{data().sessions !== 1 ? "s" : ""}
66
- </text>
67
- {data().readDedup > 0 || data().bashCapped > 0 ? <text fg={theme().textMuted}>
68
- saved: {data().readDedup} dedup · {data().bashCapped} capped
69
- </text> : null}
70
- {data().contextWarnings > 0 ? <text fg={theme().warning}>
71
- ⚠ {data().contextWarnings} context warning{data().contextWarnings !== 1 ? "s" : ""}
72
- </text> : null}
73
- </box>;
74
- }
75
- var tui = async (api) => {
76
- api.slots.register({
77
- order: 200,
78
- // after OC's built-in Context (100), before Footer
79
- slots: {
80
- sidebar_content(_ctx, props) {
81
- return <View api={api} session_id={props.session_id} />;
82
- }
83
- }
84
- });
85
- };
86
- var plugin = {
87
- tui
88
- };
89
- var tui_default = plugin;
90
- export {
91
- tui_default as default
92
- };