@orderful/droid 0.29.1 → 0.29.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @orderful/droid
2
2
 
3
+ ## 0.29.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#176](https://github.com/Orderful/droid/pull/176) [`8451100`](https://github.com/Orderful/droid/commit/845110083082198213b68f34579beebd845b608f) Thanks [@frytyler](https://github.com/frytyler)! - Add migration to remove stale opencode-skills plugin from opencode.json. This plugin was required before OpenCode got native skills support, but now causes errors because it looks for `skills/` (plural) while native OpenCode looks for `skill/` (singular).
8
+
3
9
  ## 0.29.1
4
10
 
5
11
  ### Patch Changes
package/dist/bin/droid.js CHANGED
@@ -7,9 +7,9 @@ import { program } from "commander";
7
7
  import inquirer from "inquirer";
8
8
  import chalk2 from "chalk";
9
9
  import { execSync as execSync2 } from "child_process";
10
- import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5 } from "fs";
10
+ import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
11
11
  import { join as join8 } from "path";
12
- import { homedir as homedir3 } from "os";
12
+ import { homedir as homedir4 } from "os";
13
13
 
14
14
  // src/lib/config.ts
15
15
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -182,9 +182,9 @@ function setAutoUpdateConfig(updates) {
182
182
  import {
183
183
  existsSync as existsSync5,
184
184
  readdirSync as readdirSync4,
185
- readFileSync as readFileSync5,
185
+ readFileSync as readFileSync6,
186
186
  mkdirSync as mkdirSync4,
187
- writeFileSync as writeFileSync3,
187
+ writeFileSync as writeFileSync4,
188
188
  rmSync as rmSync2
189
189
  } from "fs";
190
190
  import { join as join7, dirname as dirname5, basename } from "path";
@@ -525,9 +525,12 @@ import {
525
525
  mkdirSync as mkdirSync3,
526
526
  renameSync,
527
527
  rmSync,
528
- readdirSync as readdirSync3
528
+ readdirSync as readdirSync3,
529
+ readFileSync as readFileSync5,
530
+ writeFileSync as writeFileSync3
529
531
  } from "fs";
530
532
  import { join as join6, dirname as dirname4 } from "path";
533
+ import { homedir as homedir3 } from "os";
531
534
  var MIGRATIONS_LOG_FILE = ".migrations.log";
532
535
  function getMigrationsLogPath() {
533
536
  return join6(getConfigDir(), MIGRATIONS_LOG_FILE);
@@ -728,13 +731,57 @@ function createClaudeCodeCommandCleanupMigration(version2) {
728
731
  }
729
732
  };
730
733
  }
734
+ function createOpenCodePluginCleanupMigration(version2) {
735
+ return {
736
+ version: version2,
737
+ description: "Remove opencode-skills plugin from opencode.json",
738
+ up: () => {
739
+ const opencodeConfigPath = join6(
740
+ homedir3(),
741
+ ".config",
742
+ "opencode",
743
+ "opencode.json"
744
+ );
745
+ if (!existsSync4(opencodeConfigPath)) {
746
+ return;
747
+ }
748
+ let config;
749
+ try {
750
+ config = JSON.parse(readFileSync5(opencodeConfigPath, "utf-8"));
751
+ } catch {
752
+ return;
753
+ }
754
+ if (!Array.isArray(config.plugin)) {
755
+ return;
756
+ }
757
+ const pluginIndex = config.plugin.indexOf("opencode-skills");
758
+ if (pluginIndex === -1) {
759
+ return;
760
+ }
761
+ config.plugin.splice(pluginIndex, 1);
762
+ if (config.plugin.length === 0) {
763
+ delete config.plugin;
764
+ }
765
+ try {
766
+ writeFileSync3(
767
+ opencodeConfigPath,
768
+ JSON.stringify(config, null, 2) + "\n",
769
+ "utf-8"
770
+ );
771
+ } catch (error) {
772
+ console.warn(`Warning: Could not update opencode.json: ${error}`);
773
+ }
774
+ }
775
+ };
776
+ }
731
777
  var PACKAGE_MIGRATIONS = [
732
778
  createPlatformSyncMigration("0.25.0"),
733
779
  createConfigSkillNameMigration("0.27.2"),
734
780
  createOpenCodeSkillsPathMigration("0.28.0"),
735
781
  createClaudeCodeCommandCleanupMigration("0.28.0"),
736
782
  // Retry: 0.28.0 migration had platform check that prevented running after platform switch
737
- createClaudeCodeCommandCleanupMigration("0.28.1")
783
+ createClaudeCodeCommandCleanupMigration("0.28.1"),
784
+ createOpenCodePluginCleanupMigration("0.29.2")
738
785
  ];
