@orderful/droid 0.37.0 → 0.39.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 (113) hide show
  1. package/.claude-plugin/marketplace.json +1 -118
  2. package/.claude-plugin/plugin.json +51 -0
  3. package/AGENTS.md +4 -0
  4. package/CHANGELOG.md +53 -0
  5. package/README.md +70 -39
  6. package/dist/bin/droid.js +658 -212
  7. package/dist/commands/auth.d.ts +3 -0
  8. package/dist/commands/auth.d.ts.map +1 -0
  9. package/dist/commands/setup.d.ts.map +1 -1
  10. package/dist/commands/tui/components/PlatformBadges.d.ts.map +1 -1
  11. package/dist/commands/tui/components/SettingsDetails.d.ts.map +1 -1
  12. package/dist/commands/tui/hooks/useAppUpdate.d.ts.map +1 -1
  13. package/dist/commands/tui/views/SetupScreen.d.ts.map +1 -1
  14. package/dist/commands/update.d.ts.map +1 -1
  15. package/dist/index.js +345 -186
  16. package/dist/lib/agents.d.ts +4 -2
  17. package/dist/lib/agents.d.ts.map +1 -1
  18. package/dist/lib/migrations.d.ts.map +1 -1
  19. package/dist/lib/platform.codex.d.ts +36 -0
  20. package/dist/lib/platform.codex.d.ts.map +1 -0
  21. package/dist/lib/platforms.d.ts +30 -24
  22. package/dist/lib/platforms.d.ts.map +1 -1
  23. package/dist/lib/secrets.d.ts +7 -0
  24. package/dist/lib/secrets.d.ts.map +1 -0
  25. package/dist/lib/skills.d.ts +4 -2
  26. package/dist/lib/skills.d.ts.map +1 -1
  27. package/dist/lib/types.d.ts +2 -1
  28. package/dist/lib/types.d.ts.map +1 -1
  29. package/dist/tools/brain/.claude-plugin/plugin.json +8 -1
  30. package/dist/tools/brain/TOOL.yaml +1 -1
  31. package/dist/tools/brain/skills/brain/SKILL.md +6 -3
  32. package/dist/tools/brain/skills/brain/references/workflows.md +9 -5
  33. package/dist/tools/brain/skills/brain-obsidian/SKILL.md +2 -0
  34. package/dist/tools/coach/.claude-plugin/plugin.json +6 -0
  35. package/dist/tools/coach/skills/coach/SKILL.md +3 -0
  36. package/dist/tools/code-review/.claude-plugin/plugin.json +12 -0
  37. package/dist/tools/code-review/skills/code-review/SKILL.md +2 -0
  38. package/dist/tools/codex/.claude-plugin/plugin.json +9 -0
  39. package/dist/tools/codex/skills/codex/SKILL.md +3 -0
  40. package/dist/tools/comments/.claude-plugin/plugin.json +6 -0
  41. package/dist/tools/comments/skills/comments/SKILL.md +5 -0
  42. package/dist/tools/droid/.claude-plugin/plugin.json +8 -1
  43. package/dist/tools/droid/TOOL.yaml +4 -2
  44. package/dist/tools/droid/commands/setup.md +125 -0
  45. package/dist/tools/droid/skills/droid/SKILL.md +117 -2
  46. package/dist/tools/plan/.claude-plugin/plugin.json +6 -0
  47. package/dist/tools/plan/skills/plan/SKILL.md +2 -0
  48. package/dist/tools/project/.claude-plugin/plugin.json +6 -0
  49. package/dist/tools/project/skills/project/SKILL.md +3 -0
  50. package/dist/tools/status-update/.claude-plugin/plugin.json +22 -0
  51. package/dist/tools/status-update/TOOL.yaml +21 -0
  52. package/dist/tools/status-update/commands/status-update.md +27 -0
  53. package/dist/tools/status-update/skills/status-update/SKILL.md +253 -0
  54. package/dist/tools/status-update/skills/status-update/references/formatting.md +203 -0
  55. package/dist/tools/tech-design/.claude-plugin/plugin.json +7 -1
  56. package/dist/tools/tech-design/TOOL.yaml +1 -1
  57. package/dist/tools/tech-design/commands/tech-design.md +2 -0
  58. package/dist/tools/tech-design/skills/tech-design/SKILL.md +39 -9
  59. package/dist/tools/tech-design/skills/tech-design/references/publish.md +272 -216
  60. package/dist/tools/tech-design/skills/tech-design/references/start.md +50 -20
  61. package/dist/tools/wrapup/.claude-plugin/plugin.json +6 -0
  62. package/dist/tools/wrapup/skills/wrapup/SKILL.md +2 -0
  63. package/package.json +1 -1
  64. package/scripts/build-plugins.ts +154 -6
  65. package/src/bin/droid.ts +35 -0
  66. package/src/commands/auth.ts +150 -0
  67. package/src/commands/setup.ts +107 -2
  68. package/src/commands/tui/components/PlatformBadges.tsx +1 -0
  69. package/src/commands/tui/components/SettingsDetails.tsx +1 -0
  70. package/src/commands/tui/hooks/useAppUpdate.ts +21 -1
  71. package/src/commands/tui/views/SetupScreen.tsx +10 -1
  72. package/src/commands/update.ts +21 -1
  73. package/src/lib/agents.ts +13 -2
  74. package/src/lib/migrations.ts +81 -9
  75. package/src/lib/platform.codex.ts +131 -0
  76. package/src/lib/platforms.ts +127 -6
  77. package/src/lib/secrets.ts +12 -0
  78. package/src/lib/skills.ts +53 -6
  79. package/src/lib/types.ts +1 -0
  80. package/src/tools/brain/.claude-plugin/plugin.json +8 -1
  81. package/src/tools/brain/TOOL.yaml +1 -1
  82. package/src/tools/brain/skills/brain/SKILL.md +6 -3
  83. package/src/tools/brain/skills/brain/references/workflows.md +9 -5
  84. package/src/tools/brain/skills/brain-obsidian/SKILL.md +2 -0
  85. package/src/tools/coach/.claude-plugin/plugin.json +6 -0
  86. package/src/tools/coach/skills/coach/SKILL.md +3 -0
  87. package/src/tools/code-review/.claude-plugin/plugin.json +12 -0
  88. package/src/tools/code-review/skills/code-review/SKILL.md +2 -0
  89. package/src/tools/codex/.claude-plugin/plugin.json +9 -0
  90. package/src/tools/codex/skills/codex/SKILL.md +3 -0
  91. package/src/tools/comments/.claude-plugin/plugin.json +6 -0
  92. package/src/tools/comments/skills/comments/SKILL.md +5 -0
  93. package/src/tools/droid/.claude-plugin/plugin.json +8 -1
  94. package/src/tools/droid/TOOL.yaml +4 -2
  95. package/src/tools/droid/commands/setup.md +125 -0
  96. package/src/tools/droid/skills/droid/SKILL.md +117 -2
  97. package/src/tools/plan/.claude-plugin/plugin.json +6 -0
  98. package/src/tools/plan/skills/plan/SKILL.md +2 -0
  99. package/src/tools/project/.claude-plugin/plugin.json +6 -0
  100. package/src/tools/project/skills/project/SKILL.md +3 -0
  101. package/src/tools/status-update/.claude-plugin/plugin.json +22 -0
  102. package/src/tools/status-update/TOOL.yaml +21 -0
  103. package/src/tools/status-update/commands/status-update.md +27 -0
  104. package/src/tools/status-update/skills/status-update/SKILL.md +253 -0
  105. package/src/tools/status-update/skills/status-update/references/formatting.md +203 -0
  106. package/src/tools/tech-design/.claude-plugin/plugin.json +7 -1
  107. package/src/tools/tech-design/TOOL.yaml +1 -1
  108. package/src/tools/tech-design/commands/tech-design.md +2 -0
  109. package/src/tools/tech-design/skills/tech-design/SKILL.md +39 -9
  110. package/src/tools/tech-design/skills/tech-design/references/publish.md +272 -216
  111. package/src/tools/tech-design/skills/tech-design/references/start.md +50 -20
  112. package/src/tools/wrapup/.claude-plugin/plugin.json +6 -0
  113. package/src/tools/wrapup/skills/wrapup/SKILL.md +2 -0
package/dist/bin/droid.js CHANGED
@@ -2,14 +2,15 @@
2
2
 
3
3
  // src/bin/droid.ts
4
4
  import { program } from "commander";
5
+ import chalk12 from "chalk";
5
6
 
6
7
  // src/commands/setup.ts
7
8
  import inquirer from "inquirer";
8
9
  import chalk2 from "chalk";
9
10
  import { execSync as execSync3 } from "child_process";
10
- import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
11
- import { join as join8 } from "path";
12
- import { homedir as homedir4 } from "os";
11
+ import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
12
+ import { join as join9 } from "path";
13
+ import { homedir as homedir5 } from "os";
13
14
 
14
15
  // src/lib/config.ts
