@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 +17 -0
- package/package.json +8 -4
- package/tui.tsx +123 -0
- package/dist/tui.js +0 -92
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.
|
|
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": "./
|
|
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
|
-
};
|