@orderful/droid 0.25.2 → 0.27.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 (159) hide show
  1. package/.claude-plugin/marketplace.json +114 -0
  2. package/.github/workflows/ci.yml +20 -0
  3. package/AGENTS.md +75 -18
  4. package/CHANGELOG.md +73 -0
  5. package/README.md +30 -4
  6. package/dist/bin/droid.js +164 -75
  7. package/dist/index.js +68 -26
  8. package/dist/lib/migrations.d.ts +8 -0
  9. package/dist/lib/migrations.d.ts.map +1 -1
  10. package/dist/lib/skill-config.d.ts.map +1 -1
  11. package/dist/lib/skills.d.ts.map +1 -1
  12. package/dist/lib/tools.d.ts.map +1 -1
  13. package/dist/lib/types.d.ts +10 -2
  14. package/dist/lib/types.d.ts.map +1 -1
  15. package/dist/tools/README.md +1 -1
  16. package/dist/tools/brain/.claude-plugin/plugin.json +16 -0
  17. package/dist/tools/brain/TOOL.yaml +7 -5
  18. package/dist/tools/brain/commands/brain.md +17 -49
  19. package/dist/tools/brain/commands/scratchpad.md +13 -50
  20. package/{src/tools/brain/skills/droid-brain → dist/tools/brain/skills/brain}/SKILL.md +6 -6
  21. package/dist/tools/brain/skills/{droid-brain-obsidian → brain-obsidian}/SKILL.md +9 -9
  22. package/dist/tools/coach/.claude-plugin/plugin.json +16 -0
  23. package/dist/tools/coach/TOOL.yaml +4 -3
  24. package/dist/tools/coach/commands/coach.md +14 -54
  25. package/{src/tools/coach/skills/droid-coach → dist/tools/coach/skills/coach}/SKILL.md +5 -4
  26. package/dist/tools/code-review/.claude-plugin/plugin.json +16 -0
  27. package/dist/tools/code-review/TOOL.yaml +4 -3
  28. package/dist/tools/code-review/commands/code-review.md +18 -102
  29. package/dist/tools/code-review/skills/code-review/SKILL.md +154 -0
  30. package/dist/tools/codex/.claude-plugin/plugin.json +16 -0
  31. package/dist/tools/codex/TOOL.yaml +5 -4
  32. package/dist/tools/codex/commands/codex.md +18 -65
  33. package/dist/tools/codex/skills/{droid-codex → codex}/SKILL.md +64 -45
  34. package/{src/tools/codex/skills/droid-codex → dist/tools/codex/skills/codex}/references/loading.md +94 -55
  35. package/dist/tools/codex/skills/codex/scripts/git-finish-write.d.ts.map +1 -0
  36. package/dist/tools/codex/skills/codex/scripts/git-preamble.d.ts.map +1 -0
  37. package/dist/tools/codex/skills/codex/scripts/git-start-write.d.ts.map +1 -0
  38. package/dist/tools/comments/.claude-plugin/plugin.json +16 -0
  39. package/dist/tools/comments/TOOL.yaml +4 -3
  40. package/dist/tools/comments/commands/comments.md +12 -14
  41. package/{src/tools/comments/skills/droid-comments → dist/tools/comments/skills/comments}/SKILL.md +4 -2
  42. package/dist/tools/droid/.claude-plugin/plugin.json +15 -0
  43. package/dist/tools/droid/TOOL.yaml +1 -1
  44. package/dist/tools/droid/skills/droid/SKILL.md +1 -1
  45. package/dist/tools/project/.claude-plugin/plugin.json +16 -0
  46. package/dist/tools/project/TOOL.yaml +4 -3
  47. package/dist/tools/project/commands/project.md +12 -27
  48. package/dist/tools/project/skills/{droid-project → project}/SKILL.md +13 -12
  49. package/dist/tools/tech-design/.claude-plugin/plugin.json +16 -0
  50. package/dist/tools/tech-design/TOOL.yaml +19 -0
  51. package/dist/tools/tech-design/commands/tech-design.md +31 -0
  52. package/dist/tools/tech-design/skills/tech-design/SKILL.md +218 -0
  53. package/dist/tools/tech-design/skills/tech-design/references/draft.md +321 -0
  54. package/dist/tools/tech-design/skills/tech-design/references/gaps.md +328 -0
  55. package/dist/tools/tech-design/skills/tech-design/references/publish.md +409 -0
  56. package/dist/tools/tech-design/skills/tech-design/references/research-doc-template.md +129 -0
  57. package/dist/tools/tech-design/skills/tech-design/references/rollup-template.md +55 -0
  58. package/dist/tools/tech-design/skills/tech-design/references/start.md +353 -0
  59. package/dist/tools/tech-design/skills/tech-design/references/think.md +356 -0
  60. package/dist/tools/tech-design/skills/tech-design/references/thought-doc-template.md +72 -0
  61. package/package.json +3 -2
  62. package/scripts/build-plugins.ts +207 -0
  63. package/src/commands/tui/components/Badge.test.tsx +10 -4
  64. package/src/commands/tui.tsx +4 -4
  65. package/src/lib/migrations.ts +95 -4
  66. package/src/lib/skill-config.ts +95 -57
  67. package/src/lib/skills.test.ts +199 -74
  68. package/src/lib/skills.ts +57 -56
  69. package/src/lib/tools.ts +19 -12
  70. package/src/lib/types.ts +20 -5
  71. package/src/tools/README.md +1 -1
  72. package/src/tools/brain/.claude-plugin/plugin.json +16 -0
  73. package/src/tools/brain/TOOL.yaml +7 -5
  74. package/src/tools/brain/commands/brain.md +17 -49
  75. package/src/tools/brain/commands/scratchpad.md +13 -50
  76. package/{dist/tools/brain/skills/droid-brain → src/tools/brain/skills/brain}/SKILL.md +6 -6
  77. package/src/tools/brain/skills/{droid-brain-obsidian → brain-obsidian}/SKILL.md +9 -9
  78. package/src/tools/coach/.claude-plugin/plugin.json +16 -0
  79. package/src/tools/coach/TOOL.yaml +4 -3
  80. package/src/tools/coach/commands/coach.md +14 -54
  81. package/{dist/tools/coach/skills/droid-coach → src/tools/coach/skills/coach}/SKILL.md +5 -4
  82. package/src/tools/code-review/.claude-plugin/plugin.json +16 -0
  83. package/src/tools/code-review/TOOL.yaml +4 -3
  84. package/src/tools/code-review/commands/code-review.md +18 -102
  85. package/src/tools/code-review/skills/code-review/SKILL.md +154 -0
  86. package/src/tools/codex/.claude-plugin/plugin.json +16 -0
  87. package/src/tools/codex/TOOL.yaml +5 -4
  88. package/src/tools/codex/commands/codex.md +18 -65
  89. package/src/tools/codex/skills/{droid-codex → codex}/SKILL.md +64 -45
  90. package/{dist/tools/codex/skills/droid-codex → src/tools/codex/skills/codex}/references/loading.md +94 -55
  91. package/src/tools/comments/.claude-plugin/plugin.json +16 -0
  92. package/src/tools/comments/TOOL.yaml +4 -3
  93. package/src/tools/comments/commands/comments.md +12 -14
  94. package/{dist/tools/comments/skills/droid-comments → src/tools/comments/skills/comments}/SKILL.md +4 -2
  95. package/src/tools/droid/.claude-plugin/plugin.json +15 -0
  96. package/src/tools/droid/TOOL.yaml +1 -1
  97. package/src/tools/droid/skills/droid/SKILL.md +1 -1
  98. package/src/tools/project/.claude-plugin/plugin.json +16 -0
  99. package/src/tools/project/TOOL.yaml +4 -3
  100. package/src/tools/project/commands/project.md +12 -27
  101. package/src/tools/project/skills/{droid-project → project}/SKILL.md +13 -12
  102. package/src/tools/tech-design/.claude-plugin/plugin.json +16 -0
  103. package/src/tools/tech-design/TOOL.yaml +19 -0
  104. package/src/tools/tech-design/commands/tech-design.md +31 -0
  105. package/src/tools/tech-design/skills/tech-design/SKILL.md +218 -0
  106. package/src/tools/tech-design/skills/tech-design/references/draft.md +321 -0
  107. package/src/tools/tech-design/skills/tech-design/references/gaps.md +328 -0
  108. package/src/tools/tech-design/skills/tech-design/references/publish.md +409 -0
  109. package/src/tools/tech-design/skills/tech-design/references/research-doc-template.md +129 -0
  110. package/src/tools/tech-design/skills/tech-design/references/rollup-template.md +55 -0
  111. package/src/tools/tech-design/skills/tech-design/references/start.md +353 -0
  112. package/src/tools/tech-design/skills/tech-design/references/think.md +356 -0
  113. package/src/tools/tech-design/skills/tech-design/references/thought-doc-template.md +72 -0
  114. package/dist/tools/code-review/skills/droid-code-review/SKILL.md +0 -55
  115. package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts.map +0 -1
  116. package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts.map +0 -1
  117. package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts.map +0 -1
  118. package/src/tools/code-review/skills/droid-code-review/SKILL.md +0 -55
  119. /package/dist/tools/brain/skills/{droid-brain → brain}/references/metadata.md +0 -0
  120. /package/dist/tools/brain/skills/{droid-brain → brain}/references/naming.md +0 -0
  121. /package/dist/tools/brain/skills/{droid-brain → brain}/references/templates.md +0 -0
  122. /package/dist/tools/brain/skills/{droid-brain → brain}/references/workflows.md +0 -0
  123. /package/dist/tools/brain/skills/{droid-brain-obsidian → brain-obsidian}/references/templates.md +0 -0
  124. /package/dist/tools/brain/skills/{droid-brain-obsidian → brain-obsidian}/references/workflows.md +0 -0
  125. /package/dist/tools/codex/skills/{droid-codex → codex}/references/creating.md +0 -0
  126. /package/dist/tools/codex/skills/{droid-codex → codex}/references/decisions.md +0 -0
  127. /package/dist/tools/codex/skills/{droid-codex → codex}/references/topics.md +0 -0
  128. /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-finish-write.d.ts +0 -0
  129. /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-finish-write.ts +0 -0
  130. /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-preamble.d.ts +0 -0
  131. /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-preamble.ts +0 -0
  132. /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-scripts.test.ts +0 -0
  133. /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-start-write.d.ts +0 -0
  134. /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-start-write.ts +0 -0
  135. /package/dist/tools/project/skills/{droid-project → project}/references/changelog.md +0 -0
  136. /package/dist/tools/project/skills/{droid-project → project}/references/creating.md +0 -0
  137. /package/dist/tools/project/skills/{droid-project → project}/references/loading.md +0 -0
  138. /package/dist/tools/project/skills/{droid-project → project}/references/templates.md +0 -0
  139. /package/dist/tools/project/skills/{droid-project → project}/references/updating.md +0 -0
  140. /package/dist/tools/project/skills/{droid-project → project}/references/versioning.md +0 -0
  141. /package/src/tools/brain/skills/{droid-brain → brain}/references/metadata.md +0 -0
  142. /package/src/tools/brain/skills/{droid-brain → brain}/references/naming.md +0 -0
  143. /package/src/tools/brain/skills/{droid-brain → brain}/references/templates.md +0 -0
  144. /package/src/tools/brain/skills/{droid-brain → brain}/references/workflows.md +0 -0
  145. /package/src/tools/brain/skills/{droid-brain-obsidian → brain-obsidian}/references/templates.md +0 -0
  146. /package/src/tools/brain/skills/{droid-brain-obsidian → brain-obsidian}/references/workflows.md +0 -0
  147. /package/src/tools/codex/skills/{droid-codex → codex}/references/creating.md +0 -0
  148. /package/src/tools/codex/skills/{droid-codex → codex}/references/decisions.md +0 -0
  149. /package/src/tools/codex/skills/{droid-codex → codex}/references/topics.md +0 -0
  150. /package/src/tools/codex/skills/{droid-codex → codex}/scripts/git-finish-write.ts +0 -0
  151. /package/src/tools/codex/skills/{droid-codex → codex}/scripts/git-preamble.ts +0 -0
  152. /package/src/tools/codex/skills/{droid-codex → codex}/scripts/git-scripts.test.ts +0 -0
  153. /package/src/tools/codex/skills/{droid-codex → codex}/scripts/git-start-write.ts +0 -0
  154. /package/src/tools/project/skills/{droid-project → project}/references/changelog.md +0 -0
  155. /package/src/tools/project/skills/{droid-project → project}/references/creating.md +0 -0
  156. /package/src/tools/project/skills/{droid-project → project}/references/loading.md +0 -0
  157. /package/src/tools/project/skills/{droid-project → project}/references/templates.md +0 -0
  158. /package/src/tools/project/skills/{droid-project → project}/references/updating.md +0 -0
  159. /package/src/tools/project/skills/{droid-project → project}/references/versioning.md +0 -0
