br4zz4-opencode-skill-picker 0.1.7 → 0.1.8
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 +1 -1
- package/README.md +19 -4
- package/dist/index.js +82 -0
- package/package.json +13 -5
- package/index.tsx +0 -91
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# br4zz4-opencode-skill-picker
|
|
2
2
|
|
|
3
3
|
OpenCode TUI plugin that makes skills autocompletable in the prompt:
|
|
4
4
|
|
|
@@ -8,7 +8,17 @@ OpenCode TUI plugin that makes skills autocompletable in the prompt:
|
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
### Via npm
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"plugin": ["br4zz4-opencode-skill-picker"]
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> **Known opencode bug:** opencode installs npm plugins into `~/.cache/opencode/packages/<name>@<version>` and cannot `import` from paths containing `@`. If the npm install doesn't load, use the file path below.
|
|
20
|
+
|
|
21
|
+
### Via file (workaround)
|
|
12
22
|
|
|
13
23
|
```bash
|
|
14
24
|
git clone git@github.com:br4zz4/opencode-skill-picker.git ~/.qwert/vendor/opencode-skill-picker
|
|
@@ -18,7 +28,7 @@ Then add to `~/.config/opencode/tui.json`:
|
|
|
18
28
|
|
|
19
29
|
```json
|
|
20
30
|
{
|
|
21
|
-
"plugin": ["file:///Users/<you>/.qwert/vendor/opencode-skill-picker/index.
|
|
31
|
+
"plugin": ["file:///Users/<you>/.qwert/vendor/opencode-skill-picker/dist/index.js"]
|
|
22
32
|
}
|
|
23
33
|
```
|
|
24
34
|
|
|
@@ -26,11 +36,16 @@ Restart opencode.
|
|
|
26
36
|
|
|
27
37
|
## Development
|
|
28
38
|
|
|
39
|
+
```bash
|
|
40
|
+
npm install
|
|
41
|
+
npm run build
|
|
42
|
+
```
|
|
43
|
+
|
|
29
44
|
Run opencode against your checkout:
|
|
30
45
|
|
|
31
46
|
```json
|
|
32
47
|
{
|
|
33
|
-
"plugin": ["file:///path/to/opencode-skill-picker/index.
|
|
48
|
+
"plugin": ["file:///path/to/opencode-skill-picker/dist/index.js"]
|
|
34
49
|
}
|
|
35
50
|
```
|
|
36
51
|
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// index.tsx
|
|
2
|
+
import { jsx } from "@opentui/solid/jsx-runtime";
|
|
3
|
+
var OPENCODE_BASE_MODE = "base";
|
|
4
|
+
var tui = async (api) => {
|
|
5
|
+
let skills = [];
|
|
6
|
+
let skillsLoaded = false;
|
|
7
|
+
const loadSkills = async () => {
|
|
8
|
+
if (skillsLoaded) return skills;
|
|
9
|
+
try {
|
|
10
|
+
const result = await api.client.app.skills();
|
|
11
|
+
const list = result?.data ?? result;
|
|
12
|
+
const source = Array.isArray(list) ? list : [];
|
|
13
|
+
skills = source.filter((s) => s && typeof s.name === "string" && s.name.length > 0).map((s) => ({ name: s.name, description: s.description })).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
14
|
+
} catch {
|
|
15
|
+
skills = [];
|
|
16
|
+
} finally {
|
|
17
|
+
skillsLoaded = true;
|
|
18
|
+
}
|
|
19
|
+
return skills;
|
|
20
|
+
};
|
|
21
|
+
const insertSkill = async (name) => {
|
|
22
|
+
try {
|
|
23
|
+
await api.client.tui.appendPrompt({ text: `/${name} ` });
|
|
24
|
+
} catch {
|
|
25
|
+
api.ui.toast({ message: `Could not load skill ${name}`, variant: "error" });
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const openPicker = async () => {
|
|
29
|
+
if (api.mode.current() !== OPENCODE_BASE_MODE) return;
|
|
30
|
+
if (api.renderer.currentFocusedEditor === null) return;
|
|
31
|
+
const list = await loadSkills();
|
|
32
|
+
if (list.length === 0) {
|
|
33
|
+
api.ui.toast({ message: "No skills available", variant: "warning" });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const options = list.map((skill) => ({
|
|
37
|
+
title: skill.name,
|
|
38
|
+
value: skill.name,
|
|
39
|
+
description: skill.description?.replace(/\s+/g, " ").trim(),
|
|
40
|
+
category: "Skills"
|
|
41
|
+
}));
|
|
42
|
+
api.ui.dialog.replace(
|
|
43
|
+
() => /* @__PURE__ */ jsx(
|
|
44
|
+
api.ui.DialogSelect,
|
|
45
|
+
{
|
|
46
|
+
title: "Skills",
|
|
47
|
+
placeholder: "Search skills\u2026",
|
|
48
|
+
options,
|
|
49
|
+
onSelect: (opt) => {
|
|
50
|
+
api.ui.dialog.clear();
|
|
51
|
+
void insertSkill(opt.value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
),
|
|
55
|
+
() => {
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
const registerSkillCommands = async () => {
|
|
60
|
+
const list = await loadSkills();
|
|
61
|
+
api.command.register(
|
|
62
|
+
() => list.map((skill) => ({
|
|
63
|
+
title: skill.name,
|
|
64
|
+
value: skill.name,
|
|
65
|
+
description: skill.description?.replace(/\s+/g, " ").trim(),
|
|
66
|
+
category: "Skills",
|
|
67
|
+
slash: { name: skill.name },
|
|
68
|
+
onSelect: () => {
|
|
69
|
+
void insertSkill(skill.name);
|
|
70
|
+
}
|
|
71
|
+
}))
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
api.keymap.registerLayer({
|
|
75
|
+
bindings: [{ key: "#", desc: "Pick a skill", preventDefault: true, cmd: () => void openPicker() }]
|
|
76
|
+
});
|
|
77
|
+
void registerSkillCommands();
|
|
78
|
+
};
|
|
79
|
+
var index_default = { id: "skill-picker", tui };
|
|
80
|
+
export {
|
|
81
|
+
index_default as default
|
|
82
|
+
};
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "br4zz4-opencode-skill-picker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "OpenCode TUI plugin: pick and autocomplete skills via /slash commands and the # keybinding",
|
|
5
5
|
"author": "oporpino <dev@porpi.no>",
|
|
6
6
|
"license": "AGPL-3.0",
|
|
7
7
|
"type": "module",
|
|
8
|
-
"main": "./index.
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
9
|
"files": [
|
|
10
|
-
"
|
|
10
|
+
"dist"
|
|
11
11
|
],
|
|
12
12
|
"exports": {
|
|
13
|
-
".": "./index.
|
|
14
|
-
"./tui": "./index.
|
|
13
|
+
".": "./dist/index.js",
|
|
14
|
+
"./tui": "./dist/index.js"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"opencode",
|
|
@@ -24,5 +24,13 @@
|
|
|
24
24
|
},
|
|
25
25
|
"publishConfig": {
|
|
26
26
|
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"types": "./dist/index.js",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "node build.mjs",
|
|
31
|
+
"prepublishOnly": "npm run build"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"esbuild": "^0.28.2"
|
|
27
35
|
}
|
|
28
36
|
}
|
package/index.tsx
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import type { TuiPlugin, TuiDialogSelectOption } from "@opencode-ai/plugin/tui"
|
|
2
|
-
|
|
3
|
-
type Skill = { name: string; description?: string }
|
|
4
|
-
|
|
5
|
-
const OPENCODE_BASE_MODE = "base"
|
|
6
|
-
|
|
7
|
-
const tui: TuiPlugin = async (api) => {
|
|
8
|
-
let skills: Skill[] = []
|
|
9
|
-
let skillsLoaded = false
|
|
10
|
-
|
|
11
|
-
const loadSkills = async (): Promise<Skill[]> => {
|
|
12
|
-
if (skillsLoaded) return skills
|
|
13
|
-
try {
|
|
14
|
-
const result = await api.client.app.skills()
|
|
15
|
-
const list: unknown = result?.data ?? result
|
|
16
|
-
const source = Array.isArray(list) ? list : []
|
|
17
|
-
skills = (source as Skill[])
|
|
18
|
-
.filter((s) => s && typeof s.name === "string" && s.name.length > 0)
|
|
19
|
-
.map((s) => ({ name: s.name, description: s.description }))
|
|
20
|
-
.toSorted((a, b) => a.name.localeCompare(b.name))
|
|
21
|
-
} catch {
|
|
22
|
-
skills = []
|
|
23
|
-
} finally {
|
|
24
|
-
skillsLoaded = true
|
|
25
|
-
}
|
|
26
|
-
return skills
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const insertSkill = async (name: string) => {
|
|
30
|
-
try {
|
|
31
|
-
await api.client.tui.appendPrompt({ text: `/${name} ` })
|
|
32
|
-
} catch {
|
|
33
|
-
api.ui.toast({ message: `Could not load skill ${name}`, variant: "error" })
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const openPicker = async () => {
|
|
38
|
-
if (api.mode.current() !== OPENCODE_BASE_MODE) return
|
|
39
|
-
if (api.renderer.currentFocusedEditor === null) return
|
|
40
|
-
|
|
41
|
-
const list = await loadSkills()
|
|
42
|
-
if (list.length === 0) {
|
|
43
|
-
api.ui.toast({ message: "No skills available", variant: "warning" })
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
const options: TuiDialogSelectOption<string>[] = list.map((skill) => ({
|
|
47
|
-
title: skill.name,
|
|
48
|
-
value: skill.name,
|
|
49
|
-
description: skill.description?.replace(/\s+/g, " ").trim(),
|
|
50
|
-
category: "Skills",
|
|
51
|
-
}))
|
|
52
|
-
api.ui.dialog.replace(
|
|
53
|
-
() => (
|
|
54
|
-
<api.ui.DialogSelect<string>
|
|
55
|
-
title="Skills"
|
|
56
|
-
placeholder="Search skills…"
|
|
57
|
-
options={options}
|
|
58
|
-
onSelect={(opt) => {
|
|
59
|
-
api.ui.dialog.clear()
|
|
60
|
-
void insertSkill(opt.value)
|
|
61
|
-
}}
|
|
62
|
-
/>
|
|
63
|
-
),
|
|
64
|
-
() => {},
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const registerSkillCommands = async () => {
|
|
69
|
-
const list = await loadSkills()
|
|
70
|
-
api.command.register(() =>
|
|
71
|
-
list.map((skill) => ({
|
|
72
|
-
title: skill.name,
|
|
73
|
-
value: skill.name,
|
|
74
|
-
description: skill.description?.replace(/\s+/g, " ").trim(),
|
|
75
|
-
category: "Skills",
|
|
76
|
-
slash: { name: skill.name },
|
|
77
|
-
onSelect: () => {
|
|
78
|
-
void insertSkill(skill.name)
|
|
79
|
-
},
|
|
80
|
-
})),
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
api.keymap.registerLayer({
|
|
85
|
-
bindings: [{ key: "#", desc: "Pick a skill", preventDefault: true, cmd: () => void openPicker() }],
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
void registerSkillCommands()
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export default { id: "skill-picker", tui }
|