@encoredigitalgroup/opencode 0.1.3 → 0.2.1
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 +19 -12
- 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 -11
- package/dist/package.integration.test.d.ts +1 -0
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +29 -335
- package/dist/server/index.test.d.ts +1 -0
- package/dist/tui/index.d.ts +3 -3
- package/dist/tui/index.js +90 -191
- 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
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
`@encoredigitalgroup/opencode` adds file pickers for OpenCode planning commands.
|
|
4
4
|
|
|
5
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
|
|
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,14 +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
|
-
};
|
|
9
|
-
static commandTemplate(skillName: string): string;
|
|
10
|
-
static getTemplate(serverUrl: URL, directory: string, skillName: string): Promise<string>;
|
|
11
|
-
static expandCommandTemplate(output: {
|
|
12
|
-
parts: unknown[];
|
|
13
|
-
}, template: (skillName: string) => Promise<string>): Promise<void>;
|
|
3
|
+
static getCommands(config: PluginConfig): Record<"feature-planning", {
|
|
4
|
+
description: string;
|
|
5
|
+
skill: string;
|
|
6
|
+
}>;
|
|
14
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,292 +116,7 @@ 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-resync",
|
|
199
|
-
description: "Select a plan to resync",
|
|
200
|
-
emptyMessage: noPlansMessage,
|
|
201
|
-
matchesSelection: (argument, filePath) => argument === filePath && FilePickerCommands.isPlanPath(argument, planDirectory),
|
|
202
|
-
options: (root, excludesPlanningDirectory) => FilePickerCommands.planOptions(root, planDirectory, excludesPlanningDirectory),
|
|
203
|
-
retainsSelectedFileInPrompt: true,
|
|
204
|
-
skill: skillMappings["plan-resync"],
|
|
205
|
-
title: "Select a plan to resync"
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
argumentPlaceholder: "$ARGUMENTS",
|
|
209
|
-
command: "plan-split",
|
|
210
|
-
description: "Select a plan to split",
|
|
211
|
-
emptyMessage: noPlansMessage,
|
|
212
|
-
excludesPlanningDirectory: FilePickerCommands.hasSubPlans,
|
|
213
|
-
matchesSelection: (argument, filePath) => argument === filePath && FilePickerCommands.isPlanPath(argument, planDirectory),
|
|
214
|
-
options: (root, excludesPlanningDirectory) => FilePickerCommands.planOptions(root, planDirectory, excludesPlanningDirectory),
|
|
215
|
-
retainsSelectedFileInPrompt: true,
|
|
216
|
-
skill: skillMappings["plan-split"],
|
|
217
|
-
title: "Select a plan to split"
|
|
218
|
-
},
|
|
219
|
-
{
|
|
220
|
-
argumentPlaceholder: "$PLAN_DIR",
|
|
221
|
-
command: "plan-execute",
|
|
222
|
-
description: "Select sub-plans to execute",
|
|
223
|
-
emptyMessage: noSubPlansMessage,
|
|
224
|
-
matchesSelection: (argument, filePath) => FilePickerCommands.isSubPlanSelection(argument, filePath, planDirectory),
|
|
225
|
-
options: (root) => FilePickerCommands.subPlanOptions(root, planDirectory),
|
|
226
|
-
retainsSelectedFileInPrompt: false,
|
|
227
|
-
skill: skillMappings["plan-execute"],
|
|
228
|
-
title: "Select sub-plans to execute"
|
|
229
|
-
}
|
|
230
|
-
];
|
|
231
|
-
}
|
|
232
|
-
static findSkill(text, parts, config) {
|
|
233
|
-
const filePaths = parts.flatMap((part) => {
|
|
234
|
-
if (typeof part !== "object" || part === null || !("type" in part) || part.type !== "file" || !("source" in part)) {
|
|
235
|
-
return [];
|
|
236
|
-
}
|
|
237
|
-
const { source } = part;
|
|
238
|
-
return typeof source === "object" && source !== null && "type" in source && source.type === "file" && "path" in source && typeof source.path === "string" ? [source.path] : [];
|
|
239
|
-
});
|
|
240
|
-
for (const command of FilePickerCommands.getCommands(config)) {
|
|
241
|
-
const prefix = `/${command.command} @`;
|
|
242
|
-
if (!text.startsWith(prefix)) {
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
const argument = text.slice(prefix.length).trim();
|
|
246
|
-
const filePath = filePaths.find((path) => command.matchesSelection(argument, path));
|
|
247
|
-
if (argument !== "" && filePath !== undefined) {
|
|
248
|
-
return {
|
|
249
|
-
argument,
|
|
250
|
-
argumentPlaceholder: command.argumentPlaceholder,
|
|
251
|
-
filePath,
|
|
252
|
-
retainsSelectedFileInPrompt: command.retainsSelectedFileInPrompt,
|
|
253
|
-
skill: command.skill
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
static removeSelectedFile(parts, filePath) {
|
|
259
|
-
const selectedFileIndex = parts.findIndex((candidate) => {
|
|
260
|
-
if (typeof candidate !== "object" || candidate === null || !("type" in candidate) || candidate.type !== "file" || !("source" in candidate)) {
|
|
261
|
-
return false;
|
|
262
|
-
}
|
|
263
|
-
const { source } = candidate;
|
|
264
|
-
return typeof source === "object" && source !== null && "type" in source && source.type === "file" && "path" in source && source.path === filePath;
|
|
265
|
-
});
|
|
266
|
-
if (selectedFileIndex !== -1) {
|
|
267
|
-
parts.splice(selectedFileIndex, 1);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
static expandedSkill(text, parts, commands) {
|
|
271
|
-
for (const part of parts) {
|
|
272
|
-
if (typeof part !== "object" || part === null || !("type" in part) || part.type !== "file" || !("source" in part)) {
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
const { source } = part;
|
|
276
|
-
if (typeof source !== "object" || source === null || !("type" in source) || source.type !== "file" || !("path" in source) || !("text" in source)) {
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
const filePath = source.path;
|
|
280
|
-
if (typeof filePath !== "string" || typeof source.text !== "object" || source.text === null || !("value" in source.text) || typeof source.text.value !== "string" || !source.text.value.startsWith("@")) {
|
|
281
|
-
continue;
|
|
282
|
-
}
|
|
283
|
-
const argument = source.text.value.slice(1);
|
|
284
|
-
const command = commands.find((candidate) => candidate.matchesSelection(argument, filePath) && text.includes(candidate.argumentPlaceholder));
|
|
285
|
-
if (command !== undefined) {
|
|
286
|
-
return {
|
|
287
|
-
argument,
|
|
288
|
-
argumentPlaceholder: command.argumentPlaceholder,
|
|
289
|
-
filePath,
|
|
290
|
-
retainsSelectedFileInPrompt: command.retainsSelectedFileInPrompt,
|
|
291
|
-
skill: command.skill
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
static async expand(output, template, config) {
|
|
297
|
-
const commands = FilePickerCommands.getCommands(config);
|
|
298
|
-
for (const part of output.parts) {
|
|
299
|
-
if (typeof part !== "object" || part === null || !("type" in part) || part.type !== "text" || !("text" in part) || typeof part.text !== "string" || "synthetic" in part && part.synthetic) {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
const skill = FilePickerCommands.findSkill(part.text, output.parts, config);
|
|
303
|
-
if (skill === undefined) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
part.text = (await template(skill.skill)).replaceAll(skill.argumentPlaceholder, `@${skill.argument}`).trim();
|
|
307
|
-
if (!skill.retainsSelectedFileInPrompt) {
|
|
308
|
-
FilePickerCommands.removeSelectedFile(output.parts, skill.filePath);
|
|
309
|
-
}
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
for (const part of output.parts) {
|
|
313
|
-
if (typeof part !== "object" || part === null || !("type" in part) || part.type !== "text" || !("text" in part) || typeof part.text !== "string" || "synthetic" in part && part.synthetic) {
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
const skill = FilePickerCommands.expandedSkill(part.text, output.parts, commands);
|
|
317
|
-
if (skill === undefined) {
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
part.text = part.text.replaceAll(skill.argumentPlaceholder, `@${skill.argument}`);
|
|
321
|
-
if (!skill.retainsSelectedFileInPrompt) {
|
|
322
|
-
FilePickerCommands.removeSelectedFile(output.parts, skill.filePath);
|
|
323
|
-
}
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
static workspaceRoot(worktree, directory) {
|
|
328
|
-
return worktree === "/" ? directory : worktree;
|
|
329
|
-
}
|
|
330
|
-
static setPrompt(prompt, root, command, option) {
|
|
331
|
-
const mention = `@${option.argument}`;
|
|
332
|
-
const input = `/${command.command} ${mention} `;
|
|
333
|
-
const start = Bun.stringWidth(`/${command.command} `);
|
|
334
|
-
prompt.set({
|
|
335
|
-
input,
|
|
336
|
-
mode: "normal",
|
|
337
|
-
parts: [
|
|
338
|
-
{
|
|
339
|
-
type: "file",
|
|
340
|
-
mime: "text/plain",
|
|
341
|
-
filename: option.filePath,
|
|
342
|
-
url: pathToFileURL(resolve(root, option.filePath)).href,
|
|
343
|
-
source: {
|
|
344
|
-
type: "file",
|
|
345
|
-
path: option.filePath,
|
|
346
|
-
text: {
|
|
347
|
-
start,
|
|
348
|
-
end: start + Bun.stringWidth(mention),
|
|
349
|
-
value: mention
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
]
|
|
354
|
-
});
|
|
355
|
-
prompt.focus();
|
|
356
|
-
}
|
|
357
|
-
static register(api, prompt, config) {
|
|
358
|
-
api.keymap.registerLayer({
|
|
359
|
-
commands: FilePickerCommands.getCommands(config).map((command) => ({
|
|
360
|
-
name: `encoredigitalgroup-opencode.${command.command}`,
|
|
361
|
-
title: command.title,
|
|
362
|
-
category: "Plugin",
|
|
363
|
-
namespace: "palette",
|
|
364
|
-
slashName: command.command,
|
|
365
|
-
run() {
|
|
366
|
-
const root = FilePickerCommands.workspaceRoot(api.state.path.worktree, api.state.path.directory);
|
|
367
|
-
const options = command.options(root, command.excludesPlanningDirectory);
|
|
368
|
-
if (options.length === 0) {
|
|
369
|
-
api.ui.toast({
|
|
370
|
-
variant: "warning",
|
|
371
|
-
message: command.emptyMessage
|
|
372
|
-
});
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
const DialogSelect = api.ui.DialogSelect;
|
|
376
|
-
api.ui.dialog.setSize("medium");
|
|
377
|
-
api.ui.dialog.replace(() => createComponent(DialogSelect, {
|
|
378
|
-
title: command.title,
|
|
379
|
-
options: options.map((option) => ({ title: option.label, value: option })),
|
|
380
|
-
onSelect: (option) => {
|
|
381
|
-
api.ui.dialog.clear();
|
|
382
|
-
const promptRef = prompt();
|
|
383
|
-
if (promptRef === undefined) {
|
|
384
|
-
api.ui.toast({
|
|
385
|
-
variant: "error",
|
|
386
|
-
message: "The prompt is not ready."
|
|
387
|
-
});
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
FilePickerCommands.setPrompt(promptRef, root, command, option.value);
|
|
391
|
-
}
|
|
392
|
-
}));
|
|
393
|
-
}
|
|
394
|
-
}))
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
119
|
// src/lib/SkillCommands.ts
|
|
400
|
-
var skillMarker = "<encoredigitalgroup-opencode-skill:";
|
|
401
|
-
|
|
402
120
|
class SkillCommands {
|
|
403
121
|
static getCommands(config) {
|
|
404
122
|
return {
|
|
@@ -408,61 +126,37 @@ class SkillCommands {
|
|
|
408
126
|
}
|
|
409
127
|
};
|
|
410
128
|
}
|
|
411
|
-
static commandTemplate(skillName) {
|
|
412
|
-
return `${skillMarker}${skillName}>`;
|
|
413
|
-
}
|
|
414
|
-
static async getTemplate(serverUrl, directory, skillName) {
|
|
415
|
-
const url = new URL("/api/skill", serverUrl);
|
|
416
|
-
url.searchParams.set("directory", directory);
|
|
417
|
-
url.searchParams.set("location[directory]", directory);
|
|
418
|
-
const response = await fetch(url);
|
|
419
|
-
if (!response.ok) {
|
|
420
|
-
throw new Error(`Unable to list OpenCode skills: ${response.status} ${response.statusText}`);
|
|
421
|
-
}
|
|
422
|
-
const responseBody = await response.json();
|
|
423
|
-
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") : [];
|
|
424
|
-
const skill = skills.find((candidate) => candidate.name === skillName);
|
|
425
|
-
if (skill === undefined) {
|
|
426
|
-
throw new Error(`OpenCode skill "${skillName}" is not available.`);
|
|
427
|
-
}
|
|
428
|
-
return skill.content;
|
|
429
|
-
}
|
|
430
|
-
static async expandCommandTemplate(output, template) {
|
|
431
|
-
for (const part of output.parts) {
|
|
432
|
-
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(">")) {
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
const skillName = part.text.slice(skillMarker.length, -1);
|
|
436
|
-
part.text = await template(skillName);
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
129
|
}
|
|
441
130
|
|
|
442
131
|
// src/server/index.ts
|
|
443
|
-
var
|
|
444
|
-
id: "encoredigitalgroup
|
|
445
|
-
|
|
446
|
-
const pluginConfig = ConfigLoader.loadConfig(options);
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
132
|
+
var server_default = Plugin.define({
|
|
133
|
+
id: "@encoredigitalgroup/opencode",
|
|
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
|
+
});
|
|
456
154
|
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
await FilePickerCommands.expand(output, template, pluginConfig);
|
|
461
|
-
}
|
|
462
|
-
};
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
return registration.dispose;
|
|
463
158
|
}
|
|
464
|
-
};
|
|
465
|
-
var server_default = plugin;
|
|
159
|
+
});
|
|
466
160
|
export {
|
|
467
161
|
server_default as default
|
|
468
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,252 +185,149 @@ 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
|
-
argumentPlaceholder: "$ARGUMENTS",
|
|
198
197
|
command: "plan-resync",
|
|
199
198
|
description: "Select a plan to resync",
|
|
200
199
|
emptyMessage: noPlansMessage,
|
|
201
200
|
matchesSelection: (argument, filePath) => argument === filePath && FilePickerCommands.isPlanPath(argument, planDirectory),
|
|
202
201
|
options: (root, excludesPlanningDirectory) => FilePickerCommands.planOptions(root, planDirectory, excludesPlanningDirectory),
|
|
203
|
-
retainsSelectedFileInPrompt: true,
|
|
204
202
|
skill: skillMappings["plan-resync"],
|
|
205
203
|
title: "Select a plan to resync"
|
|
206
204
|
},
|
|
207
205
|
{
|
|
208
|
-
argumentPlaceholder: "$ARGUMENTS",
|
|
209
206
|
command: "plan-split",
|
|
210
207
|
description: "Select a plan to split",
|
|
211
208
|
emptyMessage: noPlansMessage,
|
|
212
209
|
excludesPlanningDirectory: FilePickerCommands.hasSubPlans,
|
|
213
210
|
matchesSelection: (argument, filePath) => argument === filePath && FilePickerCommands.isPlanPath(argument, planDirectory),
|
|
214
211
|
options: (root, excludesPlanningDirectory) => FilePickerCommands.planOptions(root, planDirectory, excludesPlanningDirectory),
|
|
215
|
-
retainsSelectedFileInPrompt: true,
|
|
216
212
|
skill: skillMappings["plan-split"],
|
|
217
213
|
title: "Select a plan to split"
|
|
218
214
|
},
|
|
219
215
|
{
|
|
220
|
-
argumentPlaceholder: "$PLAN_DIR",
|
|
221
216
|
command: "plan-execute",
|
|
222
217
|
description: "Select sub-plans to execute",
|
|
223
218
|
emptyMessage: noSubPlansMessage,
|
|
224
219
|
matchesSelection: (argument, filePath) => FilePickerCommands.isSubPlanSelection(argument, filePath, planDirectory),
|
|
225
220
|
options: (root) => FilePickerCommands.subPlanOptions(root, planDirectory),
|
|
226
|
-
retainsSelectedFileInPrompt: false,
|
|
227
221
|
skill: skillMappings["plan-execute"],
|
|
228
222
|
title: "Select sub-plans to execute"
|
|
229
223
|
}
|
|
230
224
|
];
|
|
231
225
|
}
|
|
232
|
-
static
|
|
233
|
-
|
|
234
|
-
if (typeof part !== "object" || part === null || !("type" in part) || part.type !== "file" || !("source" in part)) {
|
|
235
|
-
return [];
|
|
236
|
-
}
|
|
237
|
-
const { source } = part;
|
|
238
|
-
return typeof source === "object" && source !== null && "type" in source && source.type === "file" && "path" in source && typeof source.path === "string" ? [source.path] : [];
|
|
239
|
-
});
|
|
240
|
-
for (const command of FilePickerCommands.getCommands(config)) {
|
|
241
|
-
const prefix = `/${command.command} @`;
|
|
242
|
-
if (!text.startsWith(prefix)) {
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
const argument = text.slice(prefix.length).trim();
|
|
246
|
-
const filePath = filePaths.find((path) => command.matchesSelection(argument, path));
|
|
247
|
-
if (argument !== "" && filePath !== undefined) {
|
|
248
|
-
return {
|
|
249
|
-
argument,
|
|
250
|
-
argumentPlaceholder: command.argumentPlaceholder,
|
|
251
|
-
filePath,
|
|
252
|
-
retainsSelectedFileInPrompt: command.retainsSelectedFileInPrompt,
|
|
253
|
-
skill: command.skill
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
}
|
|
226
|
+
static toast(context, message) {
|
|
227
|
+
context.ui.toast.show({ message, variant: "error" });
|
|
257
228
|
}
|
|
258
|
-
static
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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);
|
|
262
236
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
});
|
|
266
|
-
if (selectedFileIndex !== -1) {
|
|
267
|
-
parts.splice(selectedFileIndex, 1);
|
|
237
|
+
} catch {
|
|
238
|
+
skills = undefined;
|
|
268
239
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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;
|
|
282
256
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return {
|
|
287
|
-
argument,
|
|
288
|
-
argumentPlaceholder: command.argumentPlaceholder,
|
|
289
|
-
filePath,
|
|
290
|
-
retainsSelectedFileInPrompt: command.retainsSelectedFileInPrompt,
|
|
291
|
-
skill: command.skill
|
|
292
|
-
};
|
|
257
|
+
sessionID = session.id;
|
|
258
|
+
if (!context.ui.tabs.focus(sessionID)) {
|
|
259
|
+
context.ui.router.navigate({ type: "session", sessionID });
|
|
293
260
|
}
|
|
294
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
|
+
}
|
|
295
275
|
}
|
|
296
|
-
static async
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
const skill = FilePickerCommands.findSkill(part.text, output.parts, config);
|
|
303
|
-
if (skill === undefined) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
part.text = (await template(skill.skill)).replaceAll(skill.argumentPlaceholder, `@${skill.argument}`).trim();
|
|
307
|
-
if (!skill.retainsSelectedFileInPrompt) {
|
|
308
|
-
FilePickerCommands.removeSelectedFile(output.parts, skill.filePath);
|
|
309
|
-
}
|
|
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 });
|
|
310
281
|
return;
|
|
311
282
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (skill === undefined) {
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
part.text = part.text.replaceAll(skill.argumentPlaceholder, `@${skill.argument}`);
|
|
321
|
-
if (!skill.retainsSelectedFileInPrompt) {
|
|
322
|
-
FilePickerCommands.removeSelectedFile(output.parts, skill.filePath);
|
|
323
|
-
}
|
|
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) {
|
|
324
288
|
return;
|
|
325
289
|
}
|
|
290
|
+
await FilePickerCommands.submit(context, location, command, option);
|
|
326
291
|
}
|
|
327
|
-
static
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
path: option.filePath,
|
|
346
|
-
text: {
|
|
347
|
-
start,
|
|
348
|
-
end: start + Bun.stringWidth(mention),
|
|
349
|
-
value: mention
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
]
|
|
354
|
-
});
|
|
355
|
-
prompt.focus();
|
|
356
|
-
}
|
|
357
|
-
static register(api, prompt, config) {
|
|
358
|
-
api.keymap.registerLayer({
|
|
359
|
-
commands: FilePickerCommands.getCommands(config).map((command) => ({
|
|
360
|
-
name: `encoredigitalgroup-opencode.${command.command}`,
|
|
361
|
-
title: command.title,
|
|
362
|
-
category: "Plugin",
|
|
363
|
-
namespace: "palette",
|
|
364
|
-
slashName: command.command,
|
|
365
|
-
run() {
|
|
366
|
-
const root = FilePickerCommands.workspaceRoot(api.state.path.worktree, api.state.path.directory);
|
|
367
|
-
const options = command.options(root, command.excludesPlanningDirectory);
|
|
368
|
-
if (options.length === 0) {
|
|
369
|
-
api.ui.toast({
|
|
370
|
-
variant: "warning",
|
|
371
|
-
message: command.emptyMessage
|
|
372
|
-
});
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
const DialogSelect = api.ui.DialogSelect;
|
|
376
|
-
api.ui.dialog.setSize("medium");
|
|
377
|
-
api.ui.dialog.replace(() => createComponent(DialogSelect, {
|
|
378
|
-
title: command.title,
|
|
379
|
-
options: options.map((option) => ({ title: option.label, value: option })),
|
|
380
|
-
onSelect: (option) => {
|
|
381
|
-
api.ui.dialog.clear();
|
|
382
|
-
const promptRef = prompt();
|
|
383
|
-
if (promptRef === undefined) {
|
|
384
|
-
api.ui.toast({
|
|
385
|
-
variant: "error",
|
|
386
|
-
message: "The prompt is not ready."
|
|
387
|
-
});
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
FilePickerCommands.setPrompt(promptRef, root, command, option.value);
|
|
391
|
-
}
|
|
392
|
-
}));
|
|
393
|
-
}
|
|
394
|
-
}))
|
|
395
|
-
});
|
|
292
|
+
static register(context, config) {
|
|
293
|
+
for (const command of FilePickerCommands.getCommands(config)) {
|
|
294
|
+
context.keymap.layer(() => ({
|
|
295
|
+
mode: "global",
|
|
296
|
+
commands: [{
|
|
297
|
+
id: `@encoredigitalgroup/opencode.${command.command}`,
|
|
298
|
+
title: command.title,
|
|
299
|
+
description: command.description,
|
|
300
|
+
group: "Plugin",
|
|
301
|
+
palette: true,
|
|
302
|
+
slash: {
|
|
303
|
+
name: command.command,
|
|
304
|
+
arguments: true
|
|
305
|
+
},
|
|
306
|
+
run: () => FilePickerCommands.run(context, command)
|
|
307
|
+
}]
|
|
308
|
+
}));
|
|
309
|
+
}
|
|
396
310
|
}
|
|
397
311
|
}
|
|
398
312
|
|
|
399
313
|
// src/tui/index.tsx
|
|
400
|
-
import { jsx } from "@opentui/solid/jsx-runtime";
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
ref: (value) => capturePrompt(value, props.ref)
|
|
415
|
-
});
|
|
416
|
-
},
|
|
417
|
-
session_prompt(_context, props) {
|
|
418
|
-
return /* @__PURE__ */ jsx(Prompt, {
|
|
419
|
-
sessionID: props.session_id,
|
|
420
|
-
visible: props.visible,
|
|
421
|
-
disabled: props.disabled,
|
|
422
|
-
onSubmit: props.on_submit,
|
|
423
|
-
ref: (value) => capturePrompt(value, props.ref)
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
}
|
|
314
|
+
import { jsx, Fragment } from "@opentui/solid/jsx-runtime";
|
|
315
|
+
function PickerCommandLayer(props) {
|
|
316
|
+
FilePickerCommands.register(props.context, props.config);
|
|
317
|
+
return /* @__PURE__ */ jsx(Fragment, {});
|
|
318
|
+
}
|
|
319
|
+
var tui_default = Plugin.define({
|
|
320
|
+
id: "@encoredigitalgroup/opencode",
|
|
321
|
+
setup(ctx) {
|
|
322
|
+
return ctx.ui.slot({
|
|
323
|
+
append: "app",
|
|
324
|
+
render: () => /* @__PURE__ */ jsx(PickerCommandLayer, {
|
|
325
|
+
context: ctx,
|
|
326
|
+
config: ConfigLoader.loadConfig(ctx.options)
|
|
327
|
+
})
|
|
427
328
|
});
|
|
428
|
-
FilePickerCommands.register(api, () => prompt, ConfigLoader.loadConfig(options));
|
|
429
329
|
}
|
|
430
|
-
};
|
|
431
|
-
var tui_default = plugin;
|
|
330
|
+
});
|
|
432
331
|
export {
|
|
433
332
|
tui_default as default
|
|
434
333
|
};
|
|
@@ -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.1
|
|
4
|
+
"version": "0.2.1",
|
|
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";
|