@@ -0,0 +1,72 @@
1
+ # Thought Doc Template
2
+
3
+ Used when creating thought doc via `/brain plan tech-design-{project}`.
4
+
5
+ ```markdown
6
+ # [Tech Design] {Project Name}
7
+
8
+ **Type:** plan
9
+ **Status:** exploring
10
+ **Created:** {YYYY-MM-DD}
11
+ **PRD:** [[codex:projects/{project}/PRD]]
12
+ **Research:** [[tech-design-{project}-research]]
13
+
14
+ Tech design for {project}.
15
+
16
+ ## Background
17
+
18
+ {Why we're doing this - 2-3 paragraphs from PRD}
19
+
20
+ {What problem we're solving, who it's for, why it matters}
21
+
22
+ See research doc for codebase context and discovered patterns.
23
+
24
+ ## Proposal
25
+
26
+ {What we're building - to be drafted}
27
+
28
+ ## Open Questions
29
+
30
+ {What we don't know yet - to be explored}
31
+ ```
32
+
33
+ ## Variable Substitutions
34
+
35
+ | Variable | Example | Notes |
36
+ | ---------------- | ----------------------- | --------------------------- |
37
+ | `{Project Name}` | "Transaction Templates" | Human-readable project name |
38
+ | `{YYYY-MM-DD}` | "2026-01-10" | Current date |
39
+ | `{project}` | "transaction-templates" | Codex project slug |
40
+
41
+ ## Philosophy
42
+
43
+ **Start minimal:** 3 sections (Background, Proposal, Open Questions)
44
+
45
+ **Gaps surface more:** Use `/tech-design gaps` to discover what sections to add (Data Model, API Surface, Rollout Plan, etc.)
46
+
47
+ **Research doc separation:** The thought doc references the research doc but doesn't duplicate codebase discoveries. Keep the thought doc focused on architectural decisions.
48
+
49
+ ## Sections That Can Be Added
50
+
51
+ Via `/tech-design draft {section}` or `/tech-design gaps`:
52
+
53
+ - **Scope** - What's in/out explicitly
54
+ - **Data Model** - Tables, schemas, types
55
+ - **API Surface** - Endpoints, methods, interfaces
56
+ - **Implementation Plan** - Step-by-step approach
57
+ - **Key Decisions** - Options considered, what we chose and why
58
+ - **Trade-off Analysis** - Pros/cons of approach
59
+ - **Risks & Mitigations** - What could go wrong, how to handle it
60
+ - **Metrics** - What we'll measure
61
+ - **Rollout Plan** - Phased deployment, feature flags, rollback
62
+ - **Security Considerations** - Auth, validation, audit logging
63
+
64
+ ## Template Evolution
65
+
66
+ The template starts with 3 sections, but the thought doc grows based on:
67
+
68
+ 1. **User adds via `/draft {section}`** - AI generates section from research
69
+ 2. **User explores via `/think {topic}`** - May add sections organically
70
+ 3. **User identifies via `/gaps`** - Shows what's missing, suggests adding
71
+
72
+ The minimal start reduces cognitive load. Sections appear when needed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orderful/droid",
3
- "version": "0.25.2",
3
+ "version": "0.27.0",
4
4
  "description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,8 @@
