@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 +6 -0
- package/dist/bin/droid.js +72 -25
- package/dist/index.js +64 -17
- package/dist/lib/migrations.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/commands/setup.test.ts +62 -54
- package/src/lib/migrations.ts +66 -0
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
|
|
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
|
|
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
|
|
185
|
+
readFileSync as readFileSync6,
|
|
186
186
|
mkdirSync as mkdirSync4,
|
|
187
|
-
writeFileSync as
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
1165
|
-
|
|
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 =
|
|
1180
|
-
|
|
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 =
|
|
1196
|
-
|
|
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 =
|
|
1217
|
-
|
|
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(
|
|
1338
|
-
const claudeDir = join8(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
193
|
+
readFileSync as readFileSync6,
|
|
194
194
|
mkdirSync as mkdirSync4,
|
|
195
|
-
writeFileSync as
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
1113
|
-
|
|
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 =
|
|
1128
|
-
|
|
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 =
|
|
1144
|
-
|
|
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 =
|
|
1165
|
-
|
|
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 =
|
|
1309
|
-
|
|
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":"
|
|
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,16 +1,12 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach
|
|
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
|
|
27
|
-
it('should
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
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: ['
|
|
58
|
+
plugin: ['opencode-skills'],
|
|
53
59
|
theme: 'dark',
|
|
54
60
|
};
|
|
55
61
|
writeFileSync(configPath, JSON.stringify(existingConfig, null, 2), 'utf-8');
|
|
56
62
|
|
|
57
|
-
//
|
|
63
|
+
// Simulate migration logic
|
|
58
64
|
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
59
|
-
|
|
60
|
-
|
|
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).
|
|
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
|
|
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: ['
|
|
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
|
|
83
|
-
|
|
84
|
-
expect(
|
|
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 (
|
|
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
|
-
|
|
108
|
-
expect(
|
|
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
|
|
114
|
-
const
|
|
115
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
145
|
+
// Simulate migration logic - should not throw
|
|
146
|
+
let processed = false;
|
|
135
147
|
try {
|
|
136
|
-
|
|
148
|
+
JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
149
|
+
processed = true;
|
|
137
150
|
} catch {
|
|
138
|
-
// Invalid JSON
|
|
139
|
-
|
|
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(
|
|
155
|
+
expect(processed).toBe(false);
|
|
148
156
|
});
|
|
149
157
|
});
|
|
150
158
|
|
package/src/lib/migrations.ts
CHANGED
|
@@ -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
|
/**
|