739
786
  var TOOL_MIGRATIONS = {
740
787
  brain: [createConfigDirMigration("droid-brain", "0.2.3")],
@@ -873,7 +920,7 @@ function updatePlatformConfigSkills(platform, installedSkills) {
873
920
  const configPath = getPlatformConfigPath(platform);
874
921
  let content = "";
875
922
  if (existsSync5(configPath)) {
876
- content = readFileSync5(configPath, "utf-8");
923
+ content = readFileSync6(configPath, "utf-8");
877
924
  }
878
925
  const skillLines = installedSkills.map((name) => {
879
926
  const relativePath = `skills/${name}/SKILL.md`;
@@ -895,7 +942,7 @@ ${DROID_SKILLS_END}` : "";
895
942
  if (!existsSync5(configDir)) {
896
943
  mkdirSync4(configDir, { recursive: true });
897
944
  }
898
- writeFileSync3(configPath, content, "utf-8");
945
+ writeFileSync4(configPath, content, "utf-8");
899
946
  }
900
947
  function parseSkillFrontmatter(content) {
901
948
  const trimmed = content.trimStart();
@@ -918,7 +965,7 @@ function loadSkillManifest(skillDir) {
918
965
  if (!existsSync5(skillMdPath)) {
919
966
  return null;
920
967
  }
921
- const content = readFileSync5(skillMdPath, "utf-8");
968
+ const content = readFileSync6(skillMdPath, "utf-8");
922
969
  const frontmatter = parseSkillFrontmatter(content);
923
970
  if (!frontmatter || !frontmatter.name) {
924
971
  return null;
@@ -1161,8 +1208,8 @@ function installSkill(skillName) {
1161
1208
  mkdirSync4(targetSkillDir, { recursive: true });
1162
1209
  }
1163
1210
  const skillMdTarget = join7(targetSkillDir, "SKILL.md");
1164
- const content = readFileSync5(skillMdSource, "utf-8");
1165
- writeFileSync3(skillMdTarget, content);
1211
+ const content = readFileSync6(skillMdSource, "utf-8");
1212
+ writeFileSync4(skillMdTarget, content);
1166
1213
  }
1167
1214
  const referencesSource = join7(skillDir, "references");
1168
1215
  if (existsSync5(referencesSource)) {
@@ -1176,8 +1223,8 @@ function installSkill(skillName) {
1176
1223
  for (const file of referenceFiles) {
1177
1224
  const sourcePath = join7(referencesSource, file);
1178
1225
  const targetPath = join7(targetReferencesDir, file);
1179
- const content = readFileSync5(sourcePath, "utf-8");
1180
- writeFileSync3(targetPath, content);
1226
+ const content = readFileSync6(sourcePath, "utf-8");
1227
+ writeFileSync4(targetPath, content);
1181
1228
  }
1182
1229
  }
1183
1230
  const scriptsSource = join7(skillDir, "scripts");
@@ -1192,8 +1239,8 @@ function installSkill(skillName) {
1192
1239
  for (const file of scriptFiles) {
1193
1240
  const sourcePath = join7(scriptsSource, file);
1194
1241
  const targetPath = join7(targetScriptsDir, file);
1195
- const content = readFileSync5(sourcePath, "utf-8");
1196
- writeFileSync3(targetPath, content);
1242
+ const content = readFileSync6(sourcePath, "utf-8");
1243
+ writeFileSync4(targetPath, content);
1197
1244
  }
1198
1245
  }
1199
1246
  if (existsSync5(commandsSource)) {
@@ -1213,8 +1260,8 @@ function installSkill(skillName) {
1213
1260
  if (shouldInstall) {
1214
1261
  const sourcePath = join7(commandsSource, file);
1215
1262
  const targetPath = join7(commandsPath, file);
1216
- const content = readFileSync5(sourcePath, "utf-8");
1217
- writeFileSync3(targetPath, content);
1263
+ const content = readFileSync6(sourcePath, "utf-8");
1264
+ writeFileSync4(targetPath, content);
1218
1265
  }
1219
1266
  }
1220
1267
  }
@@ -1334,15 +1381,15 @@ function detectGitUsername() {
1334
1381
  function configurePlatformPermissions(platform) {
1335
1382
  const added = [];
1336
1383
  if (platform === "claude-code" /* ClaudeCode */) {
1337
- const settingsPath = join8(homedir3(), ".claude", "settings.json");
1338
- const claudeDir = join8(homedir3(), ".claude");
1384
+ const settingsPath = join8(homedir4(), ".claude", "settings.json");
1385
+ const claudeDir = join8(homedir4(), ".claude");
1339
1386
  if (!existsSync6(claudeDir)) {
1340
1387
  mkdirSync5(claudeDir, { recursive: true });
1341
1388
  }
1342
1389
  let settings = {};
1343
1390
  if (existsSync6(settingsPath)) {
1344
1391
  try {
1345
- settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
1392
+ settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
1346
1393
  } catch {
1347
1394
  console.warn(chalk2.yellow("\u26A0 Claude Code settings.json appears corrupted, resetting permissions"));
1348
1395
  settings = {};
@@ -1362,7 +1409,7 @@ function configurePlatformPermissions(platform) {
1362
1409
  }
1363
1410
  if (added.length > 0) {
1364
1411
  try {
1365
- writeFileSync4(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
1412
+ writeFileSync5(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
1366
1413
  } catch (e) {
1367
1414
  const message = e instanceof Error ? e.message : "Unknown error";
1368
1415
  return { added: [], alreadyPresent: false, error: `Failed to update Claude Code settings: ${message}` };
@@ -1371,7 +1418,7 @@ function configurePlatformPermissions(platform) {
1371
1418
  return { added, alreadyPresent: added.length === 0 };
1372
1419
  }
1373
1420
  if (platform === "opencode" /* OpenCode */) {
1374
- const globalConfigDir = join8(homedir3(), ".config", "opencode");
1421
+ const globalConfigDir = join8(homedir4(), ".config", "opencode");
1375
1422
  if (!existsSync6(globalConfigDir)) {
1376
1423
  mkdirSync5(globalConfigDir, { recursive: true });
1377
1424
  }
@@ -2728,7 +2775,7 @@ function ReadmeViewer({ title, content, onClose }) {
2728
2775
  // src/commands/tui/views/ToolExplorer.tsx
2729
2776
  import { Box as Box10, Text as Text11, useInput as useInput5 } from "ink";
2730
2777
  import { useState as useState5, useMemo as useMemo3 } from "react";
2731
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
2778
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
2732
2779
  import { join as join9 } from "path";
2733
2780
  import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2734
2781
  function ToolExplorer({ tool, onViewSource, onClose }) {
@@ -2773,12 +2820,12 @@ function ToolExplorer({ tool, onViewSource, onClose }) {
2773
2820
  if (key.return && items.length > 0) {
2774
2821
  const item = items[selectedIndex];
2775
2822
  if (existsSync7(item.path)) {
2776
- const content = readFileSync7(item.path, "utf-8");
2823
+ const content = readFileSync8(item.path, "utf-8");
2777
2824
  onViewSource(`${tool.name} / ${item.name}`, content);
2778
2825
  } else {
2779
2826
  const yamlPath = item.path.replace(".md", ".yaml");
2780
2827
  if (existsSync7(yamlPath)) {
2781
- const content = readFileSync7(yamlPath, "utf-8");
2828
+ const content = readFileSync8(yamlPath, "utf-8");
2782
2829
  onViewSource(`${tool.name} / ${item.name}`, content);
2783
2830
  }
2784
2831
  }
package/dist/index.js CHANGED
@@ -190,9 +190,9 @@ function setAutoUpdateConfig(updates) {
190
190
  import {
191
191
  existsSync as existsSync5,
192
192
  readdirSync as readdirSync4,
193
- readFileSync as readFileSync5,
193
+ readFileSync as readFileSync6,
194
194
  mkdirSync as mkdirSync4,
195
- writeFileSync as writeFileSync3,
195
+ writeFileSync as writeFileSync4,
196
196
  rmSync as rmSync2
197
197
  } from "fs";
198
198
  import { join as join7, dirname as dirname5, basename } from "path";
@@ -498,9 +498,12 @@ import {
498
498
  mkdirSync as mkdirSync3,
499
499
  renameSync,
500
500
  rmSync,
501
- readdirSync as readdirSync3
501
+ readdirSync as readdirSync3,
502
+ readFileSync as readFileSync5,
503
+ writeFileSync as writeFileSync3
502
504
  } from "fs";
503
505
  import { join as join6, dirname as dirname4 } from "path";
506
+ import { homedir as homedir3 } from "os";
504
507
  var MIGRATIONS_LOG_FILE = ".migrations.log";
505
508
  function getMigrationsLogPath() {
506
509
  return join6(getConfigDir(), MIGRATIONS_LOG_FILE);
@@ -701,13 +704,57 @@ function createClaudeCodeCommandCleanupMigration(version) {
701
704
  }
702
705
  };
703
706
  }
707
+ function createOpenCodePluginCleanupMigration(version) {
708
+ return {
709
+ version,
710
+ description: "Remove opencode-skills plugin from opencode.json",
711
+ up: () => {
712
+ const opencodeConfigPath = join6(
713
+ homedir3(),
714
+ ".config",
715
+ "opencode",
716
+ "opencode.json"
717
+ );
718
+ if (!existsSync4(opencodeConfigPath)) {
719
+ return;
720
+ }
721
+ let config;
722
+ try {
723
+ config = JSON.parse(readFileSync5(opencodeConfigPath, "utf-8"));
724
+ } catch {
725
+ return;
726
+ }
727
+ if (!Array.isArray(config.plugin)) {
728
+ return;
729
+ }
730
+ const pluginIndex = config.plugin.indexOf("opencode-skills");
731
+ if (pluginIndex === -1) {
732
+ return;
733
+ }
734
+ config.plugin.splice(pluginIndex, 1);
735
+ if (config.plugin.length === 0) {
736
+ delete config.plugin;
737
+ }
738
+ try {
739
+ writeFileSync3(
740
+ opencodeConfigPath,
741
+ JSON.stringify(config, null, 2) + "\n",
742
+ "utf-8"
743
+ );
744
+ } catch (error) {
745
+ console.warn(`Warning: Could not update opencode.json: ${error}`);
746
+ }
747
+ }
748
+ };
749
+ }
704
750
  var PACKAGE_MIGRATIONS = [
705
751
  createPlatformSyncMigration("0.25.0"),
706
752
  createConfigSkillNameMigration("0.27.2"),
707
753
  createOpenCodeSkillsPathMigration("0.28.0"),
708
754
  createClaudeCodeCommandCleanupMigration("0.28.0"),
709
755
  // Retry: 0.28.0 migration had platform check that prevented running after platform switch
710
- createClaudeCodeCommandCleanupMigration("0.28.1")
756
+ createClaudeCodeCommandCleanupMigration("0.28.1"),
757
+ createOpenCodePluginCleanupMigration("0.29.2")
711
758
  ];
712
759
  var TOOL_MIGRATIONS = {
713
760
  brain: [createConfigDirMigration("droid-brain", "0.2.3")],
@@ -805,7 +852,7 @@ function updatePlatformConfigSkills(platform, installedSkills) {
805
852
  const configPath = getPlatformConfigPath(platform);
806
853
  let content = "";
807
854
  if (existsSync5(configPath)) {
808
- content = readFileSync5(configPath, "utf-8");
855
+ content = readFileSync6(configPath, "utf-8");
809
856
  }
810
857
  const skillLines = installedSkills.map((name) => {
811
858
  const relativePath = `skills/${name}/SKILL.md`;
@@ -827,7 +874,7 @@ ${DROID_SKILLS_END}` : "";
827
874
  if (!existsSync5(configDir)) {
828
875
  mkdirSync4(configDir, { recursive: true });
829
876
  }
830
- writeFileSync3(configPath, content, "utf-8");
877
+ writeFileSync4(configPath, content, "utf-8");
831
878
  }
832
879
  function parseSkillFrontmatter(content) {
833
880
  const trimmed = content.trimStart();
@@ -850,7 +897,7 @@ function loadSkillManifest(skillDir) {
850
897
  if (!existsSync5(skillMdPath)) {
851
898
  return null;
852
899
  }
853
- const content = readFileSync5(skillMdPath, "utf-8");
900
+ const content = readFileSync6(skillMdPath, "utf-8");
854
901
  const frontmatter = parseSkillFrontmatter(content);
855
902
  if (!frontmatter || !frontmatter.name) {
856
903
  return null;
@@ -1109,8 +1156,8 @@ function installSkill(skillName) {
1109
1156
  mkdirSync4(targetSkillDir, { recursive: true });
1110
1157
  }
1111
1158
  const skillMdTarget = join7(targetSkillDir, "SKILL.md");
1112
- const content = readFileSync5(skillMdSource, "utf-8");
1113
- writeFileSync3(skillMdTarget, content);
1159
+ const content = readFileSync6(skillMdSource, "utf-8");
1160
+ writeFileSync4(skillMdTarget, content);
1114
1161
  }
1115
1162
  const referencesSource = join7(skillDir, "references");
1116
1163
  if (existsSync5(referencesSource)) {
@@ -1124,8 +1171,8 @@ function installSkill(skillName) {
1124
1171
  for (const file of referenceFiles) {
1125
1172
  const sourcePath = join7(referencesSource, file);
1126
1173
  const targetPath = join7(targetReferencesDir, file);
1127
- const content = readFileSync5(sourcePath, "utf-8");
1128
- writeFileSync3(targetPath, content);
1174
+ const content = readFileSync6(sourcePath, "utf-8");
1175
+ writeFileSync4(targetPath, content);
1129
1176
  }
1130
1177
  }
1131
1178
  const scriptsSource = join7(skillDir, "scripts");
@@ -1140,8 +1187,8 @@ function installSkill(skillName) {
1140
1187
  for (const file of scriptFiles) {
1141
1188
  const sourcePath = join7(scriptsSource, file);
1142
1189
  const targetPath = join7(targetScriptsDir, file);
1143
- const content = readFileSync5(sourcePath, "utf-8");
1144
- writeFileSync3(targetPath, content);
1190
+ const content = readFileSync6(sourcePath, "utf-8");
1191
+ writeFileSync4(targetPath, content);
1145
1192
  }
1146
1193
  }
1147
1194
  if (existsSync5(commandsSource)) {
@@ -1161,8 +1208,8 @@ function installSkill(skillName) {
1161
1208
  if (shouldInstall) {
1162
1209
  const sourcePath = join7(commandsSource, file);
1163
1210
  const targetPath = join7(commandsPath, file);
1164
- const content = readFileSync5(sourcePath, "utf-8");
1165
- writeFileSync3(targetPath, content);
1211
+ const content = readFileSync6(sourcePath, "utf-8");
1212
+ writeFileSync4(targetPath, content);
1166
1213
  }
1167
1214
  }
1168
1215
  }
@@ -1305,8 +1352,8 @@ function installCommand(commandName, skillName) {
1305
1352
  const actualSourcePath = join7(commandsDir, sourceFile);
1306
1353
  const targetPath = join7(commandsPath, sourceFile);
1307
1354
  try {
1308
- const content = readFileSync5(actualSourcePath, "utf-8");
1309
- writeFileSync3(targetPath, content);
1355
+ const content = readFileSync6(actualSourcePath, "utf-8");
1356
+ writeFileSync4(targetPath, content);
1310
1357
  return { success: true, message: `Installed /${commandName}` };
1311
1358
  } catch (error) {
1312
1359
  return { success: false, message: `Failed to install command: ${error}` };
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/lib/migrations.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,KAAK,SAAS,EAIf,MAAM,SAAS,CAAC;AA4WjB;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,CAE/D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc/D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,IAAI,CAmBN;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CA2CtC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,MAAM,GACvB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAStC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG;IAC5D,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAyDA"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/lib/migrations.ts"],"names":[],"mappings":"AAaA,OAAO,EACL,KAAK,SAAS,EAIf,MAAM,SAAS,CAAC;AA2ajB;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,CAE/D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAc/D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,IAAI,CAmBN;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CA2CtC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,MAAM,GACvB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAStC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG;IAC5D,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAyDA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orderful/droid",
3
- "version": "0.29.1",
3
+ "version": "0.29.2",
4
4
  "description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,16 +1,12 @@
1
- import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from 'bun:test';
1
+ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
2
2
  import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'fs';
3
3
  import { join } from 'path';
4
4
  import { tmpdir } from 'os';
5
- import { Platform } from '../lib/types';
6
5
 
7
6
  // We need to mock homedir() before importing the module
8
7
  // Create a test directory that will act as our fake home
9
8
  let testHomeDir: string;
10
9
 
11
- // Mock the os module's homedir function
12
- const originalHomedir = await import('os').then(m => m.homedir);
13
-
14
10
  describe('configurePlatformPermissions', () => {
15
11
  beforeEach(() => {
16
12
  testHomeDir = join(tmpdir(), `droid-setup-test-${Date.now()}`);
@@ -23,66 +19,80 @@ describe('configurePlatformPermissions', () => {
23
19
  }
24
20
  });
25
21
 
26
- describe('OpenCode plugin configuration', () => {
27
- it('should add opencode-skills plugin to empty config', () => {
22
+ describe('OpenCode plugin cleanup migration', () => {
23
+ it('should remove opencode-skills plugin from config', () => {
28
24
  const configDir = join(testHomeDir, '.config', 'opencode');
29
25
  const configPath = join(configDir, 'opencode.json');
30
26
 
31
27
  mkdirSync(configDir, { recursive: true });
32
28
 
33
- const config: { plugin?: string[] } = {};
34
- if (!Array.isArray(config.plugin)) {
35
- config.plugin = [];
36
- }
37
- config.plugin.push('opencode-skills');
29
+ // Config with the stale plugin
30
+ const existingConfig = {
31
+ plugin: ['opencode-skills', 'some-other-plugin'],
32
+ theme: 'dark',
33
+ };
34
+ writeFileSync(configPath, JSON.stringify(existingConfig, null, 2), 'utf-8');
38
35
 
36
+ // Simulate migration logic
37
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
38
+ const pluginIndex = config.plugin.indexOf('opencode-skills');
39
+ if (pluginIndex !== -1) {
40
+ config.plugin.splice(pluginIndex, 1);
41
+ }
39
42
  writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
40
43
 
41
44
  const read = JSON.parse(readFileSync(configPath, 'utf-8'));
42
- expect(read.plugin).toContain('opencode-skills');
45
+ expect(read.plugin).not.toContain('opencode-skills');
46
+ expect(read.plugin).toContain('some-other-plugin');
47
+ expect(read.theme).toBe('dark');
43
48
  });
44
49
 
45
- it('should add opencode-skills plugin to config with existing plugins', () => {
50
+ it('should remove empty plugin array after cleanup', () => {
46
51
  const configDir = join(testHomeDir, '.config', 'opencode');
47
52
  const configPath = join(configDir, 'opencode.json');
48
53
 
49
54
  mkdirSync(configDir, { recursive: true });
50
55
 
56
+ // Config with only the stale plugin
51
57
  const existingConfig = {
52
- plugin: ['some-other-plugin'],
58
+ plugin: ['opencode-skills'],
53
59
  theme: 'dark',
54
60
  };
55
61
  writeFileSync(configPath, JSON.stringify(existingConfig, null, 2), 'utf-8');
56
62
 
57
- // Read and modify (simulating what the function does)
63
+ // Simulate migration logic
58
64
  const config = JSON.parse(readFileSync(configPath, 'utf-8'));
59
- if (!config.plugin.includes('opencode-skills')) {
60
- config.plugin.push('opencode-skills');
65
+ const pluginIndex = config.plugin.indexOf('opencode-skills');
66
+ if (pluginIndex !== -1) {
67
+ config.plugin.splice(pluginIndex, 1);
68
+ }
69
+ if (config.plugin.length === 0) {
70
+ delete config.plugin;
61
71
  }
62
72
  writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
63
73
 
64
74
  const read = JSON.parse(readFileSync(configPath, 'utf-8'));
65
- expect(read.plugin).toContain('opencode-skills');
66
- expect(read.plugin).toContain('some-other-plugin');
75
+ expect(read.plugin).toBeUndefined();
67
76
  expect(read.theme).toBe('dark');
68
77
  });
69
78
 
70
- it('should not duplicate opencode-skills if already present', () => {
79
+ it('should do nothing if plugin not present', () => {
71
80
  const configDir = join(testHomeDir, '.config', 'opencode');
72
81
  const configPath = join(configDir, 'opencode.json');
73
82
 
74
83
  mkdirSync(configDir, { recursive: true });
75
84
 
76
85
  const existingConfig = {
77
- plugin: ['opencode-skills'],
86
+ plugin: ['some-other-plugin'],
87
+ theme: 'dark',
78
88
  };
79
89
  writeFileSync(configPath, JSON.stringify(existingConfig, null, 2), 'utf-8');
80
90
 
91
+ // Simulate migration logic
81
92
  const config = JSON.parse(readFileSync(configPath, 'utf-8'));
82
- const alreadyPresent = config.plugin.includes('opencode-skills');
83
-
84
- expect(alreadyPresent).toBe(true);
85
- expect(config.plugin.length).toBe(1);
93
+ const pluginIndex = config.plugin.indexOf('opencode-skills');
94
+ expect(pluginIndex).toBe(-1);
95
+ expect(config.plugin).toHaveLength(1);
86
96
  });
87
97
 
88
98
  it('should handle config without plugin key', () => {
@@ -97,31 +107,32 @@ describe('configurePlatformPermissions', () => {
97
107
  };
98
108
  writeFileSync(configPath, JSON.stringify(existingConfig, null, 2), 'utf-8');
99
109
 
110
+ // Simulate migration logic - should not throw
100
111
  const config = JSON.parse(readFileSync(configPath, 'utf-8'));
101
- if (!Array.isArray(config.plugin)) {
102
- config.plugin = [];
112
+ if (Array.isArray(config.plugin)) {
113
+ const pluginIndex = config.plugin.indexOf('opencode-skills');
114
+ if (pluginIndex !== -1) {
115
+ config.plugin.splice(pluginIndex, 1);
116
+ }
103
117
  }
104
- config.plugin.push('opencode-skills');
105
- writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
106
118
 
107
- const read = JSON.parse(readFileSync(configPath, 'utf-8'));
108
- expect(read.plugin).toContain('opencode-skills');
109
- expect(read.theme).toBe('dark');
110
- expect(read.model).toBe('claude-sonnet');
119
+ expect(config.theme).toBe('dark');
120
+ expect(config.model).toBe('claude-sonnet');
111
121
  });
112
122
 
113
- it('should create config directory if it does not exist', () => {
114
- const nestedDir = join(testHomeDir, '.config', 'opencode');
115
- expect(existsSync(nestedDir)).toBe(false);
116
-
117
- mkdirSync(nestedDir, { recursive: true });
118
- expect(existsSync(nestedDir)).toBe(true);
123
+ it('should handle missing config file gracefully', () => {
124
+ const configDir = join(testHomeDir, '.config', 'opencode');
125
+ const configPath = join(configDir, 'opencode.json');
119
126
 
120
- const configPath = join(nestedDir, 'opencode.json');
121
- const config = { plugin: ['opencode-skills'] };
122
- writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
127
+ // Don't create the file - simulate missing config
128
+ expect(existsSync(configPath)).toBe(false);
123
129
 
124
- expect(existsSync(configPath)).toBe(true);
130
+ // Migration should not throw
131
+ if (existsSync(configPath)) {
132
+ // Would process file here
133
+ }
134
+ // No error thrown = success
135
+ expect(true).toBe(true);
125
136
  });
126
137
 
127
138
  it('should handle corrupted JSON gracefully', () => {
@@ -131,20 +142,17 @@ describe('configurePlatformPermissions', () => {
131
142
  mkdirSync(configDir, { recursive: true });
132
143
  writeFileSync(configPath, '{ invalid json }}}', 'utf-8');
133
144
 
134
- let config: { plugin?: string[] } = {};
145
+ // Simulate migration logic - should not throw
146
+ let processed = false;
135
147
  try {
136
- config = JSON.parse(readFileSync(configPath, 'utf-8'));
148
+ JSON.parse(readFileSync(configPath, 'utf-8'));
149
+ processed = true;
137
150
  } catch {
138
- // Invalid JSON, start fresh
139
- config = {};
140
- }
141
-
142
- if (!Array.isArray(config.plugin)) {
143
- config.plugin = [];
151
+ // Invalid JSON - migration skips this file
152
+ processed = false;
144
153
  }
145
- config.plugin.push('opencode-skills');
146
154
 
147
- expect(config.plugin).toContain('opencode-skills');
155
+ expect(processed).toBe(false);
148
156
  });
149
157
  });
150
158
 
@@ -5,8 +5,11 @@ import {
5
5
  renameSync,
6
6
  rmSync,
7
7
  readdirSync,
8
+ readFileSync,
9
+ writeFileSync,
8
10
  } from 'fs';
9
11
  import { join, dirname } from 'path';
12
+ import { homedir } from 'os';
10
13
  import { loadConfig, saveConfig, getConfigDir } from './config';
11
14
  import {
12
15
  type Migration,
@@ -344,6 +347,68 @@ function createClaudeCodeCommandCleanupMigration(version: string): Migration {
344
347
  };
345
348
  }
346
349
 
350
+ /**
351
+ * Migration: Remove opencode-skills plugin from opencode.json
352
+ *
353
+ * The opencode-skills plugin was required before OpenCode got native skills support.
354
+ * Now it causes errors because it looks for `skills/` (plural) while native OpenCode
355
+ * looks for `skill/` (singular). This migration removes the stale plugin entry.
356
+ */
357
+ function createOpenCodePluginCleanupMigration(version: string): Migration {
358
+ return {
359
+ version,
360
+ description: 'Remove opencode-skills plugin from opencode.json',
361
+ up: () => {
362
+ const opencodeConfigPath = join(
363
+ homedir(),
364
+ '.config',
365
+ 'opencode',
366
+ 'opencode.json',
367
+ );
368
+
369
+ if (!existsSync(opencodeConfigPath)) {
370
+ return;
371
+ }
372
+
373
+ let config: { plugin?: string[]; [key: string]: unknown };
374
+ try {
375
+ config = JSON.parse(readFileSync(opencodeConfigPath, 'utf-8'));
376
+ } catch {
377
+ // Invalid JSON or read error - skip
378
+ return;
379
+ }
380
+
381
+ if (!Array.isArray(config.plugin)) {
382
+ return;
383
+ }
384
+
385
+ const pluginIndex = config.plugin.indexOf('opencode-skills');
386
+ if (pluginIndex === -1) {
387
+ return;
388
+ }
389
+
390
+ // Remove the plugin
391
+ config.plugin.splice(pluginIndex, 1);
392
+
393
+ // Clean up empty plugin array
394
+ if (config.plugin.length === 0) {
395
+ delete config.plugin;
396
+ }
397
+
398
+ try {
399
+ writeFileSync(
400
+ opencodeConfigPath,
401
+ JSON.stringify(config, null, 2) + '\n',
402
+ 'utf-8',
403
+ );
404
+ } catch (error) {
405
+ // Non-fatal: Log warning but continue
406
+ console.warn(`Warning: Could not update opencode.json: ${error}`);
407
+ }
408
+ },
409
+ };
410
+ }
411
+
347
412
  /**
348
413
  * Registry of package-level migrations
349
414
  * These run when the @orderful/droid npm package updates
@@ -357,6 +422,7 @@ const PACKAGE_MIGRATIONS: Migration[] = [
357
422
  createClaudeCodeCommandCleanupMigration('0.28.0'),
358
423
  // Retry: 0.28.0 migration had platform check that prevented running after platform switch
359
424
  createClaudeCodeCommandCleanupMigration('0.28.1'),
425
+ createOpenCodePluginCleanupMigration('0.29.2'),
360
426
  ];
361
427
 
362
428
  /**