@encoredigitalgroup/opencode 0.1.2 → 0.2.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/README.md +20 -13
- package/dist/config/ConfigLoader.d.ts +1 -1
- package/dist/lib/FilePickerCommands.d.ts +5 -12
- package/dist/lib/FilePickerCommands.test.d.ts +1 -0
- package/dist/lib/SkillCommands.d.ts +4 -15
- package/dist/package.integration.test.d.ts +1 -0
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +28 -327
- package/dist/server/index.test.d.ts +1 -0
- package/dist/tui/index.d.ts +3 -3
- package/dist/tui/index.js +97 -188
- package/dist/tui/index.test.d.ts +1 -0
- package/package.json +14 -7
- package/server.js +1 -0
- package/tui.js +1 -0
package/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
`@encoredigitalgroup/opencode` adds file pickers for OpenCode planning commands.
|
|
4
4
|
|
|
5
|
-
The TUI provides `/plan-review`, `/plan-split`, and `/plan-execute` commands. Each command opens a picker for plans in `docs/_planning` and inserts the
|
|
6
|
-
into the prompt. The server entry
|
|
5
|
+
The TUI provides `/plan-review`, `/plan-resync`, `/plan-split`, and `/plan-execute` commands. Each command opens a picker for plans in `docs/_planning` and inserts the
|
|
6
|
+
selected plan into the prompt. The server entry attaches the matching installed skill to the prompt.
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
OpenCode v2 loads this package from the `plugins` array. Add an object entry with the package name and its options:
|
|
11
11
|
|
|
12
12
|
- Use `~/.config/opencode/opencode.json` to enable it for every project.
|
|
13
13
|
- Use `opencode.json` in a project to enable it only there.
|
|
@@ -15,38 +15,45 @@ Add the package to the `plugin` array in your OpenCode configuration:
|
|
|
15
15
|
```json
|
|
16
16
|
{
|
|
17
17
|
"$schema": "https://opencode.ai/config.json",
|
|
18
|
-
"
|
|
18
|
+
"plugins": [
|
|
19
|
+
{
|
|
20
|
+
"package": "@encoredigitalgroup/opencode",
|
|
21
|
+
"options": {}
|
|
22
|
+
}
|
|
23
|
+
]
|
|
19
24
|
}
|
|
20
25
|
```
|
|
21
26
|
|
|
22
|
-
Restart OpenCode after changing the configuration. OpenCode downloads missing npm plugins that appear in the `
|
|
23
|
-
with Bun or npm yourself.
|
|
27
|
+
Restart OpenCode after changing the configuration. OpenCode downloads missing npm plugins that appear in the `plugins` array, so you do not need to install this package
|
|
28
|
+
with Bun or npm yourself. This package supports OpenCode v2 only; replace the legacy `plugin` configuration entry when migrating from OpenCode v1.
|
|
24
29
|
|
|
25
30
|
The plugin resolves the mapped skills through OpenCode, so those skills can live in any location that OpenCode discovers.
|
|
26
31
|
|
|
27
32
|
## Configuration
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
Unspecified values use the defaults: `docs/_planning` and the command's existing skill name.
|
|
34
|
+
Set `planDirectory` and `skillMappings` in the object's `options` field. `planDirectory` is a non-empty path relative to the workspace. `skillMappings` can override any
|
|
35
|
+
supported command's installed skill name. Unspecified values use the defaults: `docs/_planning` and the command's existing skill name.
|
|
31
36
|
|
|
32
37
|
```json
|
|
33
38
|
{
|
|
34
39
|
"$schema": "https://opencode.ai/config.json",
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
"@encoredigitalgroup/opencode",
|
|
38
|
-
{
|
|
40
|
+
"plugins": [
|
|
41
|
+
{
|
|
42
|
+
"package": "@encoredigitalgroup/opencode",
|
|
43
|
+
"options": {
|
|
39
44
|
"planDirectory": "plans",
|
|
40
45
|
"skillMappings": {
|
|
41
46
|
"plan-review": "custom-plan-review",
|
|
42
47
|
"plan-execute": "custom-plan-execute"
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
|
-
|
|
50
|
+
}
|
|
46
51
|
]
|
|
47
52
|
}
|
|
48
53
|
```
|
|
49
54
|
|
|
55
|
+
Launching a picker from an existing session submits to that session. Launching one from the home screen creates and displays a new session before submitting the selected plan.
|
|
56
|
+
|
|
50
57
|
## Development
|
|
51
58
|
|
|
52
59
|
```sh
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PluginOptions } from "@opencode
|
|
1
|
+
import type { PluginOptions } from "@opencode/plugin";
|
|
2
2
|
import { type PluginConfig } from "./ConfigTypes.js";
|
|
3
3
|
/** Loads plugin settings from the OpenCode plugin options. */
|
|
4
4
|
export declare class ConfigLoader {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Plugin } from "@opencode/plugin/tui";
|
|
2
2
|
import type { PluginConfig } from "../config";
|
|
3
3
|
export type FilePickerOption = {
|
|
4
4
|
argument: string;
|
|
@@ -6,14 +6,12 @@ export type FilePickerOption = {
|
|
|
6
6
|
label: string;
|
|
7
7
|
};
|
|
8
8
|
export type FilePickerCommand = {
|
|
9
|
-
argumentPlaceholder: string;
|
|
10
9
|
command: string;
|
|
11
10
|
description: string;
|
|
12
11
|
emptyMessage: string;
|
|
13
12
|
excludesPlanningDirectory?(directory: string): boolean;
|
|
14
13
|
matchesSelection(argument: string, filePath: string): boolean;
|
|
15
14
|
options(root: string, excludesPlanningDirectory?: (directory: string) => boolean): FilePickerOption[];
|
|
16
|
-
retainsSelectedFileInPrompt: boolean;
|
|
17
15
|
skill: string;
|
|
18
16
|
title: string;
|
|
19
17
|
};
|
|
@@ -24,13 +22,8 @@ export declare class FilePickerCommands {
|
|
|
24
22
|
private static isSubPlanSelection;
|
|
25
23
|
private static subPlanOptions;
|
|
26
24
|
static getCommands(config: PluginConfig): readonly FilePickerCommand[];
|
|
27
|
-
private static
|
|
28
|
-
private static
|
|
29
|
-
private static
|
|
30
|
-
static
|
|
31
|
-
parts: unknown[];
|
|
32
|
-
}, template: (skill: string) => Promise<string>, config: PluginConfig): Promise<void>;
|
|
33
|
-
private static workspaceRoot;
|
|
34
|
-
private static setPrompt;
|
|
35
|
-
static register(api: TuiPluginApi, prompt: () => TuiPromptRef | undefined, config: PluginConfig): void;
|
|
25
|
+
private static toast;
|
|
26
|
+
private static submit;
|
|
27
|
+
private static run;
|
|
28
|
+
static register(context: Plugin.Context, config: PluginConfig): void;
|
|
36
29
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import type { PluginConfig } from "../config";
|
|
2
2
|
export declare class SkillCommands {
|
|
3
|
-
static getCommands(config: PluginConfig): {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
8
|
-
"plan-resync": {
|
|
9
|
-
description: string;
|
|
10
|
-
skill: string;
|
|
11
|
-
};
|
|
12
|
-
};
|
|
13
|
-
static commandTemplate(skillName: string): string;
|
|
14
|
-
static getTemplate(serverUrl: URL, directory: string, skillName: string): Promise<string>;
|
|
15
|
-
static expandCommandTemplate(output: {
|
|
16
|
-
parts: unknown[];
|
|
17
|
-
}, template: (skillName: string) => Promise<string>): Promise<void>;
|
|
3
|
+
static getCommands(config: PluginConfig): Record<"feature-planning", {
|
|
4
|
+
description: string;
|
|
5
|
+
skill: string;
|
|
6
|
+
}>;
|
|
18
7
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
declare const
|
|
3
|
-
export default
|
|
1
|
+
import { Plugin } from "@opencode/plugin";
|
|
2
|
+
declare const _default: Plugin.Plugin;
|
|
3
|
+
export default _default;
|
package/dist/server/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// src/server/index.ts
|
|
3
|
+
import { Plugin } from "@opencode/plugin";
|
|
4
|
+
|
|
2
5
|
// src/config/ConfigDefaults.ts
|
|
3
6
|
class ConfigDefaults {
|
|
4
7
|
static getDefaultSkillMappings() {
|
|
@@ -113,349 +116,47 @@ class ConfigLoader {
|
|
|
113
116
|
}
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
|
-
// src/lib/FilePickerCommands.ts
|
|
117
|
-
import { createComponent } from "@opentui/solid";
|
|
118
|
-
import { readdirSync, statSync } from "fs";
|
|
119
|
-
import { join, resolve } from "path";
|
|
120
|
-
import { pathToFileURL } from "url";
|
|
121
|
-
|
|
122
|
-
class FilePickerCommands {
|
|
123
|
-
static isPlanPath(path, planDirectory) {
|
|
124
|
-
return path.startsWith(`${planDirectory}/`) && path.endsWith("/plan.md") && !path.slice(planDirectory.length + 1, -"/plan.md".length).includes("/") && !/\s/.test(path.slice(planDirectory.length + 1, -"/plan.md".length));
|
|
125
|
-
}
|
|
126
|
-
static planOptions(root, planDirectory, excludesPlanningDirectory = () => false) {
|
|
127
|
-
const planningDirectory = join(root, planDirectory);
|
|
128
|
-
try {
|
|
129
|
-
return readdirSync(planningDirectory, { withFileTypes: true }).filter((entry) => {
|
|
130
|
-
if (!entry.isDirectory()) {
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
if (excludesPlanningDirectory(join(planningDirectory, entry.name))) {
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
try {
|
|
137
|
-
return statSync(join(planningDirectory, entry.name, "plan.md")).isFile();
|
|
138
|
-
} catch {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
}).map((entry) => ({
|
|
142
|
-
argument: `${planDirectory}/${entry.name}/plan.md`,
|
|
143
|
-
filePath: `${planDirectory}/${entry.name}/plan.md`,
|
|
144
|
-
label: entry.name
|
|
145
|
-
})).sort((left, right) => left.label.localeCompare(right.label));
|
|
146
|
-
} catch {
|
|
147
|
-
return [];
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
static hasSubPlans(directory) {
|
|
151
|
-
try {
|
|
152
|
-
return readdirSync(directory, { withFileTypes: true }).some((entry) => entry.isFile() && /^\d{2}-.+\.md$/.test(entry.name));
|
|
153
|
-
} catch {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
static isSubPlanSelection(argument, filePath, planDirectory) {
|
|
158
|
-
return argument.startsWith(`${planDirectory}/`) && !argument.slice(planDirectory.length + 1).includes("/") && !/\s/.test(argument.slice(planDirectory.length + 1)) && filePath.startsWith(`${argument}/`) && filePath !== `${argument}/plan.md` && filePath.endsWith(".md");
|
|
159
|
-
}
|
|
160
|
-
static subPlanOptions(root, planDirectory) {
|
|
161
|
-
const planningDirectory = join(root, planDirectory);
|
|
162
|
-
try {
|
|
163
|
-
return readdirSync(planningDirectory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).flatMap((entry) => {
|
|
164
|
-
const directory = join(planningDirectory, entry.name);
|
|
165
|
-
try {
|
|
166
|
-
const subPlan = readdirSync(directory, { withFileTypes: true }).filter((file) => file.isFile() && file.name !== "plan.md" && file.name.endsWith(".md")).map((file) => file.name).sort((left, right) => left.localeCompare(right))[0];
|
|
167
|
-
return subPlan === undefined ? [] : [{
|
|
168
|
-
argument: `${planDirectory}/${entry.name}`,
|
|
169
|
-
filePath: `${planDirectory}/${entry.name}/${subPlan}`,
|
|
170
|
-
label: entry.name
|
|
171
|
-
}];
|
|
172
|
-
} catch {
|
|
173
|
-
return [];
|
|
174
|
-
}
|
|
175
|
-
}).sort((left, right) => left.label.localeCompare(right.label));
|
|
176
|
-
} catch {
|
|
177
|
-
return [];
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
static getCommands(config) {
|
|
181
|
-
const { planDirectory, skillMappings } = config;
|
|
182
|
-
const noPlansMessage = `No plan.md files found in ${planDirectory}.`;
|
|
183
|
-
const noSubPlansMessage = `No sub-plan files found in ${planDirectory}.`;
|
|
184
|
-
return [
|
|
185
|
-
{
|
|
186
|
-
argumentPlaceholder: "$ARGUMENTS",
|
|
187
|
-
command: "plan-review",
|
|
188
|
-
description: "Select a plan to review",
|
|
189
|
-
emptyMessage: noPlansMessage,
|
|
190
|
-
matchesSelection: (argument, filePath) => argument === filePath && FilePickerCommands.isPlanPath(argument, planDirectory),
|
|
191
|
-
options: (root, excludesPlanningDirectory) => FilePickerCommands.planOptions(root, planDirectory, excludesPlanningDirectory),
|
|
192
|
-
retainsSelectedFileInPrompt: true,
|
|
193
|
-
skill: skillMappings["plan-review"],
|
|
194
|
-
title: "Select a plan to review"
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
argumentPlaceholder: "$ARGUMENTS",
|
|
198
|
-
command: "plan-split",
|
|
199
|
-
description: "Select a plan to split",
|
|
200
|
-
emptyMessage: noPlansMessage,
|
|
201
|
-
excludesPlanningDirectory: FilePickerCommands.hasSubPlans,
|
|
202
|
-
matchesSelection: (argument, filePath) => argument === filePath && FilePickerCommands.isPlanPath(argument, planDirectory),
|
|
203
|
-
options: (root, excludesPlanningDirectory) => FilePickerCommands.planOptions(root, planDirectory, excludesPlanningDirectory),
|
|
204
|
-
retainsSelectedFileInPrompt: true,
|
|
205
|
-
skill: skillMappings["plan-split"],
|
|
206
|
-
title: "Select a plan to split"
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
argumentPlaceholder: "$PLAN_DIR",
|
|
210
|
-
command: "plan-execute",
|
|
211
|
-
description: "Select sub-plans to execute",
|
|
212
|
-
emptyMessage: noSubPlansMessage,
|
|
213
|
-
matchesSelection: (argument, filePath) => FilePickerCommands.isSubPlanSelection(argument, filePath, planDirectory),
|
|
214
|
-
options: (root) => FilePickerCommands.subPlanOptions(root, planDirectory),
|
|
215
|
-
retainsSelectedFileInPrompt: false,
|
|
216
|
-
skill: skillMappings["plan-execute"],
|
|
217
|
-
title: "Select sub-plans to execute"
|
|
218
|
-
}
|
|
219
|
-
];
|
|
220
|
-
}
|
|
221
|
-
static findSkill(text, parts, config) {
|
|
222
|
-
const filePaths = parts.flatMap((part) => {
|
|
223
|
-
if (typeof part !== "object" || part === null || !("type" in part) || part.type !== "file" || !("source" in part)) {
|
|
224
|
-
return [];
|
|
225
|
-
}
|
|
226
|
-
const { source } = part;
|
|
227
|
-
return typeof source === "object" && source !== null && "type" in source && source.type === "file" && "path" in source && typeof source.path === "string" ? [source.path] : [];
|
|
228
|
-
});
|
|
229
|
-
for (const command of FilePickerCommands.getCommands(config)) {
|
|
230
|
-
const prefix = `/${command.command} @`;
|
|
231
|
-
if (!text.startsWith(prefix)) {
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
const argument = text.slice(prefix.length).trim();
|
|
235
|
-
const filePath = filePaths.find((path) => command.matchesSelection(argument, path));
|
|
236
|
-
if (argument !== "" && filePath !== undefined) {
|
|
237
|
-
return {
|
|
238
|
-
argument,
|
|
239
|
-
argumentPlaceholder: command.argumentPlaceholder,
|
|
240
|
-
filePath,
|
|
241
|
-
retainsSelectedFileInPrompt: command.retainsSelectedFileInPrompt,
|
|
242
|
-
skill: command.skill
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
static removeSelectedFile(parts, filePath) {
|
|
248
|
-
const selectedFileIndex = parts.findIndex((candidate) => {
|
|
249
|
-
if (typeof candidate !== "object" || candidate === null || !("type" in candidate) || candidate.type !== "file" || !("source" in candidate)) {
|
|
250
|
-
return false;
|
|
251
|
-
}
|
|
252
|
-
const { source } = candidate;
|
|
253
|
-
return typeof source === "object" && source !== null && "type" in source && source.type === "file" && "path" in source && source.path === filePath;
|
|
254
|
-
});
|
|
255
|
-
if (selectedFileIndex !== -1) {
|
|
256
|
-
parts.splice(selectedFileIndex, 1);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
static expandedSkill(text, parts, commands) {
|
|
260
|
-
for (const part of parts) {
|
|
261
|
-
if (typeof part !== "object" || part === null || !("type" in part) || part.type !== "file" || !("source" in part)) {
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
const { source } = part;
|
|
265
|
-
if (typeof source !== "object" || source === null || !("type" in source) || source.type !== "file" || !("path" in source) || !("text" in source)) {
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
const filePath = source.path;
|
|
269
|
-
if (typeof filePath !== "string" || typeof source.text !== "object" || source.text === null || !("value" in source.text) || typeof source.text.value !== "string" || !source.text.value.startsWith("@")) {
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
const argument = source.text.value.slice(1);
|
|
273
|
-
const command = commands.find((candidate) => candidate.matchesSelection(argument, filePath) && text.includes(candidate.argumentPlaceholder));
|
|
274
|
-
if (command !== undefined) {
|
|
275
|
-
return {
|
|
276
|
-
argument,
|
|
277
|
-
argumentPlaceholder: command.argumentPlaceholder,
|
|
278
|
-
filePath,
|
|
279
|
-
retainsSelectedFileInPrompt: command.retainsSelectedFileInPrompt,
|
|
280
|
-
skill: command.skill
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
static async expand(output, template, config) {
|
|
286
|
-
const commands = FilePickerCommands.getCommands(config);
|
|
287
|
-
for (const part of output.parts) {
|
|
288
|
-
if (typeof part !== "object" || part === null || !("type" in part) || part.type !== "text" || !("text" in part) || typeof part.text !== "string" || "synthetic" in part && part.synthetic) {
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
const skill = FilePickerCommands.findSkill(part.text, output.parts, config);
|
|
292
|
-
if (skill === undefined) {
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
part.text = (await template(skill.skill)).replaceAll(skill.argumentPlaceholder, `@${skill.argument}`).trim();
|
|
296
|
-
if (!skill.retainsSelectedFileInPrompt) {
|
|
297
|
-
FilePickerCommands.removeSelectedFile(output.parts, skill.filePath);
|
|
298
|
-
}
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
for (const part of output.parts) {
|
|
302
|
-
if (typeof part !== "object" || part === null || !("type" in part) || part.type !== "text" || !("text" in part) || typeof part.text !== "string" || "synthetic" in part && part.synthetic) {
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
305
|
-
const skill = FilePickerCommands.expandedSkill(part.text, output.parts, commands);
|
|
306
|
-
if (skill === undefined) {
|
|
307
|
-
continue;
|
|
308
|
-
}
|
|
309
|
-
part.text = part.text.replaceAll(skill.argumentPlaceholder, `@${skill.argument}`);
|
|
310
|
-
if (!skill.retainsSelectedFileInPrompt) {
|
|
311
|
-
FilePickerCommands.removeSelectedFile(output.parts, skill.filePath);
|
|
312
|
-
}
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
static workspaceRoot(worktree, directory) {
|
|
317
|
-
return worktree === "/" ? directory : worktree;
|
|
318
|
-
}
|
|
319
|
-
static setPrompt(prompt, root, command, option) {
|
|
320
|
-
const mention = `@${option.argument}`;
|
|
321
|
-
const input = `/${command.command} ${mention} `;
|
|
322
|
-
const start = Bun.stringWidth(`/${command.command} `);
|
|
323
|
-
prompt.set({
|
|
324
|
-
input,
|
|
325
|
-
mode: "normal",
|
|
326
|
-
parts: [
|
|
327
|
-
{
|
|
328
|
-
type: "file",
|
|
329
|
-
mime: "text/plain",
|
|
330
|
-
filename: option.filePath,
|
|
331
|
-
url: pathToFileURL(resolve(root, option.filePath)).href,
|
|
332
|
-
source: {
|
|
333
|
-
type: "file",
|
|
334
|
-
path: option.filePath,
|
|
335
|
-
text: {
|
|
336
|
-
start,
|
|
337
|
-
end: start + Bun.stringWidth(mention),
|
|
338
|
-
value: mention
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
]
|
|
343
|
-
});
|
|
344
|
-
prompt.focus();
|
|
345
|
-
}
|
|
346
|
-
static register(api, prompt, config) {
|
|
347
|
-
api.keymap.registerLayer({
|
|
348
|
-
commands: FilePickerCommands.getCommands(config).map((command) => ({
|
|
349
|
-
name: `encoredigitalgroup-opencode.${command.command}`,
|
|
350
|
-
title: command.title,
|
|
351
|
-
category: "Plugin",
|
|
352
|
-
namespace: "palette",
|
|
353
|
-
slashName: command.command,
|
|
354
|
-
run() {
|
|
355
|
-
const root = FilePickerCommands.workspaceRoot(api.state.path.worktree, api.state.path.directory);
|
|
356
|
-
const options = command.options(root, command.excludesPlanningDirectory);
|
|
357
|
-
if (options.length === 0) {
|
|
358
|
-
api.ui.toast({
|
|
359
|
-
variant: "warning",
|
|
360
|
-
message: command.emptyMessage
|
|
361
|
-
});
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
const DialogSelect = api.ui.DialogSelect;
|
|
365
|
-
api.ui.dialog.setSize("medium");
|
|
366
|
-
api.ui.dialog.replace(() => createComponent(DialogSelect, {
|
|
367
|
-
title: command.title,
|
|
368
|
-
options: options.map((option) => ({ title: option.label, value: option })),
|
|
369
|
-
onSelect: (option) => {
|
|
370
|
-
api.ui.dialog.clear();
|
|
371
|
-
const promptRef = prompt();
|
|
372
|
-
if (promptRef === undefined) {
|
|
373
|
-
api.ui.toast({
|
|
374
|
-
variant: "error",
|
|
375
|
-
message: "The prompt is not ready."
|
|
376
|
-
});
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
FilePickerCommands.setPrompt(promptRef, root, command, option.value);
|
|
380
|
-
}
|
|
381
|
-
}));
|
|
382
|
-
}
|
|
383
|
-
}))
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
119
|
// src/lib/SkillCommands.ts
|
|
389
|
-
var skillMarker = "<encoredigitalgroup-opencode-skill:";
|
|
390
|
-
|
|
391
120
|
class SkillCommands {
|
|
392
121
|
static getCommands(config) {
|
|
393
122
|
return {
|
|
394
123
|
"feature-planning": {
|
|
395
124
|
description: "Create a feature implementation plan.",
|
|
396
125
|
skill: config.skillMappings["feature-planning"]
|
|
397
|
-
},
|
|
398
|
-
"plan-resync": {
|
|
399
|
-
description: "Reconcile an implementation plan with the current codebase.",
|
|
400
|
-
skill: config.skillMappings["plan-resync"]
|
|
401
126
|
}
|
|
402
127
|
};
|
|
403
128
|
}
|
|
404
|
-
static commandTemplate(skillName) {
|
|
405
|
-
return `${skillMarker}${skillName}>`;
|
|
406
|
-
}
|
|
407
|
-
static async getTemplate(serverUrl, directory, skillName) {
|
|
408
|
-
const url = new URL("/api/skill", serverUrl);
|
|
409
|
-
url.searchParams.set("directory", directory);
|
|
410
|
-
url.searchParams.set("location[directory]", directory);
|
|
411
|
-
const response = await fetch(url);
|
|
412
|
-
if (!response.ok) {
|
|
413
|
-
throw new Error(`Unable to list OpenCode skills: ${response.status} ${response.statusText}`);
|
|
414
|
-
}
|
|
415
|
-
const responseBody = await response.json();
|
|
416
|
-
const skills = typeof responseBody === "object" && responseBody !== null && "data" in responseBody && Array.isArray(responseBody.data) ? responseBody.data.filter((value) => typeof value === "object" && value !== null && ("content" in value) && typeof value.content === "string" && ("name" in value) && typeof value.name === "string") : [];
|
|
417
|
-
const skill = skills.find((candidate) => candidate.name === skillName);
|
|
418
|
-
if (skill === undefined) {
|
|
419
|
-
throw new Error(`OpenCode skill "${skillName}" is not available.`);
|
|
420
|
-
}
|
|
421
|
-
return skill.content;
|
|
422
|
-
}
|
|
423
|
-
static async expandCommandTemplate(output, template) {
|
|
424
|
-
for (const part of output.parts) {
|
|
425
|
-
if (typeof part !== "object" || part === null || !("type" in part) || part.type !== "text" || !("text" in part) || typeof part.text !== "string" || !part.text.startsWith(skillMarker) || !part.text.endsWith(">")) {
|
|
426
|
-
continue;
|
|
427
|
-
}
|
|
428
|
-
const skillName = part.text.slice(skillMarker.length, -1);
|
|
429
|
-
part.text = await template(skillName);
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
129
|
}
|
|
434
130
|
|
|
435
131
|
// src/server/index.ts
|
|
436
|
-
var
|
|
132
|
+
var server_default = Plugin.define({
|
|
437
133
|
id: "encoredigitalgroup-opencode",
|
|
438
|
-
|
|
439
|
-
const pluginConfig = ConfigLoader.loadConfig(options);
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
134
|
+
async setup(ctx) {
|
|
135
|
+
const pluginConfig = ConfigLoader.loadConfig(ctx.options);
|
|
136
|
+
const command = "feature-planning";
|
|
137
|
+
const route = SkillCommands.getCommands(pluginConfig)[command];
|
|
138
|
+
const registration = await ctx.command.transform((commands) => {
|
|
139
|
+
commands.add({
|
|
140
|
+
name: command,
|
|
141
|
+
description: route.description,
|
|
142
|
+
execute: async (input) => {
|
|
143
|
+
const skills = await ctx.skill.list();
|
|
144
|
+
const skill = skills.data.find((candidate) => candidate.name === route.skill);
|
|
145
|
+
if (skill === undefined) {
|
|
146
|
+
throw new Error(`OpenCode skill "${route.skill}" is not available for /${command}.`);
|
|
147
|
+
}
|
|
148
|
+
await ctx.session.prompt({
|
|
149
|
+
sessionID: input.sessionID,
|
|
150
|
+
...input.prompt,
|
|
151
|
+
skills: [...input.prompt.skills ?? [], { id: skill.id }],
|
|
152
|
+
delivery: input.delivery
|
|
153
|
+
});
|
|
449
154
|
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
await FilePickerCommands.expand(output, template, pluginConfig);
|
|
454
|
-
}
|
|
455
|
-
};
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
return registration.dispose;
|
|
456
158
|
}
|
|
457
|
-
};
|
|
458
|
-
var server_default = plugin;
|
|
159
|
+
});
|
|
459
160
|
export {
|
|
460
161
|
server_default as default
|
|
461
162
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/tui/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
2
|
-
declare const
|
|
3
|
-
export default
|
|
1
|
+
import { Plugin } from "@opencode/plugin/tui";
|
|
2
|
+
declare const _default: Plugin.Definition;
|
|
3
|
+
export default _default;
|
package/dist/tui/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// src/tui/index.tsx
|
|
3
|
+
import { Plugin } from "@opencode/plugin/tui";
|
|
4
|
+
|
|
2
5
|
// src/config/ConfigDefaults.ts
|
|
3
6
|
class ConfigDefaults {
|
|
4
7
|
static getDefaultSkillMappings() {
|
|
@@ -114,7 +117,6 @@ class ConfigLoader {
|
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
119
|
// src/lib/FilePickerCommands.ts
|
|
117
|
-
import { createComponent } from "@opentui/solid";
|
|
118
120
|
import { readdirSync, statSync } from "fs";
|
|
119
121
|
import { join, resolve } from "path";
|
|
120
122
|
import { pathToFileURL } from "url";
|
|
@@ -183,241 +185,148 @@ class FilePickerCommands {
|
|
|
183
185
|
const noSubPlansMessage = `No sub-plan files found in ${planDirectory}.`;
|
|
184
186
|
return [
|
|
185
187
|
{
|
|
186
|
-
argumentPlaceholder: "$ARGUMENTS",
|
|
187
188
|
command: "plan-review",
|
|
188
189
|
description: "Select a plan to review",
|
|
189
190
|
emptyMessage: noPlansMessage,
|
|
190
191
|
matchesSelection: (argument, filePath) => argument === filePath && FilePickerCommands.isPlanPath(argument, planDirectory),
|
|
191
192
|
options: (root, excludesPlanningDirectory) => FilePickerCommands.planOptions(root, planDirectory, excludesPlanningDirectory),
|
|
192
|
-
retainsSelectedFileInPrompt: true,
|
|
193
193
|
skill: skillMappings["plan-review"],
|
|
194
194
|
title: "Select a plan to review"
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
|
-
|
|
197
|
+
command: "plan-resync",
|
|
198
|
+
description: "Select a plan to resync",
|
|
199
|
+
emptyMessage: noPlansMessage,
|
|
200
|
+
matchesSelection: (argument, filePath) => argument === filePath && FilePickerCommands.isPlanPath(argument, planDirectory),
|
|
201
|
+
options: (root, excludesPlanningDirectory) => FilePickerCommands.planOptions(root, planDirectory, excludesPlanningDirectory),
|
|
202
|
+
skill: skillMappings["plan-resync"],
|
|
203
|
+
title: "Select a plan to resync"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
198
206
|
command: "plan-split",
|
|
199
207
|
description: "Select a plan to split",
|
|
200
208
|
emptyMessage: noPlansMessage,
|
|
201
209
|
excludesPlanningDirectory: FilePickerCommands.hasSubPlans,
|
|
202
210
|
matchesSelection: (argument, filePath) => argument === filePath && FilePickerCommands.isPlanPath(argument, planDirectory),
|
|
203
211
|
options: (root, excludesPlanningDirectory) => FilePickerCommands.planOptions(root, planDirectory, excludesPlanningDirectory),
|
|
204
|
-
retainsSelectedFileInPrompt: true,
|
|
205
212
|
skill: skillMappings["plan-split"],
|
|
206
213
|
title: "Select a plan to split"
|
|
207
214
|
},
|
|
208
215
|
{
|
|
209
|
-
argumentPlaceholder: "$PLAN_DIR",
|
|
210
216
|
command: "plan-execute",
|
|
211
217
|
description: "Select sub-plans to execute",
|
|
212
218
|
emptyMessage: noSubPlansMessage,
|
|
213
219
|
matchesSelection: (argument, filePath) => FilePickerCommands.isSubPlanSelection(argument, filePath, planDirectory),
|
|
214
220
|
options: (root) => FilePickerCommands.subPlanOptions(root, planDirectory),
|
|
215
|
-
retainsSelectedFileInPrompt: false,
|
|
216
221
|
skill: skillMappings["plan-execute"],
|
|
217
222
|
title: "Select sub-plans to execute"
|
|
218
223
|
}
|
|
219
224
|
];
|
|
220
225
|
}
|
|
221
|
-
static
|
|
222
|
-
|
|
223
|
-
if (typeof part !== "object" || part === null || !("type" in part) || part.type !== "file" || !("source" in part)) {
|
|
224
|
-
return [];
|
|
225
|
-
}
|
|
226
|
-
const { source } = part;
|
|
227
|
-
return typeof source === "object" && source !== null && "type" in source && source.type === "file" && "path" in source && typeof source.path === "string" ? [source.path] : [];
|
|
228
|
-
});
|
|
229
|
-
for (const command of FilePickerCommands.getCommands(config)) {
|
|
230
|
-
const prefix = `/${command.command} @`;
|
|
231
|
-
if (!text.startsWith(prefix)) {
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
const argument = text.slice(prefix.length).trim();
|
|
235
|
-
const filePath = filePaths.find((path) => command.matchesSelection(argument, path));
|
|
236
|
-
if (argument !== "" && filePath !== undefined) {
|
|
237
|
-
return {
|
|
238
|
-
argument,
|
|
239
|
-
argumentPlaceholder: command.argumentPlaceholder,
|
|
240
|
-
filePath,
|
|
241
|
-
retainsSelectedFileInPrompt: command.retainsSelectedFileInPrompt,
|
|
242
|
-
skill: command.skill
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
}
|
|
226
|
+
static toast(context, message) {
|
|
227
|
+
context.ui.toast.show({ message, variant: "error" });
|
|
246
228
|
}
|
|
247
|
-
static
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
229
|
+
static async submit(context, location, command, option) {
|
|
230
|
+
let skills;
|
|
231
|
+
try {
|
|
232
|
+
skills = context.data.location.skill.list(location);
|
|
233
|
+
if (skills === undefined) {
|
|
234
|
+
await context.data.location.skill.sync(location);
|
|
235
|
+
skills = context.data.location.skill.list(location);
|
|
251
236
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
});
|
|
255
|
-
if (selectedFileIndex !== -1) {
|
|
256
|
-
parts.splice(selectedFileIndex, 1);
|
|
237
|
+
} catch {
|
|
238
|
+
skills = undefined;
|
|
257
239
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
240
|
+
const skill = skills?.find((candidate) => candidate.name === command.skill);
|
|
241
|
+
if (skill === undefined) {
|
|
242
|
+
FilePickerCommands.toast(context, `The configured skill "${command.skill}" is not available.`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
let sessionID;
|
|
246
|
+
const route = context.ui.router.current();
|
|
247
|
+
if (route.type === "session") {
|
|
248
|
+
sessionID = route.sessionID;
|
|
249
|
+
} else {
|
|
250
|
+
let session;
|
|
251
|
+
try {
|
|
252
|
+
session = await context.client.session.create({ location: { directory: location.directory } });
|
|
253
|
+
} catch {
|
|
254
|
+
FilePickerCommands.toast(context, "Unable to create a session for the plan request.");
|
|
255
|
+
return;
|
|
271
256
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
argument,
|
|
277
|
-
argumentPlaceholder: command.argumentPlaceholder,
|
|
278
|
-
filePath,
|
|
279
|
-
retainsSelectedFileInPrompt: command.retainsSelectedFileInPrompt,
|
|
280
|
-
skill: command.skill
|
|
281
|
-
};
|
|
257
|
+
sessionID = session.id;
|
|
258
|
+
if (!context.ui.tabs.focus(sessionID)) {
|
|
259
|
+
context.ui.router.navigate({ type: "session", sessionID });
|
|
282
260
|
}
|
|
283
261
|
}
|
|
262
|
+
const prompt = {
|
|
263
|
+
sessionID,
|
|
264
|
+
text: option.argument,
|
|
265
|
+
...command.command === "plan-execute" ? {} : {
|
|
266
|
+
files: [{ uri: pathToFileURL(resolve(location.directory, option.filePath)).href, name: option.filePath }]
|
|
267
|
+
},
|
|
268
|
+
skills: [{ id: skill.id }]
|
|
269
|
+
};
|
|
270
|
+
try {
|
|
271
|
+
await context.client.session.prompt(prompt);
|
|
272
|
+
} catch {
|
|
273
|
+
FilePickerCommands.toast(context, "Unable to submit the plan request.");
|
|
274
|
+
}
|
|
284
275
|
}
|
|
285
|
-
static async
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
const skill = FilePickerCommands.findSkill(part.text, output.parts, config);
|
|
292
|
-
if (skill === undefined) {
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
part.text = (await template(skill.skill)).replaceAll(skill.argumentPlaceholder, `@${skill.argument}`).trim();
|
|
296
|
-
if (!skill.retainsSelectedFileInPrompt) {
|
|
297
|
-
FilePickerCommands.removeSelectedFile(output.parts, skill.filePath);
|
|
298
|
-
}
|
|
276
|
+
static async run(context, command) {
|
|
277
|
+
const location = context.location ?? context.data.location.default();
|
|
278
|
+
const options = command.options(location.directory, command.excludesPlanningDirectory);
|
|
279
|
+
if (options.length === 0) {
|
|
280
|
+
context.ui.toast.show({ variant: "warning", message: command.emptyMessage });
|
|
299
281
|
return;
|
|
300
282
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (skill === undefined) {
|
|
307
|
-
continue;
|
|
308
|
-
}
|
|
309
|
-
part.text = part.text.replaceAll(skill.argumentPlaceholder, `@${skill.argument}`);
|
|
310
|
-
if (!skill.retainsSelectedFileInPrompt) {
|
|
311
|
-
FilePickerCommands.removeSelectedFile(output.parts, skill.filePath);
|
|
312
|
-
}
|
|
283
|
+
const option = await context.ui.dialog.select({
|
|
284
|
+
title: command.title,
|
|
285
|
+
options: options.map((value) => ({ title: value.label, value }))
|
|
286
|
+
});
|
|
287
|
+
if (option === undefined) {
|
|
313
288
|
return;
|
|
314
289
|
}
|
|
290
|
+
await FilePickerCommands.submit(context, location, command, option);
|
|
315
291
|
}
|
|
316
|
-
static
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
type: "file",
|
|
334
|
-
path: option.filePath,
|
|
335
|
-
text: {
|
|
336
|
-
start,
|
|
337
|
-
end: start + Bun.stringWidth(mention),
|
|
338
|
-
value: mention
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
]
|
|
343
|
-
});
|
|
344
|
-
prompt.focus();
|
|
345
|
-
}
|
|
346
|
-
static register(api, prompt, config) {
|
|
347
|
-
api.keymap.registerLayer({
|
|
348
|
-
commands: FilePickerCommands.getCommands(config).map((command) => ({
|
|
349
|
-
name: `encoredigitalgroup-opencode.${command.command}`,
|
|
350
|
-
title: command.title,
|
|
351
|
-
category: "Plugin",
|
|
352
|
-
namespace: "palette",
|
|
353
|
-
slashName: command.command,
|
|
354
|
-
run() {
|
|
355
|
-
const root = FilePickerCommands.workspaceRoot(api.state.path.worktree, api.state.path.directory);
|
|
356
|
-
const options = command.options(root, command.excludesPlanningDirectory);
|
|
357
|
-
if (options.length === 0) {
|
|
358
|
-
api.ui.toast({
|
|
359
|
-
variant: "warning",
|
|
360
|
-
message: command.emptyMessage
|
|
361
|
-
});
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
const DialogSelect = api.ui.DialogSelect;
|
|
365
|
-
api.ui.dialog.setSize("medium");
|
|
366
|
-
api.ui.dialog.replace(() => createComponent(DialogSelect, {
|
|
367
|
-
title: command.title,
|
|
368
|
-
options: options.map((option) => ({ title: option.label, value: option })),
|
|
369
|
-
onSelect: (option) => {
|
|
370
|
-
api.ui.dialog.clear();
|
|
371
|
-
const promptRef = prompt();
|
|
372
|
-
if (promptRef === undefined) {
|
|
373
|
-
api.ui.toast({
|
|
374
|
-
variant: "error",
|
|
375
|
-
message: "The prompt is not ready."
|
|
376
|
-
});
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
FilePickerCommands.setPrompt(promptRef, root, command, option.value);
|
|
380
|
-
}
|
|
381
|
-
}));
|
|
382
|
-
}
|
|
383
|
-
}))
|
|
384
|
-
});
|
|
292
|
+
static register(context, config) {
|
|
293
|
+
for (const command of FilePickerCommands.getCommands(config)) {
|
|
294
|
+
context.keymap.layer(() => ({
|
|
295
|
+
commands: [{
|
|
296
|
+
id: `encoredigitalgroup-opencode.${command.command}`,
|
|
297
|
+
title: command.title,
|
|
298
|
+
description: command.description,
|
|
299
|
+
group: "Plugin",
|
|
300
|
+
palette: true,
|
|
301
|
+
slash: {
|
|
302
|
+
name: command.command,
|
|
303
|
+
arguments: true
|
|
304
|
+
},
|
|
305
|
+
run: () => FilePickerCommands.run(context, command)
|
|
306
|
+
}]
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
385
309
|
}
|
|
386
310
|
}
|
|
387
311
|
|
|
388
312
|
// src/tui/index.tsx
|
|
389
|
-
import { jsx } from "@opentui/solid/jsx-runtime";
|
|
390
|
-
|
|
313
|
+
import { jsx, Fragment } from "@opentui/solid/jsx-runtime";
|
|
314
|
+
function PickerCommandLayer(props) {
|
|
315
|
+
FilePickerCommands.register(props.context, props.config);
|
|
316
|
+
return /* @__PURE__ */ jsx(Fragment, {});
|
|
317
|
+
}
|
|
318
|
+
var tui_default = Plugin.define({
|
|
391
319
|
id: "encoredigitalgroup-opencode",
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
api.slots.register({
|
|
400
|
-
slots: {
|
|
401
|
-
home_prompt(_context, props) {
|
|
402
|
-
return /* @__PURE__ */ jsx(Prompt, {
|
|
403
|
-
ref: (value) => capturePrompt(value, props.ref)
|
|
404
|
-
});
|
|
405
|
-
},
|
|
406
|
-
session_prompt(_context, props) {
|
|
407
|
-
return /* @__PURE__ */ jsx(Prompt, {
|
|
408
|
-
sessionID: props.session_id,
|
|
409
|
-
visible: props.visible,
|
|
410
|
-
disabled: props.disabled,
|
|
411
|
-
onSubmit: props.on_submit,
|
|
412
|
-
ref: (value) => capturePrompt(value, props.ref)
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
}
|
|
320
|
+
setup(ctx) {
|
|
321
|
+
return ctx.ui.slot({
|
|
322
|
+
append: "app",
|
|
323
|
+
render: () => /* @__PURE__ */ jsx(PickerCommandLayer, {
|
|
324
|
+
context: ctx,
|
|
325
|
+
config: ConfigLoader.loadConfig(ctx.options)
|
|
326
|
+
})
|
|
416
327
|
});
|
|
417
|
-
FilePickerCommands.register(api, () => prompt, ConfigLoader.loadConfig(options));
|
|
418
328
|
}
|
|
419
|
-
};
|
|
420
|
-
var tui_default = plugin;
|
|
329
|
+
});
|
|
421
330
|
export {
|
|
422
331
|
tui_default as default
|
|
423
332
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@encoredigitalgroup/opencode",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "OpenCode file-picker commands for planning skills.",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -16,21 +16,23 @@
|
|
|
16
16
|
"bun": ">=1.4.0"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@opentui/solid": "0.
|
|
19
|
+
"@opentui/solid": "0.5.14",
|
|
20
20
|
"solid-js": "1.9.12"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@opencode
|
|
24
|
-
"@
|
|
25
|
-
"@
|
|
23
|
+
"@opencode/cli": "2.0.22",
|
|
24
|
+
"@opencode/client": "2.0.22",
|
|
25
|
+
"@opencode/plugin": "2.0.22",
|
|
26
|
+
"@opentui/core": "0.5.14",
|
|
26
27
|
"@types/bun": "1.4.0",
|
|
27
28
|
"tsfmt": "^0.3.0-beta9",
|
|
28
29
|
"typescript": "5.8.2"
|
|
29
30
|
},
|
|
30
31
|
"scripts": {
|
|
31
|
-
"build": "rm -rf dist && bun build src/server/index.ts src/tui/index.tsx --outdir dist --target bun --external '@opencode
|
|
32
|
+
"build": "rm -rf dist && bun build src/server/index.ts src/tui/index.tsx --outdir dist --target bun --external '@opencode/plugin' --external '@opencode/plugin/*' --external '@opentui/solid' --external '@opentui/solid/*' && tsc --emitDeclarationOnly",
|
|
32
33
|
"check": "bun run typecheck && bun run build",
|
|
33
34
|
"pack:check": "bun pm pack --dry-run",
|
|
35
|
+
"test": "bun test",
|
|
34
36
|
"typecheck": "tsc --noEmit"
|
|
35
37
|
},
|
|
36
38
|
"types": "./dist/server/index.d.ts",
|
|
@@ -51,15 +53,20 @@
|
|
|
51
53
|
},
|
|
52
54
|
"files": [
|
|
53
55
|
"dist",
|
|
56
|
+
"server.js",
|
|
57
|
+
"tui.js",
|
|
54
58
|
"README.md",
|
|
55
59
|
"LICENSE.md"
|
|
56
60
|
],
|
|
57
61
|
"license": "proprietary",
|
|
58
62
|
"peerDependencies": {
|
|
59
|
-
"@opencode
|
|
63
|
+
"@opencode/plugin": "^2.0.22"
|
|
60
64
|
},
|
|
61
65
|
"oc-plugin": [
|
|
62
66
|
"server",
|
|
63
67
|
"tui"
|
|
68
|
+
],
|
|
69
|
+
"trustedDependencies": [
|
|
70
|
+
"@opencode/cli"
|
|
64
71
|
]
|
|
65
72
|
}
|
package/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {default} from "./dist/server/index.js";
|
package/tui.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {default} from "./dist/tui/index.js";
|