@orderful/droid 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.eslintrc.json +6 -4
  2. package/AGENTS.md +58 -0
  3. package/CHANGELOG.md +44 -0
  4. package/README.md +11 -6
  5. package/dist/bin/droid.js +384 -170
  6. package/dist/commands/config.d.ts +15 -1
  7. package/dist/commands/config.d.ts.map +1 -1
  8. package/dist/commands/exec.d.ts +10 -0
  9. package/dist/commands/exec.d.ts.map +1 -0
  10. package/dist/commands/tui.d.ts.map +1 -1
  11. package/dist/index.js +171 -33
  12. package/dist/lib/migrations.d.ts.map +1 -1
  13. package/dist/lib/skills.d.ts.map +1 -1
  14. package/dist/tools/codex/TOOL.yaml +2 -2
  15. package/dist/tools/codex/agents/codex-document-processor.md +8 -4
  16. package/dist/tools/codex/commands/codex.md +18 -10
  17. package/dist/tools/codex/skills/droid-codex/SKILL.md +140 -67
  18. package/dist/tools/codex/skills/droid-codex/references/creating.md +13 -51
  19. package/dist/tools/codex/skills/droid-codex/references/decisions.md +15 -19
  20. package/dist/tools/codex/skills/droid-codex/references/loading.md +49 -13
  21. package/dist/tools/codex/skills/droid-codex/references/topics.md +14 -12
  22. package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts +31 -0
  23. package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts.map +1 -0
  24. package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
  25. package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts +20 -0
  26. package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts.map +1 -0
  27. package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
  28. package/dist/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
  29. package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts +23 -0
  30. package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts.map +1 -0
  31. package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.ts +172 -0
  32. package/package.json +1 -1
  33. package/src/bin/droid.ts +9 -0
  34. package/src/commands/config.ts +38 -4
  35. package/src/commands/exec.ts +96 -0
  36. package/src/commands/install.ts +1 -1
  37. package/src/commands/setup.ts +6 -6
  38. package/src/commands/tui.tsx +254 -175
  39. package/src/lib/migrations.ts +103 -10
  40. package/src/lib/quotes.ts +6 -6
  41. package/src/lib/skills.ts +168 -45
  42. package/src/tools/codex/TOOL.yaml +2 -2
  43. package/src/tools/codex/agents/codex-document-processor.md +8 -4
  44. package/src/tools/codex/commands/codex.md +18 -10
  45. package/src/tools/codex/skills/droid-codex/SKILL.md +140 -67
  46. package/src/tools/codex/skills/droid-codex/references/creating.md +13 -51
  47. package/src/tools/codex/skills/droid-codex/references/decisions.md +15 -19
  48. package/src/tools/codex/skills/droid-codex/references/loading.md +49 -13
  49. package/src/tools/codex/skills/droid-codex/references/topics.md +14 -12
  50. package/src/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
  51. package/src/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
  52. package/src/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
  53. package/src/tools/codex/skills/droid-codex/scripts/git-start-write.ts +172 -0
package/dist/bin/droid.js CHANGED
@@ -18,6 +18,9 @@ import { join } from "path";
18
18
  import YAML from "yaml";
19
19
 
20
20
  // src/lib/types.ts
21
+ function getAITag() {
22
+ return "@droid";
23
+ }
21
24
  function getPlatformTools(config) {
22
25
  return config.platforms[config.platform]?.tools ?? {};
23
26
  }
@@ -176,7 +179,14 @@ function setAutoUpdateConfig(updates) {
176
179
  }
177
180
 
178
181
  // src/lib/skills.ts
179
- import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "fs";
182
+ import {
183
+ existsSync as existsSync5,
184
+ readdirSync as readdirSync4,
185
+ readFileSync as readFileSync5,
186
+ mkdirSync as mkdirSync4,
187
+ writeFileSync as writeFileSync3,
188
+ rmSync as rmSync2
189
+ } from "fs";
180
190
  import { join as join7, dirname as dirname5, basename } from "path";
181
191
  import { fileURLToPath as fileURLToPath4 } from "url";
182
192
  import YAML4 from "yaml";
@@ -517,7 +527,14 @@ function uninstallAgent(agentName) {
517
527
  }
518
528
 
519
529
  // src/lib/migrations.ts
520
- import { existsSync as existsSync4, appendFileSync, mkdirSync as mkdirSync3, renameSync, rmSync } from "fs";
530
+ import {
531
+ existsSync as existsSync4,
532
+ appendFileSync,
533
+ mkdirSync as mkdirSync3,
534
+ renameSync,
535
+ rmSync,
536
+ readdirSync as readdirSync3
537
+ } from "fs";
521
538
  import { join as join6, dirname as dirname4 } from "path";
522
539
  var MIGRATIONS_LOG_FILE = ".migrations.log";
523
540
  function getMigrationsLogPath() {
@@ -555,11 +572,54 @@ function createConfigDirMigration(skillName, version2) {
555
572
  }
556
573
  };
557
574
  }