8
8
  },
9
9
  "main": "dist/index.js",
10
10
  "scripts": {
11
- "build": "bun run lint && bun scripts/build.ts",
11
+ "build": "bun run lint && bun scripts/build.ts && bun scripts/build-plugins.ts",
12
+ "build:plugins": "bun scripts/build-plugins.ts",
12
13
  "dev": "tsc --watch",
13
14
  "start": "bun dist/bin/droid.js",
14
15
  "test": "bun test src/",
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Build Claude Code plugin manifests from src/tools/
4
+ *
5
+ * This script generates:
6
+ * - src/tools/{tool}/.claude-plugin/plugin.json (from TOOL.yaml)
7
+ * - .claude-plugin/marketplace.json (at repo root)
8
+ *
9
+ * No file duplication - skills, commands, and agents stay in place.
10
+ * The src/tools/ directory serves both TUI and plugin distribution.
11
+ *
12
+ * Note: All tools are published to the marketplace, including those with
13
+ * `system: true` in their TOOL.yaml. System tools can still be installed
14
+ * individually via the plugin system.
15
+ */
16
+
17
+ import { mkdirSync, writeFileSync, existsSync, readdirSync, readFileSync } from 'fs';
18
+ import { join } from 'path';
19
+ import { parse as parseYaml } from 'yaml';
20
+
21
+ interface ToolManifest {
22
+ name: string;
23
+ description: string;
24
+ version: string;
25
+ status?: string;
26
+ system?: boolean;
27
+ includes: {
28
+ skills: Array<{ name: string; required: boolean; description?: string }>;
29
+ commands: string[];
30
+ agents: string[];
31
+ };
32
+ dependencies?: string[];
33
+ config_schema?: Record<string, unknown>;
34
+ }
35
+
36
+ interface PluginJson {
37
+ name: string;
38
+ version: string;
39
+ description: string;
40
+ author: {
41
+ name: string;
42
+ url?: string;
43
+ };
44
+ repository?: string;
45
+ license?: string;
46
+ keywords?: string[];
47
+ }
48
+
49
+ interface MarketplacePlugin {
50
+ name: string;
51
+ description: string;
52
+ version: string;
53
+ source: {
54
+ source: string;
55
+ repo: string;
56
+ path: string;
57
+ };
58
+ author: {
59
+ name: string;
60
+ };
61
+ }
62
+
63
+ interface MarketplaceJson {
64
+ name: string;
65
+ description: string;
66
+ owner: {
67
+ name: string;
68
+ url?: string;
69
+ };
70
+ plugins: MarketplacePlugin[];
71
+ }
72
+
73
+ const TOOLS_DIR = 'src/tools';
74
+ const AUTHOR = {
75
+ name: 'Orderful',
76
+ url: 'https://github.com/orderful',
77
+ };
78
+ const REPO = 'https://github.com/orderful/droid';
79
+ const REPO_SHORT = 'orderful/droid';
80
+
81
+ /**
82
+ * Get the plugin name with droid- prefix (for namespace isolation).
83
+ * The 'droid' core tool keeps its name as-is to avoid 'droid-droid'.
84
+ */
85
+ function getPluginName(toolName: string): string {
86
+ if (toolName === 'droid') return 'droid';
87
+ return toolName.startsWith('droid-') ? toolName : `droid-${toolName}`;
88
+ }
89
+
90
+ function loadToolManifest(toolDir: string): ToolManifest | null {
91
+ const manifestPath = join(toolDir, 'TOOL.yaml');
92
+ if (!existsSync(manifestPath)) {
93
+ return null;
94
+ }
95
+ try {
96
+ const content = readFileSync(manifestPath, 'utf-8');
97
+ const parsed = parseYaml(content) as ToolManifest;
98
+ if (!parsed.name || !parsed.description || !parsed.version) {
99
+ console.error(`Invalid TOOL.yaml in ${toolDir}: missing required fields (name, description, version)`);
100
+ return null;
101
+ }
102
+ return parsed;
103
+ } catch (err) {
104
+ console.error(`Failed to parse TOOL.yaml in ${toolDir}:`, err);
105
+ return null;
106
+ }
107
+ }
108
+
109
+ function getToolDirs(): string[] {
110
+ const entries = readdirSync(TOOLS_DIR, { withFileTypes: true });
111
+ return entries
112
+ .filter((e) => e.isDirectory())
113
+ .map((e) => join(TOOLS_DIR, e.name))
114
+ .filter((dir) => existsSync(join(dir, 'TOOL.yaml')));
115
+ }
116
+
117
+ function createPluginJson(manifest: ToolManifest, pluginName: string): PluginJson {
118
+ return {
119
+ name: pluginName,
120
+ version: manifest.version,
121
+ description: manifest.description,
122
+ author: AUTHOR,
123
+ repository: REPO,
124
+ license: 'MIT',
125
+ keywords: [...new Set(['droid', 'ai', manifest.name])],
126
+ };
127
+ }
128
+
129
+ function buildPluginManifest(toolDir: string): MarketplacePlugin | null {
130
+ const manifest = loadToolManifest(toolDir);
131
+ if (!manifest) {
132
+ return null;
133
+ }
134
+
135
+ const pluginName = getPluginName(manifest.name);
136
+
137
+ console.log(`Generating plugin.json for: ${pluginName} (v${manifest.version})`);
138
+
139
+ // Create .claude-plugin directory in the tool directory
140
+ const claudePluginDir = join(toolDir, '.claude-plugin');
141
+ mkdirSync(claudePluginDir, { recursive: true });
142
+
143
+ // Generate plugin.json
144
+ const pluginJson = createPluginJson(manifest, pluginName);
145
+ writeFileSync(join(claudePluginDir, 'plugin.json'), JSON.stringify(pluginJson, null, 2) + '\n');
146
+
147
+ // Return marketplace entry (path is relative to repo root)
148
+ return {
149
+ name: pluginName,
150
+ description: manifest.description,
151
+ version: manifest.version,
152
+ source: {
153
+ source: 'github',
154
+ repo: REPO_SHORT,
155
+ path: toolDir, // e.g., "src/tools/brain"
156
+ },
157
+ author: {
158
+ name: AUTHOR.name,
159
+ },
160
+ };
161
+ }
162
+
163
+ function buildMarketplace(plugins: MarketplacePlugin[]): void {
164
+ // Create marketplace.json at repo root for clean discovery
165
+ const marketplaceDir = '.claude-plugin';
166
+ mkdirSync(marketplaceDir, { recursive: true });
167
+
168
+ const marketplace: MarketplaceJson = {
169
+ name: 'droid',
170
+ description: 'AI-powered development tools for Claude Code',
171
+ owner: AUTHOR,
172
+ plugins: plugins.sort((a, b) => a.name.localeCompare(b.name)),
173
+ };
174
+
175
+ writeFileSync(join(marketplaceDir, 'marketplace.json'), JSON.stringify(marketplace, null, 2) + '\n');
176
+ console.log(`\nMarketplace manifest created at .claude-plugin/marketplace.json with ${plugins.length} plugins`);
177
+ }
178
+
179
+ function main(): void {
180
+ console.log('Generating Claude Code plugin manifests in src/tools/\n');
181
+
182
+ // Build plugin.json for each tool
183
+ const toolDirs = getToolDirs();
184
+ const plugins: MarketplacePlugin[] = [];
185
+
186
+ for (const toolDir of toolDirs) {
187
+ const plugin = buildPluginManifest(toolDir);
188
+ if (plugin) {
189
+ plugins.push(plugin);
190
+ }
191
+ }
192
+
193
+ // Build marketplace manifest
194
+ buildMarketplace(plugins);
195
+
196
+ console.log('\nPlugin manifest generation complete!');
197
+ console.log(`\nTo test locally:`);
198
+ console.log(` /plugin marketplace add ${process.cwd()}`);
199
+ console.log(` /plugin install droid-brain@droid`);
200
+ }
201
+
202
+ try {
203
+ main();
204
+ } catch (err) {
205
+ console.error(err);
206
+ process.exit(1);
207
+ }
@@ -29,7 +29,9 @@ describe('Badge', () => {
29
29
  const types = ['skill', 'command', 'agent'] as const;
30
30
  for (const type of types) {
31
31
  const { lastFrame } = render(<Badge type={type} />);
32
- expect(lastFrame()).toContain(type.charAt(0).toUpperCase() + type.slice(1));
32
+ expect(lastFrame()).toContain(
33
+ type.charAt(0).toUpperCase() + type.slice(1),
34
+ );
33
35
  }
34
36
  });
35
37
  });
