@orderful/droid 0.24.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 (47) hide show
  1. package/.eslintrc.json +6 -4
  2. package/AGENTS.md +58 -0
  3. package/CHANGELOG.md +25 -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 +1 -1
  15. package/dist/tools/codex/skills/droid-codex/SKILL.md +81 -65
  16. package/dist/tools/codex/skills/droid-codex/references/creating.md +13 -51
  17. package/dist/tools/codex/skills/droid-codex/references/decisions.md +15 -19
  18. package/dist/tools/codex/skills/droid-codex/references/topics.md +14 -12
  19. package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts +31 -0
  20. package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts.map +1 -0
  21. package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
  22. package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts +20 -0
  23. package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts.map +1 -0
  24. package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
  25. package/dist/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
  26. package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts +23 -0
  27. package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts.map +1 -0
  28. package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.ts +172 -0
  29. package/package.json +1 -1
  30. package/src/bin/droid.ts +9 -0
  31. package/src/commands/config.ts +38 -4
  32. package/src/commands/exec.ts +96 -0
  33. package/src/commands/install.ts +1 -1
  34. package/src/commands/setup.ts +6 -6
  35. package/src/commands/tui.tsx +254 -175
  36. package/src/lib/migrations.ts +103 -10
  37. package/src/lib/quotes.ts +6 -6
  38. package/src/lib/skills.ts +168 -45
  39. package/src/tools/codex/TOOL.yaml +1 -1
  40. package/src/tools/codex/skills/droid-codex/SKILL.md +81 -65
  41. package/src/tools/codex/skills/droid-codex/references/creating.md +13 -51
  42. package/src/tools/codex/skills/droid-codex/references/decisions.md +15 -19
  43. package/src/tools/codex/skills/droid-codex/references/topics.md +14 -12
  44. package/src/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
  45. package/src/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
  46. package/src/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
  47. package/src/tools/codex/skills/droid-codex/scripts/git-start-write.ts +172 -0
@@ -1,8 +1,22 @@
1
- import { existsSync, appendFileSync, mkdirSync, renameSync, rmSync } from 'fs';
1
+ import {
2
+ existsSync,
3
+ appendFileSync,
4
+ mkdirSync,
5
+ renameSync,
6
+ rmSync,
7
+ readdirSync,
8
+ } from 'fs';
2
9
  import { join, dirname } from 'path';
3
10
  import { loadConfig, saveConfig, getConfigDir } from './config';
4
- import { type Migration } from './types';
11
+ import {
12
+ type Migration,
13
+ Platform,
14
+ getPlatformTools,
15
+ setPlatformTools,
16
+ } from './types';
5
17
  import { compareSemver } from './version';
18
+ import { getSkillsPath } from './platforms';
19
+ import { getBundledTools } from './tools';
6
20
 
7
21
  const MIGRATIONS_LOG_FILE = '.migrations.log';
8
22
 
