@orderful/droid 0.33.1 → 0.34.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.
- package/CHANGELOG.md +16 -0
- package/dist/bin/droid.js +673 -101
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/repos.d.ts +21 -0
- package/dist/commands/repos.d.ts.map +1 -0
- package/dist/commands/tui/components/SettingsDetails.d.ts +2 -1
- package/dist/commands/tui/components/SettingsDetails.d.ts.map +1 -1
- package/dist/commands/tui/types.d.ts +1 -1
- package/dist/commands/tui/types.d.ts.map +1 -1
- package/dist/commands/tui/views/ReposManagementScreen.d.ts +6 -0
- package/dist/commands/tui/views/ReposManagementScreen.d.ts.map +1 -0
- package/dist/commands/tui/views/ReposViewerScreen.d.ts +5 -0
- package/dist/commands/tui/views/ReposViewerScreen.d.ts.map +1 -0
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/index.js +149 -26
- package/dist/lib/config.d.ts +34 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/types.d.ts +10 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/tools/brain/skills/brain/SKILL.md +2 -2
- package/dist/tools/brain/skills/brain/references/workflows.md +10 -10
- package/dist/tools/coach/skills/coach/SKILL.md +6 -6
- package/dist/tools/codex/skills/codex/SKILL.md +5 -5
- package/dist/tools/codex/skills/codex/references/creating.md +2 -2
- package/dist/tools/codex/skills/codex/references/decisions.md +2 -2
- package/dist/tools/codex/skills/codex/references/loading.md +1 -1
- package/dist/tools/codex/skills/codex/references/topics.md +2 -2
- package/dist/tools/codex/skills/codex/scripts/git-finish-write.d.ts +1 -1
- package/dist/tools/codex/skills/codex/scripts/git-finish-write.ts +2 -2
- package/dist/tools/codex/skills/codex/scripts/git-preamble.d.ts +1 -1
- package/dist/tools/codex/skills/codex/scripts/git-preamble.ts +3 -3
- package/dist/tools/codex/skills/codex/scripts/git-start-write.d.ts +1 -1
- package/dist/tools/codex/skills/codex/scripts/git-start-write.ts +2 -2
- package/dist/tools/comments/skills/comments/SKILL.md +9 -9
- package/dist/tools/plan/skills/plan/SKILL.md +2 -2
- package/dist/tools/plan/skills/plan/references/workflows.md +2 -2
- package/dist/tools/project/skills/project/SKILL.md +1 -1
- package/dist/tools/project/skills/project/references/creating.md +2 -2
- package/dist/tools/project/skills/project/references/loading.md +1 -1
- package/dist/tools/tech-design/skills/tech-design/SKILL.md +2 -2
- package/dist/tools/tech-design/skills/tech-design/references/publish.md +3 -3
- package/dist/tools/tech-design/skills/tech-design/references/start.md +29 -3
- package/dist/tools/tech-design/skills/tech-design/references/think.md +1 -1
- package/dist/tools/wrapup/skills/wrapup/references/subagent-prompts.md +3 -3
- package/package.json +1 -1
- package/src/bin/droid.ts +39 -0
- package/src/commands/config.ts +14 -1
- package/src/commands/repos.ts +185 -0
- package/src/commands/tui/components/SettingsDetails.tsx +42 -13
- package/src/commands/tui/types.ts +1 -1
- package/src/commands/tui/views/ReposManagementScreen.tsx +291 -0
- package/src/commands/tui/views/ReposViewerScreen.tsx +49 -0
- package/src/commands/tui.tsx +51 -4
- package/src/lib/config.test.ts +228 -1
- package/src/lib/config.ts +193 -4
- package/src/lib/types.ts +13 -1
- package/src/tools/brain/skills/brain/SKILL.md +2 -2
- package/src/tools/brain/skills/brain/references/workflows.md +10 -10
- package/src/tools/coach/skills/coach/SKILL.md +6 -6
- package/src/tools/codex/skills/codex/SKILL.md +5 -5
- package/src/tools/codex/skills/codex/references/creating.md +2 -2
- package/src/tools/codex/skills/codex/references/decisions.md +2 -2
- package/src/tools/codex/skills/codex/references/loading.md +1 -1
- package/src/tools/codex/skills/codex/references/topics.md +2 -2
- package/src/tools/codex/skills/codex/scripts/git-finish-write.ts +2 -2
- package/src/tools/codex/skills/codex/scripts/git-preamble.ts +3 -3
- package/src/tools/codex/skills/codex/scripts/git-start-write.ts +2 -2
- package/src/tools/comments/skills/comments/SKILL.md +9 -9
- package/src/tools/plan/skills/plan/SKILL.md +2 -2
- package/src/tools/plan/skills/plan/references/workflows.md +2 -2
- package/src/tools/project/skills/project/SKILL.md +1 -1
- package/src/tools/project/skills/project/references/creating.md +2 -2
- package/src/tools/project/skills/project/references/loading.md +1 -1
- package/src/tools/tech-design/skills/tech-design/SKILL.md +2 -2
- package/src/tools/tech-design/skills/tech-design/references/publish.md +3 -3
- package/src/tools/tech-design/skills/tech-design/references/start.md +29 -3
- package/src/tools/tech-design/skills/tech-design/references/think.md +1 -1
- package/src/tools/wrapup/skills/wrapup/references/subagent-prompts.md +3 -3
package/src/commands/tui.tsx
CHANGED
|
@@ -29,6 +29,8 @@ import { SetupScreen } from './tui/views/SetupScreen';
|
|
|
29
29
|
import { ReadmeViewer } from './tui/views/ReadmeViewer';
|
|
30
30
|
import { ToolExplorer } from './tui/views/ToolExplorer';
|
|
31
31
|
import { SkillConfigScreen } from './tui/views/SkillConfigScreen';
|
|
32
|
+
import { ReposManagementScreen } from './tui/views/ReposManagementScreen';
|
|
33
|
+
import { ReposViewerScreen } from './tui/views/ReposViewerScreen';
|
|
32
34
|
import { useAppUpdate } from './tui/hooks/useAppUpdate';
|
|
33
35
|
import { useToolUpdates } from './tui/hooks/useToolUpdates';
|
|
34
36
|
|
|
@@ -215,10 +217,25 @@ function App() {
|
|
|
215
217
|
setSelectedAction(0);
|
|
216
218
|
}
|
|
217
219
|
if (activeTab === 'settings') {
|
|
218
|
-
// Settings detail view -
|
|
220
|
+
// Settings detail view - navigate actions (0 = edit, 1 = view repos, 2 = manage repos)
|
|
221
|
+
if (key.leftArrow) {
|
|
222
|
+
setSelectedAction((prev) => Math.max(0, prev - 1));
|
|
223
|
+
}
|
|
224
|
+
if (key.rightArrow) {
|
|
225
|
+
setSelectedAction((prev) => Math.min(2, prev + 1));
|
|
226
|
+
}
|
|
219
227
|
if (key.return) {
|
|
220
|
-
|
|
221
|
-
|
|
228
|
+
if (selectedAction === 0) {
|
|
229
|
+
// Edit config
|
|
230
|
+
setIsEditingSettings(true);
|
|
231
|
+
setView('setup');
|
|
232
|
+
} else if (selectedAction === 1) {
|
|
233
|
+
// View repos
|
|
234
|
+
setView('view-repos');
|
|
235
|
+
} else if (selectedAction === 2) {
|
|
236
|
+
// Manage repos
|
|
237
|
+
setView('repos');
|
|
238
|
+
}
|
|
222
239
|
}
|
|
223
240
|
}
|
|
224
241
|
if (key.leftArrow && activeTab === 'tools') {
|
|
@@ -336,7 +353,9 @@ function App() {
|
|
|
336
353
|
view !== 'tool-updates' &&
|
|
337
354
|
view !== 'setup' &&
|
|
338
355
|
view !== 'configure' &&
|
|
339
|
-
view !== 'explorer'
|
|
356
|
+
view !== 'explorer' &&
|
|
357
|
+
view !== 'repos' &&
|
|
358
|
+
view !== 'view-repos',
|
|
340
359
|
},
|
|
341
360
|
);
|
|
342
361
|
|
|
@@ -441,6 +460,33 @@ function App() {
|
|
|
441
460
|
);
|
|
442
461
|
}
|
|
443
462
|
|
|
463
|
+
if (view === 'view-repos') {
|
|
464
|
+
return (
|
|
465
|
+
<ReposViewerScreen
|
|
466
|
+
onClose={() => {
|
|
467
|
+
setView('detail');
|
|
468
|
+
}}
|
|
469
|
+
/>
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (view === 'repos') {
|
|
474
|
+
return (
|
|
475
|
+
<ReposManagementScreen
|
|
476
|
+
onComplete={() => {
|
|
477
|
+
setMessage({
|
|
478
|
+
text: '✓ Repos updated',
|
|
479
|
+
type: 'success',
|
|
480
|
+
});
|
|
481
|
+
setView('detail');
|
|
482
|
+
}}
|
|
483
|
+
onCancel={() => {
|
|
484
|
+
setView('detail');
|
|
485
|
+
}}
|
|
486
|
+
/>
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
444
490
|
return (
|
|
445
491
|
<Box flexDirection="row" padding={1}>
|
|
446
492
|
{/* Left column */}
|
|
@@ -550,6 +596,7 @@ function App() {
|
|
|
550
596
|
{activeTab === 'settings' && (
|
|
551
597
|
<SettingsDetails
|
|
552
598
|
isFocused={view === 'detail'}
|
|
599
|
+
selectedAction={selectedAction}
|
|
553
600
|
detectedPlatforms={detectedPlatforms}
|
|
554
601
|
onRedetect={detectedPlatforms.length === 0 ? handleRedetectPlatforms : undefined}
|
|
555
602
|
/>
|
package/src/lib/config.test.ts
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test';
|
|
2
2
|
import { existsSync, mkdirSync, rmSync, readFileSync, writeFileSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { tmpdir } from 'os';
|
|
5
5
|
import YAML from 'yaml';
|
|
6
|
+
import {
|
|
7
|
+
getRepos,
|
|
8
|
+
getRepo,
|
|
9
|
+
getRepoPath,
|
|
10
|
+
addRepo,
|
|
11
|
+
removeRepo,
|
|
12
|
+
getToolSettings,
|
|
13
|
+
setToolSettings,
|
|
14
|
+
setToolSetting,
|
|
15
|
+
loadConfig,
|
|
16
|
+
saveConfig,
|
|
17
|
+
getConfigDir,
|
|
18
|
+
} from './config';
|
|
19
|
+
import type { DroidConfig, RepoConfig } from './types';
|
|
6
20
|
|
|
7
21
|
describe('config value parsing', () => {
|
|
8
22
|
it('should parse dot notation keys correctly', () => {
|
|
@@ -87,3 +101,216 @@ describe('config file operations', () => {
|
|
|
87
101
|
expect(parsed.ai_tool).toBe('claude-code');
|
|
88
102
|
});
|
|
89
103
|
});
|
|
104
|
+
|
|
105
|
+
describe('repos management', () => {
|
|
106
|
+
let originalConfigDir: string;
|
|
107
|
+
let testConfigDir: string;
|
|
108
|
+
|
|
109
|
+
beforeEach(() => {
|
|
110
|
+
// Mock CONFIG_DIR by creating a test directory
|
|
111
|
+
testConfigDir = join(tmpdir(), `droid-config-test-${Date.now()}`);
|
|
112
|
+
mkdirSync(testConfigDir, { recursive: true });
|
|
113
|
+
|
|
114
|
+
// Mock getConfigDir to return our test directory
|
|
115
|
+
originalConfigDir = getConfigDir();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
afterEach(() => {
|
|
119
|
+
if (existsSync(testConfigDir)) {
|
|
120
|
+
rmSync(testConfigDir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('getRepos returns empty array when none configured', () => {
|
|
125
|
+
const repos = getRepos();
|
|
126
|
+
expect(Array.isArray(repos)).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('addRepo creates repo entry', () => {
|
|
130
|
+
const repo: RepoConfig = {
|
|
131
|
+
name: 'test-repo',
|
|
132
|
+
path: '~/test/repo',
|
|
133
|
+
description: 'Test repository',
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
addRepo(repo);
|
|
137
|
+
const repos = getRepos();
|
|
138
|
+
|
|
139
|
+
const found = repos.find((r) => r.name === 'test-repo');
|
|
140
|
+
expect(found).toBeDefined();
|
|
141
|
+
expect(found?.path).toBe('~/test/repo');
|
|
142
|
+
expect(found?.description).toBe('Test repository');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('getRepoPath expands tilde to home directory', () => {
|
|
146
|
+
const repo: RepoConfig = {
|
|
147
|
+
name: 'tilde-repo',
|
|
148
|
+
path: '~/code/my-repo',
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
addRepo(repo);
|
|
152
|
+
const path = getRepoPath('tilde-repo');
|
|
153
|
+
|
|
154
|
+
expect(path).toBeDefined();
|
|
155
|
+
expect(path).not.toContain('~');
|
|
156
|
+
expect(path).toContain('code/my-repo');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('removeRepo returns false when repo does not exist', () => {
|
|
160
|
+
const existed = removeRepo('non-existent-repo');
|
|
161
|
+
expect(existed).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('removeRepo returns true when repo exists', () => {
|
|
165
|
+
const repo: RepoConfig = {
|
|
166
|
+
name: 'to-remove',
|
|
167
|
+
path: '/tmp/repo',
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
addRepo(repo);
|
|
171
|
+
const existed = removeRepo('to-remove');
|
|
172
|
+
|
|
173
|
+
expect(existed).toBe(true);
|
|
174
|
+
|
|
175
|
+
const found = getRepo('to-remove');
|
|
176
|
+
expect(found).toBeUndefined();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('config round-trip preserves repo entries', () => {
|
|
180
|
+
const repo1: RepoConfig = {
|
|
181
|
+
name: 'repo1',
|
|
182
|
+
path: '/path/to/repo1',
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const repo2: RepoConfig = {
|
|
186
|
+
name: 'repo2',
|
|
187
|
+
path: '~/code/repo2',
|
|
188
|
+
description: 'Second repo',
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
addRepo(repo1);
|
|
192
|
+
addRepo(repo2);
|
|
193
|
+
|
|
194
|
+
const repos = getRepos();
|
|
195
|
+
expect(repos.length).toBeGreaterThanOrEqual(2);
|
|
196
|
+
|
|
197
|
+
const found1 = repos.find((r) => r.name === 'repo1');
|
|
198
|
+
const found2 = repos.find((r) => r.name === 'repo2');
|
|
199
|
+
|
|
200
|
+
expect(found1).toBeDefined();
|
|
201
|
+
expect(found2).toBeDefined();
|
|
202
|
+
expect(found2?.description).toBe('Second repo');
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('tool config consolidation', () => {
|
|
207
|
+
it('getToolSettings returns empty object when none configured', () => {
|
|
208
|
+
const settings = getToolSettings('nonexistent-tool');
|
|
209
|
+
expect(settings).toEqual({});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('setToolSettings writes to config.tools.{name}', () => {
|
|
213
|
+
const settings = { key1: 'value1', key2: 42 };
|
|
214
|
+
setToolSettings('test-tool', settings);
|
|
215
|
+
|
|
216
|
+
const retrieved = getToolSettings('test-tool');
|
|
217
|
+
expect(retrieved).toEqual(settings);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('setToolSetting updates single key in tool config', () => {
|
|
221
|
+
setToolSetting('test-tool-2', 'foo', 'bar');
|
|
222
|
+
setToolSetting('test-tool-2', 'baz', 123);
|
|
223
|
+
|
|
224
|
+
const settings = getToolSettings('test-tool-2');
|
|
225
|
+
expect(settings.foo).toBe('bar');
|
|
226
|
+
expect(settings.baz).toBe(123);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('migration flag is set after migration', () => {
|
|
230
|
+
// The migration should set tools_consolidated flag when complete
|
|
231
|
+
const config = loadConfig();
|
|
232
|
+
// Either undefined (not migrated) or boolean (migrated)
|
|
233
|
+
const flagType = typeof config.migrations?.tools_consolidated;
|
|
234
|
+
expect(['undefined', 'boolean']).toContain(flagType);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('loadSkillOverrides reads from new location first', () => {
|
|
238
|
+
// Set some data in the new location
|
|
239
|
+
setToolSettings('test-skill', { new_key: 'new_value' });
|
|
240
|
+
|
|
241
|
+
// Load via loadSkillOverrides
|
|
242
|
+
const overrides = require('./config').loadSkillOverrides('test-skill');
|
|
243
|
+
|
|
244
|
+
// Should read from new location
|
|
245
|
+
expect(overrides.new_key).toBe('new_value');
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe('tool config migration', () => {
|
|
250
|
+
let testDir: string;
|
|
251
|
+
|
|
252
|
+
beforeEach(() => {
|
|
253
|
+
testDir = join(tmpdir(), `droid-migration-test-${Date.now()}`);
|
|
254
|
+
mkdirSync(testDir, { recursive: true });
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
afterEach(() => {
|
|
258
|
+
if (existsSync(testDir)) {
|
|
259
|
+
rmSync(testDir, { recursive: true });
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('migrateToolConfigs copies data from old override files', () => {
|
|
264
|
+
// Create old-style override file structure
|
|
265
|
+
const skillsDir = join(testDir, 'skills', 'brain');
|
|
266
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
267
|
+
|
|
268
|
+
const overrideData = {
|
|
269
|
+
brain_dir: '~/my-brain',
|
|
270
|
+
inbox_folder: 'inbox',
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const overridesPath = join(skillsDir, 'overrides.yaml');
|
|
274
|
+
writeFileSync(overridesPath, YAML.stringify(overrideData), 'utf-8');
|
|
275
|
+
|
|
276
|
+
// Create a config file without migration flag
|
|
277
|
+
const configPath = join(testDir, 'config.yaml');
|
|
278
|
+
const config: Partial<DroidConfig> = {
|
|
279
|
+
platform: 'claude-code' as any,
|
|
280
|
+
user_mention: '@test',
|
|
281
|
+
output_preference: 'terminal' as any,
|
|
282
|
+
git_username: 'test',
|
|
283
|
+
platforms: {},
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
writeFileSync(configPath, YAML.stringify(config), 'utf-8');
|
|
287
|
+
|
|
288
|
+
// Verify old file exists
|
|
289
|
+
expect(existsSync(overridesPath)).toBe(true);
|
|
290
|
+
|
|
291
|
+
// Verify the structure matches what migrateToolConfigs expects
|
|
292
|
+
const skillDirs = require('fs').readdirSync(join(testDir, 'skills'), {
|
|
293
|
+
withFileTypes: true,
|
|
294
|
+
});
|
|
295
|
+
expect(skillDirs.length).toBeGreaterThan(0);
|
|
296
|
+
expect(skillDirs[0].isDirectory()).toBe(true);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('migration preserves old override files as backup', () => {
|
|
300
|
+
// Create old-style override file
|
|
301
|
+
const skillsDir = join(testDir, 'skills', 'codex');
|
|
302
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
303
|
+
|
|
304
|
+
const overrideData = { codex_repo: '~/codex' };
|
|
305
|
+
const overridesPath = join(skillsDir, 'overrides.yaml');
|
|
306
|
+
writeFileSync(overridesPath, YAML.stringify(overrideData), 'utf-8');
|
|
307
|
+
|
|
308
|
+
// After migration, old file should still exist
|
|
309
|
+
expect(existsSync(overridesPath)).toBe(true);
|
|
310
|
+
|
|
311
|
+
// Read it back to verify it wasn't corrupted
|
|
312
|
+
const content = readFileSync(overridesPath, 'utf-8');
|
|
313
|
+
const parsed = YAML.parse(content);
|
|
314
|
+
expect(parsed.codex_repo).toBe('~/codex');
|
|
315
|
+
});
|
|
316
|
+
});
|
package/src/lib/config.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from 'fs';
|
|
2
8
|
import { homedir } from 'os';
|
|
3
9
|
import { join } from 'path';
|
|
4
10
|
import YAML from 'yaml';
|
|
@@ -9,6 +15,8 @@ import {
|
|
|
9
15
|
type LegacyDroidConfig,
|
|
10
16
|
type SkillOverrides,
|
|
11
17
|
type AutoUpdateConfig,
|
|
18
|
+
type RepoConfig,
|
|
19
|
+
type ToolConfig,
|
|
12
20
|
} from './types';
|
|
13
21
|
|
|
14
22
|
const CONFIG_DIR = join(homedir(), '.droid');
|
|
@@ -60,6 +68,66 @@ function migrateConfig(
|
|
|
60
68
|
};
|
|
61
69
|
}
|
|
62
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Migrate tool configs from old override files to config.tools.*
|
|
73
|
+
* Keeps old files as backup (will be removed in follow-up PR)
|
|
74
|
+
*/
|
|
75
|
+
function migrateToolConfigs(config: DroidConfig): boolean {
|
|
76
|
+
// Skip if already migrated
|
|
77
|
+
if (config.migrations?.tools_consolidated) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const skillsDir = join(CONFIG_DIR, 'skills');
|
|
82
|
+
if (!existsSync(skillsDir)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let migrated = false;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
90
|
+
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
if (!entry.isDirectory()) continue;
|
|
93
|
+
|
|
94
|
+
const skillName = entry.name;
|
|
95
|
+
const overridesPath = join(skillsDir, skillName, 'overrides.yaml');
|
|
96
|
+
|
|
97
|
+
if (!existsSync(overridesPath)) continue;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const content = readFileSync(overridesPath, 'utf-8');
|
|
101
|
+
const overrides = YAML.parse(content) as SkillOverrides;
|
|
102
|
+
|
|
103
|
+
if (overrides && Object.keys(overrides).length > 0) {
|
|
104
|
+
// Migrate to new location
|
|
105
|
+
if (!config.tools) {
|
|
106
|
+
config.tools = {};
|
|
107
|
+
}
|
|
108
|
+
config.tools[skillName] = overrides;
|
|
109
|
+
migrated = true;
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Skip files that can't be parsed
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (migrated) {
|
|
118
|
+
// Record migration
|
|
119
|
+
if (!config.migrations) {
|
|
120
|
+
config.migrations = {};
|
|
121
|
+
}
|
|
122
|
+
config.migrations.tools_consolidated = true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return migrated;
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
63
131
|
/**
|
|
64
132
|
* Ensure the config directory exists
|
|
65
133
|
*/
|
|
@@ -94,11 +162,15 @@ export function loadConfig(): DroidConfig {
|
|
|
94
162
|
|
|
95
163
|
// Check if migration is needed
|
|
96
164
|
// TODO: Remove after v0.14.0 (target: late Jan 2025)
|
|
97
|
-
const
|
|
165
|
+
const needsLegacyMigration =
|
|
166
|
+
'ai_tool' in rawConfig && !('platform' in rawConfig);
|
|
98
167
|
const config = migrateConfig(rawConfig);
|
|
99
168
|
|
|
100
|
-
//
|
|
101
|
-
|
|
169
|
+
// Migrate tool configs from old override files to config.tools.*
|
|
170
|
+
const needsToolMigration = migrateToolConfigs(config);
|
|
171
|
+
|
|
172
|
+
// Save migrated config if any migration occurred
|
|
173
|
+
if (needsLegacyMigration || needsToolMigration) {
|
|
102
174
|
saveConfig(config);
|
|
103
175
|
}
|
|
104
176
|
|
|
@@ -193,8 +265,18 @@ export function getSkillOverridesPath(skillName: string): string {
|
|
|
193
265
|
|
|
194
266
|
/**
|
|
195
267
|
* Load skill-specific overrides
|
|
268
|
+
* Reads from new location first (config.tools.{name}), falls back to old override files
|
|
196
269
|
*/
|
|
197
270
|
export function loadSkillOverrides(skillName: string): SkillOverrides {
|
|
271
|
+
const normalizedName = normalizeSkillNameForConfig(skillName);
|
|
272
|
+
|
|
273
|
+
// Check new location first (config.tools.{name})
|
|
274
|
+
const toolSettings = getToolSettings(normalizedName);
|
|
275
|
+
if (Object.keys(toolSettings).length > 0) {
|
|
276
|
+
return toolSettings as SkillOverrides;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Fall back to old location (~/.droid/skills/{name}/overrides.yaml)
|
|
198
280
|
const overridesPath = getSkillOverridesPath(skillName);
|
|
199
281
|
|
|
200
282
|
if (!existsSync(overridesPath)) {
|
|
@@ -228,6 +310,35 @@ export function saveSkillOverrides(
|
|
|
228
310
|
writeFileSync(overridesPath, content, 'utf-8');
|
|
229
311
|
}
|
|
230
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Get tool settings from config.tools.{name} (returns empty object if none)
|
|
315
|
+
*/
|
|
316
|
+
export function getToolSettings(name: string): ToolConfig {
|
|
317
|
+
const config = loadConfig();
|
|
318
|
+
return (config.tools?.[name] as ToolConfig) ?? {};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Set all settings for a tool in config.tools.{name}
|
|
323
|
+
*/
|
|
324
|
+
export function setToolSettings(name: string, settings: ToolConfig): void {
|
|
325
|
+
const config = loadConfig();
|
|
326
|
+
if (!config.tools) {
|
|
327
|
+
config.tools = {};
|
|
328
|
+
}
|
|
329
|
+
config.tools[name] = settings;
|
|
330
|
+
saveConfig(config);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Set a single key in tool settings
|
|
335
|
+
*/
|
|
336
|
+
export function setToolSetting(name: string, key: string, value: unknown): void {
|
|
337
|
+
const settings = getToolSettings(name);
|
|
338
|
+
settings[key] = value;
|
|
339
|
+
setToolSettings(name, settings);
|
|
340
|
+
}
|
|
341
|
+
|
|
231
342
|
/**
|
|
232
343
|
* Get auto-update config with defaults applied
|
|
233
344
|
*/
|
|
@@ -251,3 +362,81 @@ export function setAutoUpdateConfig(updates: Partial<AutoUpdateConfig>): void {
|
|
|
251
362
|
};
|
|
252
363
|
saveConfig(config);
|
|
253
364
|
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Expand tilde (~) in paths to home directory
|
|
368
|
+
*/
|
|
369
|
+
function expandTilde(path: string): string {
|
|
370
|
+
if (path.startsWith('~/')) {
|
|
371
|
+
return join(homedir(), path.slice(2));
|
|
372
|
+
}
|
|
373
|
+
return path;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Get all repos (returns empty array if none)
|
|
378
|
+
*/
|
|
379
|
+
export function getRepos(): RepoConfig[] {
|
|
380
|
+
const config = loadConfig();
|
|
381
|
+
return config.repos ?? [];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Find repo by name
|
|
386
|
+
*/
|
|
387
|
+
export function getRepo(name: string): RepoConfig | undefined {
|
|
388
|
+
const repos = getRepos();
|
|
389
|
+
return repos.find((repo) => repo.name === name);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Get repo path with tilde expansion
|
|
394
|
+
*/
|
|
395
|
+
export function getRepoPath(name: string): string | undefined {
|
|
396
|
+
const repo = getRepo(name);
|
|
397
|
+
if (!repo) {
|
|
398
|
+
return undefined;
|
|
399
|
+
}
|
|
400
|
+
return expandTilde(repo.path);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Add or update a repo (updates if name exists)
|
|
405
|
+
*/
|
|
406
|
+
export function addRepo(repo: RepoConfig): void {
|
|
407
|
+
const config = loadConfig();
|
|
408
|
+
const repos = config.repos ?? [];
|
|
409
|
+
|
|
410
|
+
// Check if repo already exists
|
|
411
|
+
const existingIndex = repos.findIndex((r) => r.name === repo.name);
|
|
412
|
+
|
|
413
|
+
if (existingIndex >= 0) {
|
|
414
|
+
// Update existing repo
|
|
415
|
+
repos[existingIndex] = repo;
|
|
416
|
+
} else {
|
|
417
|
+
// Add new repo
|
|
418
|
+
repos.push(repo);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
config.repos = repos;
|
|
422
|
+
saveConfig(config);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Remove repo by name (returns true if existed)
|
|
427
|
+
*/
|
|
428
|
+
export function removeRepo(name: string): boolean {
|
|
429
|
+
const config = loadConfig();
|
|
430
|
+
const repos = config.repos ?? [];
|
|
431
|
+
|
|
432
|
+
const existingIndex = repos.findIndex((r) => r.name === name);
|
|
433
|
+
|
|
434
|
+
if (existingIndex >= 0) {
|
|
435
|
+
repos.splice(existingIndex, 1);
|
|
436
|
+
config.repos = repos;
|
|
437
|
+
saveConfig(config);
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return false;
|
|
442
|
+
}
|
package/src/lib/types.ts
CHANGED
|
@@ -48,19 +48,31 @@ export interface AutoUpdateConfig {
|
|
|
48
48
|
tools: boolean; // Auto-update installed tools (default: true)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export interface RepoConfig {
|
|
52
|
+
name: string;
|
|
53
|
+
path: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ToolConfig is flexible - each tool defines its own keys
|
|
58
|
+
export type ToolConfig = Record<string, unknown>;
|
|
59
|
+
|
|
51
60
|
export interface DroidConfig {
|
|
52
61
|
platform: Platform;
|
|
53
62
|
user_mention: string;
|
|
54
63
|
output_preference: OutputPreference;
|
|
55
64
|
git_username: string;
|
|
56
65
|
platforms: Record<string, PlatformConfig>;
|
|
66
|
+
repos?: RepoConfig[]; // Array - enumerate to discover available repos
|
|
67
|
+
tools?: Record<string, ToolConfig>; // Map - look up by tool name
|
|
57
68
|
auto_update?: AutoUpdateConfig;
|
|
58
69
|
ignored_platforms?: Platform[]; // Platforms to skip during multi-platform install
|
|
59
70
|
migrations?: {
|
|
60
71
|
package?: string; // Last package version migrated
|
|
61
72
|
tools?: Record<string, string>; // tool name -> last tool version migrated
|
|
73
|
+
tools_consolidated?: boolean; // Tool configs migrated to config.tools.*
|
|
62
74
|
// Legacy: old configs may have tool names at root level
|
|
63
|
-
[key: string]: string | Record<string, string> | undefined;
|
|
75
|
+
[key: string]: string | Record<string, string> | boolean | undefined;
|
|
64
76
|
};
|
|
65
77
|
}
|
|
66
78
|
|
|
@@ -26,7 +26,7 @@ Your **scratchpad** (or **brain**) - a collaborative space for planning, researc
|
|
|
26
26
|
|
|
27
27
|
## Configuration
|
|
28
28
|
|
|
29
|
-
**IMPORTANT:** Run `droid config brain` first and parse the JSON output. If `brain_dir` is not configured, **ask the user** where they want brain docs stored.
|
|
29
|
+
**IMPORTANT:** Run `droid config --get tools.brain` first and parse the JSON output. If `brain_dir` is not configured, **ask the user** where they want brain docs stored.
|
|
30
30
|
|
|
31
31
|
| Setting | Default | Description |
|
|
32
32
|
| -------------- | ------- | ------------------------------------------ |
|
|
@@ -134,7 +134,7 @@ In markdown files, use blockquote `>` prefix for @mention comments. The @mention
|
|
|
134
134
|
| `> @droid ...` | User | AI | User asking/telling the AI |
|
|
135
135
|
| `> @user ...` | AI | User | AI asking/telling the user |
|
|
136
136
|
|
|
137
|
-
Get `user_mention` from `droid config
|
|
137
|
+
Get `user_mention` from `droid config --get user_mention`. If droid's `comments` skill is installed, use `/comments check` for full support.
|
|
138
138
|
|
|
139
139
|
## Extensions
|
|
140
140
|
|
|
@@ -9,7 +9,7 @@ Detailed procedures for each brain operation.
|
|
|
9
9
|
**Steps:**
|
|
10
10
|
|
|
11
11
|
1. **Read config first (MANDATORY)**
|
|
12
|
-
- Run `droid config brain` and parse the JSON output
|
|
12
|
+
- Run `droid config --get tools.brain` and parse the JSON output
|
|
13
13
|
- Use the `brain_dir` value from the config
|
|
14
14
|
- If `brain_dir` is not configured, **ask the user** where they want brain docs stored
|
|
15
15
|
- **Do NOT skip this step or assume defaults**
|
|
@@ -48,7 +48,7 @@ Detailed procedures for each brain operation.
|
|
|
48
48
|
**Steps:**
|
|
49
49
|
|
|
50
50
|
1. **Read config first (MANDATORY)**
|
|
51
|
-
- Run `droid config brain` and parse the JSON output
|
|
51
|
+
- Run `droid config --get tools.brain` and parse the JSON output
|
|
52
52
|
- Use the `brain_dir` and `inbox_folder` values from the config
|
|
53
53
|
- If `brain_dir` is not configured, **ask the user** where they want brain docs stored
|
|
54
54
|
- **Do NOT skip this step or assume defaults**
|
|
@@ -78,8 +78,8 @@ Detailed procedures for each brain operation.
|
|
|
78
78
|
- Related work or prior decisions?
|
|
79
79
|
|
|
80
80
|
7. **Add placeholder comments** for sections needing user input:
|
|
81
|
-
- Get `user_mention` from config: `droid config
|
|
82
|
-
- Use `> @{user_mention}` for placeholders (e.g., `> @
|
|
81
|
+
- Get `user_mention` from config: `droid config --get user_mention`
|
|
82
|
+
- Use `> @{user_mention}` for placeholders (e.g., `> @{user} - Please provide requirements`)
|
|
83
83
|
- **CRITICAL:** NEVER use `@droid` - that's for user-to-AI comments, not AI-to-user
|
|
84
84
|
|
|
85
85
|
8. **Write the doc** with template structure and initial context
|
|
@@ -95,7 +95,7 @@ Detailed procedures for each brain operation.
|
|
|
95
95
|
**Steps:**
|
|
96
96
|
|
|
97
97
|
1. **Read config first (MANDATORY)**
|
|
98
|
-
- Run `droid config brain` and parse the JSON output
|
|
98
|
+
- Run `droid config --get tools.brain` and parse the JSON output
|
|
99
99
|
- Use the `brain_dir` and `inbox_folder` values from the config
|
|
100
100
|
- If `brain_dir` is not configured, **ask the user** where they want brain docs stored
|
|
101
101
|
- **Do NOT skip this step or assume defaults**
|
|
@@ -165,7 +165,7 @@ Detailed procedures for each brain operation.
|
|
|
165
165
|
2. **Read active doc**
|
|
166
166
|
|
|
167
167
|
3. **Check preserve_comments setting:**
|
|
168
|
-
- Run `droid config comments` and parse the JSON output
|
|
168
|
+
- Run `droid config --get tools.comments` and parse the JSON output
|
|
169
169
|
- Use the `preserve_comments` value (default: `true`)
|
|
170
170
|
|
|
171
171
|
4. **Find all `> @droid` comments**
|
|
@@ -174,9 +174,9 @@ Detailed procedures for each brain operation.
|
|
|
174
174
|
5. **For each comment:**
|
|
175
175
|
- Show the comment and surrounding context
|
|
176
176
|
- Address the question/request
|
|
177
|
-
- Add response as blockquote with user's mention (from `droid config brain`): `> @{user_mention} Your response here`
|
|
178
|
-
- Example: `> @
|
|
179
|
-
- **CRITICAL:** NEVER use `@droid` in your responses. Use `@{user_mention}` from config (e.g., `@
|
|
177
|
+
- Add response as blockquote with user's mention (from `droid config --get tools.brain`): `> @{user_mention} Your response here`
|
|
178
|
+
- Example: `> @{user} Yes, the index covers that query pattern.`
|
|
179
|
+
- **CRITICAL:** NEVER use `@droid` in your responses. Use `@{user_mention}` from config (e.g., `@{user}`)
|
|
180
180
|
- **CRITICAL:** Always include the `>` prefix to maintain blockquote formatting
|
|
181
181
|
- **CRITICAL:** If `preserve_comments: true`, keep the original `@droid` comment and add your response below it. Do NOT remove the original comment.
|
|
182
182
|
|
|
@@ -216,7 +216,7 @@ Detailed procedures for each brain operation.
|
|
|
216
216
|
**Steps:**
|
|
217
217
|
|
|
218
218
|
1. **Read config first (MANDATORY)**
|
|
219
|
-
- Run `droid config brain` and parse the JSON output
|
|
219
|
+
- Run `droid config --get tools.brain` and parse the JSON output
|
|
220
220
|
- Use the `brain_dir` value from the config
|
|
221
221
|
- If `brain_dir` is not configured, **ask the user** where they want brain docs stored
|
|
222
222
|
- **Do NOT skip this step or assume defaults**
|