opencode-skill-tracker-tui 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 aihaipeng
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # opencode-skill-tracker
2
+
3
+ English | [简体中文](README.zh-CN.md)
4
+
5
+ An [OpenCode](https://opencode.ai) TUI plugin that adds a `Skills` section to the right sidebar: every skill visible to OpenCode is listed per session, loaded ones are highlighted and sorted first, and individual skills can be hidden from the sidebar.
6
+
7
+ Formerly `opencode-skills-sidebar`; saved state (collapsed / hidden skills) migrates automatically.
8
+
9
+ ## Features
10
+
11
+ - `Skills` section in the session sidebar, discovery via OpenCode itself (`client.app.skills()`), deduplicated and sorted by name
12
+ - Loaded state per session, detected from three sources: `skill` tool parts (`input.name`), text parts carrying the `<skill_content name="...">` tag injected by opencode, and TUI slash-command expansion (skill body pasted as a plain user text part, matched against known skill content)
13
+ - Loaded skills are highlighted (green bullet) and sorted first, both in the sidebar and in `/skills-status`
14
+ - Collapsible panel header with a live summary — `(X loaded Y available)` (`+Z` when skills are hidden)
15
+ - Hide individual skills via the `/hide-skills` dialog; `Show all hidden` restores everything in one step, and an `N hidden (show all)` footer in the sidebar does the same
16
+ - Collapsed state and hidden set persist across restarts (plugin kv), with automatic migration from the legacy `opencode-skills-sidebar` keys
17
+ - Incremental per-session scanning (message watermark), so long sessions do not trigger repeated full rescans
18
+
19
+ ## Requirements
20
+
21
+ - OpenCode with TUI plugin support (`slots.sidebar_content`)
22
+ - [Bun](https://bun.sh) to build from source
23
+
24
+ ## Installation
25
+
26
+ This is a **TUI plugin**, so it must be configured in `~/.config/opencode/tui.json`, not in `opencode.json`.
27
+
28
+ ### Option A: from npm
29
+
30
+ Reference the package name (see step 2) — no build needed.
31
+
32
+ ### Option B: build from source
33
+
34
+ ```bash
35
+ git clone <this-repo>
36
+ cd opencode-skill-tracker
37
+ bun install
38
+ bun run build
39
+ ```
40
+
41
+ That produces `dist/tui.js`.
42
+
43
+ ### 2. Register it in OpenCode
44
+
45
+ npm installs go by package name; source builds use the absolute path to `dist/tui.js`. Add it to `~/.config/opencode/tui.json`:
46
+
47
+ ```json
48
+ {
49
+ "$schema": "https://opencode.ai/tui.json",
50
+ "plugin": [
51
+ "opencode-skill-tracker-tui"
52
+ ]
53
+ }
54
+ ```
55
+
56
+ or, for a source build:
57
+
58
+ ```json
59
+ {
60
+ "$schema": "https://opencode.ai/tui.json",
61
+ "plugin": [
62
+ "C:\\path\\to\\opencode-skill-tracker\\dist\\tui.js"
63
+ ]
64
+ }
65
+ ```
66
+
67
+ Keep any existing entries in the `plugin` array — it can hold multiple plugins.
68
+
69
+ ### 3. Restart OpenCode
70
+
71
+ TUI plugins are loaded at startup; there is no hot reload. Restart `opencode` after building or updating.
72
+
73
+ ## Usage
74
+
75
+ | Action | Result |
76
+ | --- | --- |
77
+ | Click the `Skills` header | Collapse / expand the panel |
78
+ | Click a skill row | Nothing (by design) |
79
+ | Click `N hidden (show all)` | Restore all hidden skills |
80
+ | `/skills-status` | Dialog with every non-hidden skill, `Loaded` / `Unloaded` state, loaded first |
81
+ | `/hide-skills` | Dialog to hide / restore individual skills, with search |
82
+
83
+ Notes:
84
+
85
+ - `/skills-status` reflects the currently open session and needs one to be open.
86
+ - In the collapsed header, `X` counts loaded skills that are not hidden, `Y` counts all discovered skills, `Z` counts hidden ones.
87
+ - A skill loaded via a slash command (`/some-skill`) counts as loaded once the skill body has been pasted into the session.
88
+
89
+ ## How it works
90
+
91
+ - Registers a `sidebar_content` slot via the OpenCode TUI plugin API (`@opencode-ai/plugin/tui`), slot `order: 250` (between the built-in `MCP` at 200 and `LSP` at 300)
92
+ - Skill discovery comes from `client.app.skills()`; the loaded set is tracked per session
93
+ - Loaded detection scans message parts incrementally (per-message watermark), and re-scans when the skill list refreshes (content matching needs the list)
94
+ - Refresh sources: `message.part.updated`, `message.updated`, `session.updated` (incremental), `session.created`, `project.updated`, `workspace.ready`, `worktree.ready` (skill list refetch), plus a 250ms startup retry and a 500ms backfill after opening a session
95
+ - Prose that merely quotes the literal `<skill_content>` tag (e.g. in assistant messages) is ignored: tag matches must name a known skill
96
+
97
+ ## Troubleshooting
98
+
99
+ - **No `Skills` section**: check the path in `tui.json` is absolute and correct, then restart. `opencode --pure` skips all external plugins.
100
+ - **A skill was loaded but shows as unloaded**: slash-command loading is detected by matching the pasted skill body, which requires the skill list to be loaded first. It backfills automatically; collapsing and re-expanding the panel, or switching sessions, forces an immediate rescan.
101
+ - **Updated the plugin but nothing changed**: TUI plugins load at startup — restart `opencode`.
102
+
103
+ ## Development
104
+
105
+ ```bash
106
+ bun run build # bundle to dist/tui.js + declarations
107
+ bun run typecheck # tsc --noEmit
108
+ ```
109
+
110
+ Source layout:
111
+
112
+ - `src/tui.tsx` — plugin entry: slot registration, event wiring, per-session loaded tracking, kv persistence
113
+ - `src/skill-data.ts` — skill discovery, loaded-state extraction (tool part / tag / content matching), incremental scanning, loaded-first sorting
114
+ - `src/components/skills-panel.tsx` — sidebar panel rendering
115
+ - `src/components/skills-status-dialog.tsx` — `/skills-status` dialog
116
+ - `src/components/skills-filter-dialog.tsx` — `/hide-skills` dialog
117
+
118
+ ## License
119
+
120
+ [MIT](LICENSE) — the npm package is published as [`opencode-skill-tracker-tui`](https://www.npmjs.com/package/opencode-skill-tracker-tui).
@@ -0,0 +1,120 @@
1
+ # opencode-skill-tracker
2
+
3
+ [English](README.md) | 简体中文
4
+
5
+ 一个 [OpenCode](https://opencode.ai) TUI 插件:在右侧边栏增加 `Skills` 区块,按会话列出 OpenCode 可见的全部技能,已加载的高亮并排在最前,还可隐藏个别技能。
6
+
7
+ 原 `opencode-skills-sidebar`,已保存状态(折叠 / 隐藏列表)自动迁移。
8
+
9
+ ## 功能
10
+
11
+ - 会话侧边栏 `Skills` 区块,技能发现来自 OpenCode 本体(`client.app.skills()`),去重后按名称排序
12
+ - 按会话跟踪加载状态,三路检测:`skill` 工具 part(`input.name`)、opencode 注入的 `<skill_content name="...">` 标签 text part、TUI 斜杠命令展开(技能正文以纯文本用户消息注入,按已知技能正文匹配)
13
+ - 已加载技能绿色圆点高亮并置顶,侧栏与 `/skills-status` 一致
14
+ - 面板头可折叠,实时摘要 `(X loaded Y available)`(有隐藏技能时追加 `+Z`)
15
+ - `/hide-skills` 对话框可隐藏个别技能;`Show all hidden` 一步恢复全部,侧栏 `N hidden (show all)` 页脚点击同效
16
+ - 折叠状态与隐藏列表跨重启持久化(插件 kv),并从旧 `opencode-skills-sidebar` 键自动迁移
17
+ - 按会话增量扫描(消息水位线),长会话不重复全量重扫
18
+
19
+ ## 环境要求
20
+
21
+ - 支持 TUI 插件(`slots.sidebar_content`)的 OpenCode
22
+ - 从源码构建需要 [Bun](https://bun.sh)
23
+
24
+ ## 安装
25
+
26
+ 这是 **TUI 插件**,必须配置在 `~/.config/opencode/tui.json`,不是 `opencode.json`。
27
+
28
+ ### 方式 A:npm
29
+
30
+ 直接引用包名(见第 2 步),无需构建。
31
+
32
+ ### 方式 B:源码构建
33
+
34
+ ```bash
35
+ git clone <this-repo>
36
+ cd opencode-skill-tracker
37
+ bun install
38
+ bun run build
39
+ ```
40
+
41
+ 产出 `dist/tui.js`。
42
+
43
+ ### 2. 注册到 OpenCode
44
+
45
+ npm 安装用包名;源码构建用 `dist/tui.js` 绝对路径。加入 `~/.config/opencode/tui.json`:
46
+
47
+ ```json
48
+ {
49
+ "$schema": "https://opencode.ai/tui.json",
50
+ "plugin": [
51
+ "opencode-skill-tracker-tui"
52
+ ]
53
+ }
54
+ ```
55
+
56
+ 源码构建则用:
57
+
58
+ ```json
59
+ {
60
+ "$schema": "https://opencode.ai/tui.json",
61
+ "plugin": [
62
+ "C:\\path\\to\\opencode-skill-tracker\\dist\\tui.js"
63
+ ]
64
+ }
65
+ ```
66
+
67
+ `plugin` 数组可同时装多个插件,保留已有条目即可。
68
+
69
+ ### 3. 重启 OpenCode
70
+
71
+ TUI 插件随启动加载,无热重载。构建或更新后请重启 `opencode`。
72
+
73
+ ## 使用
74
+
75
+ | 操作 | 效果 |
76
+ | --- | --- |
77
+ | 点击 `Skills` 标题 | 折叠 / 展开面板 |
78
+ | 点击技能行 | 无操作(设计如此) |
79
+ | 点击 `N hidden (show all)` | 恢复全部隐藏技能 |
80
+ | `/skills-status` | 对话框列出全部未隐藏技能与 `Loaded` / `Unloaded` 状态,已加载在前 |
81
+ | `/hide-skills` | 对话框隐藏 / 恢复个别技能,支持搜索 |
82
+
83
+ 说明:
84
+
85
+ - `/skills-status` 反映当前打开的会话,需先打开会话。
86
+ - 折叠摘要中 `X` 为未隐藏的已加载数,`Y` 为发现的全部技能数,`Z` 为隐藏数。
87
+ - 斜杠命令(`/某技能`)加载的技能,正文注入会话后即计为已加载。
88
+
89
+ ## 工作原理
90
+
91
+ - 通过 OpenCode TUI 插件 API(`@opencode-ai/plugin/tui`)注册 `sidebar_content` 插槽,`order: 250`(位于内置 `MCP` 200 与 `LSP` 300 之间)
92
+ - 技能发现来自 `client.app.skills()`;已加载集合按会话维护
93
+ - 加载检测按消息水位线增量扫描;技能列表刷新时重扫(正文匹配依赖列表)
94
+ - 刷新来源:`message.part.updated`、`message.updated`、`session.updated`(增量),`session.created`、`project.updated`、`workspace.ready`、`worktree.ready`(重取技能列表),外加启动 250ms 重试与打开会话后 500ms 兜底重扫
95
+ - 仅在散文里引用字面 `<skill_content>` 标签(如 assistant 消息)不计为加载:标签命中必须对应已知技能名
96
+
97
+ ## 故障排查
98
+
99
+ - **没有 `Skills` 区块**:检查 `tui.json` 路径为绝对路径且正确,然后重启。`opencode --pure` 会跳过所有外部插件。
100
+ - **加载了技能但显示未加载**:斜杠命令加载按注入正文匹配,需技能列表先就绪;会自动回填。折叠再展开面板或切换会话可立即触发重扫。
101
+ - **更新插件后无变化**:TUI 插件随启动加载,重启 `opencode`。
102
+
103
+ ## 开发
104
+
105
+ ```bash
106
+ bun run build # 打包到 dist/tui.js + 声明
107
+ bun run typecheck # tsc --noEmit
108
+ ```
109
+
110
+ 源码结构:
111
+
112
+ - `src/tui.tsx` — 插件入口:插槽注册、事件接线、按会话加载跟踪、kv 持久化
113
+ - `src/skill-data.ts` — 技能发现、加载检测(工具 part / 标签 / 正文匹配)、增量扫描、加载优先排序
114
+ - `src/components/skills-panel.tsx` — 侧边栏面板渲染
115
+ - `src/components/skills-status-dialog.tsx` — `/skills-status` 对话框
116
+ - `src/components/skills-filter-dialog.tsx` — `/hide-skills` 对话框
117
+
118
+ ## 许可证
119
+
120
+ [MIT](LICENSE) — npm 包名为 [`opencode-skill-tracker-tui`](https://www.npmjs.com/package/opencode-skill-tracker-tui)。
@@ -0,0 +1,12 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { Accessor } from "solid-js";
3
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
4
+ import type { SkillSummary } from "../skill-data";
5
+ export interface SkillsFilterDialogProps {
6
+ api: TuiPluginApi;
7
+ skills: Accessor<SkillSummary[]>;
8
+ hiddenNames: Accessor<Set<string>>;
9
+ onToggleHidden: (name: string) => void;
10
+ onClearHidden: () => void;
11
+ }
12
+ export declare function SkillsFilterDialog(props: SkillsFilterDialogProps): any;
@@ -0,0 +1,14 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { Accessor } from "solid-js";
3
+ import type { TuiThemeCurrent } from "@opencode-ai/plugin/tui";
4
+ import type { SkillSummary } from "../skill-data";
5
+ export interface SkillsPanelProps {
6
+ skills: Accessor<SkillSummary[]>;
7
+ loadedNames: Accessor<Set<string>>;
8
+ hiddenNames: Accessor<Set<string>>;
9
+ theme: Accessor<TuiThemeCurrent>;
10
+ collapsed: Accessor<boolean>;
11
+ onToggle: () => void;
12
+ onClearHidden: () => void;
13
+ }
14
+ export declare function SkillsPanel(props: SkillsPanelProps): any;
@@ -0,0 +1,12 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { Accessor } from "solid-js";
3
+ import type { TuiThemeCurrent } from "@opencode-ai/plugin/tui";
4
+ import type { SkillSummary } from "../skill-data";
5
+ export interface SkillsStatusDialogProps {
6
+ skills: Accessor<SkillSummary[]>;
7
+ loadedNames: Accessor<Set<string>>;
8
+ hiddenNames: Accessor<Set<string>>;
9
+ theme: Accessor<TuiThemeCurrent>;
10
+ version: Accessor<number>;
11
+ }
12
+ export declare function SkillsStatusDialog(props: SkillsStatusDialogProps): any;
@@ -0,0 +1,20 @@
1
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
2
+ import type { Part } from "@opencode-ai/sdk/v2";
3
+ export interface SkillSummary {
4
+ name: string;
5
+ description: string;
6
+ content: string;
7
+ }
8
+ export declare function loadHiddenSkills(api: TuiPluginApi): Set<string>;
9
+ export declare function saveHiddenSkills(api: TuiPluginApi, hidden: Set<string>): void;
10
+ export declare function toggleHiddenSkill(hidden: Set<string>, name: string): void;
11
+ export declare function loadAvailableSkills(api: TuiPluginApi): Promise<SkillSummary[]>;
12
+ export declare function sortSkillsByLoaded(skills: SkillSummary[], loaded: Set<string>): SkillSummary[];
13
+ export declare function extractLoadedSkillName(part: Part, skills?: SkillSummary[]): string | undefined;
14
+ /**
15
+ * Incrementally scans messages of a session, extracting loaded skill names and
16
+ * merging them into `loaded`. Only messages not already in `scannedMessageIDs`
17
+ * are inspected, so repeated calls are cheap. Returns whether anything new
18
+ * was added.
19
+ */
20
+ export declare function scanLoadedSkillNames(api: TuiPluginApi, sessionID: string, loaded: Set<string>, scannedMessageIDs: Set<string>, skills?: SkillSummary[]): boolean;
package/dist/tui.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ import type { TuiPluginModule } from "@opencode-ai/plugin/tui";
3
+ declare const plugin: TuiPluginModule & {
4
+ id: string;
5
+ };
6
+ export default plugin;
package/dist/tui.js ADDED
@@ -0,0 +1,716 @@
1
+ // @bun
2
+ // src/tui.tsx
3
+ import { createComponent as _$createComponent4 } from "@opentui/solid";
4
+ import { createSignal as createSignal2 } from "solid-js";
5
+
6
+ // src/components/skills-panel.tsx
7
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
8
+ import { createComponent as _$createComponent } from "@opentui/solid";
9
+ import { effect as _$effect } from "@opentui/solid";
10
+ import { insertNode as _$insertNode } from "@opentui/solid";
11
+ import { insert as _$insert } from "@opentui/solid";
12
+ import { use as _$use } from "@opentui/solid";
13
+ import { setProp as _$setProp } from "@opentui/solid";
14
+ import { createElement as _$createElement } from "@opentui/solid";
15
+ import { For, Show, createMemo, createSignal } from "solid-js";
16
+
17
+ // src/skill-data.ts
18
+ var HIDDEN_KEY = "opencode-skill-tracker.hidden";
19
+ var LEGACY_HIDDEN_KEY = "opencode-skills-sidebar.hidden";
20
+ function loadHiddenSkills(api) {
21
+ const raw = api.kv.get(HIDDEN_KEY, api.kv.get(LEGACY_HIDDEN_KEY, []));
22
+ return new Set(Array.isArray(raw) ? raw.filter((name) => typeof name === "string") : []);
23
+ }
24
+ function saveHiddenSkills(api, hidden) {
25
+ api.kv.set(HIDDEN_KEY, [...hidden].sort());
26
+ }
27
+ function toggleHiddenSkill(hidden, name) {
28
+ if (hidden.has(name)) {
29
+ hidden.delete(name);
30
+ } else {
31
+ hidden.add(name);
32
+ }
33
+ }
34
+ async function loadAvailableSkills(api) {
35
+ const result = await api.client.app.skills({
36
+ directory: api.state.path.directory
37
+ });
38
+ const entries = result.data ?? [];
39
+ const deduped = new Map;
40
+ for (const entry of entries) {
41
+ if (!deduped.has(entry.name)) {
42
+ deduped.set(entry.name, {
43
+ name: entry.name,
44
+ description: entry.description ?? "",
45
+ content: entry.content ?? ""
46
+ });
47
+ }
48
+ }
49
+ return [...deduped.values()].sort((a, b) => a.name.localeCompare(b.name));
50
+ }
51
+ function sortSkillsByLoaded(skills, loaded) {
52
+ return [...skills].sort((left, right) => {
53
+ const leftLoaded = loaded.has(left.name);
54
+ const rightLoaded = loaded.has(right.name);
55
+ if (leftLoaded !== rightLoaded) {
56
+ return leftLoaded ? -1 : 1;
57
+ }
58
+ return left.name.localeCompare(right.name);
59
+ });
60
+ }
61
+ function extractLoadedSkillName(part, skills = []) {
62
+ if (part.type === "tool" && part.tool === "skill") {
63
+ const candidate = part.state.input?.name;
64
+ return typeof candidate === "string" && candidate.length > 0 ? candidate : undefined;
65
+ }
66
+ if (part.type === "text" && typeof part.text === "string") {
67
+ const tagged = /<skill_content name="([^"]+)">/.exec(part.text);
68
+ if (tagged && skills.some((skill) => skill.name === tagged[1])) {
69
+ return tagged[1];
70
+ }
71
+ const text = part.text.trim();
72
+ if (text.length > 0) {
73
+ for (const skill of skills) {
74
+ const content = skill.content.trim();
75
+ if (content.length > 0 && text.includes(content)) {
76
+ return skill.name;
77
+ }
78
+ }
79
+ }
80
+ }
81
+ return;
82
+ }
83
+ function scanLoadedSkillNames(api, sessionID, loaded, scannedMessageIDs, skills = []) {
84
+ let changed = false;
85
+ for (const message of api.state.session.messages(sessionID)) {
86
+ if (scannedMessageIDs.has(message.id))
87
+ continue;
88
+ scannedMessageIDs.add(message.id);
89
+ for (const part of api.state.part(message.id)) {
90
+ const skillName = extractLoadedSkillName(part, skills);
91
+ if (skillName && !loaded.has(skillName)) {
92
+ loaded.add(skillName);
93
+ changed = true;
94
+ }
95
+ }
96
+ }
97
+ return changed;
98
+ }
99
+
100
+ // src/components/skills-panel.tsx
101
+ var ELLIPSIS = "...";
102
+ var ROW_FIXED_WIDTH = 3;
103
+ function truncateLabel(value, maxWidth) {
104
+ if (maxWidth <= 0 || value.length <= maxWidth) {
105
+ return maxWidth <= 0 ? "" : value;
106
+ }
107
+ if (maxWidth <= ELLIPSIS.length) {
108
+ return ELLIPSIS.slice(0, maxWidth);
109
+ }
110
+ return `${value.slice(0, maxWidth - ELLIPSIS.length)}${ELLIPSIS}`;
111
+ }
112
+ function SkillsPanel(props) {
113
+ const [panelWidth, setPanelWidth] = createSignal(0);
114
+ let panelBox;
115
+ const visibleSkills = createMemo(() => {
116
+ const hidden = props.hiddenNames();
117
+ const candidates = props.skills().filter((skill) => !hidden.has(skill.name));
118
+ return sortSkillsByLoaded(candidates, props.loadedNames());
119
+ });
120
+ const hiddenCount = createMemo(() => props.hiddenNames().size);
121
+ const textColor = createMemo(() => props.theme().text);
122
+ const mutedColor = createMemo(() => props.theme().textMuted);
123
+ const loadedColor = createMemo(() => props.theme().success);
124
+ const title = createMemo(() => props.collapsed() ? "\u25B6 Skills" : "\u25BC Skills");
125
+ const headerSummary = createMemo(() => {
126
+ const loaded = props.loadedNames();
127
+ const loadedCount = visibleSkills().filter((skill) => loaded.has(skill.name)).length;
128
+ const total = props.skills().length;
129
+ const hidden = props.hiddenNames().size;
130
+ if (hidden === 0) {
131
+ return `(${loadedCount} loaded ${total} available)`;
132
+ }
133
+ return `(${loadedCount} loaded ${total} available +${hidden})`;
134
+ });
135
+ return (() => {
136
+ var _el$ = _$createElement("box"), _el$2 = _$createElement("box"), _el$3 = _$createElement("text"), _el$4 = _$createElement("strong");
137
+ _$insertNode(_el$, _el$2);
138
+ _$use((element) => {
139
+ panelBox = element;
140
+ setPanelWidth(element.width);
141
+ }, _el$);
142
+ _$setProp(_el$, "flexDirection", "column");
143
+ _$setProp(_el$, "onSizeChange", () => setPanelWidth(panelBox?.width ?? 0));
144
+ _$insertNode(_el$2, _el$3);
145
+ _$setProp(_el$2, "flexDirection", "row");
146
+ _$setProp(_el$2, "columnGap", 1);
147
+ _$insertNode(_el$3, _el$4);
148
+ _$insert(_el$4, title);
149
+ _$insert(_el$2, _$createComponent(Show, {
150
+ get when() {
151
+ return props.collapsed();
152
+ },
153
+ get children() {
154
+ var _el$5 = _$createElement("text");
155
+ _$insert(_el$5, headerSummary);
156
+ _$effect((_$p) => _$setProp(_el$5, "style", {
157
+ fg: mutedColor()
158
+ }, _$p));
159
+ return _el$5;
160
+ }
161
+ }), null);
162
+ _$insert(_el$, _$createComponent(Show, {
163
+ get when() {
164
+ return !props.collapsed();
165
+ },
166
+ get children() {
167
+ return [_$createComponent(Show, {
168
+ get when() {
169
+ return visibleSkills().length > 0;
170
+ },
171
+ get fallback() {
172
+ return (() => {
173
+ var _el$8 = _$createElement("text");
174
+ _$insertNode(_el$8, _$createTextNode(`No skills available`));
175
+ _$effect((_$p) => _$setProp(_el$8, "style", {
176
+ fg: mutedColor()
177
+ }, _$p));
178
+ return _el$8;
179
+ })();
180
+ },
181
+ get children() {
182
+ return _$createComponent(For, {
183
+ get each() {
184
+ return visibleSkills();
185
+ },
186
+ children: (skill) => {
187
+ const loaded = () => props.loadedNames().has(skill.name);
188
+ const visibleName = () => {
189
+ if (panelWidth() <= 0) {
190
+ return skill.name;
191
+ }
192
+ return truncateLabel(skill.name, panelWidth() - ROW_FIXED_WIDTH);
193
+ };
194
+ return (() => {
195
+ var _el$0 = _$createElement("box"), _el$1 = _$createElement("text"), _el$11 = _$createElement("text");
196
+ _$insertNode(_el$0, _el$1);
197
+ _$insertNode(_el$0, _el$11);
198
+ _$setProp(_el$0, "flexDirection", "row");
199
+ _$setProp(_el$0, "columnGap", 1);
200
+ _$insertNode(_el$1, _$createTextNode(`\u2022`));
201
+ _$insert(_el$11, visibleName);
202
+ _$effect((_p$) => {
203
+ var _v$5 = {
204
+ fg: loaded() ? loadedColor() : mutedColor()
205
+ }, _v$6 = {
206
+ fg: textColor()
207
+ };
208
+ _v$5 !== _p$.e && (_p$.e = _$setProp(_el$1, "style", _v$5, _p$.e));
209
+ _v$6 !== _p$.t && (_p$.t = _$setProp(_el$11, "style", _v$6, _p$.t));
210
+ return _p$;
211
+ }, {
212
+ e: undefined,
213
+ t: undefined
214
+ });
215
+ return _el$0;
216
+ })();
217
+ }
218
+ });
219
+ }
220
+ }), _$createComponent(Show, {
221
+ get when() {
222
+ return hiddenCount() > 0;
223
+ },
224
+ get children() {
225
+ var _el$6 = _$createElement("box"), _el$7 = _$createElement("text");
226
+ _$insertNode(_el$6, _el$7);
227
+ _$setProp(_el$6, "flexDirection", "row");
228
+ _$setProp(_el$6, "columnGap", 1);
229
+ _$insert(_el$7, () => `${hiddenCount()} hidden (show all)`);
230
+ _$effect((_p$) => {
231
+ var _v$ = props.onClearHidden, _v$2 = {
232
+ fg: mutedColor()
233
+ };
234
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$6, "onMouseDown", _v$, _p$.e));
235
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$7, "style", _v$2, _p$.t));
236
+ return _p$;
237
+ }, {
238
+ e: undefined,
239
+ t: undefined
240
+ });
241
+ return _el$6;
242
+ }
243
+ })];
244
+ }
245
+ }), null);
246
+ _$effect((_p$) => {
247
+ var _v$3 = props.onToggle, _v$4 = {
248
+ fg: textColor()
249
+ };
250
+ _v$3 !== _p$.e && (_p$.e = _$setProp(_el$2, "onMouseDown", _v$3, _p$.e));
251
+ _v$4 !== _p$.t && (_p$.t = _$setProp(_el$3, "style", _v$4, _p$.t));
252
+ return _p$;
253
+ }, {
254
+ e: undefined,
255
+ t: undefined
256
+ });
257
+ return _el$;
258
+ })();
259
+ }
260
+
261
+ // src/components/skills-status-dialog.tsx
262
+ import { memo as _$memo } from "@opentui/solid";
263
+ import { effect as _$effect2 } from "@opentui/solid";
264
+ import { insert as _$insert2 } from "@opentui/solid";
265
+ import { createComponent as _$createComponent2 } from "@opentui/solid";
266
+ import { createTextNode as _$createTextNode2 } from "@opentui/solid";
267
+ import { insertNode as _$insertNode2 } from "@opentui/solid";
268
+ import { setProp as _$setProp2 } from "@opentui/solid";
269
+ import { createElement as _$createElement2 } from "@opentui/solid";
270
+ import { For as For2, Show as Show2, createMemo as createMemo2 } from "solid-js";
271
+ function SkillsStatusDialog(props) {
272
+ const currentLoadedNames = createMemo2(() => {
273
+ props.version();
274
+ return props.loadedNames();
275
+ });
276
+ const visibleSkills = createMemo2(() => {
277
+ const hidden = props.hiddenNames();
278
+ return props.skills().filter((skill) => !hidden.has(skill.name));
279
+ });
280
+ const orderedSkills = createMemo2(() => sortSkillsByLoaded(visibleSkills(), currentLoadedNames()));
281
+ const hiddenCount = createMemo2(() => props.hiddenNames().size);
282
+ const textColor = createMemo2(() => props.theme().text);
283
+ const mutedColor = createMemo2(() => props.theme().textMuted);
284
+ const loadedColor = createMemo2(() => props.theme().success);
285
+ return (() => {
286
+ var _el$ = _$createElement2("box"), _el$2 = _$createElement2("box"), _el$3 = _$createElement2("text"), _el$4 = _$createElement2("strong"), _el$6 = _$createElement2("text");
287
+ _$insertNode2(_el$, _el$2);
288
+ _$setProp2(_el$, "flexDirection", "column");
289
+ _$setProp2(_el$, "rowGap", 1);
290
+ _$setProp2(_el$, "paddingX", 2);
291
+ _$setProp2(_el$, "paddingY", 1);
292
+ _$setProp2(_el$, "paddingTop", 0);
293
+ _$insertNode2(_el$2, _el$3);
294
+ _$insertNode2(_el$2, _el$6);
295
+ _$setProp2(_el$2, "flexDirection", "row");
296
+ _$setProp2(_el$2, "justifyContent", "space-between");
297
+ _$setProp2(_el$2, "columnGap", 2);
298
+ _$insertNode2(_el$3, _el$4);
299
+ _$insertNode2(_el$4, _$createTextNode2(`Skills Status`));
300
+ _$insertNode2(_el$6, _$createTextNode2(`esc`));
301
+ _$insert2(_el$, _$createComponent2(Show2, {
302
+ get when() {
303
+ return orderedSkills().length > 0;
304
+ },
305
+ get fallback() {
306
+ return (() => {
307
+ var _el$0 = _$createElement2("text");
308
+ _$insertNode2(_el$0, _$createTextNode2(`No skills available`));
309
+ _$effect2((_$p) => _$setProp2(_el$0, "style", {
310
+ fg: mutedColor()
311
+ }, _$p));
312
+ return _el$0;
313
+ })();
314
+ },
315
+ get children() {
316
+ var _el$8 = _$createElement2("box");
317
+ _$setProp2(_el$8, "flexDirection", "column");
318
+ _$insert2(_el$8, _$createComponent2(For2, {
319
+ get each() {
320
+ return orderedSkills();
321
+ },
322
+ children: (skill) => {
323
+ const loaded = () => currentLoadedNames().has(skill.name);
324
+ return (() => {
325
+ var _el$10 = _$createElement2("box"), _el$11 = _$createElement2("text"), _el$13 = _$createElement2("text"), _el$14 = _$createElement2("text");
326
+ _$insertNode2(_el$10, _el$11);
327
+ _$insertNode2(_el$10, _el$13);
328
+ _$insertNode2(_el$10, _el$14);
329
+ _$setProp2(_el$10, "flexDirection", "row");
330
+ _$setProp2(_el$10, "columnGap", 1);
331
+ _$insertNode2(_el$11, _$createTextNode2(`\u2022`));
332
+ _$insert2(_el$13, () => skill.name);
333
+ _$insert2(_el$14, () => loaded() ? "Loaded" : "Unloaded");
334
+ _$effect2((_p$) => {
335
+ var _v$3 = {
336
+ fg: loaded() ? loadedColor() : mutedColor()
337
+ }, _v$4 = {
338
+ fg: textColor()
339
+ }, _v$5 = {
340
+ fg: mutedColor()
341
+ };
342
+ _v$3 !== _p$.e && (_p$.e = _$setProp2(_el$11, "style", _v$3, _p$.e));
343
+ _v$4 !== _p$.t && (_p$.t = _$setProp2(_el$13, "style", _v$4, _p$.t));
344
+ _v$5 !== _p$.a && (_p$.a = _$setProp2(_el$14, "style", _v$5, _p$.a));
345
+ return _p$;
346
+ }, {
347
+ e: undefined,
348
+ t: undefined,
349
+ a: undefined
350
+ });
351
+ return _el$10;
352
+ })();
353
+ }
354
+ }));
355
+ return _el$8;
356
+ }
357
+ }), null);
358
+ _$insert2(_el$, _$createComponent2(Show2, {
359
+ get when() {
360
+ return hiddenCount() > 0;
361
+ },
362
+ get children() {
363
+ var _el$9 = _$createElement2("text");
364
+ _$insert2(_el$9, () => `(${hiddenCount()} hidden \u2014 use /hide-skills to manage)`);
365
+ _$effect2((_$p) => _$setProp2(_el$9, "style", {
366
+ fg: mutedColor()
367
+ }, _$p));
368
+ return _el$9;
369
+ }
370
+ }), null);
371
+ _$effect2((_p$) => {
372
+ var _v$ = {
373
+ fg: textColor()
374
+ }, _v$2 = {
375
+ fg: mutedColor()
376
+ };
377
+ _v$ !== _p$.e && (_p$.e = _$setProp2(_el$3, "style", _v$, _p$.e));
378
+ _v$2 !== _p$.t && (_p$.t = _$setProp2(_el$6, "style", _v$2, _p$.t));
379
+ return _p$;
380
+ }, {
381
+ e: undefined,
382
+ t: undefined
383
+ });
384
+ return _el$;
385
+ })();
386
+ }
387
+
388
+ // src/components/skills-filter-dialog.tsx
389
+ import { createTextNode as _$createTextNode3 } from "@opentui/solid";
390
+ import { insertNode as _$insertNode3 } from "@opentui/solid";
391
+ import { setProp as _$setProp3 } from "@opentui/solid";
392
+ import { createElement as _$createElement3 } from "@opentui/solid";
393
+ import { createComponent as _$createComponent3 } from "@opentui/solid";
394
+ import { Show as Show3, createMemo as createMemo3 } from "solid-js";
395
+ var BULK_RESTORE = "__restore_all__";
396
+ function SkillsFilterDialog(props) {
397
+ const options = createMemo3(() => {
398
+ const hidden = props.hiddenNames();
399
+ const list = [];
400
+ if (hidden.size > 0) {
401
+ list.push({
402
+ title: "Show all hidden",
403
+ value: BULK_RESTORE,
404
+ description: `Restore ${hidden.size} hidden skill${hidden.size === 1 ? "" : "s"}`,
405
+ footer: "action",
406
+ category: "Filter"
407
+ });
408
+ }
409
+ for (const skill of props.skills()) {
410
+ const isHidden = hidden.has(skill.name);
411
+ list.push({
412
+ title: skill.name,
413
+ value: skill.name,
414
+ description: skill.description || undefined,
415
+ footer: isHidden ? "hidden" : "shown",
416
+ category: isHidden ? "Hidden" : "Visible"
417
+ });
418
+ }
419
+ return list;
420
+ });
421
+ return _$createComponent3(Show3, {
422
+ get when() {
423
+ return options().length > 0;
424
+ },
425
+ get fallback() {
426
+ return (() => {
427
+ var _el$ = _$createElement3("box"), _el$2 = _$createElement3("text");
428
+ _$insertNode3(_el$, _el$2);
429
+ _$setProp3(_el$, "flexDirection", "column");
430
+ _$setProp3(_el$, "paddingX", 2);
431
+ _$setProp3(_el$, "paddingY", 1);
432
+ _$setProp3(_el$, "paddingTop", 0);
433
+ _$insertNode3(_el$2, _$createTextNode3(`No skills available`));
434
+ return _el$;
435
+ })();
436
+ },
437
+ get children() {
438
+ return _$createComponent3(props.api.ui.DialogSelect, {
439
+ title: "Hide Skills",
440
+ placeholder: "Search skills\u2026",
441
+ get options() {
442
+ return options();
443
+ },
444
+ onSelect: (option) => {
445
+ if (option.value === BULK_RESTORE) {
446
+ props.onClearHidden();
447
+ return;
448
+ }
449
+ props.onToggleHidden(option.value);
450
+ }
451
+ });
452
+ }
453
+ });
454
+ }
455
+
456
+ // src/tui.tsx
457
+ var SIDEBAR_ORDER = 250;
458
+ var COLLAPSED_KEY = "opencode-skill-tracker.collapsed";
459
+ var LEGACY_COLLAPSED_KEY = "opencode-skills-sidebar.collapsed";
460
+ var tui = async (api) => {
461
+ const [skills, setSkills] = createSignal2([]);
462
+ const [loadVersion, setLoadVersion] = createSignal2(0);
463
+ const [collapsed, setCollapsed] = createSignal2(Boolean(api.kv.get(COLLAPSED_KEY, api.kv.get(LEGACY_COLLAPSED_KEY, false))));
464
+ const hiddenSkills = loadHiddenSkills(api);
465
+ const [hiddenVersion, setHiddenVersion] = createSignal2(0);
466
+ const hiddenAccessor = () => {
467
+ hiddenVersion();
468
+ return hiddenSkills;
469
+ };
470
+ const loadedBySession = new Map;
471
+ const scannedBySession = new Map;
472
+ let refreshTimer;
473
+ const loadedRefreshTimers = new Set;
474
+ let visibleSessionID;
475
+ const renderFilterDialog = () => _$createComponent4(SkillsFilterDialog, {
476
+ api,
477
+ skills,
478
+ hiddenNames: hiddenAccessor,
479
+ onToggleHidden: toggleHidden,
480
+ onClearHidden: clearHidden
481
+ });
482
+ const openFilterDialog = (dialog) => {
483
+ dialog.setSize("medium");
484
+ dialog.replace(renderFilterDialog);
485
+ };
486
+ const getActiveSessionID = () => {
487
+ const currentRoute = api.route.current;
488
+ const candidate = "params" in currentRoute ? currentRoute.params?.sessionID : undefined;
489
+ if (currentRoute.name === "session" && typeof candidate === "string") {
490
+ return candidate;
491
+ }
492
+ return visibleSessionID;
493
+ };
494
+ const toggleCollapsed = () => {
495
+ const next = !collapsed();
496
+ setCollapsed(next);
497
+ api.kv.set(COLLAPSED_KEY, next);
498
+ };
499
+ const toggleHidden = (name) => {
500
+ toggleHiddenSkill(hiddenSkills, name);
501
+ saveHiddenSkills(api, hiddenSkills);
502
+ setHiddenVersion((value) => value + 1);
503
+ api.ui.toast({
504
+ variant: "info",
505
+ title: "Skill Tracker",
506
+ message: hiddenSkills.has(name) ? `Hid "${name}"` : `Restored "${name}"`,
507
+ duration: 2000
508
+ });
509
+ };
510
+ const clearHidden = () => {
511
+ if (hiddenSkills.size === 0)
512
+ return;
513
+ const count = hiddenSkills.size;
514
+ hiddenSkills.clear();
515
+ saveHiddenSkills(api, hiddenSkills);
516
+ setHiddenVersion((value) => value + 1);
517
+ api.ui.toast({
518
+ variant: "info",
519
+ title: "Skill Tracker",
520
+ message: `Restored ${count} hidden skill${count === 1 ? "" : "s"}`,
521
+ duration: 2000
522
+ });
523
+ };
524
+ const getLoadedSkills = (sessionID) => {
525
+ const loaded = loadedBySession.get(sessionID);
526
+ const scanned = scannedBySession.get(sessionID);
527
+ if (loaded && scanned) {
528
+ return loaded;
529
+ }
530
+ const nextLoaded = loaded ?? new Set;
531
+ const nextScanned = scanned ?? new Set;
532
+ loadedBySession.set(sessionID, nextLoaded);
533
+ scannedBySession.set(sessionID, nextScanned);
534
+ scanLoadedSkillNames(api, sessionID, nextLoaded, nextScanned, skills());
535
+ return nextLoaded;
536
+ };
537
+ const refreshLoadedSkills = (sessionID) => {
538
+ const loaded = loadedBySession.get(sessionID);
539
+ const scanned = scannedBySession.get(sessionID);
540
+ if (loaded && scanned) {
541
+ if (scanLoadedSkillNames(api, sessionID, loaded, scanned, skills())) {
542
+ setLoadVersion((value) => value + 1);
543
+ }
544
+ return;
545
+ }
546
+ getLoadedSkills(sessionID);
547
+ setLoadVersion((value) => value + 1);
548
+ };
549
+ const scheduleRefreshLoadedSkills = (sessionID, delay = 0) => {
550
+ const timer = setTimeout(() => {
551
+ loadedRefreshTimers.delete(timer);
552
+ refreshLoadedSkills(sessionID);
553
+ }, delay);
554
+ loadedRefreshTimers.add(timer);
555
+ };
556
+ const scheduleSessionOpenRefresh = (sessionID) => {
557
+ scheduleRefreshLoadedSkills(sessionID, 500);
558
+ };
559
+ const markLoaded = (sessionID, skillName) => {
560
+ const loaded = getLoadedSkills(sessionID);
561
+ const sizeBefore = loaded.size;
562
+ loaded.add(skillName);
563
+ if (loaded.size !== sizeBefore) {
564
+ setLoadVersion((value) => value + 1);
565
+ }
566
+ };
567
+ const refreshSkills = async () => {
568
+ try {
569
+ setSkills(await loadAvailableSkills(api));
570
+ scannedBySession.clear();
571
+ for (const sessionID of loadedBySession.keys()) {
572
+ refreshLoadedSkills(sessionID);
573
+ }
574
+ setLoadVersion((value) => value + 1);
575
+ } catch (error) {
576
+ api.ui.toast({
577
+ variant: "error",
578
+ title: "Skill Tracker",
579
+ message: `Failed to load skills: ${error instanceof Error ? error.message : String(error)}`,
580
+ duration: 5000
581
+ });
582
+ }
583
+ };
584
+ const scheduleRefreshSkills = (delay = 0) => {
585
+ if (refreshTimer) {
586
+ clearTimeout(refreshTimer);
587
+ }
588
+ refreshTimer = setTimeout(() => {
589
+ refreshTimer = undefined;
590
+ refreshSkills();
591
+ }, delay);
592
+ };
593
+ refreshSkills();
594
+ scheduleRefreshSkills(250);
595
+ const unregisterMessagePartUpdated = api.event.on("message.part.updated", (event) => {
596
+ const skillName = extractLoadedSkillName(event.properties.part, skills());
597
+ if (skillName) {
598
+ markLoaded(event.properties.sessionID, skillName);
599
+ }
600
+ });
601
+ const unregisterMessageUpdated = api.event.on("message.updated", (event) => {
602
+ refreshLoadedSkills(event.properties.sessionID);
603
+ });
604
+ const unregisterSessionDeleted = api.event.on("session.deleted", (event) => {
605
+ const removed = loadedBySession.delete(event.properties.sessionID);
606
+ if (removed || scannedBySession.delete(event.properties.sessionID)) {
607
+ setLoadVersion((value) => value + 1);
608
+ }
609
+ });
610
+ const unregisterSessionCreated = api.event.on("session.created", () => {
611
+ scheduleRefreshSkills();
612
+ });
613
+ const unregisterSessionUpdated = api.event.on("session.updated", (event) => {
614
+ refreshLoadedSkills(event.properties.sessionID);
615
+ });
616
+ const unregisterProjectUpdated = api.event.on("project.updated", () => {
617
+ scheduleRefreshSkills();
618
+ });
619
+ const unregisterWorkspaceReady = api.event.on("workspace.ready", () => {
620
+ scheduleRefreshSkills();
621
+ });
622
+ const unregisterWorktreeReady = api.event.on("worktree.ready", () => {
623
+ scheduleRefreshSkills();
624
+ });
625
+ const unregisterKeymap = api.keymap.registerLayer({
626
+ commands: [{
627
+ name: "skills-status",
628
+ namespace: "palette",
629
+ title: "Skills Status",
630
+ desc: "Show all skills and loaded state",
631
+ category: "Skills",
632
+ slashName: "skills-status",
633
+ run() {
634
+ const sessionID = getActiveSessionID();
635
+ if (!sessionID) {
636
+ api.ui.toast({
637
+ variant: "warning",
638
+ title: "Skill Tracker",
639
+ message: "Open a session before using /skills-status.",
640
+ duration: 4000
641
+ });
642
+ return;
643
+ }
644
+ scheduleSessionOpenRefresh(sessionID);
645
+ api.ui.dialog.setSize("medium");
646
+ api.ui.dialog.replace(() => _$createComponent4(SkillsStatusDialog, {
647
+ skills,
648
+ loadedNames: () => getLoadedSkills(sessionID),
649
+ hiddenNames: hiddenAccessor,
650
+ theme: () => api.theme.current,
651
+ version: loadVersion
652
+ }));
653
+ }
654
+ }, {
655
+ name: "hide-skills",
656
+ namespace: "palette",
657
+ title: "Hide Skills",
658
+ desc: "Hide or restore skills in the sidebar",
659
+ category: "Skills",
660
+ slashName: "hide-skills",
661
+ run() {
662
+ api.ui.dialog.setSize("medium");
663
+ api.ui.dialog.replace(renderFilterDialog);
664
+ }
665
+ }]
666
+ });
667
+ api.lifecycle.onDispose(() => {
668
+ if (refreshTimer) {
669
+ clearTimeout(refreshTimer);
670
+ }
671
+ for (const timer of loadedRefreshTimers) {
672
+ clearTimeout(timer);
673
+ }
674
+ loadedRefreshTimers.clear();
675
+ unregisterMessagePartUpdated();
676
+ unregisterMessageUpdated();
677
+ unregisterSessionDeleted();
678
+ unregisterSessionCreated();
679
+ unregisterSessionUpdated();
680
+ unregisterProjectUpdated();
681
+ unregisterWorkspaceReady();
682
+ unregisterWorktreeReady();
683
+ unregisterKeymap();
684
+ });
685
+ api.slots.register({
686
+ order: SIDEBAR_ORDER,
687
+ slots: {
688
+ sidebar_content: (_ctx, props) => {
689
+ if (visibleSessionID !== props.session_id) {
690
+ visibleSessionID = props.session_id;
691
+ scheduleSessionOpenRefresh(props.session_id);
692
+ }
693
+ loadVersion();
694
+ return _$createComponent4(SkillsPanel, {
695
+ skills,
696
+ loadedNames: () => getLoadedSkills(props.session_id),
697
+ hiddenNames: hiddenAccessor,
698
+ theme: () => api.theme.current,
699
+ collapsed,
700
+ onToggle: toggleCollapsed,
701
+ onClearHidden: clearHidden
702
+ });
703
+ }
704
+ }
705
+ });
706
+ };
707
+ var plugin = {
708
+ id: "opencode-skill-tracker",
709
+ tui
710
+ };
711
+ var tui_default = plugin;
712
+ export {
713
+ tui_default as default
714
+ };
715
+
716
+ //# debugId=E1362898BDE369EF64756E2164756E21
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["src/tui.tsx", "src/components/skills-panel.tsx", "src/skill-data.ts", "src/components/skills-status-dialog.tsx", "src/components/skills-filter-dialog.tsx"],
4
+ "sourcesContent": [
5
+ "import { createComponent as _$createComponent } from \"@opentui/solid\";\n/** @jsxImportSource @opentui/solid */\n\nimport { createSignal } from \"solid-js\";\nimport { SkillsPanel } from \"./components/skills-panel\";\nimport { SkillsStatusDialog } from \"./components/skills-status-dialog\";\nimport { SkillsFilterDialog } from \"./components/skills-filter-dialog\";\nimport { extractLoadedSkillName, loadAvailableSkills, loadHiddenSkills, saveHiddenSkills, scanLoadedSkillNames, toggleHiddenSkill } from \"./skill-data\";\nconst SIDEBAR_ORDER = 250;\nconst COLLAPSED_KEY = \"opencode-skill-tracker.collapsed\";\nconst LEGACY_COLLAPSED_KEY = \"opencode-skills-sidebar.collapsed\";\nconst tui = async api => {\n const [skills, setSkills] = createSignal([]);\n const [loadVersion, setLoadVersion] = createSignal(0);\n const [collapsed, setCollapsed] = createSignal(Boolean(api.kv.get(COLLAPSED_KEY, api.kv.get(LEGACY_COLLAPSED_KEY, false))));\n const hiddenSkills = loadHiddenSkills(api);\n const [hiddenVersion, setHiddenVersion] = createSignal(0);\n const hiddenAccessor = () => {\n hiddenVersion();\n return hiddenSkills;\n };\n const loadedBySession = new Map();\n const scannedBySession = new Map();\n let refreshTimer;\n const loadedRefreshTimers = new Set();\n let visibleSessionID;\n const renderFilterDialog = () => _$createComponent(SkillsFilterDialog, {\n api: api,\n skills: skills,\n hiddenNames: hiddenAccessor,\n onToggleHidden: toggleHidden,\n onClearHidden: clearHidden\n });\n const openFilterDialog = dialog => {\n dialog.setSize(\"medium\");\n dialog.replace(renderFilterDialog);\n };\n const getActiveSessionID = () => {\n const currentRoute = api.route.current;\n const candidate = \"params\" in currentRoute ? currentRoute.params?.sessionID : undefined;\n if (currentRoute.name === \"session\" && typeof candidate === \"string\") {\n return candidate;\n }\n return visibleSessionID;\n };\n const toggleCollapsed = () => {\n const next = !collapsed();\n setCollapsed(next);\n api.kv.set(COLLAPSED_KEY, next);\n };\n const toggleHidden = name => {\n toggleHiddenSkill(hiddenSkills, name);\n saveHiddenSkills(api, hiddenSkills);\n setHiddenVersion(value => value + 1);\n api.ui.toast({\n variant: \"info\",\n title: \"Skill Tracker\",\n message: hiddenSkills.has(name) ? `Hid \"${name}\"` : `Restored \"${name}\"`,\n duration: 2000\n });\n };\n const clearHidden = () => {\n if (hiddenSkills.size === 0) return;\n const count = hiddenSkills.size;\n hiddenSkills.clear();\n saveHiddenSkills(api, hiddenSkills);\n setHiddenVersion(value => value + 1);\n api.ui.toast({\n variant: \"info\",\n title: \"Skill Tracker\",\n message: `Restored ${count} hidden skill${count === 1 ? \"\" : \"s\"}`,\n duration: 2000\n });\n };\n const getLoadedSkills = sessionID => {\n const loaded = loadedBySession.get(sessionID);\n const scanned = scannedBySession.get(sessionID);\n if (loaded && scanned) {\n return loaded;\n }\n const nextLoaded = loaded ?? new Set();\n const nextScanned = scanned ?? new Set();\n loadedBySession.set(sessionID, nextLoaded);\n scannedBySession.set(sessionID, nextScanned);\n scanLoadedSkillNames(api, sessionID, nextLoaded, nextScanned, skills());\n return nextLoaded;\n };\n const refreshLoadedSkills = sessionID => {\n const loaded = loadedBySession.get(sessionID);\n const scanned = scannedBySession.get(sessionID);\n if (loaded && scanned) {\n if (scanLoadedSkillNames(api, sessionID, loaded, scanned, skills())) {\n setLoadVersion(value => value + 1);\n }\n return;\n }\n getLoadedSkills(sessionID);\n setLoadVersion(value => value + 1);\n };\n const scheduleRefreshLoadedSkills = (sessionID, delay = 0) => {\n const timer = setTimeout(() => {\n loadedRefreshTimers.delete(timer);\n refreshLoadedSkills(sessionID);\n }, delay);\n loadedRefreshTimers.add(timer);\n };\n const scheduleSessionOpenRefresh = sessionID => {\n // Session history can hydrate just after navigation. Immediate scans\n // happen through the side bar render and message/session events; this is\n // a single slower retry to backfill anything hydrated afterwards.\n scheduleRefreshLoadedSkills(sessionID, 500);\n };\n const markLoaded = (sessionID, skillName) => {\n const loaded = getLoadedSkills(sessionID);\n const sizeBefore = loaded.size;\n loaded.add(skillName);\n if (loaded.size !== sizeBefore) {\n setLoadVersion(value => value + 1);\n }\n };\n const refreshSkills = async () => {\n try {\n setSkills(await loadAvailableSkills(api));\n // Content-based matching needs the skills list; rescan messages that\n // were scanned before the list (or a newer list) was available.\n scannedBySession.clear();\n for (const sessionID of loadedBySession.keys()) {\n refreshLoadedSkills(sessionID);\n }\n setLoadVersion(value => value + 1);\n } catch (error) {\n api.ui.toast({\n variant: \"error\",\n title: \"Skill Tracker\",\n message: `Failed to load skills: ${error instanceof Error ? error.message : String(error)}`,\n duration: 5000\n });\n }\n };\n const scheduleRefreshSkills = (delay = 0) => {\n if (refreshTimer) {\n clearTimeout(refreshTimer);\n }\n refreshTimer = setTimeout(() => {\n refreshTimer = undefined;\n void refreshSkills();\n }, delay);\n };\n void refreshSkills();\n\n // OpenCode may initialize TUI plugins before workspace/worktree state is fully ready.\n // Refresh after readiness events so skill discovery does not get stuck empty.\n scheduleRefreshSkills(250);\n const unregisterMessagePartUpdated = api.event.on(\"message.part.updated\", event => {\n const skillName = extractLoadedSkillName(event.properties.part, skills());\n if (skillName) {\n markLoaded(event.properties.sessionID, skillName);\n }\n });\n const unregisterMessageUpdated = api.event.on(\"message.updated\", event => {\n refreshLoadedSkills(event.properties.sessionID);\n });\n const unregisterSessionDeleted = api.event.on(\"session.deleted\", event => {\n const removed = loadedBySession.delete(event.properties.sessionID);\n if (removed || scannedBySession.delete(event.properties.sessionID)) {\n setLoadVersion(value => value + 1);\n }\n });\n const unregisterSessionCreated = api.event.on(\"session.created\", () => {\n scheduleRefreshSkills();\n });\n const unregisterSessionUpdated = api.event.on(\"session.updated\", event => {\n refreshLoadedSkills(event.properties.sessionID);\n });\n const unregisterProjectUpdated = api.event.on(\"project.updated\", () => {\n scheduleRefreshSkills();\n });\n const unregisterWorkspaceReady = api.event.on(\"workspace.ready\", () => {\n scheduleRefreshSkills();\n });\n const unregisterWorktreeReady = api.event.on(\"worktree.ready\", () => {\n scheduleRefreshSkills();\n });\n const unregisterKeymap = api.keymap.registerLayer({\n commands: [{\n name: \"skills-status\",\n namespace: \"palette\",\n title: \"Skills Status\",\n desc: \"Show all skills and loaded state\",\n category: \"Skills\",\n slashName: \"skills-status\",\n run() {\n const sessionID = getActiveSessionID();\n if (!sessionID) {\n api.ui.toast({\n variant: \"warning\",\n title: \"Skill Tracker\",\n message: \"Open a session before using /skills-status.\",\n duration: 4000\n });\n return;\n }\n scheduleSessionOpenRefresh(sessionID);\n api.ui.dialog.setSize(\"medium\");\n api.ui.dialog.replace(() => _$createComponent(SkillsStatusDialog, {\n skills: skills,\n loadedNames: () => getLoadedSkills(sessionID),\n hiddenNames: hiddenAccessor,\n theme: () => api.theme.current,\n version: loadVersion\n }));\n }\n }, {\n name: \"hide-skills\",\n namespace: \"palette\",\n title: \"Hide Skills\",\n desc: \"Hide or restore skills in the sidebar\",\n category: \"Skills\",\n slashName: \"hide-skills\",\n run() {\n api.ui.dialog.setSize(\"medium\");\n api.ui.dialog.replace(renderFilterDialog);\n }\n }]\n });\n api.lifecycle.onDispose(() => {\n if (refreshTimer) {\n clearTimeout(refreshTimer);\n }\n for (const timer of loadedRefreshTimers) {\n clearTimeout(timer);\n }\n loadedRefreshTimers.clear();\n unregisterMessagePartUpdated();\n unregisterMessageUpdated();\n unregisterSessionDeleted();\n unregisterSessionCreated();\n unregisterSessionUpdated();\n unregisterProjectUpdated();\n unregisterWorkspaceReady();\n unregisterWorktreeReady();\n unregisterKeymap();\n });\n api.slots.register({\n order: SIDEBAR_ORDER,\n slots: {\n sidebar_content: (_ctx, props) => {\n if (visibleSessionID !== props.session_id) {\n visibleSessionID = props.session_id;\n scheduleSessionOpenRefresh(props.session_id);\n }\n loadVersion();\n return _$createComponent(SkillsPanel, {\n skills: skills,\n loadedNames: () => getLoadedSkills(props.session_id),\n hiddenNames: hiddenAccessor,\n theme: () => api.theme.current,\n collapsed: collapsed,\n onToggle: toggleCollapsed,\n onClearHidden: clearHidden\n });\n }\n }\n });\n};\nconst plugin = {\n id: \"opencode-skill-tracker\",\n tui\n};\nexport default plugin;",
6
+ "import { createTextNode as _$createTextNode } from \"@opentui/solid\";\nimport { createComponent as _$createComponent } from \"@opentui/solid\";\nimport { effect as _$effect } from \"@opentui/solid\";\nimport { insertNode as _$insertNode } from \"@opentui/solid\";\nimport { insert as _$insert } from \"@opentui/solid\";\nimport { use as _$use } from \"@opentui/solid\";\nimport { setProp as _$setProp } from \"@opentui/solid\";\nimport { createElement as _$createElement } from \"@opentui/solid\";\n/** @jsxImportSource @opentui/solid */\n\nimport { For, Show, createMemo, createSignal } from \"solid-js\";\nimport { sortSkillsByLoaded } from \"../skill-data\";\nconst ELLIPSIS = \"...\";\nconst ROW_FIXED_WIDTH = 3;\nfunction truncateLabel(value, maxWidth) {\n if (maxWidth <= 0 || value.length <= maxWidth) {\n return maxWidth <= 0 ? \"\" : value;\n }\n if (maxWidth <= ELLIPSIS.length) {\n return ELLIPSIS.slice(0, maxWidth);\n }\n return `${value.slice(0, maxWidth - ELLIPSIS.length)}${ELLIPSIS}`;\n}\nexport function SkillsPanel(props) {\n const [panelWidth, setPanelWidth] = createSignal(0);\n let panelBox;\n const visibleSkills = createMemo(() => {\n const hidden = props.hiddenNames();\n const candidates = props.skills().filter(skill => !hidden.has(skill.name));\n return sortSkillsByLoaded(candidates, props.loadedNames());\n });\n const hiddenCount = createMemo(() => props.hiddenNames().size);\n const textColor = createMemo(() => props.theme().text);\n const mutedColor = createMemo(() => props.theme().textMuted);\n const loadedColor = createMemo(() => props.theme().success);\n const title = createMemo(() => props.collapsed() ? \"▶ Skills\" : \"▼ Skills\");\n const headerSummary = createMemo(() => {\n const loaded = props.loadedNames();\n const loadedCount = visibleSkills().filter(skill => loaded.has(skill.name)).length;\n const total = props.skills().length;\n const hidden = props.hiddenNames().size;\n if (hidden === 0) {\n return `(${loadedCount} loaded ${total} available)`;\n }\n return `(${loadedCount} loaded ${total} available +${hidden})`;\n });\n return (() => {\n var _el$ = _$createElement(\"box\"),\n _el$2 = _$createElement(\"box\"),\n _el$3 = _$createElement(\"text\"),\n _el$4 = _$createElement(\"strong\");\n _$insertNode(_el$, _el$2);\n _$use(element => {\n panelBox = element;\n setPanelWidth(element.width);\n }, _el$);\n _$setProp(_el$, \"flexDirection\", \"column\");\n _$setProp(_el$, \"onSizeChange\", () => setPanelWidth(panelBox?.width ?? 0));\n _$insertNode(_el$2, _el$3);\n _$setProp(_el$2, \"flexDirection\", \"row\");\n _$setProp(_el$2, \"columnGap\", 1);\n _$insertNode(_el$3, _el$4);\n _$insert(_el$4, title);\n _$insert(_el$2, _$createComponent(Show, {\n get when() {\n return props.collapsed();\n },\n get children() {\n var _el$5 = _$createElement(\"text\");\n _$insert(_el$5, headerSummary);\n _$effect(_$p => _$setProp(_el$5, \"style\", {\n fg: mutedColor()\n }, _$p));\n return _el$5;\n }\n }), null);\n _$insert(_el$, _$createComponent(Show, {\n get when() {\n return !props.collapsed();\n },\n get children() {\n return [_$createComponent(Show, {\n get when() {\n return visibleSkills().length > 0;\n },\n get fallback() {\n return (() => {\n var _el$8 = _$createElement(\"text\");\n _$insertNode(_el$8, _$createTextNode(`No skills available`));\n _$effect(_$p => _$setProp(_el$8, \"style\", {\n fg: mutedColor()\n }, _$p));\n return _el$8;\n })();\n },\n get children() {\n return _$createComponent(For, {\n get each() {\n return visibleSkills();\n },\n children: skill => {\n const loaded = () => props.loadedNames().has(skill.name);\n const visibleName = () => {\n if (panelWidth() <= 0) {\n return skill.name;\n }\n return truncateLabel(skill.name, panelWidth() - ROW_FIXED_WIDTH);\n };\n return (() => {\n var _el$0 = _$createElement(\"box\"),\n _el$1 = _$createElement(\"text\"),\n _el$11 = _$createElement(\"text\");\n _$insertNode(_el$0, _el$1);\n _$insertNode(_el$0, _el$11);\n _$setProp(_el$0, \"flexDirection\", \"row\");\n _$setProp(_el$0, \"columnGap\", 1);\n _$insertNode(_el$1, _$createTextNode(`•`));\n _$insert(_el$11, visibleName);\n _$effect(_p$ => {\n var _v$5 = {\n fg: loaded() ? loadedColor() : mutedColor()\n },\n _v$6 = {\n fg: textColor()\n };\n _v$5 !== _p$.e && (_p$.e = _$setProp(_el$1, \"style\", _v$5, _p$.e));\n _v$6 !== _p$.t && (_p$.t = _$setProp(_el$11, \"style\", _v$6, _p$.t));\n return _p$;\n }, {\n e: undefined,\n t: undefined\n });\n return _el$0;\n })();\n }\n });\n }\n }), _$createComponent(Show, {\n get when() {\n return hiddenCount() > 0;\n },\n get children() {\n var _el$6 = _$createElement(\"box\"),\n _el$7 = _$createElement(\"text\");\n _$insertNode(_el$6, _el$7);\n _$setProp(_el$6, \"flexDirection\", \"row\");\n _$setProp(_el$6, \"columnGap\", 1);\n _$insert(_el$7, () => `${hiddenCount()} hidden (show all)`);\n _$effect(_p$ => {\n var _v$ = props.onClearHidden,\n _v$2 = {\n fg: mutedColor()\n };\n _v$ !== _p$.e && (_p$.e = _$setProp(_el$6, \"onMouseDown\", _v$, _p$.e));\n _v$2 !== _p$.t && (_p$.t = _$setProp(_el$7, \"style\", _v$2, _p$.t));\n return _p$;\n }, {\n e: undefined,\n t: undefined\n });\n return _el$6;\n }\n })];\n }\n }), null);\n _$effect(_p$ => {\n var _v$3 = props.onToggle,\n _v$4 = {\n fg: textColor()\n };\n _v$3 !== _p$.e && (_p$.e = _$setProp(_el$2, \"onMouseDown\", _v$3, _p$.e));\n _v$4 !== _p$.t && (_p$.t = _$setProp(_el$3, \"style\", _v$4, _p$.t));\n return _p$;\n }, {\n e: undefined,\n t: undefined\n });\n return _el$;\n })();\n}",
7
+ "import type { TuiPluginApi } from \"@opencode-ai/plugin/tui\"\r\nimport type { Part } from \"@opencode-ai/sdk/v2\"\r\n\r\nconst HIDDEN_KEY = \"opencode-skill-tracker.hidden\"\r\nconst LEGACY_HIDDEN_KEY = \"opencode-skills-sidebar.hidden\"\r\n\r\nexport interface SkillSummary {\r\n name: string\r\n description: string\r\n content: string\r\n}\r\n\r\nexport function loadHiddenSkills(api: TuiPluginApi): Set<string> {\r\n const raw = api.kv.get<string[]>(HIDDEN_KEY, api.kv.get<string[]>(LEGACY_HIDDEN_KEY, []))\r\n return new Set(Array.isArray(raw) ? raw.filter((name) => typeof name === \"string\") : [])\r\n}\r\n\r\nexport function saveHiddenSkills(api: TuiPluginApi, hidden: Set<string>): void {\r\n api.kv.set(HIDDEN_KEY, [...hidden].sort())\r\n}\r\n\r\nexport function toggleHiddenSkill(hidden: Set<string>, name: string): void {\r\n if (hidden.has(name)) {\r\n hidden.delete(name)\r\n } else {\r\n hidden.add(name)\r\n }\r\n}\r\n\r\nexport async function loadAvailableSkills(api: TuiPluginApi): Promise<SkillSummary[]> {\r\n const result = await api.client.app.skills({\r\n directory: api.state.path.directory,\r\n })\r\n\r\n const entries = result.data ?? []\r\n const deduped = new Map<string, SkillSummary>()\r\n\r\n for (const entry of entries) {\r\n if (!deduped.has(entry.name)) {\r\n deduped.set(entry.name, {\r\n name: entry.name,\r\n description: entry.description ?? \"\",\r\n content: entry.content ?? \"\",\r\n })\r\n }\r\n }\r\n\r\n return [...deduped.values()].sort((a, b) => a.name.localeCompare(b.name))\r\n}\r\n\r\nexport function sortSkillsByLoaded(skills: SkillSummary[], loaded: Set<string>): SkillSummary[] {\r\n return [...skills].sort((left, right) => {\r\n const leftLoaded = loaded.has(left.name)\r\n const rightLoaded = loaded.has(right.name)\r\n\r\n if (leftLoaded !== rightLoaded) {\r\n return leftLoaded ? -1 : 1\r\n }\r\n\r\n return left.name.localeCompare(right.name)\r\n })\r\n}\r\n\r\nexport function extractLoadedSkillName(part: Part, skills: SkillSummary[] = []): string | undefined {\r\n if (part.type === \"tool\" && part.tool === \"skill\") {\r\n const candidate = part.state.input?.name\r\n return typeof candidate === \"string\" && candidate.length > 0 ? candidate : undefined\r\n }\r\n\r\n // opencode injects loaded skills as text parts; the opening tag carries the\r\n // canonical skill name (see SkillTool.toModelOutput in opencode core).\r\n // Prose quoting the literal tag (e.g. `<skill_content name=\"...\">` in\r\n // assistant messages) must not count, so require the name to be a known\r\n // skill.\r\n if (part.type === \"text\" && typeof part.text === \"string\") {\r\n const tagged = /<skill_content name=\"([^\"]+)\">/.exec(part.text)\r\n if (tagged && skills.some((skill) => skill.name === tagged[1])) {\r\n return tagged[1]\r\n }\r\n\r\n // TUI slash-command expansion pastes the skill body as a plain user text\r\n // part with no tag; match it against known skill content.\r\n const text = part.text.trim()\r\n if (text.length > 0) {\r\n for (const skill of skills) {\r\n const content = skill.content.trim()\r\n if (content.length > 0 && text.includes(content)) {\r\n return skill.name\r\n }\r\n }\r\n }\r\n }\r\n\r\n return undefined\r\n}\r\n\r\n/**\r\n * Incrementally scans messages of a session, extracting loaded skill names and\r\n * merging them into `loaded`. Only messages not already in `scannedMessageIDs`\r\n * are inspected, so repeated calls are cheap. Returns whether anything new\r\n * was added.\r\n */\r\nexport function scanLoadedSkillNames(\r\n api: TuiPluginApi,\r\n sessionID: string,\r\n loaded: Set<string>,\r\n scannedMessageIDs: Set<string>,\r\n skills: SkillSummary[] = [],\r\n): boolean {\r\n let changed = false\r\n\r\n for (const message of api.state.session.messages(sessionID)) {\r\n if (scannedMessageIDs.has(message.id)) continue\r\n scannedMessageIDs.add(message.id)\r\n\r\n for (const part of api.state.part(message.id)) {\r\n const skillName = extractLoadedSkillName(part, skills)\r\n if (skillName && !loaded.has(skillName)) {\r\n loaded.add(skillName)\r\n changed = true\r\n }\r\n }\r\n }\r\n\r\n return changed\r\n}\r\n",
8
+ "import { memo as _$memo } from \"@opentui/solid\";\nimport { effect as _$effect } from \"@opentui/solid\";\nimport { insert as _$insert } from \"@opentui/solid\";\nimport { createComponent as _$createComponent } from \"@opentui/solid\";\nimport { createTextNode as _$createTextNode } from \"@opentui/solid\";\nimport { insertNode as _$insertNode } from \"@opentui/solid\";\nimport { setProp as _$setProp } from \"@opentui/solid\";\nimport { createElement as _$createElement } from \"@opentui/solid\";\n/** @jsxImportSource @opentui/solid */\n\nimport { For, Show, createMemo } from \"solid-js\";\nimport { sortSkillsByLoaded } from \"../skill-data\";\nexport function SkillsStatusDialog(props) {\n const currentLoadedNames = createMemo(() => {\n props.version();\n return props.loadedNames();\n });\n const visibleSkills = createMemo(() => {\n const hidden = props.hiddenNames();\n return props.skills().filter(skill => !hidden.has(skill.name));\n });\n const orderedSkills = createMemo(() => sortSkillsByLoaded(visibleSkills(), currentLoadedNames()));\n const hiddenCount = createMemo(() => props.hiddenNames().size);\n const textColor = createMemo(() => props.theme().text);\n const mutedColor = createMemo(() => props.theme().textMuted);\n const loadedColor = createMemo(() => props.theme().success);\n return (() => {\n var _el$ = _$createElement(\"box\"),\n _el$2 = _$createElement(\"box\"),\n _el$3 = _$createElement(\"text\"),\n _el$4 = _$createElement(\"strong\"),\n _el$6 = _$createElement(\"text\");\n _$insertNode(_el$, _el$2);\n _$setProp(_el$, \"flexDirection\", \"column\");\n _$setProp(_el$, \"rowGap\", 1);\n _$setProp(_el$, \"paddingX\", 2);\n _$setProp(_el$, \"paddingY\", 1);\n _$setProp(_el$, \"paddingTop\", 0);\n _$insertNode(_el$2, _el$3);\n _$insertNode(_el$2, _el$6);\n _$setProp(_el$2, \"flexDirection\", \"row\");\n _$setProp(_el$2, \"justifyContent\", \"space-between\");\n _$setProp(_el$2, \"columnGap\", 2);\n _$insertNode(_el$3, _el$4);\n _$insertNode(_el$4, _$createTextNode(`Skills Status`));\n _$insertNode(_el$6, _$createTextNode(`esc`));\n _$insert(_el$, _$createComponent(Show, {\n get when() {\n return orderedSkills().length > 0;\n },\n get fallback() {\n return (() => {\n var _el$0 = _$createElement(\"text\");\n _$insertNode(_el$0, _$createTextNode(`No skills available`));\n _$effect(_$p => _$setProp(_el$0, \"style\", {\n fg: mutedColor()\n }, _$p));\n return _el$0;\n })();\n },\n get children() {\n var _el$8 = _$createElement(\"box\");\n _$setProp(_el$8, \"flexDirection\", \"column\");\n _$insert(_el$8, _$createComponent(For, {\n get each() {\n return orderedSkills();\n },\n children: skill => {\n const loaded = () => currentLoadedNames().has(skill.name);\n return (() => {\n var _el$10 = _$createElement(\"box\"),\n _el$11 = _$createElement(\"text\"),\n _el$13 = _$createElement(\"text\"),\n _el$14 = _$createElement(\"text\");\n _$insertNode(_el$10, _el$11);\n _$insertNode(_el$10, _el$13);\n _$insertNode(_el$10, _el$14);\n _$setProp(_el$10, \"flexDirection\", \"row\");\n _$setProp(_el$10, \"columnGap\", 1);\n _$insertNode(_el$11, _$createTextNode(`•`));\n _$insert(_el$13, () => skill.name);\n _$insert(_el$14, () => loaded() ? \"Loaded\" : \"Unloaded\");\n _$effect(_p$ => {\n var _v$3 = {\n fg: loaded() ? loadedColor() : mutedColor()\n },\n _v$4 = {\n fg: textColor()\n },\n _v$5 = {\n fg: mutedColor()\n };\n _v$3 !== _p$.e && (_p$.e = _$setProp(_el$11, \"style\", _v$3, _p$.e));\n _v$4 !== _p$.t && (_p$.t = _$setProp(_el$13, \"style\", _v$4, _p$.t));\n _v$5 !== _p$.a && (_p$.a = _$setProp(_el$14, \"style\", _v$5, _p$.a));\n return _p$;\n }, {\n e: undefined,\n t: undefined,\n a: undefined\n });\n return _el$10;\n })();\n }\n }));\n return _el$8;\n }\n }), null);\n _$insert(_el$, _$createComponent(Show, {\n get when() {\n return hiddenCount() > 0;\n },\n get children() {\n var _el$9 = _$createElement(\"text\");\n _$insert(_el$9, () => `(${hiddenCount()} hidden — use /hide-skills to manage)`);\n _$effect(_$p => _$setProp(_el$9, \"style\", {\n fg: mutedColor()\n }, _$p));\n return _el$9;\n }\n }), null);\n _$effect(_p$ => {\n var _v$ = {\n fg: textColor()\n },\n _v$2 = {\n fg: mutedColor()\n };\n _v$ !== _p$.e && (_p$.e = _$setProp(_el$3, \"style\", _v$, _p$.e));\n _v$2 !== _p$.t && (_p$.t = _$setProp(_el$6, \"style\", _v$2, _p$.t));\n return _p$;\n }, {\n e: undefined,\n t: undefined\n });\n return _el$;\n })();\n}",
9
+ "import { createTextNode as _$createTextNode } from \"@opentui/solid\";\nimport { insertNode as _$insertNode } from \"@opentui/solid\";\nimport { setProp as _$setProp } from \"@opentui/solid\";\nimport { createElement as _$createElement } from \"@opentui/solid\";\nimport { createComponent as _$createComponent } from \"@opentui/solid\";\n/** @jsxImportSource @opentui/solid */\n\nimport { Show, createMemo } from \"solid-js\";\nconst BULK_RESTORE = \"__restore_all__\";\nexport function SkillsFilterDialog(props) {\n const options = createMemo(() => {\n const hidden = props.hiddenNames();\n const list = [];\n if (hidden.size > 0) {\n list.push({\n title: \"Show all hidden\",\n value: BULK_RESTORE,\n description: `Restore ${hidden.size} hidden skill${hidden.size === 1 ? \"\" : \"s\"}`,\n footer: \"action\",\n category: \"Filter\"\n });\n }\n for (const skill of props.skills()) {\n const isHidden = hidden.has(skill.name);\n list.push({\n title: skill.name,\n value: skill.name,\n description: skill.description || undefined,\n footer: isHidden ? \"hidden\" : \"shown\",\n category: isHidden ? \"Hidden\" : \"Visible\"\n });\n }\n return list;\n });\n return _$createComponent(Show, {\n get when() {\n return options().length > 0;\n },\n get fallback() {\n return (() => {\n var _el$ = _$createElement(\"box\"),\n _el$2 = _$createElement(\"text\");\n _$insertNode(_el$, _el$2);\n _$setProp(_el$, \"flexDirection\", \"column\");\n _$setProp(_el$, \"paddingX\", 2);\n _$setProp(_el$, \"paddingY\", 1);\n _$setProp(_el$, \"paddingTop\", 0);\n _$insertNode(_el$2, _$createTextNode(`No skills available`));\n return _el$;\n })();\n },\n get children() {\n return _$createComponent(props.api.ui.DialogSelect, {\n title: \"Hide Skills\",\n placeholder: \"Search skills\\u2026\",\n get options() {\n return options();\n },\n onSelect: option => {\n if (option.value === BULK_RESTORE) {\n props.onClearHidden();\n return;\n }\n props.onToggleHidden(option.value);\n }\n });\n }\n });\n}"
10
+ ],
11
+ "mappings": "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",
12
+ "debugId": "E1362898BDE369EF64756E2164756E21",
13
+ "names": []
14
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "opencode-skill-tracker-tui",
3
+ "version": "0.1.0",
4
+ "description": "OpenCode TUI plugin that shows skills and loaded state in the sidebar",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "aihaipeng",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/aihaipeng/opencode-skill-tracker.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/aihaipeng/opencode-skill-tracker/issues"
14
+ },
15
+ "homepage": "https://github.com/aihaipeng/opencode-skill-tracker#readme",
16
+ "keywords": [
17
+ "opencode",
18
+ "opencode-plugin",
19
+ "tui",
20
+ "skills",
21
+ "sidebar"
22
+ ],
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "exports": {
27
+ ".": {
28
+ "import": "./dist/tui.js",
29
+ "types": "./dist/tui.d.ts"
30
+ },
31
+ "./tui": {
32
+ "import": "./dist/tui.js",
33
+ "types": "./dist/tui.d.ts"
34
+ }
35
+ },
36
+ "scripts": {
37
+ "build": "bun run scripts/build.mjs && tsc --emitDeclarationOnly",
38
+ "typecheck": "tsc --noEmit",
39
+ "prepare": "bun run build"
40
+ },
41
+ "dependencies": {
42
+ "@opencode-ai/plugin": "^1.14.29",
43
+ "@opentui/core": "^0.1.105",
44
+ "@opentui/solid": "^0.1.105",
45
+ "solid-js": "^1.9.11"
46
+ },
47
+ "devDependencies": {
48
+ "typescript": "^5.8.2"
49
+ }
50
+ }