@@ -21,7 +35,7 @@ function logMigration(
21
35
  fromVersion: string,
22
36
  toVersion: string,
23
37
  status: 'OK' | 'FAILED',
24
- error?: string
38
+ error?: string,
25
39
  ): void {
26
40
  const timestamp = new Date().toISOString();
27
41
  const logEntry = error
@@ -50,7 +64,10 @@ function logMigration(
50
64
  * This fixes a bug where config was saved to prefixed paths but SKILL.md
51
65
  * tells Claude to read from unprefixed paths.
52
66
  */
53
- function createConfigDirMigration(skillName: string, version: string): Migration {
67
+ function createConfigDirMigration(
68
+ skillName: string,
69
+ version: string,
70
+ ): Migration {
54
71
  const unprefixedName = skillName.replace(/^droid-/, '');
55
72
  return {
56
73
  version,
@@ -75,6 +92,67 @@ function createConfigDirMigration(skillName: string, version: string): Migration
75
92
  };
76
93
  }
77
94
 
95
+ /**
96
+ * Migration: Sync installed tools from disk to config
97
+ *
98
+ * Detects tools that are physically installed (skill directories exist) but not
99
+ * tracked in the platform's config. This handles platform switching scenarios.
100
+ */
101
+ function createPlatformSyncMigration(version: string): Migration {
102
+ return {
103
+ version,
104
+ description: 'Sync installed tools from disk to config',
105
+ up: () => {
106
+ const config = loadConfig();
107
+ const bundledTools = getBundledTools();
108
+ let configChanged = false;
109
+ const originalPlatform = config.platform;
110
+
111
+ // Check both platforms
112
+ for (const platformKey of [Platform.ClaudeCode, Platform.OpenCode]) {
113
+ const skillsPath = getSkillsPath(platformKey);
114
+ if (!existsSync(skillsPath)) continue;
115
+
116
+ config.platform = platformKey;
117
+ const trackedTools = getPlatformTools(config);
118
+ const installedDirs = readdirSync(skillsPath, { withFileTypes: true })
119
+ .filter((dirent) => dirent.isDirectory())
120
+ .map((dirent) => dirent.name);
121
+
122
+ for (const skillName of installedDirs) {
123
+ const normalizedName = skillName.replace(/^droid-/, '');
124
+ const isTracked =
125
+ trackedTools[skillName] ||
126
+ trackedTools[`droid-${normalizedName}`] ||
127
+ trackedTools[normalizedName];
128
+ if (isTracked) continue;
129
+
130
+ const matchingTool = bundledTools.find((tool) =>
131
+ tool.includes.skills.some((s) => s.name === skillName),
132
+ );
133
+
134
+ if (matchingTool) {
135
+ trackedTools[skillName] = {
136
+ version: matchingTool.version,
137
+ installed_at: new Date().toISOString(),
138
+ };
139
+ configChanged = true;
140
+ }
141
+ }
142
+
143
+ if (configChanged) {
144
+ setPlatformTools(config, trackedTools);
145
+ }
146
+ }
147
+
148
+ config.platform = originalPlatform;
149
+ if (configChanged) {
150
+ saveConfig(config);
151
+ }
152
+ },
153
+ };
154
+ }
155
+
78
156
  /**
79
157
  * Registry of all tool migrations
80
158
  * Key: tool name (e.g., "brain", "comments")
@@ -91,6 +169,8 @@ const TOOL_MIGRATIONS: Record<string, Migration[]> = {
91
169
  comments: [createConfigDirMigration('droid-comments', '0.2.6')],
92
170
  project: [createConfigDirMigration('droid-project', '0.1.5')],
93
171
  coach: [createConfigDirMigration('droid-coach', '0.1.3')],
172
+ // Global migration for the droid meta-tool
173
+ droid: [createPlatformSyncMigration('0.25.0')],
94
174
  };
95
175
 
96
176
  /**
@@ -111,7 +191,10 @@ export function getLastMigratedVersion(toolName: string): string {
111
191
  /**
112
192
  * Update the last migrated version for a tool
113
193
  */
114
- export function setLastMigratedVersion(toolName: string, version: string): void {
194
+ export function setLastMigratedVersion(
195
+ toolName: string,
196
+ version: string,
197
+ ): void {
115
198
  const config = loadConfig();
116
199
  if (!config.migrations) {
117
200
  config.migrations = {};
@@ -127,7 +210,7 @@ export function setLastMigratedVersion(toolName: string, version: string): void
127
210
  export function runMigrations(
128
211
  toolName: string,
129
212
  fromVersion: string,
130
- toVersion: string
213
+ toVersion: string,
131
214
  ): { success: boolean; error?: string } {
132
215
  const migrations = getToolMigrations(toolName);
133
216
 
@@ -151,10 +234,20 @@ export function runMigrations(
151
234
  migration.up(configDir);
152
235
  logMigration(toolName, fromVersion, migration.version, 'OK');
153
236
  } catch (error) {
154
- const errorMessage = error instanceof Error ? error.message : String(error);
155
- logMigration(toolName, fromVersion, migration.version, 'FAILED', errorMessage);
237
+ const errorMessage =
238
+ error instanceof Error ? error.message : String(error);
239
+ logMigration(
240
+ toolName,
241
+ fromVersion,
242
+ migration.version,
243
+ 'FAILED',
244
+ errorMessage,
245
+ );
156
246
  // Don't update version marker on failure - will retry next time
157
- return { success: false, error: `Migration ${migration.version} failed: ${errorMessage}` };
247
+ return {
248
+ success: false,
249
+ error: `Migration ${migration.version} failed: ${errorMessage}`,
250
+ };
158
251
  }
159
252
  }
160
253
 
@@ -169,7 +262,7 @@ export function runMigrations(
169
262
  */
170
263
  export function runToolMigrations(
171
264
  toolName: string,
172
- installedVersion: string
265
+ installedVersion: string,
173
266
  ): { success: boolean; error?: string } {
174
267
  const lastMigrated = getLastMigratedVersion(toolName);
175
268
 
package/src/lib/quotes.ts CHANGED
@@ -5,13 +5,13 @@
5
5
  export const quotes = [
6
6
  "I'm probably the droid you are looking for.",
7
7
  "These are the skills you're looking for.",
8
- "I have a bad feeling about this, we might be too efficient.",
9
- "May the source be with you.",
10
- "The Force is strong with this one.",
11
- "This is the way.",
8
+ 'I have a bad feeling about this, we might be too efficient.',
9
+ 'May the source be with you.',
10
+ 'The Force is strong with this one.',
11
+ 'This is the way.',
12
12
  "I'm fluent in over six million forms of communication. - `/comments`",
13
- "Much to learn, you still have. - `/coach`",
14
- "If into the archives you go, only knowledge will you find. - `/project`",
13
+ 'Much to learn, you still have. - `/coach`',
14
+ 'If into the archives you go, only knowledge will you find. - `/project`',
15
15
  ];
16
16
 
17
17
  /**
package/src/lib/skills.ts CHANGED
@@ -1,10 +1,29 @@
1
- import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, rmSync } from 'fs';
1
+ import {
2
+ existsSync,
3
+ readdirSync,
4
+ readFileSync,
5
+ mkdirSync,
6
+ writeFileSync,
7
+ rmSync,
8
+ } from 'fs';
2
9
  import { join, dirname, basename } from 'path';
3
10
  import { fileURLToPath } from 'url';
4
11
  import YAML from 'yaml';
5
12
  import { loadConfig, saveConfig } from './config';
6
- import { Platform, SkillStatus, type SkillManifest, type InstalledSkill, getPlatformTools, setPlatformTools } from './types';
7
- import { getInstalledAgentsDir, installAgentFromPath, uninstallAgent, isAgentInstalled } from './agents';
13
+ import {
14
+ Platform,
15
+ SkillStatus,
16
+ type SkillManifest,
17
+ type InstalledSkill,
18
+ getPlatformTools,
19
+ setPlatformTools,
20
+ } from './types';
21
+ import {
22
+ getInstalledAgentsDir,
23
+ installAgentFromPath,
24
+ uninstallAgent,
25
+ isAgentInstalled,
26
+ } from './agents';
8
27
  import { getSkillsPath, getCommandsPath, getConfigPath } from './platforms';
9
28
  import { loadToolManifest } from './tools';
10
29
  import { runToolMigrations } from './migrations';
@@ -47,7 +66,10 @@ export function getPlatformConfigPath(platform: Platform): string {
47
66
  /**
48
67
  * Update the platform's config file with skill registrations
49
68
  */
50
- export function updatePlatformConfigSkills(platform: Platform, installedSkills: string[]): void {
69
+ export function updatePlatformConfigSkills(
70
+ platform: Platform,
71
+ installedSkills: string[],
72
+ ): void {
51
73
  const configPath = getPlatformConfigPath(platform);
52
74
 
53
75
  let content = '';
@@ -56,14 +78,15 @@ export function updatePlatformConfigSkills(platform: Platform, installedSkills:
56
78
  }
57
79
 
58
80
  // Generate skills section
59
- const skillLines = installedSkills.map(name => {
81
+ const skillLines = installedSkills.map((name) => {
60
82
  const relativePath = `skills/${name}/SKILL.md`;
61
83
  return `- [${name}](${relativePath})`;
62
84
  });
63
85
 
64
- const skillsSection = installedSkills.length > 0
65
- ? `${DROID_SKILLS_START}\n## Droid Skills\n\n${skillLines.join('\n')}\n${DROID_SKILLS_END}`
66
- : '';
86
+ const skillsSection =
87
+ installedSkills.length > 0
88
+ ? `${DROID_SKILLS_START}\n## Droid Skills\n\n${skillLines.join('\n')}\n${DROID_SKILLS_END}`
89
+ : '';
67
90
 
68
91
  // Check if section already exists
69
92
  const startIdx = content.indexOf(DROID_SKILLS_START);
@@ -71,7 +94,10 @@ export function updatePlatformConfigSkills(platform: Platform, installedSkills:
71
94
 
72
95
  if (startIdx !== -1 && endIdx !== -1) {
73
96
  // Replace existing section
74
- content = content.slice(0, startIdx) + skillsSection + content.slice(endIdx + DROID_SKILLS_END.length);
97
+ content =
98
+ content.slice(0, startIdx) +
99
+ skillsSection +
100
+ content.slice(endIdx + DROID_SKILLS_END.length);
75
101
  } else if (skillsSection) {
76
102
  // Append new section
77
103
  content = content.trim() + '\n\n' + skillsSection + '\n';
@@ -89,7 +115,9 @@ export function updatePlatformConfigSkills(platform: Platform, installedSkills:
89
115
  /**
90
116
  * Parse YAML frontmatter from SKILL.md content
91
117
  */
92
- function parseSkillFrontmatter(content: string): Record<string, unknown> | null {
118
+ function parseSkillFrontmatter(
119
+ content: string,
120
+ ): Record<string, unknown> | null {
93
121
  const trimmed = content.trimStart();
94
122
  if (!trimmed.startsWith('---')) {
95
123
  return null;
@@ -131,7 +159,7 @@ export function loadSkillManifest(skillDir: string): SkillManifest | null {
131
159
 
132
160
  return {
133
161
  name: frontmatter.name as string,
134
- description: frontmatter.description as string || '',
162
+ description: (frontmatter.description as string) || '',
135
163
  version: toolManifest?.version || '0.0.0',
136
164
  status: toolManifest?.status,
137
165
  dependencies: toolManifest?.dependencies,
@@ -143,7 +171,9 @@ export function loadSkillManifest(skillDir: string): SkillManifest | null {
143
171
  * Find the path to a skill within the tools directory structure
144
172
  * Returns { toolDir, skillDir } or null if not found
145
173
  */
146
- export function findSkillPath(skillName: string): { toolDir: string; skillDir: string } | null {
174
+ export function findSkillPath(
175
+ skillName: string,
176
+ ): { toolDir: string; skillDir: string } | null {
147
177
  if (!existsSync(BUNDLED_SKILLS_DIR)) {
148
178
  return null;
149
179
  }
@@ -257,7 +287,11 @@ export function getSkillsWithUpdates(): Array<{
257
287
  }> {
258
288
  const config = loadConfig();
259
289
  const tools = getPlatformTools(config);
260
- const updates: Array<{ name: string; installedVersion: string; bundledVersion: string }> = [];
290
+ const updates: Array<{
291
+ name: string;
292
+ installedVersion: string;
293
+ bundledVersion: string;
294
+ }> = [];
261
295
 
262
296
  for (const skillName of Object.keys(tools)) {
263
297
  const status = getSkillUpdateStatus(skillName);
@@ -276,7 +310,10 @@ export function getSkillsWithUpdates(): Array<{
276
310
  /**
277
311
  * Update a skill to the latest bundled version
278
312
  */
279
- export function updateSkill(skillName: string): { success: boolean; message: string } {
313
+ export function updateSkill(skillName: string): {
314
+ success: boolean;
315
+ message: string;
316
+ } {
280
317
  const status = getSkillUpdateStatus(skillName);
281
318
 
282
319
  if (!status.installedVersion) {
@@ -284,11 +321,17 @@ export function updateSkill(skillName: string): { success: boolean; message: str
284
321
  }
285
322
 
286
323
  if (!status.bundledVersion) {
287
- return { success: false, message: `Skill '${skillName}' not found in bundled skills` };
324
+ return {
325
+ success: false,
326
+ message: `Skill '${skillName}' not found in bundled skills`,
327
+ };
288
328
  }
289
329
 
290
330
  if (!status.hasUpdate) {
291
- return { success: false, message: `Skill '${skillName}' is already at latest version (${status.installedVersion})` };
331
+ return {
332
+ success: false,
333
+ message: `Skill '${skillName}' is already at latest version (${status.installedVersion})`,
334
+ };
292
335
  }
293
336
 
294
337
  // Reinstall the skill (install handles overwriting existing files)
@@ -348,7 +391,10 @@ export function updateAllSkills(): {
348
391
  /**
349
392
  * Install a skill
350
393
  */
351
- export function installSkill(skillName: string): { success: boolean; message: string } {
394
+ export function installSkill(skillName: string): {
395
+ success: boolean;
396
+ message: string;
397
+ } {
352
398
  const config = loadConfig();
353
399
  const skillPath = findSkillPath(skillName);
354
400
 
@@ -359,7 +405,10 @@ export function installSkill(skillName: string): { success: boolean; message: st
359
405
  const { toolDir, skillDir } = skillPath;
360
406
  const manifest = loadSkillManifest(skillDir);
361
407
  if (!manifest) {
362
- return { success: false, message: `Invalid skill manifest for '${skillName}'` };
408
+ return {
409
+ success: false,
410
+ message: `Invalid skill manifest for '${skillName}'`,
411
+ };
363
412
  }
364
413
 
365
414
  // Check dependencies
@@ -412,7 +461,9 @@ export function installSkill(skillName: string): { success: boolean; message: st
412
461
  rmSync(oldSkillDir, { recursive: true });
413
462
  } catch (error) {
414
463
  // Non-fatal: Log warning but continue installation
415
- console.warn(`Warning: Could not remove old skill directory ${oldSkillDir}: ${error}`);
464
+ console.warn(
465
+ `Warning: Could not remove old skill directory ${oldSkillDir}: ${error}`,
466
+ );
416
467
  }
417
468
  }
418
469
 
@@ -433,14 +484,40 @@ export function installSkill(skillName: string): { success: boolean; message: st
433
484
  // Check for collisions BEFORE installing (only if not already installed by droid)
434
485
  // Note: If skill folder exists but skill isn't in config, we allow overwriting (stale state
435
486
  // from platform switch or manual cleanup). Command/agent collisions still checked.
436
- if (!tools[skillName]) {
487
+ //
488
+ // Special handling for droid- prefix migration (temporary workaround for Claude Code bug #14945):
489
+ // If installing droid-comments, check if 'comments' is in config (old naming)
490
+ const oldSkillName = skillName.startsWith('droid-')
491
+ ? skillName.replace(/^droid-/, '')
492
+ : null;
493
+ const isAlreadyInstalled =
494
+ tools[skillName] || (oldSkillName && tools[oldSkillName]);
495
+
496
+ if (!isAlreadyInstalled) {
497
+ // For collision detection, also check if this is the same tool (with/without droid- prefix)
498
+ // E.g., installing droid-comments should be allowed to overwrite existing comments.md
499
+ //
500
+ // TO REVERSE WHEN BUG #14945 IS FIXED:
501
+ // Remove the normalizedToolName logic - tools will have original names without droid- prefix
502
+ const toolName = basename(toolDir);
503
+ const normalizedToolName = toolName.replace(/^droid-/, '');
504
+
437
505
  // Check command file collisions (these could conflict with other skills)
438
506
  if (existsSync(commandsSource)) {
439
- const commandFiles = readdirSync(commandsSource).filter(f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
507
+ const commandFiles = readdirSync(commandsSource).filter(
508
+ (f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md',
509
+ );
440
510
  for (const file of commandFiles) {
441
511
  const targetCommandPath = join(commandsPath, file);
442
512
  if (existsSync(targetCommandPath)) {
443
513
  const commandName = file.replace('.md', '');
514
+
515
+ // Allow overwrite if the command name matches the tool name (with/without prefix)
516
+ // E.g., installing 'comments' tool can overwrite 'comments.md'
517
+ if (commandName === toolName || commandName === normalizedToolName) {
518
+ continue; // Same tool, allow overwrite
519
+ }
520
+
444
521
  return {
445
522
  success: false,
446
523
  message: `Cannot install: command /${commandName} already exists at ${targetCommandPath}`,
@@ -451,10 +528,10 @@ export function installSkill(skillName: string): { success: boolean; message: st
451
528
 
452
529
  // Check bundled agent collisions
453
530
  if (existsSync(agentsSource)) {
454
- const agentDirs = readdirSync(agentsSource, { withFileTypes: true })
455
- .filter(dirent => dirent.isDirectory())
456
- .map(dirent => dirent.name);
457
- for (const agentName of agentDirs) {
531
+ const agentFiles = readdirSync(agentsSource, { withFileTypes: true })
532
+ .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))
533
+ .map((dirent) => dirent.name.replace('.md', ''));
534
+ for (const agentName of agentFiles) {
458
535
  if (isAgentInstalled(agentName)) {
459
536
  return {
460
537
  success: false,
@@ -488,7 +565,9 @@ export function installSkill(skillName: string): { success: boolean; message: st
488
565
  if (!existsSync(targetReferencesDir)) {
489
566
  mkdirSync(targetReferencesDir, { recursive: true });
490
567
  }
491
- const referenceFiles = readdirSync(referencesSource).filter(f => f.endsWith('.md'));
568
+ const referenceFiles = readdirSync(referencesSource).filter((f) =>
569
+ f.endsWith('.md'),
570
+ );
492
571
  for (const file of referenceFiles) {
493
572
  const sourcePath = join(referencesSource, file);
494
573
  const targetPath = join(targetReferencesDir, file);
@@ -497,12 +576,32 @@ export function installSkill(skillName: string): { success: boolean; message: st
497
576
  }
498
577
  }
499
578
 
579
+ // Copy scripts if present (deterministic CLI scripts for the skill)
580
+ const scriptsSource = join(skillDir, 'scripts');
581
+ if (existsSync(scriptsSource)) {
582
+ const targetScriptsDir = join(targetSkillDir, 'scripts');
583
+ if (!existsSync(targetScriptsDir)) {
584
+ mkdirSync(targetScriptsDir, { recursive: true });
585
+ }
586
+ const scriptFiles = readdirSync(scriptsSource).filter(
587
+ (f) => f.endsWith('.ts') || f.endsWith('.js') || f.endsWith('.py'),
588
+ );
589
+ for (const file of scriptFiles) {
590
+ const sourcePath = join(scriptsSource, file);
591
+ const targetPath = join(targetScriptsDir, file);
592
+ const content = readFileSync(sourcePath, 'utf-8');
593
+ writeFileSync(targetPath, content);
594
+ }
595
+ }
596
+
500
597
  // Copy commands if present (from tool level)
501
598
  if (existsSync(commandsSource)) {
502
599
  if (!existsSync(commandsPath)) {
503
600
  mkdirSync(commandsPath, { recursive: true });
504
601
  }
505
- const commandFiles = readdirSync(commandsSource).filter(f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
602
+ const commandFiles = readdirSync(commandsSource).filter(
603
+ (f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md',
604
+ );
506
605
  for (const file of commandFiles) {
507
606
  const sourcePath = join(commandsSource, file);
508
607
  const targetPath = join(commandsPath, file);
@@ -514,13 +613,13 @@ export function installSkill(skillName: string): { success: boolean; message: st
514
613
  // Install bundled agents if present (from tool level)
515
614
  const installedAgents: string[] = [];
516
615
  if (existsSync(agentsSource)) {
517
- const agentDirs = readdirSync(agentsSource, { withFileTypes: true })
518
- .filter(dirent => dirent.isDirectory())
519
- .map(dirent => dirent.name);
616
+ const agentFiles = readdirSync(agentsSource, { withFileTypes: true })
617
+ .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))
618
+ .map((dirent) => dirent.name.replace('.md', ''));
520
619
 
521
- for (const agentName of agentDirs) {
522
- const agentDir = join(agentsSource, agentName);
523
- const result = installAgentFromPath(agentDir, agentName);
620
+ for (const agentName of agentFiles) {
621
+ const agentPath = join(agentsSource, `${agentName}.md`);
622
+ const result = installAgentFromPath(agentPath, agentName);
524
623
  if (result.success) {
525
624
  installedAgents.push(agentName);
526
625
  }
@@ -548,16 +647,24 @@ export function installSkill(skillName: string): { success: boolean; message: st
548
647
  const migrationResult = runToolMigrations(toolName, manifest.version);
549
648
  if (!migrationResult.success) {
550
649
  // Log warning but don't fail the install - migration will retry next time
551
- console.warn(`Warning: Migration failed for ${toolName}: ${migrationResult.error}`);
650
+ console.warn(
651
+ `Warning: Migration failed for ${toolName}: ${migrationResult.error}`,
652
+ );
552
653
  }
553
654
 
554
- return { success: true, message: `Installed ${skillName} v${manifest.version}` };
655
+ return {
656
+ success: true,
657
+ message: `Installed ${skillName} v${manifest.version}`,
658
+ };
555
659
  }
556
660
 
557
661
  /**
558
662
  * Uninstall a skill
559
663
  */
560
- export function uninstallSkill(skillName: string): { success: boolean; message: string } {
664
+ export function uninstallSkill(skillName: string): {
665
+ success: boolean;
666
+ message: string;
667
+ } {
561
668
  const config = loadConfig();
562
669
  const tools = getPlatformTools(config);
563
670
 
@@ -577,7 +684,9 @@ export function uninstallSkill(skillName: string): { success: boolean; message:
577
684
  const commandsPath = getCommandsInstallPath(config.platform);
578
685
  const commandsSource = skillPath ? join(skillPath.toolDir, 'commands') : null;
579
686
  if (commandsSource && existsSync(commandsSource)) {
580
- const commandFiles = readdirSync(commandsSource).filter(f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
687
+ const commandFiles = readdirSync(commandsSource).filter(
688
+ (f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md',
689
+ );
581
690
  for (const file of commandFiles) {
582
691
  const commandPath = join(commandsPath, file);
583
692
  if (existsSync(commandPath)) {
@@ -627,7 +736,10 @@ export function getSkillStatusDisplay(status?: SkillStatus): string {
627
736
  * @param commandName - The display name (e.g., "comments check")
628
737
  * @param skillName - The skill the command belongs to
629
738
  */
630
- export function isCommandInstalled(commandName: string, skillName: string): boolean {
739
+ export function isCommandInstalled(
740
+ commandName: string,
741
+ skillName: string,
742
+ ): boolean {
631
743
  // If the parent skill is installed, the command is installed with it
632
744
  if (isSkillInstalled(skillName)) {
633
745
  return true;
@@ -652,7 +764,7 @@ export function isCommandInstalled(commandName: string, skillName: string): bool
652
764
  */
653
765
  export function installCommand(
654
766
  commandName: string,
655
- skillName: string
767
+ skillName: string,
656
768
  ): { success: boolean; message: string } {
657
769
  const config = loadConfig();
658
770
 
@@ -664,7 +776,10 @@ export function installCommand(
664
776
 
665
777
  const commandsDir = join(skillPath.toolDir, 'commands');
666
778
  if (!existsSync(commandsDir)) {
667
- return { success: false, message: `No commands found for skill '${skillName}'` };
779
+ return {
780
+ success: false,
781
+ message: `No commands found for skill '${skillName}'`,
782
+ };
668
783
  }
669
784
 
670
785
  // Find the source command file
@@ -672,14 +787,19 @@ export function installCommand(
672
787
  ? commandName.slice(skillName.length + 1)
673
788
  : commandName;
674
789
 
675
- const files = readdirSync(commandsDir).filter(f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
676
- const sourceFile = files.find(f => {
790
+ const files = readdirSync(commandsDir).filter(
791
+ (f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md',
792
+ );
793
+ const sourceFile = files.find((f) => {
677
794
  const base = f.replace('.md', '');
678
795
  return base === cmdPart || base === cmdPart.replace(/\s+/g, '-');
679
796
  });
680
797
 
681
798
  if (!sourceFile) {
682
- return { success: false, message: `Command file not found for: ${commandName}` };
799
+ return {
800
+ success: false,
801
+ message: `Command file not found for: ${commandName}`,
802
+ };
683
803
  }
684
804
 
685
805
  // Ensure commands directory exists
@@ -705,7 +825,7 @@ export function installCommand(
705
825
  */
706
826
  export function uninstallCommand(
707
827
  commandName: string,
708
- skillName: string
828
+ skillName: string,
709
829
  ): { success: boolean; message: string } {
710
830
  const config = loadConfig();
711
831
 
@@ -737,7 +857,10 @@ export function uninstallCommand(
737
857
  rmSync(commandPath);
738
858
  return { success: true, message: `Uninstalled /${commandName}` };
739
859
  } catch (error) {
740
- return { success: false, message: `Failed to uninstall command: ${error}` };
860
+ return {
861
+ success: false,
862
+ message: `Failed to uninstall command: ${error}`,
863
+ };
741
864
  }
742
865
  }
743
866
  }
@@ -1,6 +1,6 @@
1
1
  name: codex
2
2
  description: "Shared organizational knowledge - PRDs, tech designs, domains, proposals, patterns, and explored topics. Use when loading project context, searching codex, capturing decisions, or creating new entries."
3
- version: 0.1.0
3
+ version: 0.1.2
4
4
  status: beta
5
5
 
6
6
  includes: