@encoredigitalgroup/opencode 0.1.1 → 0.1.3

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 CHANGED
@@ -2,8 +2,8 @@
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 selected plan
6
- into the prompt. The server entry expands the selected plan into the matching installed skill prompt.
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 expands the selected plan into the matching installed skill prompt.
7
7
 
8
8
  ## Install
9
9
 
@@ -5,11 +5,10 @@ export declare class SkillCommands {
5
5
  description: string;
6
6
  skill: string;
7
7
  };
8
- "plan-resync": {
9
- description: string;
10
- skill: string;
11
- };
12
8
  };
13
- private static isSkill;
9
+ static commandTemplate(skillName: string): string;
14
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>;
15
14
  }
@@ -193,6 +193,17 @@ class FilePickerCommands {
193
193
  skill: skillMappings["plan-review"],
194
194
  title: "Select a plan to review"
195
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
+ },
196
207
  {
197
208
  argumentPlaceholder: "$ARGUMENTS",
198
209
  command: "plan-split",
@@ -386,7 +397,7 @@ class FilePickerCommands {
386
397
  }
387
398
 
388
399
  // src/lib/SkillCommands.ts
389
- import { readFile } from "fs/promises";
400
+ var skillMarker = "<encoredigitalgroup-opencode-skill:";
390
401
 
391
402
  class SkillCommands {
392
403
  static getCommands(config) {
@@ -394,15 +405,11 @@ class SkillCommands {
394
405
  "feature-planning": {
395
406
  description: "Create a feature implementation plan.",
396
407
  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
408
  }
402
409
  };
403
410
  }
404
- static isSkill(value) {
405
- return typeof value === "object" && value !== null && "location" in value && typeof value.location === "string" && "name" in value && typeof value.name === "string";
411
+ static commandTemplate(skillName) {
412
+ return `${skillMarker}${skillName}>`;
406
413
  }
407
414
  static async getTemplate(serverUrl, directory, skillName) {
408
415
  const url = new URL("/api/skill", serverUrl);
@@ -413,12 +420,22 @@ class SkillCommands {
413
420
  throw new Error(`Unable to list OpenCode skills: ${response.status} ${response.statusText}`);
414
421
  }
415
422
  const responseBody = await response.json();
416
- const skills = typeof responseBody === "object" && responseBody !== null && "data" in responseBody && Array.isArray(responseBody.data) ? responseBody.data.filter(SkillCommands.isSkill) : [];
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") : [];
417
424
  const skill = skills.find((candidate) => candidate.name === skillName);
418
425
  if (skill === undefined) {
419
426
  throw new Error(`OpenCode skill "${skillName}" is not available.`);
420
427
  }
421
- return readFile(skill.location, "utf8");
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
+ }
422
439
  }
423
440
  }
424
441
 
@@ -434,11 +451,14 @@ var plugin = {
434
451
  for (const [command, route] of Object.entries(SkillCommands.getCommands(pluginConfig))) {
435
452
  config.command[command] = {
436
453
  description: route.description,
437
- template: await template(route.skill)
454
+ template: SkillCommands.commandTemplate(route.skill)
438
455
  };
439
456
  }
440
457
  },
441
- "chat.message": async (_input, output) => FilePickerCommands.expand(output, template, pluginConfig)
458
+ "chat.message": async (_input, output) => {
459
+ await SkillCommands.expandCommandTemplate(output, template);
460
+ await FilePickerCommands.expand(output, template, pluginConfig);
461
+ }
442
462
  };
443
463
  }
444
464
  };
package/dist/tui/index.js CHANGED
@@ -193,6 +193,17 @@ class FilePickerCommands {
193
193
  skill: skillMappings["plan-review"],
194
194
  title: "Select a plan to review"
195
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
+ },
196
207
  {
197
208
  argumentPlaceholder: "$ARGUMENTS",
198
209
  command: "plan-split",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@encoredigitalgroup/opencode",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "description": "OpenCode file-picker commands for planning skills.",
6
6
  "publishConfig": {
7
7
  "access": "public"