@@ -44,7 +46,7 @@ describe('ComponentBadges', () => {
44
46
  { name: 'skill1', required: true },
45
47
  { name: 'skill2', required: false },
46
48
  ],
47
- commands: ['cmd1'],
49
+ commands: [{ name: 'cmd1', is_alias: false }],
48
50
  agents: [],
49
51
  },
50
52
  };
@@ -63,7 +65,11 @@ describe('ComponentBadges', () => {
63
65
  it('renders singular form for single item', () => {
64
66
  const singleSkillTool: ToolManifest = {
65
67
  ...mockTool,
66
- includes: { skills: [{ name: 'skill1', required: true }], commands: [], agents: [] },
68
+ includes: {
69
+ skills: [{ name: 'skill1', required: true }],
70
+ commands: [],
71
+ agents: [],
72
+ },
67
73
  };
68
74
  const { lastFrame } = render(<ComponentBadges tool={singleSkillTool} />);
69
75
  expect(lastFrame()).toContain('1 skill');
@@ -82,7 +88,7 @@ describe('ComponentBadges', () => {
82
88
  ...mockTool,
83
89
  includes: {
84
90
  skills: [{ name: 's1', required: true }],
85
- commands: ['c1'],
91
+ commands: [{ name: 'c1', is_alias: false }],
86
92
  agents: ['a1'],
87
93
  },
88
94
  };
@@ -14,7 +14,7 @@ import {
14
14
  import { configExists, loadConfig, getAutoUpdateConfig } from '../lib/config';
15
15
  import { Platform, type ConfigOption, type ToolManifest } from '../lib/types';
16
16
  import { getVersion } from '../lib/version';
17
- import { runToolMigrations } from '../lib/migrations';
17
+ import { runPackageMigrations } from '../lib/migrations';
18
18
  import { type Tab, type View } from './tui/types';
19
19
  import { colors, MAX_VISIBLE_ITEMS } from './tui/constants';
20
20
  import { TabBar } from './tui/components/TabBar';
@@ -72,10 +72,10 @@ function App() {
72
72
  },
73
73
  });
74
74
 
75
- // Run droid meta-tool migration on startup (sync installed tools across platforms)
75
+ // Run droid package migrations on startup (sync installed tools across platforms)
76
76
  useEffect(() => {
77
- const droidVersion = getVersion();
78
- runToolMigrations('droid', droidVersion);
77
+ const packageVersion = getVersion();
78
+ runPackageMigrations(packageVersion);
79
79
  }, []);
80
80
 
81
81
  // Auto-update app if enabled and update available
@@ -153,6 +153,14 @@ function createPlatformSyncMigration(version: string): Migration {
153
153
  };
154
154
  }
155
155
 
156
+ /**
157
+ * Registry of package-level migrations
158
+ * These run when the @orderful/droid npm package updates
159
+ * Triggered by package version from package.json (e.g., 0.26.0)
160
+ * Run at TUI startup
161
+ */
162
+ const PACKAGE_MIGRATIONS: Migration[] = [createPlatformSyncMigration('0.25.0')];
163
+
156
164
  /**
157
165
  * Registry of all tool migrations
158
166
  * Key: tool name (e.g., "brain", "comments")
@@ -169,8 +177,6 @@ const TOOL_MIGRATIONS: Record<string, Migration[]> = {
169
177
  comments: [createConfigDirMigration('droid-comments', '0.2.6')],
170
178
  project: [createConfigDirMigration('droid-project', '0.1.5')],
171
179
  coach: [createConfigDirMigration('droid-coach', '0.1.3')],
172
- // Global migration for the droid meta-tool
173
- droid: [createPlatformSyncMigration('0.25.0')],
174
180
  };
175
181
 
176
182
  /**
@@ -185,7 +191,18 @@ export function getToolMigrations(toolName: string): Migration[] {
185
191
  */
186
192
  export function getLastMigratedVersion(toolName: string): string {
187
193
  const config = loadConfig();
188
- return config.migrations?.[toolName] ?? '0.0.0';
194
+
195
+ // Check new location first
196
+ if (config.migrations?.tools?.[toolName]) {
197
+ return config.migrations.tools[toolName];
198
+ }
199
+
200
+ // Fall back to old location (backward compatibility)
201
+ if (config.migrations && typeof config.migrations[toolName] === 'string') {
202
+ return config.migrations[toolName] as string;
203
+ }
204
+
205
+ return '0.0.0';
189
206
  }
190
207
 
191
208
  /**
@@ -196,10 +213,22 @@ export function setLastMigratedVersion(
196
213
  version: string,
197
214
  ): void {
198
215
  const config = loadConfig();
216
+
217
+ // Ensure new structure exists
199
218
  if (!config.migrations) {
200
219
  config.migrations = {};
201
220
  }
202
- config.migrations[toolName] = version;
221
+ if (!config.migrations.tools) {
222
+ config.migrations.tools = {};
223
+ }
224
+
225
+ config.migrations.tools[toolName] = version;
226
+
227
+ // Clean up old location if it exists
228
+ if (typeof config.migrations[toolName] === 'string') {
229
+ delete config.migrations[toolName];
230
+ }
231
+
203
232
  saveConfig(config);
204
233
  }
205
234
 
@@ -273,3 +302,65 @@ export function runToolMigrations(
273
302
 
274
303
  return runMigrations(toolName, lastMigrated, installedVersion);
275
304
  }
305
+
306
+ /**
307
+ * Run package-level migrations
308
+ * Call this on TUI startup with package version from package.json
309
+ */
310
+ export function runPackageMigrations(packageVersion: string): {
311
+ success: boolean;
312
+ error?: string;
313
+ } {
314
+ const config = loadConfig();
315
+ const lastMigrated = config.migrations?.package ?? '0.0.0';
316
+
317
+ // Only run if the package version is newer than last migrated
318
+ if (compareSemver(packageVersion, lastMigrated) <= 0) {
319
+ return { success: true };
320
+ }
321
+
322
+ // Filter migrations that need to run
323
+ const pendingMigrations = PACKAGE_MIGRATIONS.filter((m) => {
324
+ const afterFrom = compareSemver(m.version, lastMigrated) > 0;
325
+ const beforeOrAtTo = compareSemver(m.version, packageVersion) <= 0;
326
+ return afterFrom && beforeOrAtTo;
327
+ });
328
+
329
+ if (pendingMigrations.length === 0) {
330
+ // No migrations to run, but still update the version marker
331
+ config.migrations = config.migrations || {};
332
+ config.migrations.package = packageVersion;
333
+ saveConfig(config);
334
+ return { success: true };
335
+ }
336
+
337
+ const configDir = getConfigDir();
338
+
339
+ for (const migration of pendingMigrations) {
340
+ try {
341
+ migration.up(configDir);
342
+ logMigration('package', lastMigrated, migration.version, 'OK');
343
+ } catch (error) {
344
+ const errorMessage =
345
+ error instanceof Error ? error.message : String(error);
346
+ logMigration(
347
+ 'package',
348
+ lastMigrated,
349
+ migration.version,
350
+ 'FAILED',
351
+ errorMessage,
352
+ );
353
+ // Don't update version marker on failure - will retry next time
354
+ return {
355
+ success: false,
356
+ error: `Package migration ${migration.version} failed: ${errorMessage}`,
357
+ };
358
+ }
359
+ }
360
+
361
+ // All migrations succeeded, update version marker
362
+ config.migrations = config.migrations || {};
363
+ config.migrations.package = packageVersion;
364
+ saveConfig(config);
365
+ return { success: true };
366
+ }
@@ -1,7 +1,11 @@
1
1
  import chalk from 'chalk';
2
2
  import inquirer from 'inquirer';
3
3
  import { saveSkillOverrides, loadConfig, loadSkillOverrides } from './config';
4
- import { ConfigOptionType, type SkillOverrides, type ConfigOption } from './types';
4
+ import {
5
+ ConfigOptionType,
6
+ type SkillOverrides,
7
+ type ConfigOption,
8
+ } from './types';
5
9
 
6
10
  /**
7
11
  * Prompt user to configure a skill after install
@@ -9,12 +13,15 @@ import { ConfigOptionType, type SkillOverrides, type ConfigOption } from './type
9
13
  export async function promptForSkillConfig(
10
14
  skillName: string,
11
15
  configSchema: Record<string, ConfigOption>,
12
- askFirst: boolean = true
16
+ askFirst: boolean = true,
13
17
  ): Promise<void> {
14
18
  const globalConfig = loadConfig();
19
+ let shouldPrompt = true;
15
20
 
16
21
  if (askFirst) {
17
- const { wantsConfigure } = await inquirer.prompt<{ wantsConfigure: boolean }>([
22
+ const { wantsConfigure } = await inquirer.prompt<{
23
+ wantsConfigure: boolean;
24
+ }>([
18
25
  {
19
26
  type: 'confirm',
20
27
  name: 'wantsConfigure',
@@ -23,73 +30,104 @@ export async function promptForSkillConfig(
23
30
  },
24
31
  ]);
25
32
 
26
- if (!wantsConfigure) {
27
- return;
28
- }
33
+ shouldPrompt = wantsConfigure;
29
34
  }
30
35
 
31
- console.log(chalk.bold(`\n⚙️ Configure ${skillName}\n`));
32
-
33
36
  // Load existing overrides to use as defaults
34
37
  const existingOverrides = loadSkillOverrides(skillName);
35
38
 
36
- const questions = Object.entries(configSchema).map(([key, option]) => {
37
- const baseQuestion = {
38
- name: key,
39
- message: option.description,
40
- };
39
+ let overrides: SkillOverrides = {};
40
+
41
+ if (shouldPrompt) {
42
+ console.log(chalk.bold(`\n⚙️ Configure ${skillName}\n`));
41
43
 
42
- // Use existing override as default if present
43
- const existingValue = existingOverrides[key];
44
+ const questions = Object.entries(configSchema).map(([key, option]) => {
45
+ const baseQuestion = {
46
+ name: key,
47
+ message: option.description,
48
+ };
44
49
 
45
- switch (option.type) {
46
- case ConfigOptionType.Boolean:
47
- return {
48
- ...baseQuestion,
49
- type: 'confirm' as const,
50
- default: existingValue ?? option.default ?? false,
51
- };
52
- case ConfigOptionType.Select:
53
- return {
54
- ...baseQuestion,
55
- type: 'list' as const,
56
- choices: option.options || [],
57
- default: existingValue ?? option.default,
58
- };
59
- case ConfigOptionType.String:
60
- default: {
61
- // For user_mention, default to global config value if no existing override
62
- let defaultValue = existingValue ?? option.default ?? '';
63
- if (key === 'user_mention' && !existingValue && globalConfig.user_mention) {
64
- defaultValue = globalConfig.user_mention;
50
+ // Use existing override as default if present
51
+ const existingValue = existingOverrides[key];
52
+
53
+ switch (option.type) {
54
+ case ConfigOptionType.Boolean:
55
+ return {
56
+ ...baseQuestion,
57
+ type: 'confirm' as const,
58
+ default: existingValue ?? option.default ?? false,
59
+ };
60
+ case ConfigOptionType.Select:
61
+ return {
62
+ ...baseQuestion,
63
+ type: 'list' as const,
64
+ choices: option.options || [],
65
+ default: existingValue ?? option.default,
66
+ };
67
+ case ConfigOptionType.String:
68
+ default: {
69
+ // For user_mention, default to global config value if no existing override
70
+ let defaultValue = existingValue ?? option.default ?? '';
71
+ if (
72
+ key === 'user_mention' &&
73
+ !existingValue &&
74
+ globalConfig.user_mention
75
+ ) {
76
+ defaultValue = globalConfig.user_mention;
77
+ }
78
+ return {
79
+ ...baseQuestion,
80
+ type: 'input' as const,
81
+ default: defaultValue,
82
+ };
65
83
  }
66
- return {
67
- ...baseQuestion,
68
- type: 'input' as const,
69
- default: defaultValue,
70
- };
84
+ }
85
+ });
86
+
87
+ const answers = await inquirer.prompt(questions);
88
+
89
+ // Save all answers (including defaults)
90
+ for (const [key, value] of Object.entries(answers)) {
91
+ // Save all non-empty values
92
+ if (value !== '' && value !== undefined && value !== null) {
93
+ overrides[key] = value as string | boolean | number;
71
94
  }
72
95
  }
73
- });
96
+ } else {
97
+ // User declined to configure - use defaults from schema
98
+ for (const [key, option] of Object.entries(configSchema)) {
99
+ const existingValue = existingOverrides[key];
100
+ let defaultValue =
101
+ existingValue ??
102
+ option.default ??
103
+ (option.type === ConfigOptionType.Boolean ? false : '');
74
104
 
75
- const answers = await inquirer.prompt(questions);
105
+ // Special handling for user_mention
106
+ if (
107
+ key === 'user_mention' &&
108
+ !existingValue &&
109
+ globalConfig.user_mention
110
+ ) {
111
+ defaultValue = globalConfig.user_mention;
112
+ }
76
113
 
77
- // Filter out empty/default values to keep overrides minimal
78
- const overrides: SkillOverrides = {};
79
- for (const [key, value] of Object.entries(answers)) {
80
- const schema = configSchema[key];
81
- // Only save if different from default
82
- if (value !== schema.default && value !== '' && value !== false) {
83
- overrides[key] = value as string | boolean | number;
114
+ if (
115
+ defaultValue !== '' &&
116
+ defaultValue !== undefined &&
117
+ defaultValue !== null
118
+ ) {
119
+ overrides[key] = defaultValue as string | boolean | number;
120
+ }
84
121
  }
85
122
  }
86
123
 
87
- if (Object.keys(overrides).length > 0) {
88
- saveSkillOverrides(skillName, overrides);
89
- // Strip droid- prefix for display since that's where the file actually goes
90
- const displayName = skillName.replace(/^droid-/, '');
91
- console.log(chalk.green(`\n✓ Configuration saved to ~/.droid/skills/${displayName}/overrides.yaml`));
92
- } else {
93
- console.log(chalk.gray('\nNo custom configuration set (using defaults).'));
94
- }
124
+ // Always save overrides.yaml when a skill has a config schema
125
+ // Skills expect this file to exist even if all values are defaults
126
+ saveSkillOverrides(skillName, overrides);
127
+ const displayName = skillName.replace(/^droid-/, '');
128
+ console.log(
129
+ chalk.green(
130
+ `\n✓ Configuration saved to ~/.droid/skills/${displayName}/overrides.yaml`,
131
+ ),
132
+ );
95
133
  }