15
16
  import {
@@ -297,49 +298,124 @@ function removeRepo(name) {
297
298
 
298
299
  // src/lib/skills.ts
299
300
  import {
300
- existsSync as existsSync6,
301
- readdirSync as readdirSync5,
301
+ existsSync as existsSync7,
302
+ readdirSync as readdirSync6,
302
303
  readFileSync as readFileSync6,
303
- mkdirSync as mkdirSync4,
304
+ mkdirSync as mkdirSync5,
304
305
  writeFileSync as writeFileSync4,
305
- rmSync as rmSync2
306
+ rmSync as rmSync3
306
307
  } from "fs";
307
- import { join as join7, dirname as dirname5, basename } from "path";
308
+ import { join as join8, dirname as dirname6, basename } from "path";
308
309
  import { fileURLToPath as fileURLToPath4 } from "url";
309
310
  import YAML4 from "yaml";
310
311
 
311
312
  // src/lib/agents.ts
312
- import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
313
- import { join as join5, dirname as dirname3 } from "path";
313
+ import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
314
+ import { join as join6, dirname as dirname4 } from "path";
314
315
  import { fileURLToPath as fileURLToPath3 } from "url";
315
316
  import YAML3 from "yaml";
316
317
 
317
318
  // src/lib/platforms.ts
318
- import { join as join2 } from "path";
319
- import { homedir as homedir2 } from "os";
319
+ import { join as join3 } from "path";
320
+ import { homedir as homedir3 } from "os";
320
321
  import { execSync } from "child_process";
321
- import { existsSync as existsSync2 } from "fs";
322
+ import { existsSync as existsSync3 } from "fs";
323
+
324
+ // src/lib/platform.codex.ts
325
+ import { join as join2, dirname } from "path";
326
+ import { homedir as homedir2 } from "os";
327
+ import {
328
+ existsSync as existsSync2,
329
+ mkdirSync as mkdirSync2,
330
+ rmSync,
331
+ symlinkSync,
332
+ lstatSync,
333
+ readdirSync as readdirSync2,
334
+ readlinkSync
335
+ } from "fs";
322
336
  var UNIFIED_SKILLS_PATH = join2(homedir2(), ".claude", "skills");
337
+ var CODEX_SKILLS_PATH = join2(homedir2(), ".codex", "skills");
338
+ function createCodexSymlink(skillName) {
339
+ const source = join2(UNIFIED_SKILLS_PATH, skillName);
340
+ const target = join2(CODEX_SKILLS_PATH, skillName);
341
+ if (!existsSync2(source)) {
342
+ console.warn(`Warning: Cannot create Codex symlink - source skill not found: ${source}`);
343
+ return;
344
+ }
345
+ if (!existsSync2(dirname(target))) {
346
+ mkdirSync2(dirname(target), { recursive: true });
347
+ }
348
+ if (existsSync2(target)) {
349
+ try {
350
+ const stat = lstatSync(target);
351
+ if (stat.isSymbolicLink()) {
352
+ const currentTarget = readlinkSync(target);
353
+ if (currentTarget === source) {
354
+ return;
355
+ }
356
+ rmSync(target);
357
+ } else {
358
+ console.warn(`Warning: ${target} exists and is not a symlink - skipping to preserve user content`);
359
+ return;
360
+ }
361
+ } catch (error) {
362
+ console.warn(`Warning: Could not check ${target}: ${error}`);
363
+ return;
364
+ }
365
+ }
366
+ try {
367
+ symlinkSync(source, target);
368
+ } catch (error) {
369
+ console.warn(`Warning: Could not create Codex symlink ${target} \u2192 ${source}: ${error}`);
370
+ }
371
+ }
372
+ function removeCodexSymlink(skillName) {
373
+ const target = join2(CODEX_SKILLS_PATH, skillName);
374
+ if (!existsSync2(target)) {
375
+ return;
376
+ }
377
+ try {
378
+ const stat = lstatSync(target);
379
+ if (stat.isSymbolicLink()) {
380
+ rmSync(target);
381
+ }
382
+ } catch (error) {
383
+ console.warn(`Warning: Could not remove Codex symlink ${target}: ${error}`);
384
+ }
385
+ }
386
+
387
+ // src/lib/platforms.ts
388
+ var UNIFIED_SKILLS_PATH2 = join3(homedir3(), ".claude", "skills");
323
389
  var PLATFORM_PATHS = {
324
390
  ["claude-code" /* ClaudeCode */]: {
325
- skills: UNIFIED_SKILLS_PATH,
326
- commands: join2(homedir2(), ".claude", "commands"),
327
- agents: join2(homedir2(), ".claude", "agents"),
328
- config: join2(homedir2(), ".claude", "CLAUDE.md")
391
+ skills: UNIFIED_SKILLS_PATH2,
392
+ commands: join3(homedir3(), ".claude", "commands"),
393
+ agents: join3(homedir3(), ".claude", "agents"),
394
+ config: join3(homedir3(), ".claude", "CLAUDE.md")
329
395
  },
330
396
  ["opencode" /* OpenCode */]: {
331
- skills: UNIFIED_SKILLS_PATH,
332
- commands: join2(homedir2(), ".config", "opencode", "command"),
333
- agents: join2(homedir2(), ".config", "opencode", "agent"),
334
- config: join2(homedir2(), ".config", "opencode", "AGENTS.md")
397
+ skills: UNIFIED_SKILLS_PATH2,
398
+ commands: join3(homedir3(), ".config", "opencode", "command"),
399
+ agents: join3(homedir3(), ".config", "opencode", "agent"),
400
+ config: join3(homedir3(), ".config", "opencode", "AGENTS.md")
335
401
  },
336
402
  ["cursor" /* Cursor */]: {
337
- skills: UNIFIED_SKILLS_PATH,
403
+ skills: UNIFIED_SKILLS_PATH2,
338
404
  // Cursor doesn't have a documented global commands path - commands are discovered from projects
339
405
  // We use this path as a placeholder; commands may not work the same as Claude Code/OpenCode
340
- commands: join2(homedir2(), ".cursor", "commands"),
341
- agents: join2(homedir2(), ".cursor", "agents"),
342
- config: join2(homedir2(), ".cursor", "rules")
406
+ commands: join3(homedir3(), ".cursor", "commands"),
407
+ agents: join3(homedir3(), ".cursor", "agents"),
408
+ config: join3(homedir3(), ".cursor", "rules")
409
+ },
410
+ ["openai-codex" /* OpenAICodex */]: {
411
+ // Codex reads from ~/.codex/skills/ but we install to unified path and symlink
412
+ skills: UNIFIED_SKILLS_PATH2,
413
+ commands: null,
414
+ // Not supported - Codex has built-in commands only
415
+ agents: null,
416
+ // Not supported - Codex is single-agent
417
+ config: null
418
+ // No config file integration
343
419
  }
344
420
  };
345
421
  function getSkillsPath(platform) {
@@ -361,8 +437,8 @@ function detectAllPlatforms() {
361
437
  detected.push("claude-code" /* ClaudeCode */);
362
438
  } catch {
363
439
  }
364
- const cursorDir = join2(homedir2(), ".cursor");
365
- if (existsSync2(cursorDir)) {
440
+ const cursorDir = join3(homedir3(), ".cursor");
441
+ if (existsSync3(cursorDir)) {
366
442
  detected.push("cursor" /* Cursor */);
367
443
  }
368
444
  try {
@@ -370,6 +446,10 @@ function detectAllPlatforms() {
370
446
  detected.push("opencode" /* OpenCode */);
371
447
  } catch {
372
448
  }
449
+ const codexDir = join3(homedir3(), ".codex");
450
+ if (existsSync3(codexDir)) {
451
+ detected.push("openai-codex" /* OpenAICodex */);
452
+ }
373
453
  return detected;
374
454
  }
375
455
  function getActivePlatforms(config) {
@@ -377,21 +457,56 @@ function getActivePlatforms(config) {
377
457
  const ignored = config.ignored_platforms ?? [];
378
458
  return detected.filter((p) => !ignored.includes(p));
379
459
  }
460
+ function syncNewPlatforms(config) {
461
+ const detected = detectAllPlatforms();
462
+ const ignored = config.ignored_platforms ?? [];
463
+ const active = detected.filter((p) => !ignored.includes(p));
464
+ const initializedPlatforms = Object.keys(config.platforms ?? {});
465
+ const newPlatforms = active.filter((p) => !initializedPlatforms.includes(p));
466
+ if (newPlatforms.length === 0) {
467
+ return [];
468
+ }
469
+ const primaryTools = config.platforms?.["claude-code"]?.tools ?? {};
470
+ for (const platform of newPlatforms) {
471
+ initializePlatformTools(platform, primaryTools, config);
472
+ }
473
+ return newPlatforms;
474
+ }
475
+ function initializePlatformTools(platform, primaryTools, config) {
476
+ if (platform === "openai-codex" /* OpenAICodex */) {
477
+ for (const skillName of Object.keys(primaryTools)) {
478
+ createCodexSymlink(skillName);
479
+ }
480
+ if (!config.platforms) {
481
+ config.platforms = {};
482
+ }
483
+ config.platforms[platform] = {
484
+ tools: { ...primaryTools }
485
+ };
486
+ return;
487
+ }
488
+ if (!config.platforms) {
489
+ config.platforms = {};
490
+ }
491
+ config.platforms[platform] = {
492
+ tools: { ...primaryTools }
493
+ };
494
+ }
380
495
 
381
496
  // src/lib/tools.ts
382
- import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
383
- import { join as join4, dirname as dirname2 } from "path";
497
+ import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync3 } from "fs";
498
+ import { join as join5, dirname as dirname3 } from "path";
384
499
  import { fileURLToPath as fileURLToPath2 } from "url";
385
500
  import YAML2 from "yaml";
386
501
 
387
502
  // src/lib/version.ts
388
503
  import { readFileSync as readFileSync2 } from "fs";
389
504
  import { fileURLToPath } from "url";
390
- import { dirname, join as join3 } from "path";
505
+ import { dirname as dirname2, join as join4 } from "path";
391
506
  import { execSync as execSync2 } from "child_process";
392
507
  import chalk from "chalk";
393
- var __dirname = dirname(fileURLToPath(import.meta.url));
394
- var packageJsonPath = join3(__dirname, "../../package.json");
508
+ var __dirname = dirname2(fileURLToPath(import.meta.url));
509
+ var packageJsonPath = join4(__dirname, "../../package.json");
395
510
  function getVersion() {
396
511
  try {
397
512
  const pkg = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
@@ -445,14 +560,14 @@ function runUpdate() {
445
560
  }
446
561
 
447
562
  // src/lib/tools.ts
448
- var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
449
- var BUNDLED_TOOLS_DIR = join4(__dirname2, "../tools");
563
+ var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
564
+ var BUNDLED_TOOLS_DIR = join5(__dirname2, "../tools");
450
565
  function getBundledToolsDir() {
451
566
  return BUNDLED_TOOLS_DIR;
452
567
  }
453
568
  function loadToolManifest(toolDir) {
454
- const manifestPath = join4(toolDir, "TOOL.yaml");
455
- if (!existsSync3(manifestPath)) {
569
+ const manifestPath = join5(toolDir, "TOOL.yaml");
570
+ if (!existsSync4(manifestPath)) {
456
571
  return null;
457
572
  }
458
573
  try {
@@ -472,13 +587,13 @@ function loadToolManifest(toolDir) {
472
587
  }
473
588
  }
474
589
  function getBundledTools() {
475
- if (!existsSync3(BUNDLED_TOOLS_DIR)) {
590
+ if (!existsSync4(BUNDLED_TOOLS_DIR)) {
476
591
  return [];
477
592
  }
478
- const toolDirs = readdirSync2(BUNDLED_TOOLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
593
+ const toolDirs = readdirSync3(BUNDLED_TOOLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
479
594
  const tools = [];
480
595
  for (const toolName of toolDirs) {
481
- const manifest = loadToolManifest(join4(BUNDLED_TOOLS_DIR, toolName));
596
+ const manifest = loadToolManifest(join5(BUNDLED_TOOLS_DIR, toolName));
482
597
  if (manifest) {
483
598
  tools.push(manifest);
484
599
  }
@@ -547,8 +662,8 @@ function getToolsWithUpdates() {
547
662
  }
548
663
 
549
664
  // src/lib/agents.ts
550
- var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
551
- var BUNDLED_TOOLS_DIR2 = join5(__dirname3, "../tools");
665
+ var __dirname3 = dirname4(fileURLToPath3(import.meta.url));
666
+ var BUNDLED_TOOLS_DIR2 = join6(__dirname3, "../tools");
552
667
  function getAgentsInstallPath(platform) {
553
668
  return getAgentsPath(platform);
554
669
  }
@@ -569,7 +684,7 @@ function parseAgentFrontmatter(content) {
569
684
  }
570
685
  }
571
686
  function loadAgentManifest(agentPath) {
572
- if (!existsSync4(agentPath)) {
687
+ if (!existsSync5(agentPath)) {
573
688
  return null;
574
689
  }
575
690
  const content = readFileSync4(agentPath, "utf-8");
@@ -577,7 +692,7 @@ function loadAgentManifest(agentPath) {
577
692
  if (!frontmatter || !frontmatter.name) {
578
693
  return null;
579
694
  }
580
- const toolDir = dirname3(dirname3(agentPath));
695
+ const toolDir = dirname4(dirname4(agentPath));
581
696
  const toolManifest = loadToolManifest(toolDir);
582
697
  return {
583
698
  name: frontmatter.name,
@@ -597,8 +712,9 @@ function getInstalledAgentsDir() {
597
712
  function isAgentInstalled(agentName) {
598
713
  const config = loadConfig();
599
714
  const agentsDir = getAgentsInstallPath(config.platform);
600
- const agentPath = join5(agentsDir, `${agentName}.md`);
601
- return existsSync4(agentPath);
715
+ if (!agentsDir) return false;
716
+ const agentPath = join6(agentsDir, `${agentName}.md`);
717
+ return existsSync5(agentPath);
602
718
  }
603
719
  function generateClaudeCodeAgent(manifest, agentContent) {
604
720
  const lines = [
@@ -644,10 +760,13 @@ function installAgentFromPath(agentPath, agentName, platform) {
644
760
  const agentContent = frontmatterMatch ? rawContent.slice(frontmatterMatch[0].length) : rawContent;
645
761
  const installedContent = targetPlatform === "claude-code" /* ClaudeCode */ ? generateClaudeCodeAgent(manifest, agentContent) : generateOpenCodeAgent(manifest, agentContent);
646
762
  const agentsDir = getAgentsInstallPath(targetPlatform);
647
- if (!existsSync4(agentsDir)) {
648
- mkdirSync2(agentsDir, { recursive: true });
763
+ if (!agentsDir) {
764
+ return { success: false, message: `Platform ${targetPlatform} does not support agents` };
765
+ }
766
+ if (!existsSync5(agentsDir)) {
767
+ mkdirSync3(agentsDir, { recursive: true });
649
768
  }
650
- const outputPath = join5(agentsDir, `${agentName}.md`);
769
+ const outputPath = join6(agentsDir, `${agentName}.md`);
651
770
  writeFileSync2(outputPath, installedContent);
652
771
  const targetDir = targetPlatform === "claude-code" /* ClaudeCode */ ? "~/.claude/agents/" : targetPlatform === "cursor" /* Cursor */ ? "~/.cursor/agents/" : "~/.config/opencode/agent/";
653
772
  return { success: true, message: `Installed ${agentName} to ${targetDir}` };
@@ -659,8 +778,11 @@ function uninstallAgent(agentName, platform) {
659
778
  const config = loadConfig();
660
779
  const targetPlatform = platform ?? config.platform;
661
780
  const agentsDir = getAgentsInstallPath(targetPlatform);
662
- const agentPath = join5(agentsDir, `${agentName}.md`);
663
- if (!existsSync4(agentPath)) {
781
+ if (!agentsDir) {
782
+ return { success: true, message: `Platform ${targetPlatform} does not support agents` };
783
+ }
784
+ const agentPath = join6(agentsDir, `${agentName}.md`);
785
+ if (!existsSync5(agentPath)) {
664
786
  return { success: false, message: `Agent not installed: ${agentName}` };
665
787
  }
666
788
  try {
@@ -673,20 +795,20 @@ function uninstallAgent(agentName, platform) {
673
795
 
674
796
  // src/lib/migrations.ts
675
797
  import {
676
- existsSync as existsSync5,
798
+ existsSync as existsSync6,
677
799
  appendFileSync,
678
- mkdirSync as mkdirSync3,
800
+ mkdirSync as mkdirSync4,
679
801
  renameSync,
680
- rmSync,
681
- readdirSync as readdirSync4,
802
+ rmSync as rmSync2,
803
+ readdirSync as readdirSync5,
682
804
  readFileSync as readFileSync5,
683
805
  writeFileSync as writeFileSync3
684
806
  } from "fs";
685
- import { join as join6, dirname as dirname4 } from "path";
686
- import { homedir as homedir3 } from "os";
807
+ import { join as join7, dirname as dirname5 } from "path";
808
+ import { homedir as homedir4 } from "os";
687
809
  var MIGRATIONS_LOG_FILE = ".migrations.log";
688
810
  function getMigrationsLogPath() {
689
- return join6(getConfigDir(), MIGRATIONS_LOG_FILE);
811
+ return join7(getConfigDir(), MIGRATIONS_LOG_FILE);
690
812
  }
691
813
  function logMigration(toolName, fromVersion, toVersion, status, error) {
692
814
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -694,9 +816,9 @@ function logMigration(toolName, fromVersion, toVersion, status, error) {
694
816
  ` : `${timestamp} ${toolName} ${fromVersion} \u2192 ${toVersion} ${status}
695
817
  `;
696
818
  const logPath = getMigrationsLogPath();
697
- const logDir = dirname4(logPath);
698
- if (!existsSync5(logDir)) {
699
- mkdirSync3(logDir, { recursive: true });
819
+ const logDir = dirname5(logPath);
820
+ if (!existsSync6(logDir)) {
821
+ mkdirSync4(logDir, { recursive: true });
700
822
  }
701
823
  appendFileSync(logPath, logEntry);
702
824
  }
@@ -706,16 +828,16 @@ function createConfigDirMigration(skillName, version2) {
706
828
  version: version2,
707
829
  description: `Move ${skillName} config to unprefixed location`,
708
830
  up: (configDir) => {
709
- const oldDir = join6(configDir, "skills", skillName);
710
- const newDir = join6(configDir, "skills", unprefixedName);
711
- if (existsSync5(oldDir) && !existsSync5(newDir)) {
712
- const parentDir = dirname4(newDir);
713
- if (!existsSync5(parentDir)) {
714
- mkdirSync3(parentDir, { recursive: true });
831
+ const oldDir = join7(configDir, "skills", skillName);
832
+ const newDir = join7(configDir, "skills", unprefixedName);
833
+ if (existsSync6(oldDir) && !existsSync6(newDir)) {
834
+ const parentDir = dirname5(newDir);
835
+ if (!existsSync6(parentDir)) {
836
+ mkdirSync4(parentDir, { recursive: true });
715
837
  }
716
838
  renameSync(oldDir, newDir);
717
- } else if (existsSync5(oldDir) && existsSync5(newDir)) {
718
- rmSync(oldDir, { recursive: true });
839
+ } else if (existsSync6(oldDir) && existsSync6(newDir)) {
840
+ rmSync2(oldDir, { recursive: true });
719
841
  }
720
842
  }
721
843
  };
@@ -731,10 +853,10 @@ function createPlatformSyncMigration(version2) {
731
853
  const originalPlatform = config.platform;
732
854
  for (const platformKey of ["claude-code" /* ClaudeCode */, "opencode" /* OpenCode */, "cursor" /* Cursor */]) {
733
855
  const skillsPath = getSkillsPath(platformKey);
734
- if (!existsSync5(skillsPath)) continue;
856
+ if (!existsSync6(skillsPath)) continue;
735
857
  config.platform = platformKey;
736
858
  const trackedTools = getPlatformTools(config);
737
- const installedDirs = readdirSync4(skillsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
859
+ const installedDirs = readdirSync5(skillsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
738
860
  for (const skillName of installedDirs) {
739
861
  const normalizedName = skillName.replace(/^droid-/, "");
740
862
  const isTracked = trackedTools[skillName] || trackedTools[`droid-${normalizedName}`] || trackedTools[normalizedName];
@@ -807,12 +929,12 @@ function createOpenCodeSkillsPathMigration(version2) {
807
929
  if (config.platform !== "opencode" /* OpenCode */) {
808
930
  return;
809
931
  }
810
- const oldSkillsPath = join6(getSkillsPath("opencode" /* OpenCode */), "..", "skills");
932
+ const oldSkillsPath = join7(getSkillsPath("opencode" /* OpenCode */), "..", "skills");
811
933
  const newSkillsPath = getSkillsPath("opencode" /* OpenCode */);
812
- if (!existsSync5(oldSkillsPath)) {
934
+ if (!existsSync6(oldSkillsPath)) {
813
935
  return;
814
936
  }
815
- if (!existsSync5(newSkillsPath)) {
937
+ if (!existsSync6(newSkillsPath)) {
816
938
  try {
817
939
  renameSync(oldSkillsPath, newSkillsPath);
818
940
  } catch (error) {
@@ -822,11 +944,11 @@ function createOpenCodeSkillsPathMigration(version2) {
822
944
  }
823
945
  return;
824
946
  }
825
- const skillDirs = readdirSync4(oldSkillsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
947
+ const skillDirs = readdirSync5(oldSkillsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
826
948
  for (const skillName of skillDirs) {
827
- const oldSkillDir = join6(oldSkillsPath, skillName);
828
- const newSkillDir = join6(newSkillsPath, skillName);
829
- if (!existsSync5(newSkillDir)) {
949
+ const oldSkillDir = join7(oldSkillsPath, skillName);
950
+ const newSkillDir = join7(newSkillsPath, skillName);
951
+ if (!existsSync6(newSkillDir)) {
830
952
  try {
831
953
  renameSync(oldSkillDir, newSkillDir);
832
954
  } catch (error) {
@@ -837,9 +959,9 @@ function createOpenCodeSkillsPathMigration(version2) {
837
959
  }
838
960
  }
839
961
  try {
840
- const remaining = readdirSync4(oldSkillsPath);
962
+ const remaining = readdirSync5(oldSkillsPath);
841
963
  if (remaining.length === 0) {
842
- rmSync(oldSkillsPath, { recursive: true });
964
+ rmSync2(oldSkillsPath, { recursive: true });
843
965
  }
844
966
  } catch (error) {
845
967
  console.warn(
@@ -855,25 +977,25 @@ function createClaudeCodeCommandCleanupMigration(version2) {
855
977
  description: "Remove non-alias commands from Claude Code",
856
978
  up: () => {
857
979
  const commandsPath = getCommandsPath("claude-code" /* ClaudeCode */);
858
- if (!existsSync5(commandsPath)) {
980
+ if (!commandsPath || !existsSync6(commandsPath)) {
859
981
  return;
860
982
  }
861
983
  const bundledTools = getBundledTools();
862
- const aliasCommands = /* @__PURE__ */ new Set();
984
+ const deletableDroidCommands = /* @__PURE__ */ new Set();
863
985
  for (const tool of bundledTools) {
864
986
  for (const cmd of tool.includes.commands) {
865
- if (typeof cmd === "object" && cmd.is_alias) {
866
- aliasCommands.add(cmd.name);
987
+ if (!cmd.is_alias) {
988
+ deletableDroidCommands.add(cmd.name);
867
989
  }
868
990
  }
869
991
  }
870
- const commandFiles = readdirSync4(commandsPath, { withFileTypes: true }).filter((dirent) => dirent.isFile() && dirent.name.endsWith(".md")).map((dirent) => dirent.name);
992
+ const commandFiles = readdirSync5(commandsPath, { withFileTypes: true }).filter((dirent) => dirent.isFile() && dirent.name.endsWith(".md")).map((dirent) => dirent.name);
871
993
  for (const file of commandFiles) {
872
994
  const commandName = file.replace(".md", "");
873
- if (!aliasCommands.has(commandName)) {
874
- const commandFilePath = join6(commandsPath, file);
995
+ if (deletableDroidCommands.has(commandName)) {
996
+ const commandFilePath = join7(commandsPath, file);
875
997
  try {
876
- rmSync(commandFilePath);
998
+ rmSync2(commandFilePath);
877
999
  } catch (error) {
878
1000
  console.warn(
879
1001
  `Warning: Could not remove command ${commandFilePath}: ${error}`
@@ -889,13 +1011,13 @@ function createOpenCodePluginCleanupMigration(version2) {
889
1011
  version: version2,
890
1012
  description: "Remove opencode-skills plugin from opencode.json",
891
1013
  up: () => {
892
- const opencodeConfigPath = join6(
893
- homedir3(),
1014
+ const opencodeConfigPath = join7(
1015
+ homedir4(),
894
1016
  ".config",
895
1017
  "opencode",
896
1018
  "opencode.json"
897
1019
  );
898
- if (!existsSync5(opencodeConfigPath)) {
1020
+ if (!existsSync6(opencodeConfigPath)) {
899
1021
  return;
900
1022
  }
901
1023
  let config;
@@ -932,24 +1054,24 @@ function createUnifiedSkillsPathMigration(version2) {
932
1054
  version: version2,
933
1055
  description: "Copy OpenCode skills to unified ~/.claude/skills/ location",
934
1056
  up: () => {
935
- const oldOpenCodeSkillsPath = join6(
936
- homedir3(),
1057
+ const oldOpenCodeSkillsPath = join7(
1058
+ homedir4(),
937
1059
  ".config",
938
1060
  "opencode",
939
1061
  "skill"
940
1062
  );
941
- const unifiedSkillsPath = join6(homedir3(), ".claude", "skills");
942
- if (!existsSync5(oldOpenCodeSkillsPath)) {
1063
+ const unifiedSkillsPath = join7(homedir4(), ".claude", "skills");
1064
+ if (!existsSync6(oldOpenCodeSkillsPath)) {
943
1065
  return;
944
1066
  }
945
- if (!existsSync5(unifiedSkillsPath)) {
946
- mkdirSync3(unifiedSkillsPath, { recursive: true });
1067
+ if (!existsSync6(unifiedSkillsPath)) {
1068
+ mkdirSync4(unifiedSkillsPath, { recursive: true });
947
1069
  }
948
- const skillDirs = readdirSync4(oldOpenCodeSkillsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
1070
+ const skillDirs = readdirSync5(oldOpenCodeSkillsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
949
1071
  for (const skillName of skillDirs) {
950
- const sourcePath = join6(oldOpenCodeSkillsPath, skillName);
951
- const destPath = join6(unifiedSkillsPath, skillName);
952
- if (existsSync5(destPath)) {
1072
+ const sourcePath = join7(oldOpenCodeSkillsPath, skillName);
1073
+ const destPath = join7(unifiedSkillsPath, skillName);
1074
+ if (existsSync6(destPath)) {
953
1075
  continue;
954
1076
  }
955
1077
  try {
@@ -964,11 +1086,11 @@ function createUnifiedSkillsPathMigration(version2) {
964
1086
  };
965
1087
  }
966
1088
  function copyDirRecursive(src, dest) {
967
- mkdirSync3(dest, { recursive: true });
968
- const entries = readdirSync4(src, { withFileTypes: true });
1089
+ mkdirSync4(dest, { recursive: true });
1090
+ const entries = readdirSync5(src, { withFileTypes: true });
969
1091
  for (const entry of entries) {
970
- const srcPath = join6(src, entry.name);
971
- const destPath = join6(dest, entry.name);
1092
+ const srcPath = join7(src, entry.name);
1093
+ const destPath = join7(dest, entry.name);
972
1094
  if (entry.isDirectory()) {
973
1095
  copyDirRecursive(srcPath, destPath);
974
1096
  } else {
@@ -977,6 +1099,49 @@ function copyDirRecursive(src, dest) {
977
1099
  }
978
1100
  }
979
1101
  }
1102
+ function createOpenCodeSkillsCleanupMigration(version2) {
1103
+ return {
1104
+ version: version2,
1105
+ description: "Remove droid-managed skills from OpenCode platform-specific directory",
1106
+ up: () => {
1107
+ const oldOpenCodeSkillsPath = join7(
1108
+ homedir4(),
1109
+ ".config",
1110
+ "opencode",
1111
+ "skill"
1112
+ );
1113
+ const unifiedSkillsPath = join7(homedir4(), ".claude", "skills");
1114
+ if (!existsSync6(oldOpenCodeSkillsPath)) {
1115
+ return;
1116
+ }
1117
+ const bundledTools = getBundledTools();
1118
+ const droidManagedSkills = /* @__PURE__ */ new Set();
1119
+ for (const tool of bundledTools) {
1120
+ for (const skill of tool.includes.skills) {
1121
+ droidManagedSkills.add(skill.name);
1122
+ }
1123
+ }
1124
+ const skillDirs = readdirSync5(oldOpenCodeSkillsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
1125
+ for (const skillName of skillDirs) {
1126
+ if (!droidManagedSkills.has(skillName)) {
1127
+ continue;
1128
+ }
1129
+ const unifiedPath = join7(unifiedSkillsPath, skillName);
1130
+ if (!existsSync6(unifiedPath)) {
1131
+ continue;
1132
+ }
1133
+ const oldPath = join7(oldOpenCodeSkillsPath, skillName);
1134
+ try {
1135
+ rmSync2(oldPath, { recursive: true });
1136
+ } catch (error) {
1137
+ console.warn(
1138
+ `Warning: Could not remove OpenCode skill ${skillName}: ${error}`
1139
+ );
1140
+ }
1141
+ }
1142
+ }
1143
+ };
1144
+ }
980
1145
  var PACKAGE_MIGRATIONS = [
981
1146
  createPlatformSyncMigration("0.25.0"),
982
1147
  createConfigSkillNameMigration("0.27.2"),
@@ -985,7 +1150,8 @@ var PACKAGE_MIGRATIONS = [
985
1150
  // Retry: 0.28.0 migration had platform check that prevented running after platform switch
986
1151
  createClaudeCodeCommandCleanupMigration("0.28.1"),
987
1152
  createOpenCodePluginCleanupMigration("0.29.2"),
988
- createUnifiedSkillsPathMigration("0.30.0")
1153
+ createUnifiedSkillsPathMigration("0.30.0"),
1154
+ createOpenCodeSkillsCleanupMigration("0.37.1")
989
1155
  ];
990
1156
  var TOOL_MIGRATIONS = {
991
1157
  brain: [createConfigDirMigration("droid-brain", "0.2.3")],
@@ -1109,8 +1275,8 @@ function runPackageMigrations(packageVersion) {
1109
1275
  // src/lib/skills.ts
1110
1276
  var DROID_SKILLS_START = "<!-- droid-skills-start -->";
1111
1277
  var DROID_SKILLS_END = "<!-- droid-skills-end -->";
1112
- var __dirname4 = dirname5(fileURLToPath4(import.meta.url));
1113
- var BUNDLED_SKILLS_DIR = join7(__dirname4, "../tools");
1278
+ var __dirname4 = dirname6(fileURLToPath4(import.meta.url));
1279
+ var BUNDLED_SKILLS_DIR = join8(__dirname4, "../tools");
1114
1280
  function getSkillsInstallPath(platform) {
1115
1281
  return getSkillsPath(platform);
1116
1282
  }
@@ -1122,8 +1288,11 @@ function getPlatformConfigPath(platform) {
1122
1288
  }
1123
1289
  function updatePlatformConfigSkills(platform, installedSkills) {
1124
1290
  const configPath = getPlatformConfigPath(platform);
1291
+ if (!configPath) {
1292
+ return;
1293
+ }
1125
1294
  let content = "";
1126
- if (existsSync6(configPath)) {
1295
+ if (existsSync7(configPath)) {
1127
1296
  content = readFileSync6(configPath, "utf-8");
1128
1297
  }
1129
1298
  const skillLines = installedSkills.map((name) => {
@@ -1142,9 +1311,9 @@ ${DROID_SKILLS_END}` : "";
1142
1311
  } else if (skillsSection) {
1143
1312
  content = content.trim() + "\n\n" + skillsSection + "\n";
1144
1313
  }
1145
- const configDir = dirname5(configPath);
1146
- if (!existsSync6(configDir)) {
1147
- mkdirSync4(configDir, { recursive: true });
1314
+ const configDir = dirname6(configPath);
1315
+ if (!existsSync7(configDir)) {
1316
+ mkdirSync5(configDir, { recursive: true });
1148
1317
  }
1149
1318
  writeFileSync4(configPath, content, "utf-8");
1150
1319
  }
@@ -1165,8 +1334,8 @@ function parseSkillFrontmatter(content) {
1165
1334
  }
1166
1335
  }
1167
1336
  function loadSkillManifest(skillDir) {
1168
- const skillMdPath = join7(skillDir, "SKILL.md");
1169
- if (!existsSync6(skillMdPath)) {
1337
+ const skillMdPath = join8(skillDir, "SKILL.md");
1338
+ if (!existsSync7(skillMdPath)) {
1170
1339
  return null;
1171
1340
  }
1172
1341
  const content = readFileSync6(skillMdPath, "utf-8");
@@ -1174,7 +1343,7 @@ function loadSkillManifest(skillDir) {
1174
1343
  if (!frontmatter || !frontmatter.name) {
1175
1344
  return null;
1176
1345
  }
1177
- const toolDir = dirname5(dirname5(skillDir));
1346
+ const toolDir = dirname6(dirname6(skillDir));
1178
1347
  const toolManifest = loadToolManifest(toolDir);
1179
1348
  return {
1180
1349
  name: frontmatter.name,
@@ -1186,17 +1355,17 @@ function loadSkillManifest(skillDir) {
1186
1355
  };
1187
1356
  }
1188
1357
  function findSkillPath(skillName) {
1189
- if (!existsSync6(BUNDLED_SKILLS_DIR)) {
1358
+ if (!existsSync7(BUNDLED_SKILLS_DIR)) {
1190
1359
  return null;
1191
1360
  }
1192
- const toolDirs = readdirSync5(BUNDLED_SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
1361
+ const toolDirs = readdirSync6(BUNDLED_SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
1193
1362
  for (const toolName of toolDirs) {
1194
- const skillsDir = join7(BUNDLED_SKILLS_DIR, toolName, "skills");
1195
- if (!existsSync6(skillsDir)) continue;
1196
- const skillDir = join7(skillsDir, skillName);
1197
- if (existsSync6(skillDir) && existsSync6(join7(skillDir, "SKILL.md"))) {
1363
+ const skillsDir = join8(BUNDLED_SKILLS_DIR, toolName, "skills");
1364
+ if (!existsSync7(skillsDir)) continue;
1365
+ const skillDir = join8(skillsDir, skillName);
1366
+ if (existsSync7(skillDir) && existsSync7(join8(skillDir, "SKILL.md"))) {
1198
1367
  return {
1199
- toolDir: join7(BUNDLED_SKILLS_DIR, toolName),
1368
+ toolDir: join8(BUNDLED_SKILLS_DIR, toolName),
1200
1369
  skillDir
1201
1370
  };
1202
1371
  }
@@ -1204,17 +1373,17 @@ function findSkillPath(skillName) {
1204
1373
  return null;
1205
1374
  }
1206
1375
  function getBundledSkills() {
1207
- if (!existsSync6(BUNDLED_SKILLS_DIR)) {
1376
+ if (!existsSync7(BUNDLED_SKILLS_DIR)) {
1208
1377
  return [];
1209
1378
  }
1210
- const toolDirs = readdirSync5(BUNDLED_SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
1379
+ const toolDirs = readdirSync6(BUNDLED_SKILLS_DIR, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
1211
1380
  const skills = [];
1212
1381
  for (const toolName of toolDirs) {
1213
- const skillsDir = join7(BUNDLED_SKILLS_DIR, toolName, "skills");
1214
- if (!existsSync6(skillsDir)) continue;
1215
- const skillSubdirs = readdirSync5(skillsDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
1382
+ const skillsDir = join8(BUNDLED_SKILLS_DIR, toolName, "skills");
1383
+ if (!existsSync7(skillsDir)) continue;
1384
+ const skillSubdirs = readdirSync6(skillsDir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
1216
1385
  for (const skillName of skillSubdirs) {
1217
- const manifest = loadSkillManifest(join7(skillsDir, skillName));
1386
+ const manifest = loadSkillManifest(join8(skillsDir, skillName));
1218
1387
  if (manifest) {
1219
1388
  skills.push(manifest);
1220
1389
  }
@@ -1306,13 +1475,13 @@ function updateAllSkills() {
1306
1475
  return result;
1307
1476
  }
1308
1477
  function copyDirectoryRecursive(source, target, fileFilter) {
1309
- if (!existsSync6(target)) {
1310
- mkdirSync4(target, { recursive: true });
1478
+ if (!existsSync7(target)) {
1479
+ mkdirSync5(target, { recursive: true });
1311
1480
  }
1312
- const entries = readdirSync5(source, { withFileTypes: true });
1481
+ const entries = readdirSync6(source, { withFileTypes: true });
1313
1482
  for (const entry of entries) {
1314
- const sourcePath = join7(source, entry.name);
1315
- const targetPath = join7(target, entry.name);
1483
+ const sourcePath = join8(source, entry.name);
1484
+ const targetPath = join8(target, entry.name);
1316
1485
  if (entry.isDirectory()) {
1317
1486
  copyDirectoryRecursive(sourcePath, targetPath, fileFilter);
1318
1487
  } else if (entry.isFile() && fileFilter(entry.name)) {
@@ -1353,7 +1522,7 @@ function installSkill(skillName) {
1353
1522
  }
1354
1523
  }
1355
1524
  const skillsPath = getSkillsInstallPath(config.platform);
1356
- const targetSkillDir = join7(skillsPath, skillName);
1525
+ const targetSkillDir = join8(skillsPath, skillName);
1357
1526
  const commandsPath = getCommandsInstallPath(config.platform);
1358
1527
  const tools = getPlatformTools(config);
1359
1528
  const renamedSkills = [
@@ -1368,10 +1537,10 @@ function installSkill(skillName) {
1368
1537
  ];
1369
1538
  if (renamedSkills.includes(skillName)) {
1370
1539
  const droidPrefixedName = `droid-${skillName}`;
1371
- const droidPrefixedDir = join7(skillsPath, droidPrefixedName);
1372
- if (existsSync6(droidPrefixedDir)) {
1540
+ const droidPrefixedDir = join8(skillsPath, droidPrefixedName);
1541
+ if (existsSync7(droidPrefixedDir)) {
1373
1542
  try {
1374
- rmSync2(droidPrefixedDir, { recursive: true });
1543
+ rmSync3(droidPrefixedDir, { recursive: true });
1375
1544
  } catch (error) {
1376
1545
  console.warn(
1377
1546
  `Warning: Could not remove old skill directory ${droidPrefixedDir}: ${error}`
@@ -1384,18 +1553,18 @@ function installSkill(skillName) {
1384
1553
  saveConfig(config);
1385
1554
  }
1386
1555
  }
1387
- const commandsSource = join7(toolDir, "commands");
1388
- const agentsSource = join7(toolDir, "agents");
1556
+ const commandsSource = join8(toolDir, "commands");
1557
+ const agentsSource = join8(toolDir, "agents");
1389
1558
  const isAlreadyInstalled = tools[skillName];
1390
1559
  if (!isAlreadyInstalled) {
1391
1560
  const toolName2 = basename(toolDir);
1392
- if (existsSync6(commandsSource)) {
1393
- const commandFiles = readdirSync5(commandsSource).filter(
1561
+ if (commandsPath && existsSync7(commandsSource)) {
1562
+ const commandFiles = readdirSync6(commandsSource).filter(
1394
1563
  (f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md"
1395
1564
  );
1396
1565
  for (const file of commandFiles) {
1397
- const targetCommandPath = join7(commandsPath, file);
1398
- if (existsSync6(targetCommandPath)) {
1566
+ const targetCommandPath = join8(commandsPath, file);
1567
+ if (existsSync7(targetCommandPath)) {
1399
1568
  const commandName = file.replace(".md", "");
1400
1569
  if (commandName === toolName2) {
1401
1570
  continue;
@@ -1407,8 +1576,8 @@ function installSkill(skillName) {
1407
1576
  }
1408
1577
  }
1409
1578
  }
1410
- if (existsSync6(agentsSource)) {
1411
- const agentFiles = readdirSync5(agentsSource, { withFileTypes: true }).filter((dirent) => dirent.isFile() && dirent.name.endsWith(".md")).map((dirent) => dirent.name.replace(".md", ""));
1579
+ if (existsSync7(agentsSource)) {
1580
+ const agentFiles = readdirSync6(agentsSource, { withFileTypes: true }).filter((dirent) => dirent.isFile() && dirent.name.endsWith(".md")).map((dirent) => dirent.name.replace(".md", ""));
1412
1581
  for (const agentName of agentFiles) {
1413
1582
  if (isAgentInstalled(agentName)) {
1414
1583
  return {
@@ -1419,30 +1588,30 @@ function installSkill(skillName) {
1419
1588
  }
1420
1589
  }
1421
1590
  }
1422
- if (!existsSync6(skillsPath)) {
1423
- mkdirSync4(skillsPath, { recursive: true });
1591
+ if (!existsSync7(skillsPath)) {
1592
+ mkdirSync5(skillsPath, { recursive: true });
1424
1593
  }
1425
- const skillMdSource = join7(skillDir, "SKILL.md");
1426
- if (existsSync6(skillMdSource)) {
1427
- if (!existsSync6(targetSkillDir)) {
1428
- mkdirSync4(targetSkillDir, { recursive: true });
1594
+ const skillMdSource = join8(skillDir, "SKILL.md");
1595
+ if (existsSync7(skillMdSource)) {
1596
+ if (!existsSync7(targetSkillDir)) {
1597
+ mkdirSync5(targetSkillDir, { recursive: true });
1429
1598
  }
1430
- const skillMdTarget = join7(targetSkillDir, "SKILL.md");
1599
+ const skillMdTarget = join8(targetSkillDir, "SKILL.md");
1431
1600
  const content = readFileSync6(skillMdSource, "utf-8");
1432
1601
  writeFileSync4(skillMdTarget, content);
1433
1602
  }
1434
- const referencesSource = join7(skillDir, "references");
1435
- if (existsSync6(referencesSource)) {
1436
- const targetReferencesDir = join7(targetSkillDir, "references");
1603
+ const referencesSource = join8(skillDir, "references");
1604
+ if (existsSync7(referencesSource)) {
1605
+ const targetReferencesDir = join8(targetSkillDir, "references");
1437
1606
  copyDirectoryRecursive(
1438
1607
  referencesSource,
1439
1608
  targetReferencesDir,
1440
1609
  (f) => f.endsWith(".md")
1441
1610
  );
1442
1611
  }
1443
- const scriptsSource = join7(skillDir, "scripts");
1444
- if (existsSync6(scriptsSource)) {
1445
- const targetScriptsDir = join7(targetSkillDir, "scripts");
1612
+ const scriptsSource = join8(skillDir, "scripts");
1613
+ if (existsSync7(scriptsSource)) {
1614
+ const targetScriptsDir = join8(targetSkillDir, "scripts");
1446
1615
  copyDirectoryRecursive(
1447
1616
  scriptsSource,
1448
1617
  targetScriptsDir,
@@ -1451,14 +1620,20 @@ function installSkill(skillName) {
1451
1620
  }
1452
1621
  const activePlatforms = getActivePlatforms(config);
1453
1622
  const targetPlatforms = activePlatforms.length > 0 ? activePlatforms : [config.platform];
1454
- if (existsSync6(commandsSource)) {
1455
- const commandFiles = readdirSync5(commandsSource).filter(
1623
+ if (targetPlatforms.includes("openai-codex" /* OpenAICodex */)) {
1624
+ createCodexSymlink(skillName);
1625
+ }
1626
+ if (existsSync7(commandsSource)) {
1627
+ const commandFiles = readdirSync6(commandsSource).filter(
1456
1628
  (f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md"
1457
1629
  );
1458
1630
  for (const platform of targetPlatforms) {
1459
1631
  const platformCommandsPath = getCommandsInstallPath(platform);
1460
- if (!existsSync6(platformCommandsPath)) {
1461
- mkdirSync4(platformCommandsPath, { recursive: true });
1632
+ if (!platformCommandsPath) {
1633
+ continue;
1634
+ }
1635
+ if (!existsSync7(platformCommandsPath)) {
1636
+ mkdirSync5(platformCommandsPath, { recursive: true });
1462
1637
  }
1463
1638
  for (const file of commandFiles) {
1464
1639
  const commandName = file.replace(".md", "");
@@ -1468,8 +1643,8 @@ function installSkill(skillName) {
1468
1643
  const isAlias = typeof commandMeta === "object" && commandMeta.is_alias;
1469
1644
  const shouldInstall = platform === "opencode" /* OpenCode */ || platform === "cursor" /* Cursor */ || isAlias;
1470
1645
  if (shouldInstall) {
1471
- const sourcePath = join7(commandsSource, file);
1472
- const targetPath = join7(platformCommandsPath, file);
1646
+ const sourcePath = join8(commandsSource, file);
1647
+ const targetPath = join8(platformCommandsPath, file);
1473
1648
  const content = readFileSync6(sourcePath, "utf-8");
1474
1649
  writeFileSync4(targetPath, content);
1475
1650
  }
@@ -1477,10 +1652,10 @@ function installSkill(skillName) {
1477
1652
  }
1478
1653
  }
1479
1654
  const installedAgents = [];
1480
- if (existsSync6(agentsSource)) {
1481
- const agentFiles = readdirSync5(agentsSource, { withFileTypes: true }).filter((dirent) => dirent.isFile() && dirent.name.endsWith(".md")).map((dirent) => dirent.name.replace(".md", ""));
1655
+ if (existsSync7(agentsSource)) {
1656
+ const agentFiles = readdirSync6(agentsSource, { withFileTypes: true }).filter((dirent) => dirent.isFile() && dirent.name.endsWith(".md")).map((dirent) => dirent.name.replace(".md", ""));
1482
1657
  for (const agentName of agentFiles) {
1483
- const agentPath = join7(agentsSource, `${agentName}.md`);
1658
+ const agentPath = join8(agentsSource, `${agentName}.md`);
1484
1659
  let anySuccess = false;
1485
1660
  for (const platform of targetPlatforms) {
1486
1661
  const result = installAgentFromPath(agentPath, agentName, platform);
@@ -1527,23 +1702,27 @@ function uninstallSkill(skillName) {
1527
1702
  }
1528
1703
  const activePlatforms = getActivePlatforms(config);
1529
1704
  const targetPlatforms = activePlatforms.length > 0 ? activePlatforms : [config.platform];
1705
+ removeCodexSymlink(skillName);
1530
1706
  const skillsPath = getSkillsInstallPath(config.platform);
1531
- const skillDir = join7(skillsPath, skillName);
1532
- if (existsSync6(skillDir)) {
1533
- rmSync2(skillDir, { recursive: true });
1707
+ const skillDir = join8(skillsPath, skillName);
1708
+ if (existsSync7(skillDir)) {
1709
+ rmSync3(skillDir, { recursive: true });
1534
1710
  }
1535
1711
  const skillPath = findSkillPath(skillName);
1536
- const commandsSource = skillPath ? join7(skillPath.toolDir, "commands") : null;
1537
- if (commandsSource && existsSync6(commandsSource)) {
1538
- const commandFiles = readdirSync5(commandsSource).filter(
1712
+ const commandsSource = skillPath ? join8(skillPath.toolDir, "commands") : null;
1713
+ if (commandsSource && existsSync7(commandsSource)) {
1714
+ const commandFiles = readdirSync6(commandsSource).filter(
1539
1715
  (f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md"
1540
1716
  );
1541
1717
  for (const platform of targetPlatforms) {
1542
1718
  const platformCommandsPath = getCommandsInstallPath(platform);
1719
+ if (!platformCommandsPath) {
1720
+ continue;
1721
+ }
1543
1722
  for (const file of commandFiles) {
1544
- const commandPath = join7(platformCommandsPath, file);
1545
- if (existsSync6(commandPath)) {
1546
- rmSync2(commandPath);
1723
+ const commandPath = join8(platformCommandsPath, file);
1724
+ if (existsSync7(commandPath)) {
1725
+ rmSync3(commandPath);
1547
1726
  }
1548
1727
  }
1549
1728
  }
@@ -1555,9 +1734,9 @@ function uninstallSkill(skillName) {
1555
1734
  agentsToRemove.add(agentName);
1556
1735
  }
1557
1736
  }
1558
- const agentsSource = skillPath ? join7(skillPath.toolDir, "agents") : null;
1559
- if (agentsSource && existsSync6(agentsSource)) {
1560
- const agentFiles = readdirSync5(agentsSource, { withFileTypes: true }).filter((dirent) => dirent.isFile() && dirent.name.endsWith(".md")).map((dirent) => dirent.name.replace(".md", ""));
1737
+ const agentsSource = skillPath ? join8(skillPath.toolDir, "agents") : null;
1738
+ if (agentsSource && existsSync7(agentsSource)) {
1739
+ const agentFiles = readdirSync6(agentsSource, { withFileTypes: true }).filter((dirent) => dirent.isFile() && dirent.name.endsWith(".md")).map((dirent) => dirent.name.replace(".md", ""));
1561
1740
  for (const agentName of agentFiles) {
1562
1741
  agentsToRemove.add(agentName);
1563
1742
  }
@@ -1581,7 +1760,8 @@ function uninstallSkill(skillName) {
1581
1760
  var PLATFORM_LABELS = {
1582
1761
  ["claude-code" /* ClaudeCode */]: "Claude Code",
1583
1762
  ["cursor" /* Cursor */]: "Cursor",
1584
- ["opencode" /* OpenCode */]: "OpenCode"
1763
+ ["opencode" /* OpenCode */]: "OpenCode",
1764
+ ["openai-codex" /* OpenAICodex */]: "Codex"
1585
1765
  };
1586
1766
  var DROID_PERMISSIONS = [
1587
1767
  "Read(~/.droid/**)",
@@ -1590,6 +1770,75 @@ var DROID_PERMISSIONS = [
1590
1770
  "Glob(~/.droid/**)",
1591
1771
  "Grep(~/.droid/**)"
1592
1772
  ];
1773
+ var SKILL_OVERRIDES_TEMPLATE = `---
1774
+ overrides: []
1775
+ ---
1776
+
1777
+ # Skill Override Template
1778
+
1779
+ Copy this file to \`{skill_name}.md\` to override a skill's behaviour.
1780
+
1781
+ ## Format
1782
+
1783
+ \`\`\`yaml
1784
+ ---
1785
+ overrides: [command1, command2]
1786
+ ---
1787
+ \`\`\`
1788
+
1789
+ Then add sections for each command you're overriding:
1790
+
1791
+ \`\`\`markdown
1792
+ ## {command1}
1793
+
1794
+ Your custom instructions for this command...
1795
+ \`\`\`
1796
+
1797
+ ---
1798
+
1799
+ ## Example: Override brain search with qmd
1800
+
1801
+ Create \`brain.md\` with:
1802
+
1803
+ \`\`\`yaml
1804
+ ---
1805
+ overrides: [search]
1806
+ ---
1807
+ \`\`\`
1808
+
1809
+ ## search
1810
+
1811
+ Use qmd for semantic search:
1812
+
1813
+ 1. Run \`qmd query "{query}" -c brain -n 10 --files\`
1814
+ 2. Parse CSV output, extract filepath (field 3)
1815
+ 3. If qmd fails, fall back to Glob
1816
+
1817
+ ---
1818
+
1819
+ ## Registration
1820
+
1821
+ After creating an override file, ask droid to register it:
1822
+
1823
+ > "Register my brain override"
1824
+
1825
+ Droid will:
1826
+ 1. Parse the frontmatter to find which commands are overridden
1827
+ 2. Update config.yaml with override metadata
1828
+ 3. The skill will use your override on next invocation
1829
+ `;
1830
+ function createSkillOverridesDir() {
1831
+ const overridesDir = join9(getConfigDir(), "skill_overrides");
1832
+ const templatePath = join9(overridesDir, "_template.md");
1833
+ if (existsSync8(templatePath)) {
1834
+ return { created: false, alreadyExists: true };
1835
+ }
1836
+ if (!existsSync8(overridesDir)) {
1837
+ mkdirSync6(overridesDir, { recursive: true });
1838
+ }
1839
+ writeFileSync5(templatePath, SKILL_OVERRIDES_TEMPLATE, "utf-8");
1840
+ return { created: true, alreadyExists: false };
1841
+ }
1593
1842
  function detectGitUsername() {
1594
1843
  try {
1595
1844
  return execSync3("git config user.name", { encoding: "utf-8" }).trim();
@@ -1600,13 +1849,13 @@ function detectGitUsername() {
1600
1849
  function configurePlatformPermissions(platform) {
1601
1850
  const added = [];
1602
1851
  if (platform === "claude-code" /* ClaudeCode */) {
1603
- const settingsPath = join8(homedir4(), ".claude", "settings.json");
1604
- const claudeDir = join8(homedir4(), ".claude");
1605
- if (!existsSync7(claudeDir)) {
1606
- mkdirSync5(claudeDir, { recursive: true });
1852
+ const settingsPath = join9(homedir5(), ".claude", "settings.json");
1853
+ const claudeDir = join9(homedir5(), ".claude");
1854
+ if (!existsSync8(claudeDir)) {
1855
+ mkdirSync6(claudeDir, { recursive: true });
1607
1856
  }
1608
1857
  let settings = {};
1609
- if (existsSync7(settingsPath)) {
1858
+ if (existsSync8(settingsPath)) {
1610
1859
  try {
1611
1860
  settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
1612
1861
  } catch {
@@ -1637,20 +1886,20 @@ function configurePlatformPermissions(platform) {
1637
1886
  return { added, alreadyPresent: added.length === 0 };
1638
1887
  }
1639
1888
  if (platform === "opencode" /* OpenCode */) {
1640
- const globalConfigDir = join8(homedir4(), ".config", "opencode");
1641
- if (!existsSync7(globalConfigDir)) {
1642
- mkdirSync5(globalConfigDir, { recursive: true });
1889
+ const globalConfigDir = join9(homedir5(), ".config", "opencode");
1890
+ if (!existsSync8(globalConfigDir)) {
1891
+ mkdirSync6(globalConfigDir, { recursive: true });
1643
1892
  }
1644
1893
  return { added: [], alreadyPresent: true };
1645
1894
  }
1646
1895
  if (platform === "cursor" /* Cursor */) {
1647
- const cursorDir = join8(homedir4(), ".cursor");
1648
- if (!existsSync7(cursorDir)) {
1649
- mkdirSync5(cursorDir, { recursive: true });
1896
+ const cursorDir = join9(homedir5(), ".cursor");
1897
+ if (!existsSync8(cursorDir)) {
1898
+ mkdirSync6(cursorDir, { recursive: true });
1650
1899
  }
1651
- const agentsDir = join8(cursorDir, "agents");
1652
- if (!existsSync7(agentsDir)) {
1653
- mkdirSync5(agentsDir, { recursive: true });
1900
+ const agentsDir = join9(cursorDir, "agents");
1901
+ if (!existsSync8(agentsDir)) {
1902
+ mkdirSync6(agentsDir, { recursive: true });
1654
1903
  }
1655
1904
  return { added: [], alreadyPresent: true };
1656
1905
  }
@@ -1758,6 +2007,12 @@ async function setupCommand() {
1758
2007
  };
1759
2008
  saveConfig(config);
1760
2009
  console.log(chalk2.green("\n\u2713 Config saved to ~/.droid/config.yaml"));
2010
+ const { created: overridesCreated, alreadyExists: overridesExist } = createSkillOverridesDir();
2011
+ if (overridesCreated) {
2012
+ console.log(chalk2.green("\u2713 Created ~/.droid/skill_overrides/ with template"));
2013
+ } else if (overridesExist) {
2014
+ console.log(chalk2.gray(" Skill overrides directory already exists"));
2015
+ }
1761
2016
  for (const platform of activePlatforms) {
1762
2017
  const { added, alreadyPresent, error } = configurePlatformPermissions(platform);
1763
2018
  if (error) {
@@ -1772,6 +2027,16 @@ async function setupCommand() {
1772
2027
  console.log(chalk2.gray(` ${PLATFORM_LABELS[platform]} directories ready`));
1773
2028
  }
1774
2029
  }
2030
+ if (activePlatforms.includes("openai-codex" /* OpenAICodex */)) {
2031
+ const trackedTools = getPlatformTools(config);
2032
+ const toolNames = Object.keys(trackedTools);
2033
+ if (toolNames.length > 0) {
2034
+ for (const skillName of toolNames) {
2035
+ createCodexSymlink(skillName);
2036
+ }
2037
+ console.log(chalk2.green("\u2713 Created Codex symlinks for installed tools"));
2038
+ }
2039
+ }
1775
2040
  if (activePlatforms.length > 1) {
1776
2041
  console.log(chalk2.green(`
1777
2042
  \u2713 Will install to ${activePlatforms.length} platforms: ${activePlatforms.map((p) => PLATFORM_LABELS[p]).join(", ")}`));
@@ -2159,7 +2424,7 @@ async function uninstallCommand(toolName) {
2159
2424
 
2160
2425
  // src/commands/update.ts
2161
2426
  import chalk8 from "chalk";
2162
- import { execSync as execSync5 } from "child_process";
2427
+ import { execSync as execSync5, spawnSync } from "child_process";
2163
2428
  async function updateCommand(tool, options) {
2164
2429
  if (tool) {
2165
2430
  console.log(chalk8.yellow("\n\u26A0 Per-tool updates not implemented yet"));
@@ -2214,6 +2479,22 @@ async function updateCommand(tool, options) {
2214
2479
  execSync5("npm install -g @orderful/droid@latest", { stdio: "inherit" });
2215
2480
  console.log(chalk8.green(`
2216
2481
  \u2713 Updated to v${latestVersion}`));
2482
+ const autoUpdateConfig = getAutoUpdateConfig();
2483
+ if (autoUpdateConfig.tools) {
2484
+ console.log(chalk8.bold("\n\u{1F916} Syncing tools...\n"));
2485
+ const toolResult = spawnSync("droid", ["update", "--tools"], {
2486
+ stdio: "inherit",
2487
+ timeout: 6e4
2488
+ // 60s timeout for tool sync
2489
+ });
2490
+ if (toolResult.error || toolResult.status !== 0) {
2491
+ console.log(
2492
+ chalk8.yellow(
2493
+ "Tool sync incomplete - run `droid update --tools` to retry"
2494
+ )
2495
+ );
2496
+ }
2497
+ }
2217
2498
  } catch {
2218
2499
  console.log(chalk8.yellow("\n\u26A0 Could not check for updates"));
2219
2500
  console.log(chalk8.gray("Package may not be published yet."));
@@ -2413,7 +2694,8 @@ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2413
2694
  var PLATFORM_LABELS2 = {
2414
2695
  ["claude-code" /* ClaudeCode */]: "Claude Code",
2415
2696
  ["cursor" /* Cursor */]: "Cursor",
2416
- ["opencode" /* OpenCode */]: "OpenCode"
2697
+ ["opencode" /* OpenCode */]: "OpenCode",
2698
+ ["openai-codex" /* OpenAICodex */]: "OpenAI Codex"
2417
2699
  };
2418
2700
  function SettingsDetails({
2419
2701
  isFocused,
@@ -2510,7 +2792,8 @@ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2510
2792
  var PLATFORM_INFO = {
2511
2793
  ["claude-code" /* ClaudeCode */]: { color: "#da7756", label: "Claude" },
2512
2794
  ["cursor" /* Cursor */]: { color: "#22d3ee", label: "Cursor" },
2513
- ["opencode" /* OpenCode */]: { color: "#4ade80", label: "OpenCode" }
2795
+ ["opencode" /* OpenCode */]: { color: "#4ade80", label: "OpenCode" },
2796
+ ["openai-codex" /* OpenAICodex */]: { color: "#10b981", label: "Codex" }
2514
2797
  };
2515
2798
  function PlatformBadges({
2516
2799
  detected,
@@ -2790,7 +3073,8 @@ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2790
3073
  var PLATFORM_LABELS3 = {
2791
3074
  ["claude-code" /* ClaudeCode */]: "Claude Code",
2792
3075
  ["cursor" /* Cursor */]: "Cursor",
2793
- ["opencode" /* OpenCode */]: "OpenCode"
3076
+ ["opencode" /* OpenCode */]: "OpenCode",
3077
+ ["openai-codex" /* OpenAICodex */]: "Codex"
2794
3078
  };
2795
3079
  function SetupScreen({ onComplete, onSkip, initialConfig, detectedPlatforms }) {
2796
3080
  const [step, setStep] = useState3("user_mention");
@@ -2853,6 +3137,12 @@ function SetupScreen({ onComplete, onSkip, initialConfig, detectedPlatforms }) {
2853
3137
  for (const platform of detectedPlatforms) {
2854
3138
  configurePlatformPermissions(platform);
2855
3139
  }
3140
+ if (detectedPlatforms.includes("openai-codex" /* OpenAICodex */)) {
3141
+ const trackedTools = getPlatformTools(config);
3142
+ for (const skillName of Object.keys(trackedTools)) {
3143
+ createCodexSymlink(skillName);
3144
+ }
3145
+ }
2856
3146
  onComplete();
2857
3147
  }
2858
3148
  }
@@ -3088,8 +3378,8 @@ function ReadmeViewer({ title, content, onClose }) {
3088
3378
  // src/commands/tui/views/ToolExplorer.tsx
3089
3379
  import { Box as Box11, Text as Text12, useInput as useInput5 } from "ink";
3090
3380
  import { useState as useState5, useMemo as useMemo3 } from "react";
3091
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
3092
- import { join as join9 } from "path";
3381
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
3382
+ import { join as join10 } from "path";
3093
3383
  import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3094
3384
  function ToolExplorer({ tool, onViewSource, onClose }) {
3095
3385
  const [selectedIndex, setSelectedIndex] = useState5(0);
@@ -3100,7 +3390,7 @@ function ToolExplorer({ tool, onViewSource, onClose }) {
3100
3390
  result.push({
3101
3391
  type: "skill",
3102
3392
  name: skill.name,
3103
- path: join9(toolDir, tool.name, "skills", skill.name, "SKILL.md")
3393
+ path: join10(toolDir, tool.name, "skills", skill.name, "SKILL.md")
3104
3394
  });
3105
3395
  }
3106
3396
  for (const cmd of tool.includes.commands) {
@@ -3108,14 +3398,14 @@ function ToolExplorer({ tool, onViewSource, onClose }) {
3108
3398
  result.push({
3109
3399
  type: "command",
3110
3400
  name: `/${cmdName}`,
3111
- path: join9(toolDir, tool.name, "commands", `${cmdName}.md`)
3401
+ path: join10(toolDir, tool.name, "commands", `${cmdName}.md`)
3112
3402
  });
3113
3403
  }
3114
3404
  for (const agent of tool.includes.agents) {
3115
3405
  result.push({
3116
3406
  type: "agent",
3117
3407
  name: agent,
3118
- path: join9(toolDir, tool.name, "agents", agent, "AGENT.md")
3408
+ path: join10(toolDir, tool.name, "agents", agent, "AGENT.md")
3119
3409
  });
3120
3410
  }
3121
3411
  return result;
@@ -3133,12 +3423,12 @@ function ToolExplorer({ tool, onViewSource, onClose }) {
3133
3423
  }
3134
3424
  if (key.return && items.length > 0) {
3135
3425
  const item = items[selectedIndex];
3136
- if (existsSync8(item.path)) {
3426
+ if (existsSync9(item.path)) {
3137
3427
  const content = readFileSync8(item.path, "utf-8");
3138
3428
  onViewSource(`${tool.name} / ${item.name}`, content);
3139
3429
  } else {
3140
3430
  const yamlPath = item.path.replace(".md", ".yaml");
3141
- if (existsSync8(yamlPath)) {
3431
+ if (existsSync9(yamlPath)) {
3142
3432
  const content = readFileSync8(yamlPath, "utf-8");
3143
3433
  onViewSource(`${tool.name} / ${item.name}`, content);
3144
3434
  }
@@ -3677,6 +3967,7 @@ function ReposViewerScreen({ onClose }) {
3677
3967
  // src/commands/tui/hooks/useAppUpdate.ts
3678
3968
  import { useState as useState8, useMemo as useMemo5 } from "react";
3679
3969
  import { useApp } from "ink";
3970
+ import { spawnSync as spawnSync2 } from "child_process";
3680
3971
  function useAppUpdate({ onUpdateSuccess, onUpdateFailure }) {
3681
3972
  const { exit } = useApp();
3682
3973
  const [isUpdating, setIsUpdating] = useState8(false);
@@ -3689,6 +3980,18 @@ function useAppUpdate({ onUpdateSuccess, onUpdateFailure }) {
3689
3980
  const blue = "\x1B[38;2;99;102;241m";
3690
3981
  const dim = "\x1B[38;2;106;106;106m";
3691
3982
  const reset = "\x1B[0m";
3983
+ const autoUpdateConfig = getAutoUpdateConfig();
3984
+ let toolStatus = `${blue}App updated.${reset}`;
3985
+ if (autoUpdateConfig.tools) {
3986
+ console.log("\nSyncing tools...");
3987
+ const toolResult = spawnSync2("droid", ["update", "--tools"], {
3988
+ stdio: "inherit",
3989
+ timeout: 6e4
3990
+ // 60s timeout for tool sync
3991
+ });
3992
+ const toolsSynced = !toolResult.error && toolResult.status === 0;
3993
+ toolStatus = toolsSynced ? `${blue}App and tools updated.${reset}` : `${blue}App updated.${reset} Tools sync incomplete - run ${blue}droid update --tools${reset} to retry.`;
3994
+ }
3692
3995
  const message = `
3693
3996
  ${dim}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${reset}
3694
3997
 
@@ -3696,6 +3999,7 @@ ${dim}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u
3696
3999
  ${dim}\u2551${reset} ${blue}\u25CF${reset} ${blue}\u25CF${reset} ${dim}\u2551${reset} ${blue}"It's quite possible this system${reset}
3697
4000
  ${dim}\u255A\u2550\u2566\u2550\u2566\u2550\u255D${reset} ${blue}is now fully operational."${reset}
3698
4001
 
4002
+ ${toolStatus}
3699
4003
  Run ${blue}droid${reset} to start the new version.
3700
4004
 
3701
4005
  ${dim}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${reset}
@@ -4254,8 +4558,8 @@ async function tuiCommand() {
4254
4558
  // src/commands/exec.ts
4255
4559
  import chalk9 from "chalk";
4256
4560
  import { spawn } from "child_process";
4257
- import { existsSync as existsSync9 } from "fs";
4258
- import { join as join10 } from "path";
4561
+ import { existsSync as existsSync10 } from "fs";
4562
+ import { join as join11 } from "path";
4259
4563
  function getRuntime(toolPath) {
4260
4564
  if (toolPath.endsWith(".ts") || toolPath.endsWith(".js")) {
4261
4565
  return { cmd: "bun", args: ["run", toolPath] };
@@ -4268,21 +4572,21 @@ function getRuntime(toolPath) {
4268
4572
  async function execCommand(tool, script, args) {
4269
4573
  const config = loadConfig();
4270
4574
  const skillsPath = getSkillsPath(config.platform);
4271
- const toolDir = join10(skillsPath, tool);
4272
- if (!existsSync9(toolDir)) {
4575
+ const toolDir = join11(skillsPath, tool);
4576
+ if (!existsSync10(toolDir)) {
4273
4577
  console.error(chalk9.red(`Tool '${tool}' not found at ${toolDir}`));
4274
4578
  process.exit(1);
4275
4579
  }
4276
- const scriptsDir = join10(toolDir, "scripts");
4277
- if (!existsSync9(scriptsDir)) {
4580
+ const scriptsDir = join11(toolDir, "scripts");
4581
+ if (!existsSync10(scriptsDir)) {
4278
4582
  console.error(chalk9.red(`No scripts directory in tool '${tool}'`));
4279
4583
  process.exit(1);
4280
4584
  }
4281
4585
  const extensions = [".ts", ".js", ".py"];
4282
4586
  let scriptPath = null;
4283
4587
  for (const ext of extensions) {
4284
- const candidate = join10(scriptsDir, script + ext);
4285
- if (existsSync9(candidate)) {
4588
+ const candidate = join11(scriptsDir, script + ext);
4589
+ if (existsSync10(candidate)) {
4286
4590
  scriptPath = candidate;
4287
4591
  break;
4288
4592
  }
@@ -4444,6 +4748,135 @@ async function reposGetCommand(name) {
4444
4748
  console.log(JSON.stringify(repo, null, 2));
4445
4749
  }
4446
4750
 
4751
+ // src/commands/auth.ts
4752
+ import inquirer5 from "inquirer";
4753
+ import chalk11 from "chalk";
4754
+ import { execSync as execSync6 } from "child_process";
4755
+
4756
+ // src/lib/secrets.ts
4757
+ function hasSlackToken() {
4758
+ return !!process.env.SLACK_USER_TOKEN;
4759
+ }
4760
+
4761
+ // src/commands/auth.ts
4762
+ var SLACK_SCOPES = "chat:write,canvases:write";
4763
+ var REDIRECT_URI = "https://localhost:9876/callback";
4764
+ function getShellConfig() {
4765
+ const shell = process.env.SHELL || "/bin/zsh";
4766
+ if (shell.includes("zsh")) return "~/.zshrc";
4767
+ if (shell.includes("bash")) return "~/.bashrc";
4768
+ if (shell.includes("fish")) return "~/.config/fish/config.fish";
4769
+ return "~/.profile";
4770
+ }
4771
+ async function authSlackCommand() {
4772
+ console.log(chalk11.bold("\n\u{1F510} Slack Authentication Setup\n"));
4773
+ if (hasSlackToken()) {
4774
+ console.log(chalk11.green("\u2713 SLACK_USER_TOKEN is already set in your environment\n"));
4775
+ const { reauth } = await inquirer5.prompt([
4776
+ {
4777
+ type: "confirm",
4778
+ name: "reauth",
4779
+ message: "Re-authenticate anyway?",
4780
+ default: false
4781
+ }
4782
+ ]);
4783
+ if (!reauth) {
4784
+ return;
4785
+ }
4786
+ console.log("");
4787
+ }
4788
+ const clientId = process.env.SLACK_CLIENT_ID;
4789
+ const clientSecret = process.env.SLACK_CLIENT_SECRET;
4790
+ if (!clientId || !clientSecret) {
4791
+ const shellConfig = getShellConfig();
4792
+ console.log(chalk11.yellow("Missing Slack app credentials.\n"));
4793
+ console.log(chalk11.gray('Get Client ID and Client Secret from 1Password ("Droid Slack App")'));
4794
+ console.log(chalk11.gray(`and add to your ${shellConfig}:
4795
+ `));
4796
+ console.log(chalk11.white(' export SLACK_CLIENT_ID="your-client-id"'));
4797
+ console.log(chalk11.white(' export SLACK_CLIENT_SECRET="your-client-secret"\n'));
4798
+ console.log(chalk11.gray("Then reload and run this again:\n"));
4799
+ console.log(chalk11.white(` source ${shellConfig} && droid auth slack
4800
+ `));
4801
+ return;
4802
+ }
4803
+ const authorizeUrl = `https://slack.com/oauth/v2/authorize?client_id=${clientId}&user_scope=${SLACK_SCOPES}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}`;
4804
+ console.log(chalk11.yellow("Step 1: Authorize in Slack\n"));
4805
+ console.log(chalk11.gray(' Go to the "Droid Setup" canvas in #rnd-updates and click "Install Droid Slack App"'));
4806
+ console.log(chalk11.gray(" Or open this URL:\n"));
4807
+ console.log(chalk11.cyan(` ${authorizeUrl}
4808
+ `));
4809
+ console.log(chalk11.gray(" After clicking Allow, you'll be redirected to a URL that won't load."));
4810
+ console.log(chalk11.gray(' Copy the "code" parameter from the URL bar.\n'));
4811
+ const { code } = await inquirer5.prompt([
4812
+ {
4813
+ type: "input",
4814
+ name: "code",
4815
+ message: "Paste the code:",
4816
+ validate: (input) => input.length > 0 || "Code is required"
4817
+ }
4818
+ ]);
4819
+ console.log(chalk11.yellow("\nStep 2: Exchanging code for token...\n"));
4820
+ try {
4821
+ const response = execSync6(
4822
+ `curl -s -X POST https://slack.com/api/oauth.v2.access -d "client_id=${clientId}" -d "client_secret=${clientSecret}" -d "code=${code}" -d "redirect_uri=${REDIRECT_URI}"`,
4823
+ { encoding: "utf-8" }
4824
+ );
4825
+ const result = JSON.parse(response);
4826
+ if (!result.ok) {
4827
+ console.log(chalk11.red(`\u2717 Slack API error: ${result.error}`));
4828
+ if (result.error === "invalid_code") {
4829
+ console.log(chalk11.gray(" The code may have expired. Try again with a fresh code."));
4830
+ }
4831
+ return;
4832
+ }
4833
+ const token = result.authed_user?.access_token;
4834
+ if (!token) {
4835
+ console.log(chalk11.red("\u2717 No user token in response"));
4836
+ console.log(chalk11.gray(" Response:"), JSON.stringify(result, null, 2));
4837
+ return;
4838
+ }
4839
+ const shellConfig = getShellConfig();
4840
+ console.log(chalk11.green("\u2713 Got token!\n"));
4841
+ console.log(chalk11.yellow("Step 3: Save the token\n"));
4842
+ console.log(chalk11.gray(` Add to your ${shellConfig}:
4843
+ `));
4844
+ console.log(chalk11.white(` export SLACK_USER_TOKEN="${token}"
4845
+ `));
4846
+ console.log(chalk11.gray(" Then reload your shell:\n"));
4847
+ console.log(chalk11.white(` source ${shellConfig}
4848
+ `));
4849
+ } catch (error) {
4850
+ console.log(chalk11.red("\u2717 Failed to exchange code for token"));
4851
+ if (error instanceof Error) {
4852
+ console.log(chalk11.gray(` ${error.message}`));
4853
+ }
4854
+ }
4855
+ }
4856
+ async function authStatusCommand() {
4857
+ console.log(chalk11.bold("\n\u{1F510} Auth Status\n"));
4858
+ const clientId = process.env.SLACK_CLIENT_ID;
4859
+ const clientSecret = process.env.SLACK_CLIENT_SECRET;
4860
+ const hasCredentials = clientId && clientSecret;
4861
+ if (hasCredentials) {
4862
+ console.log(chalk11.green("\u2713 Slack app credentials configured"));
4863
+ } else {
4864
+ console.log(chalk11.yellow("\u2717 Slack app credentials missing"));
4865
+ console.log(chalk11.gray(" Run: droid auth slack"));
4866
+ }
4867
+ if (hasSlackToken()) {
4868
+ const token = process.env.SLACK_USER_TOKEN;
4869
+ const masked = token.slice(0, 10) + "..." + token.slice(-4);
4870
+ console.log(chalk11.green("\u2713 Slack user token configured"));
4871
+ console.log(chalk11.gray(` Token: ${masked}`));
4872
+ } else {
4873
+ console.log(chalk11.yellow("\u2717 Slack user token missing"));
4874
+ if (hasCredentials) {
4875
+ console.log(chalk11.gray(" Run: droid auth slack"));
4876
+ }
4877
+ }
4878
+ }
4879
+
4447
4880
  // src/bin/droid.ts
4448
4881
  var version = getVersion();
4449
4882
  program.name("droid").description("Droid, teaching your AI new tricks").version(version);
@@ -4463,6 +4896,19 @@ program.command("uninstall <tool>").description("Uninstall a tool").action(unins
4463
4896
  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);
4464
4897
  program.command("tui").description("Launch interactive TUI dashboard").action(tuiCommand);
4465
4898
  program.command("exec <tool> <script>").description("Execute a tool script").argument("[args...]", "Arguments to pass to the script").allowUnknownOption().action(execCommand);
4899
+ var auth = program.command("auth").description("Set up authentication for external services");
4900
+ auth.command("slack").description("Set up Slack authentication for status updates").action(authSlackCommand);
4901
+ auth.command("status").description("Show authentication status").action(authStatusCommand);
4902
+ auth.action(authStatusCommand);
4903
+ if (configExists()) {
4904
+ const config = loadConfig();
4905
+ const newPlatforms = syncNewPlatforms(config);
4906
+ if (newPlatforms.length > 0) {
4907
+ console.log(chalk12.green(`\u2713 Detected new platform(s): ${newPlatforms.join(", ")}`));
4908
+ console.log(chalk12.gray(" Synced installed tools to new platform(s)\n"));
4909
+ saveConfig(config);
4910
+ }
4911
+ }
4466
4912
  if (process.argv.length === 2) {
4467
4913
  tuiCommand();
4468
4914
  } else {