575
+ function createPlatformSyncMigration(version2) {
576
+ return {
577
+ version: version2,
578
+ description: "Sync installed tools from disk to config",
579
+ up: () => {
580
+ const config = loadConfig();
581
+ const bundledTools = getBundledTools();
582
+ let configChanged = false;
583
+ const originalPlatform = config.platform;
584
+ for (const platformKey of ["claude-code" /* ClaudeCode */, "opencode" /* OpenCode */]) {
585
+ const skillsPath = getSkillsPath(platformKey);
586
+ if (!existsSync4(skillsPath)) continue;
587
+ config.platform = platformKey;
588
+ const trackedTools = getPlatformTools(config);
589
+ const installedDirs = readdirSync3(skillsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
590
+ for (const skillName of installedDirs) {
591
+ const normalizedName = skillName.replace(/^droid-/, "");
592
+ const isTracked = trackedTools[skillName] || trackedTools[`droid-${normalizedName}`] || trackedTools[normalizedName];
593
+ if (isTracked) continue;
594
+ const matchingTool = bundledTools.find(
595
+ (tool) => tool.includes.skills.some((s) => s.name === skillName)
596
+ );
597
+ if (matchingTool) {
598
+ trackedTools[skillName] = {
599
+ version: matchingTool.version,
600
+ installed_at: (/* @__PURE__ */ new Date()).toISOString()
601
+ };
602
+ configChanged = true;
603
+ }
604
+ }
605
+ if (configChanged) {
606
+ setPlatformTools(config, trackedTools);
607
+ }
608
+ }
609
+ config.platform = originalPlatform;
610
+ if (configChanged) {
611
+ saveConfig(config);
612
+ }
613
+ }
614
+ };
615
+ }
558
616
  var TOOL_MIGRATIONS = {
559
617
  brain: [createConfigDirMigration("droid-brain", "0.2.3")],
560
618
  comments: [createConfigDirMigration("droid-comments", "0.2.6")],
561
619
  project: [createConfigDirMigration("droid-project", "0.1.5")],
562
- coach: [createConfigDirMigration("droid-coach", "0.1.3")]
620
+ coach: [createConfigDirMigration("droid-coach", "0.1.3")],
621
+ // Global migration for the droid meta-tool
622
+ droid: [createPlatformSyncMigration("0.25.0")]
563
623
  };
564
624
  function getToolMigrations(toolName) {
565
625
  return TOOL_MIGRATIONS[toolName] ?? [];
@@ -594,8 +654,17 @@ function runMigrations(toolName, fromVersion, toVersion) {
594
654
  logMigration(toolName, fromVersion, migration.version, "OK");
595
655
  } catch (error) {
596
656
  const errorMessage = error instanceof Error ? error.message : String(error);
597
- logMigration(toolName, fromVersion, migration.version, "FAILED", errorMessage);
598
- return { success: false, error: `Migration ${migration.version} failed: ${errorMessage}` };
657
+ logMigration(
658
+ toolName,
659
+ fromVersion,
660
+ migration.version,
661
+ "FAILED",
662
+ errorMessage
663
+ );
664
+ return {
665
+ success: false,
666
+ error: `Migration ${migration.version} failed: ${errorMessage}`
667
+ };
599
668
  }
600
669
  }
601
670
  setLastMigratedVersion(toolName, toVersion);
@@ -692,7 +761,7 @@ function findSkillPath(skillName) {
692
761
  if (!existsSync5(BUNDLED_SKILLS_DIR)) {
693
762
  return null;
694
763
  }
695
- const toolDirs = readdirSync3(BUNDLED_SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
764
+ const toolDirs = readdirSync4(BUNDLED_SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
696
765
  for (const toolName of toolDirs) {
697
766
  const skillsDir = join7(BUNDLED_SKILLS_DIR, toolName, "skills");
698
767
  if (!existsSync5(skillsDir)) continue;
@@ -710,12 +779,12 @@ function getBundledSkills() {
710
779
  if (!existsSync5(BUNDLED_SKILLS_DIR)) {
711
780
  return [];
712
781
  }
713
- const toolDirs = readdirSync3(BUNDLED_SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
782
+ const toolDirs = readdirSync4(BUNDLED_SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
714
783
  const skills = [];
715
784
  for (const toolName of toolDirs) {
716
785
  const skillsDir = join7(BUNDLED_SKILLS_DIR, toolName, "skills");
717
786
  if (!existsSync5(skillsDir)) continue;
718
- const skillSubdirs = readdirSync3(skillsDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
787
+ const skillSubdirs = readdirSync4(skillsDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
719
788
  for (const skillName of skillSubdirs) {
720
789
  const manifest = loadSkillManifest(join7(skillsDir, skillName));
721
790
  if (manifest) {
@@ -758,10 +827,16 @@ function updateSkill(skillName) {
758
827
  return { success: false, message: `Skill '${skillName}' is not installed` };
759
828
  }
760
829
  if (!status.bundledVersion) {
761
- return { success: false, message: `Skill '${skillName}' not found in bundled skills` };
830
+ return {
831
+ success: false,
832
+ message: `Skill '${skillName}' not found in bundled skills`
833
+ };
762
834
  }
763
835
  if (!status.hasUpdate) {
764
- return { success: false, message: `Skill '${skillName}' is already at latest version (${status.installedVersion})` };
836
+ return {
837
+ success: false,
838
+ message: `Skill '${skillName}' is already at latest version (${status.installedVersion})`
839
+ };
765
840
  }
766
841
  const result = installSkill(skillName);
767
842
  if (result.success) {
@@ -781,7 +856,10 @@ function installSkill(skillName) {
781
856
  const { toolDir, skillDir } = skillPath;
782
857
  const manifest = loadSkillManifest(skillDir);
783
858
  if (!manifest) {
784
- return { success: false, message: `Invalid skill manifest for '${skillName}'` };
859
+ return {
860
+ success: false,
861
+ message: `Invalid skill manifest for '${skillName}'`
862
+ };
785
863
  }
786
864
  if (manifest.dependencies) {
787
865
  for (const dep of manifest.dependencies) {
@@ -798,30 +876,41 @@ function installSkill(skillName) {
798
876
  const commandsPath = getCommandsInstallPath(config.platform);
799
877
  const tools = getPlatformTools(config);
800
878
  if (skillName.startsWith("droid-")) {
801
- const oldSkillName = skillName.replace(/^droid-/, "");
802
- const oldSkillDir = join7(skillsPath, oldSkillName);
879
+ const oldSkillName2 = skillName.replace(/^droid-/, "");
880
+ const oldSkillDir = join7(skillsPath, oldSkillName2);
803
881
  if (existsSync5(oldSkillDir)) {
804
882
  try {
805
883
  rmSync2(oldSkillDir, { recursive: true });
806
884
  } catch (error) {
807
- console.warn(`Warning: Could not remove old skill directory ${oldSkillDir}: ${error}`);
885
+ console.warn(
886
+ `Warning: Could not remove old skill directory ${oldSkillDir}: ${error}`
887
+ );
808
888
  }
809
889
  }
810
- if (tools[oldSkillName]) {
811
- delete tools[oldSkillName];
890
+ if (tools[oldSkillName2]) {
891
+ delete tools[oldSkillName2];
812
892
  setPlatformTools(config, tools);
813
893
  saveConfig(config);
814
894
  }
815
895
  }
816
896
  const commandsSource = join7(toolDir, "commands");
817
897
  const agentsSource = join7(toolDir, "agents");
818
- if (!tools[skillName]) {
898
+ const oldSkillName = skillName.startsWith("droid-") ? skillName.replace(/^droid-/, "") : null;
899
+ const isAlreadyInstalled = tools[skillName] || oldSkillName && tools[oldSkillName];
900
+ if (!isAlreadyInstalled) {
901
+ const toolName2 = basename(toolDir);
902
+ const normalizedToolName = toolName2.replace(/^droid-/, "");
819
903
  if (existsSync5(commandsSource)) {
820
- const commandFiles = readdirSync3(commandsSource).filter((f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md");
904
+ const commandFiles = readdirSync4(commandsSource).filter(
905
+ (f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md"
906
+ );
821
907
  for (const file of commandFiles) {
822
908
  const targetCommandPath = join7(commandsPath, file);
823
909
  if (existsSync5(targetCommandPath)) {
824
910
  const commandName = file.replace(".md", "");
911
+ if (commandName === toolName2 || commandName === normalizedToolName) {
912
+ continue;
913
+ }
825
914
  return {
826
915
  success: false,
827
916
  message: `Cannot install: command /${commandName} already exists at ${targetCommandPath}`
@@ -830,8 +919,8 @@ function installSkill(skillName) {
830
919
  }
831
920
  }
832
921
  if (existsSync5(agentsSource)) {
833
- const agentDirs = readdirSync3(agentsSource, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
834
- for (const agentName of agentDirs) {
922
+ const agentFiles = readdirSync4(agentsSource, { withFileTypes: true }).filter((dirent) => dirent.isFile() && dirent.name.endsWith(".md")).map((dirent) => dirent.name.replace(".md", ""));
923
+ for (const agentName of agentFiles) {
835
924
  if (isAgentInstalled(agentName)) {
836
925
  return {
837
926
  success: false,
@@ -859,7 +948,9 @@ function installSkill(skillName) {
859
948
  if (!existsSync5(targetReferencesDir)) {
860
949
  mkdirSync4(targetReferencesDir, { recursive: true });
861
950
  }
862
- const referenceFiles = readdirSync3(referencesSource).filter((f) => f.endsWith(".md"));
951
+ const referenceFiles = readdirSync4(referencesSource).filter(
952
+ (f) => f.endsWith(".md")
953
+ );
863
954
  for (const file of referenceFiles) {
864
955
  const sourcePath = join7(referencesSource, file);
865
956
  const targetPath = join7(targetReferencesDir, file);
@@ -867,11 +958,29 @@ function installSkill(skillName) {
867
958
  writeFileSync3(targetPath, content);
868
959
  }
869
960
  }
961
+ const scriptsSource = join7(skillDir, "scripts");
962
+ if (existsSync5(scriptsSource)) {
963
+ const targetScriptsDir = join7(targetSkillDir, "scripts");
964
+ if (!existsSync5(targetScriptsDir)) {
965
+ mkdirSync4(targetScriptsDir, { recursive: true });
966
+ }
967
+ const scriptFiles = readdirSync4(scriptsSource).filter(
968
+ (f) => f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".py")
969
+ );
970
+ for (const file of scriptFiles) {
971
+ const sourcePath = join7(scriptsSource, file);
972
+ const targetPath = join7(targetScriptsDir, file);
973
+ const content = readFileSync5(sourcePath, "utf-8");
974
+ writeFileSync3(targetPath, content);
975
+ }
976
+ }
870
977
  if (existsSync5(commandsSource)) {
871
978
  if (!existsSync5(commandsPath)) {
872
979
  mkdirSync4(commandsPath, { recursive: true });
873
980
  }
874
- const commandFiles = readdirSync3(commandsSource).filter((f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md");
981
+ const commandFiles = readdirSync4(commandsSource).filter(
982
+ (f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md"
983
+ );
875
984
  for (const file of commandFiles) {
876
985
  const sourcePath = join7(commandsSource, file);
877
986
  const targetPath = join7(commandsPath, file);
@@ -881,10 +990,10 @@ function installSkill(skillName) {
881
990
  }
882
991
  const installedAgents = [];
883
992
  if (existsSync5(agentsSource)) {
884
- const agentDirs = readdirSync3(agentsSource, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
885
- for (const agentName of agentDirs) {
886
- const agentDir = join7(agentsSource, agentName);
887
- const result = installAgentFromPath(agentDir, agentName);
993
+ const agentFiles = readdirSync4(agentsSource, { withFileTypes: true }).filter((dirent) => dirent.isFile() && dirent.name.endsWith(".md")).map((dirent) => dirent.name.replace(".md", ""));
994
+ for (const agentName of agentFiles) {
995
+ const agentPath = join7(agentsSource, `${agentName}.md`);
996
+ const result = installAgentFromPath(agentPath, agentName);
888
997
  if (result.success) {
889
998
  installedAgents.push(agentName);
890
999
  }
@@ -905,9 +1014,14 @@ function installSkill(skillName) {
905
1014
  const toolName = basename(toolDir);
906
1015
  const migrationResult = runToolMigrations(toolName, manifest.version);
907
1016
  if (!migrationResult.success) {
908
- console.warn(`Warning: Migration failed for ${toolName}: ${migrationResult.error}`);
1017
+ console.warn(
1018
+ `Warning: Migration failed for ${toolName}: ${migrationResult.error}`
1019
+ );
909
1020
  }
910
- return { success: true, message: `Installed ${skillName} v${manifest.version}` };
1021
+ return {
1022
+ success: true,
1023
+ message: `Installed ${skillName} v${manifest.version}`
1024
+ };
911
1025
  }
912
1026
  function uninstallSkill(skillName) {
913
1027
  const config = loadConfig();
@@ -924,7 +1038,9 @@ function uninstallSkill(skillName) {
924
1038
  const commandsPath = getCommandsInstallPath(config.platform);
925
1039
  const commandsSource = skillPath ? join7(skillPath.toolDir, "commands") : null;
926
1040
  if (commandsSource && existsSync5(commandsSource)) {
927
- const commandFiles = readdirSync3(commandsSource).filter((f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md");
1041
+ const commandFiles = readdirSync4(commandsSource).filter(
1042
+ (f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md"
1043
+ );
928
1044
  for (const file of commandFiles) {
929
1045
  const commandPath = join7(commandsPath, file);
930
1046
  if (existsSync5(commandPath)) {
@@ -1140,17 +1256,17 @@ async function setupCommand() {
1140
1256
  console.log(chalk2.yellow(" You may need to manually configure your platform"));
1141
1257
  } else if (answers.platform === "claude-code" /* ClaudeCode */) {
1142
1258
  if (added.length > 0) {
1143
- console.log(chalk2.green(`\u2713 Added droid permissions to Claude Code settings`));
1259
+ console.log(chalk2.green("\u2713 Added droid permissions to Claude Code settings"));
1144
1260
  } else if (alreadyPresent) {
1145
- console.log(chalk2.gray(` Droid permissions already configured in Claude Code`));
1261
+ console.log(chalk2.gray(" Droid permissions already configured in Claude Code"));
1146
1262
  }
1147
1263
  } else if (answers.platform === "opencode" /* OpenCode */) {
1148
1264
  if (added.length > 0) {
1149
- console.log(chalk2.green(`\u2713 Added opencode-skills plugin to OpenCode config`));
1150
- console.log(chalk2.gray(` This enables Claude Code-style skills in OpenCode`));
1151
- console.log(chalk2.gray(` Restart OpenCode to activate the plugin`));
1265
+ console.log(chalk2.green("\u2713 Added opencode-skills plugin to OpenCode config"));
1266
+ console.log(chalk2.gray(" This enables Claude Code-style skills in OpenCode"));
1267
+ console.log(chalk2.gray(" Restart OpenCode to activate the plugin"));
1152
1268
  } else if (alreadyPresent) {
1153
- console.log(chalk2.gray(` opencode-skills plugin already configured in OpenCode`));
1269
+ console.log(chalk2.gray(" opencode-skills plugin already configured in OpenCode"));
1154
1270
  }
1155
1271
  }
1156
1272
  console.log(chalk2.gray("\nRun `droid skills` to browse and install skills."));
@@ -1159,8 +1275,23 @@ async function setupCommand() {
1159
1275
  // src/commands/config.ts
1160
1276
  import chalk3 from "chalk";
1161
1277
  import { execSync as execSync3 } from "child_process";
1162
- async function configCommand(options) {
1163
- if (options.edit) {
1278
+ function getToolConfig(toolName) {
1279
+ const globalConfig = loadConfig();
1280
+ const overrides = loadSkillOverrides(toolName);
1281
+ return {
1282
+ user_mention: globalConfig.user_mention,
1283
+ ai_mention: getAITag(),
1284
+ ...overrides
1285
+ // Tool handles its own defaults
1286
+ };
1287
+ }
1288
+ async function configCommand(tool, options) {
1289
+ if (tool) {
1290
+ const toolConfig = getToolConfig(tool);
1291
+ console.log(JSON.stringify(toolConfig, null, 2));
1292
+ return;
1293
+ }
1294
+ if (options?.edit) {
1164
1295
  const configPath = getConfigPath();
1165
1296
  const editor = process.env.EDITOR || "vim";
1166
1297
  if (!configExists()) {
@@ -1175,7 +1306,7 @@ async function configCommand(options) {
1175
1306
  }
1176
1307
  return;
1177
1308
  }
1178
- if (options.get) {
1309
+ if (options?.get) {
1179
1310
  const value = getConfigValue(options.get);
1180
1311
  if (value === void 0) {
1181
1312
  console.log(chalk3.yellow(`Config key '${options.get}' not found`));
@@ -1188,7 +1319,7 @@ async function configCommand(options) {
1188
1319
  }
1189
1320
  return;
1190
1321
  }
1191
- if (options.set) {
1322
+ if (options?.set) {
1192
1323
  const match = options.set.match(/^([^=]+)=(.*)$/);
1193
1324
  if (!match) {
1194
1325
  console.error(chalk3.red("Invalid format. Use: --set key=value"));
@@ -1464,7 +1595,7 @@ async function installCommand(toolName) {
1464
1595
  }
1465
1596
  console.log(chalk6.gray("\nNext steps:"));
1466
1597
  console.log(chalk6.gray(" - Run your AI tool to start using it"));
1467
- console.log(chalk6.gray(` - Reconfigure anytime with \`droid\` \u2192 Configure`));
1598
+ console.log(chalk6.gray(" - Reconfigure anytime with `droid` \u2192 Configure"));
1468
1599
  } else {
1469
1600
  console.error(chalk6.red(`\u2717 ${result.message}`));
1470
1601
  process.exit(1);
@@ -2842,6 +2973,10 @@ function App() {
2842
2973
  }
2843
2974
  }
2844
2975
  });
2976
+ useEffect(() => {
2977
+ const droidVersion = getVersion();
2978
+ runToolMigrations("droid", droidVersion);
2979
+ }, []);
2845
2980
  useEffect(() => {
2846
2981
  const autoUpdateConfig = getAutoUpdateConfig();
2847
2982
  if (autoUpdateConfig.app && updateInfo.hasUpdate && view === "welcome" && !isUpdating) {
@@ -2890,142 +3025,149 @@ function App() {
2890
3025
  };
2891
3026
  const tools = getBundledTools();
2892
3027
  const skills = getBundledSkills();
2893
- useInput7((input, key) => {
2894
- if (message) setMessage(null);
2895
- if (input === "q") {
2896
- exit();
2897
- return;
2898
- }
2899
- if (view === "menu") {
2900
- if (key.leftArrow) {
2901
- const newIndex = Math.max(0, tabIndex - 1);
2902
- setTabIndex(newIndex);
2903
- setActiveTab(tabs[newIndex].id);
2904
- setSelectedIndex(0);
2905
- setScrollOffset(0);
2906
- }
2907
- if (key.rightArrow) {
2908
- const newIndex = Math.min(tabs.length - 1, tabIndex + 1);
2909
- setTabIndex(newIndex);
2910
- setActiveTab(tabs[newIndex].id);
2911
- setSelectedIndex(0);
2912
- setScrollOffset(0);
2913
- }
2914
- if (key.upArrow) {
2915
- setSelectedIndex((prev) => {
2916
- const newIndex = Math.max(0, prev - 1);
2917
- if (newIndex < scrollOffset) {
2918
- setScrollOffset(newIndex);
2919
- }
2920
- return newIndex;
2921
- });
2922
- setSelectedAction(0);
2923
- }
2924
- if (key.downArrow) {
2925
- const maxIndex = activeTab === "tools" ? tools.length - 1 : 0;
2926
- setSelectedIndex((prev) => {
2927
- const newIndex = Math.min(maxIndex, prev + 1);
2928
- if (newIndex >= scrollOffset + MAX_VISIBLE_ITEMS) {
2929
- setScrollOffset(newIndex - MAX_VISIBLE_ITEMS + 1);
2930
- }
2931
- return newIndex;
2932
- });
2933
- setSelectedAction(0);
3028
+ useInput7(
3029
+ (input, key) => {
3030
+ if (message) setMessage(null);
3031
+ if (input === "q") {
3032
+ exit();
3033
+ return;
2934
3034
  }
2935
- if (key.return) {
2936
- if (activeTab === "tools" && tools.length > 0) {
2937
- setView("detail");
2938
- } else if (activeTab === "settings") {
2939
- setView("detail");
3035
+ if (view === "menu") {
3036
+ if (key.leftArrow) {
3037
+ const newIndex = Math.max(0, tabIndex - 1);
3038
+ setTabIndex(newIndex);
3039
+ setActiveTab(tabs[newIndex].id);
3040
+ setSelectedIndex(0);
3041
+ setScrollOffset(0);
2940
3042
  }
2941
- }
2942
- } else if (view === "detail") {
2943
- if (key.escape || key.backspace) {
2944
- setView("menu");
2945
- setSelectedAction(0);
2946
- }
2947
- if (activeTab === "settings") {
2948
- if (key.return) {
2949
- setIsEditingSettings(true);
2950
- setView("setup");
3043
+ if (key.rightArrow) {
3044
+ const newIndex = Math.min(tabs.length - 1, tabIndex + 1);
3045
+ setTabIndex(newIndex);
3046
+ setActiveTab(tabs[newIndex].id);
3047
+ setSelectedIndex(0);
3048
+ setScrollOffset(0);
2951
3049
  }
2952
- }
2953
- if (key.leftArrow && activeTab === "tools") {
2954
- setSelectedAction((prev) => Math.max(0, prev - 1));
2955
- }
2956
- if (key.rightArrow && activeTab === "tools") {
2957
- let maxActions = 0;
2958
- const tool = tools[selectedIndex];
2959
- const installed = tool ? isToolInstalled(tool.name) : false;
2960
- const hasUpdate = tool ? getToolUpdateStatus(tool.name).hasUpdate : false;
2961
- const isSystem = tool ? tool.system === true : false;
2962
- maxActions = installed ? hasUpdate ? isSystem ? 2 : 3 : isSystem ? 1 : 2 : 1;
2963
- setSelectedAction((prev) => Math.min(maxActions, prev + 1));
2964
- }
2965
- if (key.return && activeTab === "tools") {
2966
- const tool = tools[selectedIndex];
2967
- if (tool) {
2968
- const installed = isToolInstalled(tool.name);
2969
- const toolUpdateStatus = getToolUpdateStatus(tool.name);
2970
- const isSystemTool = tool.system === true;
2971
- const toolActions = installed ? [
2972
- { id: "explore" },
2973
- ...toolUpdateStatus.hasUpdate ? [{ id: "update" }] : [],
2974
- { id: "configure" },
2975
- ...!isSystemTool ? [{ id: "uninstall" }] : []
2976
- ] : [{ id: "explore" }, { id: "install" }];
2977
- const actionId = toolActions[selectedAction]?.id;
2978
- if (actionId === "explore") {
2979
- setView("explorer");
2980
- } else if (actionId === "update") {
2981
- const primarySkill = tool.includes.skills.find((s) => s.required)?.name || tool.name;
2982
- const result = updateSkill(primarySkill);
2983
- setMessage({
2984
- text: result.success ? `\u2713 Updated ${tool.name}` : `\u2717 ${result.message}`,
2985
- type: result.success ? "success" : "error"
2986
- });
2987
- if (result.success) {
2988
- setSelectedAction(0);
3050
+ if (key.upArrow) {
3051
+ setSelectedIndex((prev) => {
3052
+ const newIndex = Math.max(0, prev - 1);
3053
+ if (newIndex < scrollOffset) {
3054
+ setScrollOffset(newIndex);
2989
3055
  }
2990
- } else if (actionId === "configure") {
2991
- setView("configure");
2992
- } else if (actionId === "uninstall") {
2993
- const primarySkill = tool.includes.skills.find((s) => s.required)?.name || tool.name;
2994
- const result = uninstallSkill(primarySkill);
2995
- setMessage({
2996
- text: result.success ? `\u2713 Uninstalled ${tool.name}` : `\u2717 ${result.message}`,
2997
- type: result.success ? "success" : "error"
2998
- });
2999
- if (result.success) {
3000
- setView("menu");
3001
- setSelectedAction(0);
3056
+ return newIndex;
3057
+ });
3058
+ setSelectedAction(0);
3059
+ }
3060
+ if (key.downArrow) {
3061
+ const maxIndex = activeTab === "tools" ? tools.length - 1 : 0;
3062
+ setSelectedIndex((prev) => {
3063
+ const newIndex = Math.min(maxIndex, prev + 1);
3064
+ if (newIndex >= scrollOffset + MAX_VISIBLE_ITEMS) {
3065
+ setScrollOffset(newIndex - MAX_VISIBLE_ITEMS + 1);
3002
3066
  }
3003
- } else if (actionId === "install") {
3004
- const primarySkill = tool.includes.skills.find((s) => s.required)?.name || tool.name;
3005
- const result = installSkill(primarySkill);
3006
- setMessage({
3007
- text: result.success ? `\u2713 Installed ${tool.name}` : `\u2717 ${result.message}`,
3008
- type: result.success ? "success" : "error"
3009
- });
3010
- if (result.success) {
3011
- const configSchema = tool.config_schema || {};
3012
- const hasRequiredConfig = Object.values(configSchema).some(
3013
- (option) => option.default === void 0
3014
- );
3015
- if (hasRequiredConfig) {
3016
- setView("configure");
3017
- } else {
3067
+ return newIndex;
3068
+ });
3069
+ setSelectedAction(0);
3070
+ }
3071
+ if (key.return) {
3072
+ if (activeTab === "tools" && tools.length > 0) {
3073
+ setView("detail");
3074
+ } else if (activeTab === "settings") {
3075
+ setView("detail");
3076
+ }
3077
+ }
3078
+ } else if (view === "detail") {
3079
+ if (key.escape || key.backspace) {
3080
+ setView("menu");
3081
+ setSelectedAction(0);
3082
+ }
3083
+ if (activeTab === "settings") {
3084
+ if (key.return) {
3085
+ setIsEditingSettings(true);
3086
+ setView("setup");
3087
+ }
3088
+ }
3089
+ if (key.leftArrow && activeTab === "tools") {
3090
+ setSelectedAction((prev) => Math.max(0, prev - 1));
3091
+ }
3092
+ if (key.rightArrow && activeTab === "tools") {
3093
+ let maxActions = 0;
3094
+ const tool = tools[selectedIndex];
3095
+ const installed = tool ? isToolInstalled(tool.name) : false;
3096
+ const hasUpdate = tool ? getToolUpdateStatus(tool.name).hasUpdate : false;
3097
+ const isSystem = tool ? tool.system === true : false;
3098
+ maxActions = installed ? hasUpdate ? isSystem ? 2 : 3 : isSystem ? 1 : 2 : 1;
3099
+ setSelectedAction((prev) => Math.min(maxActions, prev + 1));
3100
+ }
3101
+ if (key.return && activeTab === "tools") {
3102
+ const tool = tools[selectedIndex];
3103
+ if (tool) {
3104
+ const installed = isToolInstalled(tool.name);
3105
+ const toolUpdateStatus = getToolUpdateStatus(tool.name);
3106
+ const isSystemTool = tool.system === true;
3107
+ const toolActions = installed ? [
3108
+ { id: "explore" },
3109
+ ...toolUpdateStatus.hasUpdate ? [{ id: "update" }] : [],
3110
+ { id: "configure" },
3111
+ ...!isSystemTool ? [{ id: "uninstall" }] : []
3112
+ ] : [{ id: "explore" }, { id: "install" }];
3113
+ const actionId = toolActions[selectedAction]?.id;
3114
+ if (actionId === "explore") {
3115
+ setView("explorer");
3116
+ } else if (actionId === "update") {
3117
+ const primarySkill = tool.includes.skills.find((s) => s.required)?.name || tool.name;
3118
+ const result = updateSkill(primarySkill);
3119
+ setMessage({
3120
+ text: result.success ? `\u2713 Updated ${tool.name}` : `\u2717 ${result.message}`,
3121
+ type: result.success ? "success" : "error"
3122
+ });
3123
+ if (result.success) {
3124
+ setSelectedAction(0);
3125
+ }
3126
+ } else if (actionId === "configure") {
3127
+ setView("configure");
3128
+ } else if (actionId === "uninstall") {
3129
+ const primarySkill = tool.includes.skills.find((s) => s.required)?.name || tool.name;
3130
+ const result = uninstallSkill(primarySkill);
3131
+ setMessage({
3132
+ text: result.success ? `\u2713 Uninstalled ${tool.name}` : `\u2717 ${result.message}`,
3133
+ type: result.success ? "success" : "error"
3134
+ });
3135
+ if (result.success) {
3018
3136
  setView("menu");
3019
3137
  setSelectedAction(0);
3020
3138
  }
3139
+ } else if (actionId === "install") {
3140
+ const primarySkill = tool.includes.skills.find((s) => s.required)?.name || tool.name;
3141
+ const result = installSkill(primarySkill);
3142
+ setMessage({
3143
+ text: result.success ? `\u2713 Installed ${tool.name}` : `\u2717 ${result.message}`,
3144
+ type: result.success ? "success" : "error"
3145
+ });
3146
+ if (result.success) {
3147
+ const configSchema = tool.config_schema || {};
3148
+ const hasRequiredConfig = Object.values(configSchema).some(
3149
+ (option) => option.default === void 0
3150
+ );
3151
+ if (hasRequiredConfig) {
3152
+ setView("configure");
3153
+ } else {
3154
+ setView("menu");
3155
+ setSelectedAction(0);
3156
+ }
3157
+ }
3021
3158
  }
3022
3159
  }
3023
3160
  }
3024
3161
  }
3162
+ },
3163
+ {
3164
+ isActive: view !== "welcome" && view !== "tool-updates" && view !== "setup" && view !== "configure" && view !== "explorer"
3025
3165
  }
3026
- }, { isActive: view !== "welcome" && view !== "tool-updates" && view !== "setup" && view !== "configure" && view !== "explorer" });
3166
+ );
3027
3167
  const selectedTool = activeTab === "tools" ? tools[selectedIndex] ?? null : null;
3028
- const selectedSkillForConfig = selectedTool ? skills.find((s) => s.name === (selectedTool.includes.skills.find((sk) => sk.required)?.name || selectedTool.name)) : null;
3168
+ const selectedSkillForConfig = selectedTool ? skills.find(
3169
+ (s) => s.name === (selectedTool.includes.skills.find((sk) => sk.required)?.name || selectedTool.name)
3170
+ ) : null;
3029
3171
  if (view === "welcome") {
3030
3172
  return /* @__PURE__ */ jsx13(
3031
3173
  WelcomeScreen,
@@ -3102,7 +3244,10 @@ function App() {
3102
3244
  {
3103
3245
  skill: selectedSkillForConfig,
3104
3246
  onComplete: () => {
3105
- setMessage({ text: `\u2713 Configuration saved for ${selectedTool?.name || selectedSkillForConfig.name}`, type: "success" });
3247
+ setMessage({
3248
+ text: `\u2713 Configuration saved for ${selectedTool?.name || selectedSkillForConfig.name}`,
3249
+ type: "success"
3250
+ });
3106
3251
  setView("detail");
3107
3252
  },
3108
3253
  onCancel: () => {
@@ -3163,18 +3308,26 @@ function App() {
3163
3308
  ] }),
3164
3309
  activeTab === "settings" && /* @__PURE__ */ jsx13(Box12, { paddingX: 1, children: /* @__PURE__ */ jsx13(Text13, { color: colors.textDim, children: "View and edit config" }) })
3165
3310
  ] }),
3166
- message && /* @__PURE__ */ jsx13(Box12, { paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx13(Text13, { color: message.type === "success" ? colors.success : colors.error, children: message.text }) }),
3311
+ message && /* @__PURE__ */ jsx13(Box12, { paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx13(
3312
+ Text13,
3313
+ {
3314
+ color: message.type === "success" ? colors.success : colors.error,
3315
+ children: message.text
3316
+ }
3317
+ ) }),
3167
3318
  /* @__PURE__ */ jsx13(Box12, { paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx13(Text13, { color: colors.textDim, children: view === "menu" ? "\u2190\u2192 \u2191\u2193 enter q" : "\u2190\u2192 enter esc q" }) })
3168
3319
  ]
3169
3320
  }
3170
3321
  ),
3171
- activeTab === "tools" && /* @__PURE__ */ jsx13(ToolDetails, { tool: selectedTool, isFocused: view === "detail", selectedAction }),
3172
- activeTab === "settings" && /* @__PURE__ */ jsx13(
3173
- SettingsDetails,
3322
+ activeTab === "tools" && /* @__PURE__ */ jsx13(
3323
+ ToolDetails,
3174
3324
  {
3175
- isFocused: view === "detail"
3325
+ tool: selectedTool,
3326
+ isFocused: view === "detail",
3327
+ selectedAction
3176
3328
  }
3177
- )
3329
+ ),
3330
+ activeTab === "settings" && /* @__PURE__ */ jsx13(SettingsDetails, { isFocused: view === "detail" })
3178
3331
  ] });
3179
3332
  }
3180
3333
  async function tuiCommand() {
@@ -3189,16 +3342,77 @@ async function tuiCommand() {
3189
3342
  }
3190
3343
  }
3191
3344
 
3345
+ // src/commands/exec.ts
3346
+ import chalk9 from "chalk";
3347
+ import { spawn } from "child_process";
3348
+ import { existsSync as existsSync8 } from "fs";
3349
+ import { join as join10 } from "path";
3350
+ function getRuntime(toolPath) {
3351
+ if (toolPath.endsWith(".ts") || toolPath.endsWith(".js")) {
3352
+ return { cmd: "bun", args: ["run", toolPath] };
3353
+ }
3354
+ if (toolPath.endsWith(".py")) {
3355
+ return { cmd: "python3", args: [toolPath] };
3356
+ }
3357
+ return null;
3358
+ }
3359
+ async function execCommand(tool, script, args) {
3360
+ const config = loadConfig();
3361
+ const skillsPath = getSkillsPath(config.platform);
3362
+ const toolDir = join10(skillsPath, tool);
3363
+ if (!existsSync8(toolDir)) {
3364
+ console.error(chalk9.red(`Tool '${tool}' not found at ${toolDir}`));
3365
+ process.exit(1);
3366
+ }
3367
+ const scriptsDir = join10(toolDir, "scripts");
3368
+ if (!existsSync8(scriptsDir)) {
3369
+ console.error(chalk9.red(`No scripts directory in tool '${tool}'`));
3370
+ process.exit(1);
3371
+ }
3372
+ const extensions = [".ts", ".js", ".py"];
3373
+ let scriptPath = null;
3374
+ for (const ext of extensions) {
3375
+ const candidate = join10(scriptsDir, script + ext);
3376
+ if (existsSync8(candidate)) {
3377
+ scriptPath = candidate;
3378
+ break;
3379
+ }
3380
+ }
3381
+ if (!scriptPath) {
3382
+ console.error(chalk9.red(`Script '${script}' not found in ${scriptsDir}`));
3383
+ console.error(chalk9.gray(`Looked for: ${extensions.map((e) => script + e).join(", ")}`));
3384
+ process.exit(1);
3385
+ }
3386
+ const runtime = getRuntime(scriptPath);
3387
+ if (!runtime) {
3388
+ console.error(chalk9.red(`Unsupported script type: ${scriptPath}`));
3389
+ process.exit(1);
3390
+ }
3391
+ const fullArgs = [...runtime.args, ...args];
3392
+ const child = spawn(runtime.cmd, fullArgs, {
3393
+ stdio: "inherit",
3394
+ cwd: process.cwd()
3395
+ });
3396
+ child.on("error", (err) => {
3397
+ console.error(chalk9.red(`Failed to execute tool: ${err.message}`));
3398
+ process.exit(1);
3399
+ });
3400
+ child.on("close", (code) => {
3401
+ process.exit(code ?? 0);
3402
+ });
3403
+ }
3404
+
3192
3405
  // src/bin/droid.ts
3193
3406
  var version = getVersion();
3194
3407
  program.name("droid").description("Droid, teaching your AI new tricks").version(version);
3195
3408
  program.command("setup").description("Interactive wizard for global configuration").action(setupCommand);
3196
- program.command("config").description("View or edit configuration").option("-e, --edit", "Open config in editor").option("-g, --get <key>", "Get a specific config value").option("-s, --set <key=value>", "Set a config value").action(configCommand);
3409
+ program.command("config").description("View or edit configuration").argument("[tool]", "Get merged config for a tool (for deterministic scripts)").option("-e, --edit", "Open config in editor").option("-g, --get <key>", "Get a specific config value").option("-s, --set <key=value>", "Set a config value").action(configCommand);
3197
3410
  program.command("tools").description("Browse and manage available tools").action(skillsCommand);
3198
3411
  program.command("install <tool>").description("Install a tool and run its setup wizard").action(installCommand);
3199
3412
  program.command("uninstall <tool>").description("Uninstall a tool").action(uninstallCommand);
3200
3413
  program.command("update").description("Update droid and installed tools").option("--tools", "Only update tools").option("--cli", "Only update the CLI").argument("[tool]", "Update a specific tool").action(updateCommand);
3201
3414
  program.command("tui").description("Launch interactive TUI dashboard").action(tuiCommand);
3415
+ program.command("exec <tool> <script>").description("Execute a tool script").argument("[args...]", "Arguments to pass to the script").allowUnknownOption().action(execCommand);
3202
3416
  if (process.argv.length === 2) {
3203
3417
  tuiCommand();
3204
3418
  } else {